feat: wire payment return url payloads
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { CreateOrderResult, MethodLimit } from '@/types/payment'
|
||||
import {
|
||||
buildCreateOrderPayload,
|
||||
decidePaymentLaunch,
|
||||
getVisibleMethods,
|
||||
readPaymentRecoverySnapshot,
|
||||
@@ -106,6 +107,42 @@ describe('decidePaymentLaunch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildCreateOrderPayload', () => {
|
||||
it('normalizes visible method aliases and attaches a canonical result URL', () => {
|
||||
expect(buildCreateOrderPayload({
|
||||
amount: 88,
|
||||
paymentType: 'alipay_direct',
|
||||
orderType: 'balance',
|
||||
origin: 'https://app.example.com/',
|
||||
isWechatBrowser: false,
|
||||
})).toEqual({
|
||||
amount: 88,
|
||||
payment_type: 'alipay',
|
||||
order_type: 'balance',
|
||||
return_url: 'https://app.example.com/payment/result',
|
||||
payment_source: 'hosted_redirect',
|
||||
})
|
||||
})
|
||||
|
||||
it('uses WeChat in-app resume source for visible WeChat payments in the WeChat browser', () => {
|
||||
expect(buildCreateOrderPayload({
|
||||
amount: 128,
|
||||
paymentType: 'wxpay',
|
||||
orderType: 'subscription',
|
||||
planId: 7,
|
||||
origin: 'https://app.example.com',
|
||||
isWechatBrowser: true,
|
||||
})).toEqual({
|
||||
amount: 128,
|
||||
payment_type: 'wxpay',
|
||||
order_type: 'subscription',
|
||||
plan_id: 7,
|
||||
return_url: 'https://app.example.com/payment/result',
|
||||
payment_source: 'wechat_in_app_resume',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('readPaymentRecoverySnapshot', () => {
|
||||
it('restores an unexpired snapshot when the resume token matches', () => {
|
||||
const snapshot: PaymentRecoverySnapshot = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CreateOrderResult, MethodLimit, OrderType } from '@/types/payment'
|
||||
import type { CreateOrderRequest, CreateOrderResult, MethodLimit, OrderType } from '@/types/payment'
|
||||
|
||||
export const PAYMENT_RECOVERY_STORAGE_KEY = 'payment.recovery.current'
|
||||
|
||||
@@ -49,6 +49,15 @@ export interface PaymentLaunchDecision {
|
||||
stripeMethod?: StripeVisibleMethod
|
||||
}
|
||||
|
||||
export interface BuildCreateOrderPayloadInput {
|
||||
amount: number
|
||||
paymentType: string
|
||||
orderType: OrderType
|
||||
planId?: number
|
||||
origin?: string
|
||||
isWechatBrowser: boolean
|
||||
}
|
||||
|
||||
type CreateOrderFlowResult = CreateOrderResult & {
|
||||
resume_token?: string
|
||||
}
|
||||
@@ -77,6 +86,28 @@ export function getVisibleMethods(methods: Record<string, MethodLimit>): Record<
|
||||
return visible
|
||||
}
|
||||
|
||||
export function buildCreateOrderPayload(input: BuildCreateOrderPayloadInput): CreateOrderRequest {
|
||||
const visibleMethod = normalizeVisibleMethod(input.paymentType) || input.paymentType.trim()
|
||||
const normalizedOrigin = (input.origin || '').trim().replace(/\/+$/, '')
|
||||
const payload: CreateOrderRequest = {
|
||||
amount: input.amount,
|
||||
payment_type: visibleMethod,
|
||||
order_type: input.orderType,
|
||||
payment_source: visibleMethod === 'wxpay' && input.isWechatBrowser
|
||||
? 'wechat_in_app_resume'
|
||||
: 'hosted_redirect',
|
||||
}
|
||||
|
||||
if (input.planId) {
|
||||
payload.plan_id = input.planId
|
||||
}
|
||||
if (normalizedOrigin) {
|
||||
payload.return_url = `${normalizedOrigin}/payment/result`
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
export function decidePaymentLaunch(
|
||||
result: CreateOrderFlowResult,
|
||||
context: PaymentLaunchContext,
|
||||
|
||||
@@ -154,6 +154,8 @@ export interface CreateOrderRequest {
|
||||
payment_type: string
|
||||
order_type: string
|
||||
plan_id?: number
|
||||
return_url?: string
|
||||
payment_source?: string
|
||||
}
|
||||
|
||||
export interface CreateOrderResult {
|
||||
@@ -166,6 +168,7 @@ export interface CreateOrderResult {
|
||||
fee_rate: number
|
||||
expires_at: string
|
||||
payment_mode?: string
|
||||
resume_token?: string
|
||||
}
|
||||
|
||||
export interface DashboardStats {
|
||||
|
||||
@@ -267,6 +267,7 @@ import PaymentMethodSelector from '@/components/payment/PaymentMethodSelector.vu
|
||||
import { METHOD_ORDER, POPUP_WINDOW_FEATURES, STRIPE_POPUP_WINDOW_FEATURES } from '@/components/payment/providerConfig'
|
||||
import {
|
||||
PAYMENT_RECOVERY_STORAGE_KEY,
|
||||
buildCreateOrderPayload,
|
||||
clearPaymentRecoverySnapshot,
|
||||
decidePaymentLaunch,
|
||||
getVisibleMethods,
|
||||
@@ -563,12 +564,14 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
|
||||
submitting.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await paymentStore.createOrder({
|
||||
const result = await paymentStore.createOrder(buildCreateOrderPayload({
|
||||
amount: orderAmount,
|
||||
payment_type: selectedMethod.value,
|
||||
order_type: orderType,
|
||||
plan_id: planId,
|
||||
}) as CreateOrderResult & { resume_token?: string }
|
||||
paymentType: selectedMethod.value,
|
||||
orderType,
|
||||
planId,
|
||||
origin: typeof window !== 'undefined' ? window.location.origin : '',
|
||||
isWechatBrowser: typeof window !== 'undefined' && /MicroMessenger/i.test(window.navigator.userAgent),
|
||||
})) as CreateOrderResult & { resume_token?: string }
|
||||
const openWindow = (url: string, features = POPUP_WINDOW_FEATURES) => {
|
||||
const win = window.open(url, 'paymentPopup', features)
|
||||
if (!win || win.closed) {
|
||||
|
||||
Reference in New Issue
Block a user