fix(payment): return paymentUrl from adapters, strip base64 from tool output

Alipay/WeChat adapters now return the source payment URL alongside the
QR base64. The generate_payment tool only returns paymentUrl (short text)
to Claude API — base64 qrCodeUrl is stripped to prevent AI from dumping
raw data:image into text responses. Frontend QRCodeSVG renders from
paymentUrl instead of base64.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-07 11:44:54 -08:00
parent 6609e50100
commit 389f975e33
5 changed files with 12 additions and 13 deletions

View File

@ -553,6 +553,9 @@ export class ImmigrationToolsService {
return { success: false, error: '生成支付失败,请稍后重试', orderId: order.id };
}
// paymentUrl 是支付链接(短文本),前端用 QRCodeSVG 从它生成二维码
// qrCodeUrl 是 base64 data URL~数KB绝不能传给 AI会被塞进文本回复
// 只返回 paymentUrl永远不返回 qrCodeUrlbase64
return {
success: true,
orderId: order.id,
@ -560,10 +563,9 @@ export class ImmigrationToolsService {
amount: payment.amount,
currency: order.currency,
paymentMethod: payment.method,
qrCodeUrl: payment.qrCodeUrl,
paymentUrl: payment.paymentUrl,
expiresAt: payment.expiresAt,
message: `请扫描二维码支付 ¥${payment.amount} 完成${category}类别的移民资格评估服务`,
_ui_hint: '前端已自动渲染支付二维码回复中不要包含任何链接、URL或二维码数据',
};
}

View File

@ -5,6 +5,7 @@ import { OrderEntity } from '../../../domain/entities/order.entity';
export interface AlipayPaymentResult {
qrCodeUrl: string;
paymentUrl: string;
tradeNo: string;
}
@ -49,6 +50,7 @@ export class AlipayAdapter {
return {
qrCodeUrl: qrCodeDataUrl,
paymentUrl: mockPaymentUrl,
tradeNo: `ALIPAY_${Date.now()}`,
};
}

View File

@ -5,6 +5,7 @@ import { OrderEntity } from '../../../domain/entities/order.entity';
export interface WechatPaymentResult {
qrCodeUrl: string;
paymentUrl: string;
prepayId: string;
}
@ -49,6 +50,7 @@ export class WechatPayAdapter {
return {
qrCodeUrl: qrCodeDataUrl,
paymentUrl: mockPaymentUrl,
prepayId: `WECHAT_${Date.now()}`,
};
}

View File

@ -87,11 +87,13 @@ export class PaymentService {
case PaymentMethod.ALIPAY:
const alipayResult = await this.alipayAdapter.createPayment(order);
qrCodeUrl = alipayResult.qrCodeUrl;
paymentUrl = alipayResult.paymentUrl;
break;
case PaymentMethod.WECHAT:
const wechatResult = await this.wechatPayAdapter.createPayment(order);
qrCodeUrl = wechatResult.qrCodeUrl;
paymentUrl = wechatResult.paymentUrl;
break;
case PaymentMethod.CREDIT_CARD:

View File

@ -220,19 +220,10 @@ function ToolCallResult({
<div className="mt-3 p-4 bg-white rounded-lg border border-secondary-200">
<p className="text-sm text-secondary-600 mb-3">{result.message}</p>
<div className="flex flex-col items-center">
{result.qrCodeUrl ? (
{result.paymentUrl ? (
<div className="p-3 bg-white rounded-lg border border-secondary-100">
<QRCodeSVG value={result.qrCodeUrl} size={160} level="M" />
<QRCodeSVG value={result.paymentUrl} size={160} level="M" />
</div>
) : result.paymentUrl ? (
<a
href={result.paymentUrl}
target="_blank"
rel="noopener noreferrer"
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
</a>
) : null}
<p className="mt-3 text-xl font-semibold text-primary-600">
¥{result.amount}