fix wechat mini program virtual payment flow
This commit is contained in:
@@ -2081,7 +2081,7 @@ function pickDraftCompletionDialogSourceId(
|
||||
function buildDraftCompletionDialogSource(
|
||||
kind: CreationWorkShelfKind,
|
||||
ids: Array<string | null | undefined>,
|
||||
) {
|
||||
): string {
|
||||
const sourceId = pickDraftCompletionDialogSourceId(ids);
|
||||
switch (kind) {
|
||||
case 'rpg':
|
||||
@@ -2103,6 +2103,7 @@ function buildDraftCompletionDialogSource(
|
||||
case 'baby-object-match':
|
||||
return formatPlatformTaskCompletionSource('宝贝识物草稿', sourceId);
|
||||
}
|
||||
return formatPlatformTaskCompletionSource('创作草稿', sourceId);
|
||||
}
|
||||
|
||||
function createMiniGameDraftGenerationStateForRestoredDraft(
|
||||
|
||||
@@ -1417,8 +1417,9 @@ test('profile recharge modal posts virtual payment params in mini program web-vi
|
||||
'requestId',
|
||||
);
|
||||
expect(requestId).toBeTruthy();
|
||||
expect(screen.getByRole('dialog', { name: '正在支付' })).toBeTruthy();
|
||||
act(() => {
|
||||
window.location.hash = `wx_pay_result=${requestId}:success`;
|
||||
window.location.hash = `wx_pay_result=${requestId}:success:order-wechat-1`;
|
||||
window.dispatchEvent(new HashChangeEvent('hashchange'));
|
||||
});
|
||||
expect(navigateUrl).toContain('order-wechat-1');
|
||||
@@ -1512,6 +1513,87 @@ test('profile recharge modal posts membership goods virtual payment params in mi
|
||||
expect(decodeURIComponent(navigateUrl)).toContain('"paySig":"pay-sig"');
|
||||
});
|
||||
|
||||
test('profile recharge modal releases submitting state and shows virtual payment failure detail', async () => {
|
||||
const user = userEvent.setup();
|
||||
window.history.replaceState(null, '', '/?clientRuntime=wechat_mini_program');
|
||||
const navigateTo = vi.fn((options: { url: string; success?: () => void }) => {
|
||||
options.success?.();
|
||||
});
|
||||
window.wx = {
|
||||
miniProgram: {
|
||||
navigateTo,
|
||||
},
|
||||
};
|
||||
mockCreateRpgProfileRechargeOrder.mockResolvedValueOnce({
|
||||
order: {
|
||||
orderId: 'order-wechat-sandbox-fail',
|
||||
productId: 'points_60',
|
||||
productTitle: '60泥点',
|
||||
kind: 'points',
|
||||
amountCents: 600,
|
||||
status: 'pending' as const,
|
||||
paymentChannel: 'wechat_mp_virtual',
|
||||
paidAt: null as string | null,
|
||||
providerTransactionId: null,
|
||||
createdAt: '2026-04-25T10:00:00Z',
|
||||
pointsDelta: 0,
|
||||
membershipExpiresAt: null,
|
||||
},
|
||||
center: {
|
||||
walletBalance: 0,
|
||||
membership: {
|
||||
status: 'normal',
|
||||
tier: 'normal',
|
||||
startedAt: null,
|
||||
expiresAt: null,
|
||||
updatedAt: null,
|
||||
},
|
||||
pointProducts: [],
|
||||
membershipProducts: [],
|
||||
benefits: [],
|
||||
latestOrder: null,
|
||||
hasPointsRecharged: false,
|
||||
},
|
||||
wechatMiniProgramPayParams: {
|
||||
mode: 'short_series_coin',
|
||||
signData:
|
||||
'{"offerId":"offer-1","buyQuantity":1,"env":1,"currencyType":"CNY","outTradeNo":"order-wechat-sandbox-fail","attach":"mud_points_60"}',
|
||||
paySig: 'sandbox-pay-sig',
|
||||
signature: 'user-sig',
|
||||
},
|
||||
});
|
||||
|
||||
renderProfileView();
|
||||
await openRechargeModal(user);
|
||||
const buyButton = await screen.findByRole('button', { name: /60泥点/u });
|
||||
await user.click(buyButton);
|
||||
|
||||
const navigateUrl = navigateTo.mock.calls[0]?.[0].url ?? '';
|
||||
const requestId = new URL(`https://mini.test${navigateUrl}`).searchParams.get(
|
||||
'requestId',
|
||||
);
|
||||
expect(requestId).toBeTruthy();
|
||||
act(() => {
|
||||
window.location.hash = `wx_pay_result=${requestId}:fail:order-wechat-sandbox-fail:${encodeURIComponent('{"errCode":-1,"errMsg":"requestVirtualPayment:fail sandbox"}')}`;
|
||||
window.dispatchEvent(new HashChangeEvent('hashchange'));
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByRole('dialog', { name: '支付未完成' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText(/requestVirtualPayment:fail sandbox/u),
|
||||
).toBeTruthy();
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
within(screen.getByRole('button', { name: /60泥点/u })).getByText(
|
||||
'购买',
|
||||
{ selector: 'span' },
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('profile recharge modal waits for paid confirmation before refreshing dashboard', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onRechargeSuccess = vi.fn();
|
||||
|
||||
@@ -329,6 +329,7 @@ type WechatPayResult = {
|
||||
requestId: string;
|
||||
orderId: string | null;
|
||||
status: WechatMiniProgramPaymentStatus;
|
||||
errorMessage: string | null;
|
||||
};
|
||||
type RechargePaymentResultKind = 'success' | 'pending' | 'cancel' | 'failed';
|
||||
type RechargePaymentResult = {
|
||||
@@ -2681,22 +2682,34 @@ function readWechatPayResultFromHash(): WechatPayResult | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [requestId = '', rawStatus = ''] = result.split(':');
|
||||
const orderId = requestId
|
||||
const [requestId = '', rawStatus = '', explicitOrderId = '', ...rawErrors] =
|
||||
result.split(':');
|
||||
const inferredOrderId = requestId
|
||||
.replace(/^wechat_pay_/, '')
|
||||
.replace(/_\d+$/, '')
|
||||
.trim();
|
||||
const orderId = explicitOrderId.trim() || inferredOrderId;
|
||||
const status =
|
||||
rawStatus === 'success'
|
||||
? 'success'
|
||||
: rawStatus === 'cancel'
|
||||
? 'cancel'
|
||||
: 'fail';
|
||||
let errorMessage: string | null = null;
|
||||
const rawError = rawErrors.join(':');
|
||||
if (rawError) {
|
||||
try {
|
||||
errorMessage = decodeURIComponent(rawError);
|
||||
} catch (_error) {
|
||||
errorMessage = rawError;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
requestId,
|
||||
orderId: orderId || null,
|
||||
status,
|
||||
errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4586,6 +4599,7 @@ export function RpgEntryHomeView({
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmittingRechargeProductId(null);
|
||||
if (payResult.status === 'success') {
|
||||
setRechargePaymentResult({
|
||||
kind: 'pending',
|
||||
@@ -4635,10 +4649,13 @@ export function RpgEntryHomeView({
|
||||
});
|
||||
refreshRechargeState();
|
||||
} else {
|
||||
const detail = payResult.errorMessage
|
||||
? `微信返回:${payResult.errorMessage}`
|
||||
: '微信支付没有完成,本次不会入账。';
|
||||
setRechargePaymentResult({
|
||||
kind: 'failed',
|
||||
title: '支付未完成',
|
||||
message: '微信支付没有完成,本次不会入账。',
|
||||
message: detail,
|
||||
});
|
||||
refreshRechargeState();
|
||||
}
|
||||
@@ -4679,11 +4696,16 @@ export function RpgEntryHomeView({
|
||||
.then(async (response) => {
|
||||
if (paymentChannel === WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_CHANNEL) {
|
||||
pendingWechatRechargeOrderIdRef.current = response.order.orderId;
|
||||
setRechargeCenter(response.center);
|
||||
setRechargePaymentResult({
|
||||
kind: 'pending',
|
||||
title: '正在支付',
|
||||
message: '请在微信小程序支付页完成支付,返回后会自动刷新状态。',
|
||||
});
|
||||
await requestWechatMiniProgramPayment(
|
||||
response.wechatMiniProgramPayParams,
|
||||
response.order.orderId,
|
||||
);
|
||||
setRechargeCenter(response.center);
|
||||
return;
|
||||
}
|
||||
if (paymentChannel === WECHAT_H5_PAYMENT_CHANNEL) {
|
||||
|
||||
Reference in New Issue
Block a user