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 }; return { success: false, error: '生成支付失败,请稍后重试', orderId: order.id };
} }
// paymentUrl 是支付链接(短文本),前端用 QRCodeSVG 从它生成二维码
// qrCodeUrl 是 base64 data URL~数KB绝不能传给 AI会被塞进文本回复
// 只返回 paymentUrl永远不返回 qrCodeUrlbase64
return { return {
success: true, success: true,
orderId: order.id, orderId: order.id,
@ -560,10 +563,9 @@ export class ImmigrationToolsService {
amount: payment.amount, amount: payment.amount,
currency: order.currency, currency: order.currency,
paymentMethod: payment.method, paymentMethod: payment.method,
qrCodeUrl: payment.qrCodeUrl,
paymentUrl: payment.paymentUrl, paymentUrl: payment.paymentUrl,
expiresAt: payment.expiresAt, 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 { export interface AlipayPaymentResult {
qrCodeUrl: string; qrCodeUrl: string;
paymentUrl: string;
tradeNo: string; tradeNo: string;
} }
@ -49,6 +50,7 @@ export class AlipayAdapter {
return { return {
qrCodeUrl: qrCodeDataUrl, qrCodeUrl: qrCodeDataUrl,
paymentUrl: mockPaymentUrl,
tradeNo: `ALIPAY_${Date.now()}`, tradeNo: `ALIPAY_${Date.now()}`,
}; };
} }

View File

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

View File

@ -87,11 +87,13 @@ export class PaymentService {
case PaymentMethod.ALIPAY: case PaymentMethod.ALIPAY:
const alipayResult = await this.alipayAdapter.createPayment(order); const alipayResult = await this.alipayAdapter.createPayment(order);
qrCodeUrl = alipayResult.qrCodeUrl; qrCodeUrl = alipayResult.qrCodeUrl;
paymentUrl = alipayResult.paymentUrl;
break; break;
case PaymentMethod.WECHAT: case PaymentMethod.WECHAT:
const wechatResult = await this.wechatPayAdapter.createPayment(order); const wechatResult = await this.wechatPayAdapter.createPayment(order);
qrCodeUrl = wechatResult.qrCodeUrl; qrCodeUrl = wechatResult.qrCodeUrl;
paymentUrl = wechatResult.paymentUrl;
break; break;
case PaymentMethod.CREDIT_CARD: 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"> <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> <p className="text-sm text-secondary-600 mb-3">{result.message}</p>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
{result.qrCodeUrl ? ( {result.paymentUrl ? (
<div className="p-3 bg-white rounded-lg border border-secondary-100"> <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> </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} ) : null}
<p className="mt-3 text-xl font-semibold text-primary-600"> <p className="mt-3 text-xl font-semibold text-primary-600">
¥{result.amount} ¥{result.amount}