fix mini program payment bridge
This commit is contained in:
@@ -117,6 +117,7 @@
|
|||||||
2. 弹窗顶部标题为 `账户充值`,右上角关闭。
|
2. 弹窗顶部标题为 `账户充值`,右上角关闭。
|
||||||
3. 默认打开 `泥点充值`,可切换到 `会员卡充值`。
|
3. 默认打开 `泥点充值`,可切换到 `会员卡充值`。
|
||||||
4. 点击套餐后调用下单接口,按钮进入处理中状态;小程序环境走 native 支付页拉起 `wx.requestPayment`,支付页返回后刷新 `profileDashboard`。
|
4. 点击套餐后调用下单接口,按钮进入处理中状态;小程序环境走 native 支付页拉起 `wx.requestPayment`,支付页返回后刷新 `profileDashboard`。
|
||||||
|
- 小程序 web-view 内的 H5 只负责加载微信 JS-SDK 并通过 `wx.miniProgram.navigateTo` 跳转到 `/pages/wechat-pay/index`;实际支付必须在小程序 native 页调用 `wx.requestPayment`,不要切换为 H5 支付产品。
|
||||||
5. 弹窗内不写大段说明文案,只保留必要金额、泥点、会员权益和状态反馈。
|
5. 弹窗内不写大段说明文案,只保留必要金额、泥点、会员权益和状态反馈。
|
||||||
6. 会员卡充值区以套餐卡片优先展示周期、价格和处理状态;移动端单列,桌面端三列,权益表允许横向滚动,避免小屏挤压。
|
6. 会员卡充值区以套餐卡片优先展示周期、价格和处理状态;移动端单列,桌面端三列,权益表允许横向滚动,避免小屏挤压。
|
||||||
|
|
||||||
|
|||||||
@@ -859,6 +859,10 @@ afterEach(() => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
vi.unstubAllEnvs();
|
vi.unstubAllEnvs();
|
||||||
vi.unstubAllGlobals();
|
vi.unstubAllGlobals();
|
||||||
|
window.wx = undefined;
|
||||||
|
document
|
||||||
|
.querySelectorAll('script[src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"]')
|
||||||
|
.forEach((script) => script.remove());
|
||||||
mockGetRpgProfileReferralInviteCenter.mockResolvedValue(
|
mockGetRpgProfileReferralInviteCenter.mockResolvedValue(
|
||||||
mockBuildReferralCenter(),
|
mockBuildReferralCenter(),
|
||||||
);
|
);
|
||||||
@@ -1044,6 +1048,84 @@ test('profile recharge modal posts requestPayment params in mini program web-vie
|
|||||||
expect(await screen.findByText('支付已提交')).toBeTruthy();
|
expect(await screen.findByText('支付已提交')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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'));
|
||||||
|
});
|
||||||
|
mockCreateRpgProfileRechargeOrder.mockResolvedValueOnce({
|
||||||
|
order: {
|
||||||
|
orderId: 'order-wechat-sdk-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',
|
||||||
|
signType: 'RSA',
|
||||||
|
paySign: 'signature',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
renderProfileView();
|
||||||
|
const shortcutRegion = screen.getByRole('region', { name: '常用功能' });
|
||||||
|
await user.click(
|
||||||
|
within(shortcutRegion).getByRole('button', { name: /充值/u }),
|
||||||
|
);
|
||||||
|
await user.click(await screen.findByRole('button', { name: /60泥点/u }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const script = document.querySelector<HTMLScriptElement>(
|
||||||
|
'script[src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"]',
|
||||||
|
);
|
||||||
|
expect(script).toBeTruthy();
|
||||||
|
window.wx = {
|
||||||
|
miniProgram: {
|
||||||
|
navigateTo,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
script?.dispatchEvent(new Event('load'));
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(navigateTo).toHaveBeenCalledWith({
|
||||||
|
url: expect.stringContaining('/pages/wechat-pay/index?'),
|
||||||
|
fail: expect.any(Function),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(await screen.findByText('支付已提交')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
test('profile daily task shortcut opens task center and claims reward', async () => {
|
test('profile daily task shortcut opens task center and claims reward', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const onRechargeSuccess = vi.fn();
|
const onRechargeSuccess = vi.fn();
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ const RECOMMEND_ENTRY_SWIPE_THRESHOLD_PX = 36;
|
|||||||
const RECOMMEND_ENTRY_COMMIT_ANIMATION_MS = 180;
|
const RECOMMEND_ENTRY_COMMIT_ANIMATION_MS = 180;
|
||||||
const RECOMMEND_ENTRY_DRAG_LIMIT_PX = 160;
|
const RECOMMEND_ENTRY_DRAG_LIMIT_PX = 160;
|
||||||
const WECHAT_MINI_PROGRAM_PAYMENT_CHANNEL = 'wechat_mp';
|
const WECHAT_MINI_PROGRAM_PAYMENT_CHANNEL = 'wechat_mp';
|
||||||
|
const WECHAT_JS_SDK_URL = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js';
|
||||||
|
|
||||||
type ProfilePopupPanel = 'invite' | 'redeem' | 'community';
|
type ProfilePopupPanel = 'invite' | 'redeem' | 'community';
|
||||||
type RechargeTab = 'points' | 'membership';
|
type RechargeTab = 'points' | 'membership';
|
||||||
@@ -2341,16 +2342,56 @@ function clearWechatPayResultHash() {
|
|||||||
window.history.replaceState(null, '', nextUrl);
|
window.history.replaceState(null, '', nextUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestWechatMiniProgramPayment(
|
function loadWechatJsSdk() {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return Promise.reject(new Error('请在微信小程序内完成支付'));
|
||||||
|
}
|
||||||
|
if (window.wx?.miniProgram?.navigateTo) {
|
||||||
|
return Promise.resolve(window.wx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<NonNullable<Window['wx']>>((resolve, reject) => {
|
||||||
|
const existingScript = document.querySelector<HTMLScriptElement>(
|
||||||
|
`script[src="${WECHAT_JS_SDK_URL}"]`,
|
||||||
|
);
|
||||||
|
const complete = () => {
|
||||||
|
if (window.wx?.miniProgram?.navigateTo) {
|
||||||
|
resolve(window.wx);
|
||||||
|
} else {
|
||||||
|
reject(new Error('请在微信小程序内完成支付'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existingScript) {
|
||||||
|
existingScript.addEventListener('load', complete, { once: true });
|
||||||
|
existingScript.addEventListener(
|
||||||
|
'error',
|
||||||
|
() => reject(new Error('请在微信小程序内完成支付')),
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = WECHAT_JS_SDK_URL;
|
||||||
|
script.async = true;
|
||||||
|
script.onload = complete;
|
||||||
|
script.onerror = () => reject(new Error('请在微信小程序内完成支付'));
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestWechatMiniProgramPayment(
|
||||||
payload: WechatMiniProgramPayParams | null | undefined,
|
payload: WechatMiniProgramPayParams | null | undefined,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
) {
|
) {
|
||||||
const miniProgram = window.wx?.miniProgram;
|
if (!payload) {
|
||||||
if (
|
return Promise.reject(new Error('请在微信小程序内完成支付'));
|
||||||
!payload ||
|
}
|
||||||
!miniProgram ||
|
const wxBridge = await loadWechatJsSdk();
|
||||||
typeof miniProgram.navigateTo !== 'function'
|
const miniProgram = wxBridge.miniProgram;
|
||||||
) {
|
if (!miniProgram || typeof miniProgram.navigateTo !== 'function') {
|
||||||
return Promise.reject(new Error('请在微信小程序内完成支付'));
|
return Promise.reject(new Error('请在微信小程序内完成支付'));
|
||||||
}
|
}
|
||||||
const navigateTo = miniProgram.navigateTo;
|
const navigateTo = miniProgram.navigateTo;
|
||||||
|
|||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -14,4 +14,5 @@ interface Window {
|
|||||||
postMessage?: (message: unknown) => void;
|
postMessage?: (message: unknown) => void;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
WeixinJSBridge?: unknown;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user