feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代
This commit is contained in:
@@ -1,239 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import type { WechatPayOrderResult } from '@dukang/shared-types';
|
||||
import SubPageHeader from '../components/SubPageHeader';
|
||||
import PhoneVerifySheet from '../components/PhoneVerifySheet';
|
||||
import { request } from '../lib/api';
|
||||
import { buildOrderConfirmUrl } from '../lib/navigation';
|
||||
import {
|
||||
authorizeWechatForPay,
|
||||
fetchClientConfig,
|
||||
fetchUserProfile,
|
||||
isWechatAuthRequiredError,
|
||||
needsWechatAuthForPay,
|
||||
saveWechatLoginResult,
|
||||
} from '../lib/pay-wechat';
|
||||
import { applyWechatLoginResult, handleWechatAuthCallback } from '../lib/wechat-auth';
|
||||
import { isWechatEnv } from '../lib/weixin';
|
||||
import { usePageView } from '../lib/usePageView';
|
||||
import { track } from '../lib/analytics';
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function waitOrderPaid(orderId: string, maxAttempts = 15) {
|
||||
for (let i = 0; i < maxAttempts; i += 1) {
|
||||
const order = await request<{ payStatus?: string }>('USER_H5', `/trade/orders/${orderId}`);
|
||||
if (order.payStatus === 'PAID') return true;
|
||||
await sleep(2000);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default function PayPage() {
|
||||
const [params] = useSearchParams();
|
||||
const orderId = params.get('orderId') || '';
|
||||
usePageView('pay_page_view', { orderId });
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [authLoading, setAuthLoading] = useState(false);
|
||||
const [mockMode, setMockMode] = useState(true);
|
||||
const [needsWechatAuth, setNeedsWechatAuth] = useState(false);
|
||||
const [msg, setMsg] = useState('');
|
||||
const [showBindPhone, setShowBindPhone] = useState(false);
|
||||
const [wxSessionKey, setWxSessionKey] = useState<string | null>(null);
|
||||
const [orderNo, setOrderNo] = useState('');
|
||||
const [deliveryType, setDeliveryType] = useState('');
|
||||
|
||||
const refreshPayReadiness = useCallback(async () => {
|
||||
try {
|
||||
const [config, profile] = await Promise.all([fetchClientConfig(), fetchUserProfile()]);
|
||||
setMockMode(config.mockPay);
|
||||
setNeedsWechatAuth(needsWechatAuthForPay(config, profile));
|
||||
return profile;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleWechatLoginResult = useCallback(
|
||||
async (result: Parameters<typeof applyWechatLoginResult>[0]) => {
|
||||
if (result.needBindPhone && result.wxSessionKey) {
|
||||
setWxSessionKey(result.wxSessionKey);
|
||||
setShowBindPhone(true);
|
||||
setMsg('');
|
||||
return;
|
||||
}
|
||||
if (saveWechatLoginResult(result)) {
|
||||
setMsg('');
|
||||
await refreshPayReadiness();
|
||||
}
|
||||
},
|
||||
[refreshPayReadiness],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
refreshPayReadiness();
|
||||
}, [refreshPayReadiness]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orderId) {
|
||||
setOrderNo('');
|
||||
return;
|
||||
}
|
||||
request<{ orderNo?: string; deliveryType?: string }>('USER_H5', `/trade/orders/${orderId}`)
|
||||
.then((order) => {
|
||||
setOrderNo(order.orderNo || '');
|
||||
setDeliveryType(order.deliveryType || '');
|
||||
})
|
||||
.catch(() => {
|
||||
setOrderNo('');
|
||||
setDeliveryType('');
|
||||
});
|
||||
}, [orderId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWechatEnv()) return;
|
||||
handleWechatAuthCallback()
|
||||
.then((result) => {
|
||||
if (result) void handleWechatLoginResult(result);
|
||||
})
|
||||
.catch((e) => setMsg(e instanceof Error ? e.message : '微信授权失败'));
|
||||
}, [handleWechatLoginResult]);
|
||||
|
||||
function goBackConfirm() {
|
||||
navigate(
|
||||
buildOrderConfirmUrl({
|
||||
productId: params.get('productId'),
|
||||
qty: params.get('qty'),
|
||||
addressId: params.get('addressId'),
|
||||
cross: params.get('cross'),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function wechatAuthorize() {
|
||||
setAuthLoading(true);
|
||||
setMsg('');
|
||||
try {
|
||||
if (!isWechatEnv()) {
|
||||
setMsg('请在微信内打开以授权微信支付');
|
||||
return;
|
||||
}
|
||||
const result = await authorizeWechatForPay();
|
||||
if (result) await handleWechatLoginResult(result);
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '微信授权失败');
|
||||
} finally {
|
||||
setAuthLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function pay() {
|
||||
if (needsWechatAuth) {
|
||||
setMsg('请先完成微信授权后再支付');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
setMsg('');
|
||||
try {
|
||||
const result = await request<WechatPayOrderResult>('USER_H5', `/trade/orders/${orderId}/pay`, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (result.mode === 'jsapi' && result.prepay) {
|
||||
setMockMode(false);
|
||||
const { weixinSdk } = await import('../lib/weixin');
|
||||
await weixinSdk.pay(result.prepay);
|
||||
const paid = await waitOrderPaid(orderId);
|
||||
if (!paid) {
|
||||
alert('支付结果确认中,请稍后在订单列表查看');
|
||||
}
|
||||
navigate(
|
||||
deliveryType === 'ON_SITE_PICKUP' ? '/orders?tab=completed' : '/orders?tab=paid',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(deliveryType === 'ON_SITE_PICKUP' ? '/orders?tab=completed' : '/orders?tab=paid');
|
||||
} catch (e) {
|
||||
if (isWechatAuthRequiredError(e)) {
|
||||
setNeedsWechatAuth(true);
|
||||
setMsg('微信支付需要先完成微信授权');
|
||||
return;
|
||||
}
|
||||
setMsg(e instanceof Error ? e.message : '支付失败');
|
||||
track('pay_fail', {
|
||||
orderId,
|
||||
failReason: e instanceof Error ? e.message : '支付失败',
|
||||
pagePath: '/pay',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-no-tab pay-page">
|
||||
<SubPageHeader title="微信支付" onBack={goBackConfirm} />
|
||||
<div className="pay-body sub-page-body">
|
||||
<div className="pay-icon">
|
||||
<span className="material-symbols-outlined">account_balance_wallet</span>
|
||||
</div>
|
||||
<p className="headline-lg text-primary">
|
||||
{needsWechatAuth ? '授权微信后可支付' : mockMode ? (isWechatEnv() ? '微信支付' : 'Mock 微信支付') : '微信支付'}
|
||||
</p>
|
||||
<p className="text-muted body-md" style={{ marginTop: 8 }}>
|
||||
{needsWechatAuth
|
||||
? '使用微信支付前,需先授权微信账号以完成付款'
|
||||
: mockMode
|
||||
? 'preV1 Mock 模式:点击确认即完成;开启真实支付后将调起微信收银台'
|
||||
: '请在微信内完成支付,支付成功后自动跳转'}
|
||||
</p>
|
||||
|
||||
{needsWechatAuth && (
|
||||
<div className="pay-wechat-auth-card">
|
||||
<p className="pay-wechat-auth-title">尚未授权微信</p>
|
||||
<p className="pay-wechat-auth-desc">授权后可安全调起微信支付,不会重复扣款</p>
|
||||
<button
|
||||
type="button"
|
||||
className="login-wechat-btn pay-wechat-auth-btn"
|
||||
disabled={authLoading}
|
||||
onClick={wechatAuthorize}
|
||||
>
|
||||
<span className="material-symbols-outlined login-wechat-icon">chat</span>
|
||||
<span>{authLoading ? '授权中...' : '微信一键授权'}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{msg && <p className="pay-wechat-auth-msg">{msg}</p>}
|
||||
{orderNo && <p className="label-md text-muted" style={{ marginTop: 24 }}>订单号 {orderNo}</p>}
|
||||
</div>
|
||||
<div className="page-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-block"
|
||||
disabled={loading || !orderId || needsWechatAuth}
|
||||
onClick={pay}
|
||||
>
|
||||
{loading ? '支付中...' : needsWechatAuth ? '请先授权微信' : '确认支付'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<PhoneVerifySheet
|
||||
open={showBindPhone}
|
||||
mode="wechat_bind_phone"
|
||||
wxSessionKey={wxSessionKey ?? undefined}
|
||||
onClose={() => {
|
||||
setShowBindPhone(false);
|
||||
setWxSessionKey(null);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
void refreshPayReadiness();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user