fix(proxy-order): remove mock pay button, use real WeChat pay
This commit is contained in:
@@ -63,7 +63,6 @@ export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrder
|
||||
const [created, setCreated] = useState<ProxyOrderCreateResponse | null>(null);
|
||||
const [codeUrl, setCodeUrl] = useState<string | null>(null);
|
||||
const [payLoading, setPayLoading] = useState(false);
|
||||
const [mockConfirming, setMockConfirming] = useState(false);
|
||||
const pollRef = useRef<number | null>(null);
|
||||
|
||||
const region = useMemo(() => parseRegionCodes(regionCodes), [regionCodes]);
|
||||
@@ -215,26 +214,6 @@ export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrder
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMockConfirm() {
|
||||
if (!created) return;
|
||||
setMockConfirming(true);
|
||||
try {
|
||||
const st = await request<PayStatus>(`/admin/proxy-orders/${created.id}/pay/mock-confirm`, {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
});
|
||||
stopPoll();
|
||||
message.success(`支付成功:${st.orderNo}`);
|
||||
const payload = { id: st.id, orderNo: st.orderNo };
|
||||
resetForm();
|
||||
onSuccess(payload);
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '模拟支付失败');
|
||||
} finally {
|
||||
setMockConfirming(false);
|
||||
}
|
||||
}
|
||||
|
||||
const deliveryLabel =
|
||||
preview?.deliveryType === 'ON_SITE_PICKUP'
|
||||
? '现场提货'
|
||||
@@ -253,9 +232,6 @@ export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrder
|
||||
step === 'pay' ? (
|
||||
<Space>
|
||||
<Button onClick={handleClose}>关闭</Button>
|
||||
<Button type="primary" loading={mockConfirming} onClick={() => void handleMockConfirm()}>
|
||||
模拟支付成功
|
||||
</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
@@ -285,7 +261,7 @@ export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrder
|
||||
) : codeUrl ? (
|
||||
<Space direction="vertical" size={12} align="center">
|
||||
<QRCode value={codeUrl} size={200} />
|
||||
<Typography.Text type="secondary">请使用微信扫一扫完成支付(本地可用下方模拟支付)</Typography.Text>
|
||||
<Typography.Text type="secondary">请使用微信扫一扫完成支付,成功后自动关闭</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12, wordBreak: 'break-all' }}>
|
||||
{codeUrl}
|
||||
</Typography.Text>
|
||||
|
||||
@@ -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`, {
|
||||
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}/pay`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
body: JSON.stringify({ payMethod: method }),
|
||||
silent: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
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' ? (
|
||||
{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: 16 }}
|
||||
style={{ marginTop: 12 }}
|
||||
disabled={paying}
|
||||
onClick={() => void continuePay()}
|
||||
onClick={() => void startPay('JSAPI')}
|
||||
>
|
||||
{paying ? '处理中…' : '继续支付(模拟)'}
|
||||
{paying ? '支付中…' : '调起微信代付'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{payMsg ? (
|
||||
<p className="partner-form-error" role="alert" style={{ marginTop: 12 }}>
|
||||
{payMsg}
|
||||
</p>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{!isOnSite && order.payStatus === 'PAID' ? (
|
||||
|
||||
@@ -305,25 +305,6 @@ export default function ProxyOrderPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMockConfirm() {
|
||||
if (!created) return;
|
||||
setPaying(true);
|
||||
try {
|
||||
await request('PARTNER_H5', `/partner/proxy-orders/${created.id}/pay/mock-confirm`, {
|
||||
method: 'POST',
|
||||
body: '{}',
|
||||
silent: true,
|
||||
});
|
||||
stopPoll();
|
||||
toastSuccess('支付成功');
|
||||
navigate(`/center/proxy-orders/${created.id}`, { replace: true });
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '模拟支付失败');
|
||||
} finally {
|
||||
setPaying(false);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedProduct = options?.products.find((p) => p.id === productId);
|
||||
const deliveryLabel =
|
||||
preview?.deliveryType === 'ON_SITE_PICKUP'
|
||||
@@ -382,21 +363,21 @@ export default function ProxyOrderPage() {
|
||||
<p className="label-md text-muted">{paying ? '生成收款码中…' : '暂无收款码'}</p>
|
||||
)}
|
||||
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||
请使用微信扫码支付
|
||||
请使用微信扫码支付,支付成功后自动跳转
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-block"
|
||||
className="btn btn-block"
|
||||
style={{ marginTop: 16 }}
|
||||
disabled={paying}
|
||||
onClick={() => void handleMockConfirm()}
|
||||
onClick={() => void startPay(created.id, 'NATIVE')}
|
||||
>
|
||||
{paying ? '处理中…' : '模拟支付成功'}
|
||||
{paying ? '生成中…' : '重新生成收款码'}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<p className="label-md text-muted">将调起微信支付(本地 Mock 可能直接入账)</p>
|
||||
<p className="label-md text-muted">将调起微信支付,由合伙人微信完成代付</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-block"
|
||||
@@ -406,15 +387,6 @@ export default function ProxyOrderPage() {
|
||||
>
|
||||
{paying ? '支付中…' : '重新调起微信代付'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-block"
|
||||
style={{ marginTop: 8 }}
|
||||
disabled={paying}
|
||||
onClick={() => void handleMockConfirm()}
|
||||
>
|
||||
模拟支付成功
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user