fix: 修复微信支付回跳刷新与查单确认

This commit is contained in:
2026-05-14 23:52:01 +08:00
parent cf3dcc6195
commit 2801b55d2f
21 changed files with 880 additions and 119 deletions

View File

@@ -17,6 +17,7 @@ import type {
PublicUserSummary,
} from '../../../packages/shared/src/contracts/auth';
import type {
ConfirmWechatProfileRechargeOrderResponse,
CreateProfileRechargeOrderResponse,
ProfileReferralInviteCenterResponse,
ProfileTaskCenterResponse,
@@ -39,6 +40,7 @@ const {
mockBuildReferralCenter,
mockBuildTaskCenter,
mockClaimRpgProfileTaskReward,
mockConfirmWechatRpgProfileRechargeOrder,
mockCreateRpgProfileRechargeOrder,
mockGetRpgProfileReferralInviteCenter,
mockGetRpgProfileRechargeCenter,
@@ -219,6 +221,65 @@ const {
},
}),
),
mockConfirmWechatRpgProfileRechargeOrder: vi.fn(
async (): Promise<ConfirmWechatProfileRechargeOrderResponse> => ({
order: {
orderId: 'order-wechat-1',
productId: 'points_60',
productTitle: '60泥点',
kind: 'points',
amountCents: 600,
status: 'paid',
paymentChannel: 'wechat_mp',
paidAt: '2026-04-25T10:01:00Z',
providerTransactionId: 'wx-transaction-1',
createdAt: '2026-04-25T10:00:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
center: {
walletBalance: 120,
membership: {
status: 'normal',
tier: 'normal',
startedAt: null,
expiresAt: null,
updatedAt: null,
},
pointProducts: [
{
productId: 'points_60',
title: '60泥点',
priceCents: 600,
kind: 'points',
pointsAmount: 60,
bonusPoints: 0,
durationDays: 0,
badgeLabel: '',
description: '60泥点',
tier: 'normal',
},
],
membershipProducts: [],
benefits: [],
latestOrder: {
orderId: 'order-wechat-1',
productId: 'points_60',
productTitle: '60泥点',
kind: 'points',
amountCents: 600,
status: 'paid',
paymentChannel: 'wechat_mp',
providerTransactionId: 'wx-transaction-1',
createdAt: '2026-04-25T10:00:00Z',
paidAt: '2026-04-25T10:01:00Z',
pointsDelta: 120,
membershipExpiresAt: null,
},
hasPointsRecharged: true,
},
}),
),
mockRedeemRpgProfileReferralInviteCode: vi.fn(async () => ({
center: buildReferralCenter({
invitedUsers: [],
@@ -303,6 +364,8 @@ vi.mock('../../services/rpg-entry/rpgProfileClient', () => ({
redeemRpgProfileReferralInviteCode: mockRedeemRpgProfileReferralInviteCode,
getRpgProfileRechargeCenter: mockGetRpgProfileRechargeCenter,
createRpgProfileRechargeOrder: mockCreateRpgProfileRechargeOrder,
confirmWechatRpgProfileRechargeOrder:
mockConfirmWechatRpgProfileRechargeOrder,
}));
vi.mock('../ResolvedAssetImage', () => ({
@@ -975,11 +1038,8 @@ test('profile recharge modal buys points through mock channel outside mini progr
test('profile recharge modal posts requestPayment params in mini program web-view', async () => {
const user = userEvent.setup();
window.history.replaceState(null, '', '/?clientRuntime=wechat_mini_program');
const navigateTo = vi.fn((options: { url: string }) => {
const url = new URL(`https://mini.test${options.url}`);
const requestId = url.searchParams.get('requestId');
window.location.hash = `wx_pay_result=${requestId}:success`;
window.dispatchEvent(new HashChangeEvent('hashchange'));
const navigateTo = vi.fn((options: { url: string; success?: () => void }) => {
options.success?.();
});
window.wx = {
miniProgram: {
@@ -1040,23 +1100,32 @@ test('profile recharge modal posts requestPayment params in mini program web-vie
});
expect(navigateTo).toHaveBeenCalledWith({
url: expect.stringContaining('/pages/wechat-pay/index?'),
success: expect.any(Function),
fail: expect.any(Function),
});
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}:success`;
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
expect(navigateUrl).toContain('order-wechat-1');
expect(decodeURIComponent(navigateUrl)).toContain('prepay_id=wx-prepay');
expect(await screen.findByText('支付已提交')).toBeTruthy();
expect(await screen.findByText('已到账')).toBeTruthy();
expect(mockConfirmWechatRpgProfileRechargeOrder).toHaveBeenCalledWith(
'order-wechat-1',
);
});
test('profile recharge modal loads wechat js sdk before mini program payment bridge', async () => {
const user = userEvent.setup();
window.history.replaceState(null, '', '/?clientRuntime=wechat_mini_program');
window.wx = undefined;
const navigateTo = vi.fn((options: { url: string }) => {
const url = new URL(`https://mini.test${options.url}`);
const requestId = url.searchParams.get('requestId');
window.location.hash = `wx_pay_result=${requestId}:success`;
window.dispatchEvent(new HashChangeEvent('hashchange'));
const navigateTo = vi.fn((options: { url: string; success?: () => void }) => {
options.success?.();
});
mockCreateRpgProfileRechargeOrder.mockResolvedValueOnce({
order: {
@@ -1120,10 +1189,110 @@ test('profile recharge modal loads wechat js sdk before mini program payment bri
await waitFor(() => {
expect(navigateTo).toHaveBeenCalledWith({
url: expect.stringContaining('/pages/wechat-pay/index?'),
success: expect.any(Function),
fail: expect.any(Function),
});
});
expect(await screen.findByText('支付已提交')).toBeTruthy();
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}:success`;
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
expect(await screen.findByText('已到账')).toBeTruthy();
});
test('profile recharge modal releases submitting state after cancelled wechat pay result', 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-cancel-1',
productId: 'points_60',
productTitle: '60泥点',
kind: 'points',
amountCents: 600,
status: 'pending' as const,
paymentChannel: 'wechat_mp',
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: {
timeStamp: '1777110165',
nonceStr: 'nonce',
package: 'prepay_id=wx-prepay-cancel',
signType: 'RSA',
paySign: 'signature',
},
});
renderProfileView();
const shortcutRegion = screen.getByRole('region', { name: '常用功能' });
await user.click(
within(shortcutRegion).getByRole('button', { name: //u }),
);
const buyButton = await screen.findByRole('button', { name: /60/u });
await user.click(buyButton);
await waitFor(() => {
expect(mockCreateRpgProfileRechargeOrder).toHaveBeenCalledWith(
'points_60',
'wechat_mp',
);
});
expect(
within(buyButton).getByText('处理中', { selector: 'span' }),
).toBeTruthy();
const requestUrl = navigateTo.mock.calls[0]?.[0].url ?? '';
const requestId = new URL(`https://mini.test${requestUrl}`).searchParams.get(
'requestId',
);
expect(requestId).toBeTruthy();
act(() => {
window.location.hash = `wx_pay_result=${requestId}:cancel`;
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
expect(await screen.findByText('支付已取消')).toBeTruthy();
await waitFor(() => {
expect(
within(screen.getByRole('button', { name: /60/u })).getByText(
'购买',
{ selector: 'span' },
),
).toBeTruthy();
});
expect(mockConfirmWechatRpgProfileRechargeOrder).not.toHaveBeenCalled();
});
test('profile daily task shortcut opens task center and claims reward', async () => {