feat: add Airwallex payments and multi-currency support

This commit is contained in:
shaw
2026-05-11 10:45:07 +08:00
parent dbc8ae658c
commit b23055af5b
65 changed files with 3164 additions and 162 deletions
+28 -1
View File
@@ -15,15 +15,17 @@ const VISIBLE_METHOD_ALIASES = {
wxpay: 'wxpay',
wxpay_direct: 'wxpay',
stripe: 'stripe',
airwallex: 'airwallex',
} as const
export type VisiblePaymentMethod = 'alipay' | 'wxpay' | 'stripe'
export type VisiblePaymentMethod = 'alipay' | 'wxpay' | 'stripe' | 'airwallex'
export type StripeVisibleMethod = 'alipay' | 'wechat_pay'
export type PaymentLaunchKind =
| 'qr_waiting'
| 'redirect_waiting'
| 'stripe_popup'
| 'stripe_route'
| 'airwallex_route'
| 'wechat_oauth'
| 'wechat_jsapi'
| 'unhandled'
@@ -37,6 +39,10 @@ export interface PaymentRecoverySnapshot {
payUrl: string
outTradeNo: string
clientSecret: string
intentId: string
currency: string
countryCode: string
paymentEnv: string
payAmount: number
orderType: OrderType | ''
paymentMode: string
@@ -52,6 +58,7 @@ export interface PaymentLaunchContext {
now?: number
stripePopupUrl?: string
stripeRouteUrl?: string
airwallexRouteUrl?: string
}
export interface PaymentLaunchDecision {
@@ -138,12 +145,24 @@ export function decidePaymentLaunch(
payUrl: result.pay_url || '',
outTradeNo: result.out_trade_no || '',
clientSecret: result.client_secret || '',
intentId: result.intent_id || '',
currency: result.currency || '',
countryCode: result.country_code || '',
paymentEnv: result.payment_env || '',
payAmount: result.pay_amount,
orderType: context.orderType,
paymentMode: (result.payment_mode || '').trim(),
resumeToken: result.resume_token || '',
}, context.now)
if (visibleMethod === 'airwallex' && baseState.clientSecret && baseState.intentId) {
if (!context.airwallexRouteUrl) {
return { kind: 'unhandled', paymentState: baseState, recovery: baseState }
}
const paymentState = { ...baseState, payUrl: context.airwallexRouteUrl || '' }
return { kind: 'airwallex_route', paymentState, recovery: paymentState }
}
if (baseState.clientSecret) {
// visibleMethod === 'stripe' means the user clicked the dedicated Stripe button
// and should land on the full Payment Element to choose a sub-method themselves.
@@ -239,6 +258,10 @@ export function readPaymentRecoverySnapshot(
|| typeof parsed.payUrl !== 'string'
|| (parsed.outTradeNo != null && typeof parsed.outTradeNo !== 'string')
|| typeof parsed.clientSecret !== 'string'
|| (parsed.intentId != null && typeof parsed.intentId !== 'string')
|| (parsed.currency != null && typeof parsed.currency !== 'string')
|| (parsed.countryCode != null && typeof parsed.countryCode !== 'string')
|| (parsed.paymentEnv != null && typeof parsed.paymentEnv !== 'string')
|| typeof parsed.payAmount !== 'number'
|| typeof parsed.paymentMode !== 'string'
|| typeof parsed.resumeToken !== 'string'
@@ -265,6 +288,10 @@ export function readPaymentRecoverySnapshot(
payUrl: parsed.payUrl,
outTradeNo: parsed.outTradeNo || '',
clientSecret: parsed.clientSecret,
intentId: parsed.intentId || '',
currency: parsed.currency || '',
countryCode: parsed.countryCode || '',
paymentEnv: parsed.paymentEnv || '',
payAmount: parsed.payAmount,
orderType: parsed.orderType === 'subscription' ? 'subscription' : 'balance',
paymentMode: parsed.paymentMode,