fix(proxy-order): remove mock pay button, use real WeChat pay
This commit is contained in:
@@ -1,14 +1,18 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import QRCode from 'qrcode';
|
||||
import PageHeader from '@dukang/shared-ui/PageHeader';
|
||||
import type { PartnerProxyOrderListItem } from '@dukang/shared-types';
|
||||
import { request } from '../lib/api';
|
||||
import { invokeWechatPay } from '@dukang/weixin-sdk';
|
||||
import type { PartnerProxyOrderListItem, ProxyOrderPayResponse, ProxyPayMethod } from '@dukang/shared-types';
|
||||
import { getToken, request } from '../lib/api';
|
||||
import {
|
||||
proxyOrderPayLabel,
|
||||
proxyOrderStatusColor,
|
||||
proxyOrderStatusLabel,
|
||||
} from '../lib/proxyOrderStatus';
|
||||
import { toastError, toastSuccess } from '../lib/toast';
|
||||
import { isWechatEnv } from '../lib/weixin';
|
||||
import { fetchPartnerProfile, partnerHasWechatBinding } from '../lib/wechat-auth';
|
||||
|
||||
function fmtMoney(n: number) {
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
@@ -51,6 +55,20 @@ export default function ProxyOrderDetailPage() {
|
||||
const [track, setTrack] = useState<TrackResult | null>(null);
|
||||
const [trackError, setTrackError] = useState('');
|
||||
const [paying, setPaying] = useState(false);
|
||||
const [payMsg, setPayMsg] = useState('');
|
||||
const [payMethod, setPayMethod] = useState<ProxyPayMethod>('NATIVE');
|
||||
const [codeUrl, setCodeUrl] = useState<string | null>(null);
|
||||
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
|
||||
const pollRef = useRef<number | null>(null);
|
||||
|
||||
function stopPoll() {
|
||||
if (pollRef.current != null) {
|
||||
window.clearInterval(pollRef.current);
|
||||
pollRef.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => () => stopPoll(), []);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = '代下单详情';
|
||||
@@ -75,28 +93,113 @@ export default function ProxyOrderDetailPage() {
|
||||
.catch((e) => setTrackError(e instanceof Error ? e.message : '物流暂不可用'));
|
||||
}, [id, order]);
|
||||
|
||||
async function continuePay() {
|
||||
useEffect(() => {
|
||||
if (!codeUrl) {
|
||||
setQrDataUrl(null);
|
||||
return;
|
||||
}
|
||||
void QRCode.toDataURL(codeUrl, { width: 220, margin: 1 })
|
||||
.then(setQrDataUrl)
|
||||
.catch(() => setQrDataUrl(null));
|
||||
}, [codeUrl]);
|
||||
|
||||
const unpaid = order?.payStatus === 'UNPAID' || order?.status === 'PENDING_PAY';
|
||||
|
||||
function startPoll(orderId: string) {
|
||||
stopPoll();
|
||||
pollRef.current = window.setInterval(() => {
|
||||
void request<{ payStatus: string; orderNo: string }>(
|
||||
'PARTNER_H5',
|
||||
`/partner/proxy-orders/${orderId}/pay-status`,
|
||||
{ silent: true },
|
||||
)
|
||||
.then((st) => {
|
||||
if (st.payStatus === 'PAID') {
|
||||
stopPoll();
|
||||
toastSuccess(`支付成功:${st.orderNo}`);
|
||||
void request<PartnerProxyOrderListItem>('PARTNER_H5', `/partner/proxy-orders/${orderId}`)
|
||||
.then(setOrder)
|
||||
.catch(() => undefined);
|
||||
setCodeUrl(null);
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async function startPay(method: ProxyPayMethod) {
|
||||
if (!id) return;
|
||||
setPaying(true);
|
||||
setPayMsg('');
|
||||
setPayMethod(method);
|
||||
try {
|
||||
await request('PARTNER_H5', `/partner/proxy-orders/${id}/pay/mock-confirm`, {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
silent: true,
|
||||
});
|
||||
toastSuccess('支付成功');
|
||||
const refreshed = await request<PartnerProxyOrderListItem>(
|
||||
if (method === 'JSAPI') {
|
||||
if (!isWechatEnv()) {
|
||||
throw new Error('请在微信内打开合伙人端以使用微信代付');
|
||||
}
|
||||
const profile = await fetchPartnerProfile();
|
||||
if (!partnerHasWechatBinding(profile)) {
|
||||
throw new Error('请先绑定微信后再代付');
|
||||
}
|
||||
}
|
||||
|
||||
const pay = await request<ProxyOrderPayResponse>(
|
||||
'PARTNER_H5',
|
||||
`/partner/proxy-orders/${id}`,
|
||||
`/partner/proxy-orders/${id}/pay`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ payMethod: method }),
|
||||
silent: true,
|
||||
},
|
||||
);
|
||||
setOrder(refreshed);
|
||||
|
||||
if (pay.mode === 'mock') {
|
||||
toastSuccess('支付成功');
|
||||
const refreshed = await request<PartnerProxyOrderListItem>(
|
||||
'PARTNER_H5',
|
||||
`/partner/proxy-orders/${id}`,
|
||||
);
|
||||
setOrder(refreshed);
|
||||
setCodeUrl(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pay.mode === 'native' && pay.codeUrl) {
|
||||
setCodeUrl(pay.codeUrl);
|
||||
startPoll(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pay.mode === 'jsapi' && pay.prepay) {
|
||||
setCodeUrl(null);
|
||||
await invokeWechatPay(pay.prepay, {
|
||||
apiBase: '/api/v1',
|
||||
clientApp: 'PARTNER_H5',
|
||||
getAccessToken: getToken,
|
||||
platform: 'wechat-h5',
|
||||
});
|
||||
startPoll(id);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('支付发起失败');
|
||||
} catch (e) {
|
||||
setPayMsg(e instanceof Error ? e.message : '支付失败');
|
||||
toastError(e instanceof Error ? e.message : '支付失败');
|
||||
} finally {
|
||||
setPaying(false);
|
||||
}
|
||||
}
|
||||
|
||||
const autoPayStartedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!id || !unpaid || autoPayStartedRef.current) return;
|
||||
autoPayStartedRef.current = true;
|
||||
void startPay('NATIVE');
|
||||
// 仅首次进入未支付详情时自动拉收款码
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, unpaid]);
|
||||
|
||||
const img = order?.imageResource?.url || '';
|
||||
const isOnSite = String(order?.deliveryType || '').toUpperCase() === 'ON_SITE_PICKUP';
|
||||
|
||||
@@ -175,16 +278,67 @@ export default function ProxyOrderDetailPage() {
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{order.payStatus === 'UNPAID' || order.status === 'PENDING_PAY' ? (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-block"
|
||||
style={{ marginTop: 16 }}
|
||||
disabled={paying}
|
||||
onClick={() => void continuePay()}
|
||||
>
|
||||
{paying ? '处理中…' : '继续支付(模拟)'}
|
||||
</button>
|
||||
{unpaid ? (
|
||||
<section className="partner-form-card" style={{ marginTop: 12 }}>
|
||||
<h3 className="headline-md" style={{ marginBottom: 12 }}>继续支付</h3>
|
||||
<div className="partner-proxy-mode-tabs">
|
||||
<button
|
||||
type="button"
|
||||
className={`partner-proxy-mode-tab${payMethod === 'NATIVE' ? ' is-active' : ''}`}
|
||||
onClick={() => void startPay('NATIVE')}
|
||||
>
|
||||
收款码
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`partner-proxy-mode-tab${payMethod === 'JSAPI' ? ' is-active' : ''}`}
|
||||
onClick={() => void startPay('JSAPI')}
|
||||
>
|
||||
微信代付
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{payMethod === 'NATIVE' ? (
|
||||
<div style={{ textAlign: 'center', marginTop: 16 }}>
|
||||
{qrDataUrl ? (
|
||||
<img src={qrDataUrl} alt="收款码" width={220} height={220} style={{ margin: '0 auto' }} />
|
||||
) : (
|
||||
<p className="label-md text-muted">{paying ? '生成收款码中…' : '暂无收款码'}</p>
|
||||
)}
|
||||
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||
请使用微信扫码支付,支付成功后自动更新
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-block"
|
||||
style={{ marginTop: 12 }}
|
||||
disabled={paying}
|
||||
onClick={() => void startPay('NATIVE')}
|
||||
>
|
||||
{paying ? '生成中…' : '重新生成收款码'}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<p className="label-md text-muted">将调起微信支付,由合伙人微信完成代付</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-block"
|
||||
style={{ marginTop: 12 }}
|
||||
disabled={paying}
|
||||
onClick={() => void startPay('JSAPI')}
|
||||
>
|
||||
{paying ? '支付中…' : '调起微信代付'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{payMsg ? (
|
||||
<p className="partner-form-error" role="alert" style={{ marginTop: 12 }}>
|
||||
{payMsg}
|
||||
</p>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{!isOnSite && order.payStatus === 'PAID' ? (
|
||||
|
||||
Reference in New Issue
Block a user