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 [created, setCreated] = useState<ProxyOrderCreateResponse | null>(null);
|
||||||
const [codeUrl, setCodeUrl] = useState<string | null>(null);
|
const [codeUrl, setCodeUrl] = useState<string | null>(null);
|
||||||
const [payLoading, setPayLoading] = useState(false);
|
const [payLoading, setPayLoading] = useState(false);
|
||||||
const [mockConfirming, setMockConfirming] = useState(false);
|
|
||||||
const pollRef = useRef<number | null>(null);
|
const pollRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const region = useMemo(() => parseRegionCodes(regionCodes), [regionCodes]);
|
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 =
|
const deliveryLabel =
|
||||||
preview?.deliveryType === 'ON_SITE_PICKUP'
|
preview?.deliveryType === 'ON_SITE_PICKUP'
|
||||||
? '现场提货'
|
? '现场提货'
|
||||||
@@ -253,9 +232,6 @@ export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrder
|
|||||||
step === 'pay' ? (
|
step === 'pay' ? (
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={handleClose}>关闭</Button>
|
<Button onClick={handleClose}>关闭</Button>
|
||||||
<Button type="primary" loading={mockConfirming} onClick={() => void handleMockConfirm()}>
|
|
||||||
模拟支付成功
|
|
||||||
</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
) : (
|
) : (
|
||||||
<Space>
|
<Space>
|
||||||
@@ -285,7 +261,7 @@ export default function ProxyOrderModal({ open, onClose, onSuccess }: ProxyOrder
|
|||||||
) : codeUrl ? (
|
) : codeUrl ? (
|
||||||
<Space direction="vertical" size={12} align="center">
|
<Space direction="vertical" size={12} align="center">
|
||||||
<QRCode value={codeUrl} size={200} />
|
<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' }}>
|
<Typography.Text type="secondary" style={{ fontSize: 12, wordBreak: 'break-all' }}>
|
||||||
{codeUrl}
|
{codeUrl}
|
||||||
</Typography.Text>
|
</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 { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
import PageHeader from '@dukang/shared-ui/PageHeader';
|
import PageHeader from '@dukang/shared-ui/PageHeader';
|
||||||
import type { PartnerProxyOrderListItem } from '@dukang/shared-types';
|
import { invokeWechatPay } from '@dukang/weixin-sdk';
|
||||||
import { request } from '../lib/api';
|
import type { PartnerProxyOrderListItem, ProxyOrderPayResponse, ProxyPayMethod } from '@dukang/shared-types';
|
||||||
|
import { getToken, request } from '../lib/api';
|
||||||
import {
|
import {
|
||||||
proxyOrderPayLabel,
|
proxyOrderPayLabel,
|
||||||
proxyOrderStatusColor,
|
proxyOrderStatusColor,
|
||||||
proxyOrderStatusLabel,
|
proxyOrderStatusLabel,
|
||||||
} from '../lib/proxyOrderStatus';
|
} from '../lib/proxyOrderStatus';
|
||||||
import { toastError, toastSuccess } from '../lib/toast';
|
import { toastError, toastSuccess } from '../lib/toast';
|
||||||
|
import { isWechatEnv } from '../lib/weixin';
|
||||||
|
import { fetchPartnerProfile, partnerHasWechatBinding } from '../lib/wechat-auth';
|
||||||
|
|
||||||
function fmtMoney(n: number) {
|
function fmtMoney(n: number) {
|
||||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
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 [track, setTrack] = useState<TrackResult | null>(null);
|
||||||
const [trackError, setTrackError] = useState('');
|
const [trackError, setTrackError] = useState('');
|
||||||
const [paying, setPaying] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
document.title = '代下单详情';
|
document.title = '代下单详情';
|
||||||
@@ -75,28 +93,113 @@ export default function ProxyOrderDetailPage() {
|
|||||||
.catch((e) => setTrackError(e instanceof Error ? e.message : '物流暂不可用'));
|
.catch((e) => setTrackError(e instanceof Error ? e.message : '物流暂不可用'));
|
||||||
}, [id, order]);
|
}, [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;
|
if (!id) return;
|
||||||
setPaying(true);
|
setPaying(true);
|
||||||
|
setPayMsg('');
|
||||||
|
setPayMethod(method);
|
||||||
try {
|
try {
|
||||||
await request('PARTNER_H5', `/partner/proxy-orders/${id}/pay/mock-confirm`, {
|
if (method === 'JSAPI') {
|
||||||
method: 'POST',
|
if (!isWechatEnv()) {
|
||||||
body: '{}',
|
throw new Error('请在微信内打开合伙人端以使用微信代付');
|
||||||
silent: true,
|
}
|
||||||
});
|
const profile = await fetchPartnerProfile();
|
||||||
toastSuccess('支付成功');
|
if (!partnerHasWechatBinding(profile)) {
|
||||||
const refreshed = await request<PartnerProxyOrderListItem>(
|
throw new Error('请先绑定微信后再代付');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pay = await request<ProxyOrderPayResponse>(
|
||||||
'PARTNER_H5',
|
'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) {
|
} catch (e) {
|
||||||
|
setPayMsg(e instanceof Error ? e.message : '支付失败');
|
||||||
toastError(e instanceof Error ? e.message : '支付失败');
|
toastError(e instanceof Error ? e.message : '支付失败');
|
||||||
} finally {
|
} finally {
|
||||||
setPaying(false);
|
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 img = order?.imageResource?.url || '';
|
||||||
const isOnSite = String(order?.deliveryType || '').toUpperCase() === 'ON_SITE_PICKUP';
|
const isOnSite = String(order?.deliveryType || '').toUpperCase() === 'ON_SITE_PICKUP';
|
||||||
|
|
||||||
@@ -175,16 +278,67 @@ export default function ProxyOrderDetailPage() {
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{order.payStatus === 'UNPAID' || order.status === 'PENDING_PAY' ? (
|
{unpaid ? (
|
||||||
<button
|
<section className="partner-form-card" style={{ marginTop: 12 }}>
|
||||||
type="button"
|
<h3 className="headline-md" style={{ marginBottom: 12 }}>继续支付</h3>
|
||||||
className="btn btn-primary btn-block"
|
<div className="partner-proxy-mode-tabs">
|
||||||
style={{ marginTop: 16 }}
|
<button
|
||||||
disabled={paying}
|
type="button"
|
||||||
onClick={() => void continuePay()}
|
className={`partner-proxy-mode-tab${payMethod === 'NATIVE' ? ' is-active' : ''}`}
|
||||||
>
|
onClick={() => void startPay('NATIVE')}
|
||||||
{paying ? '处理中…' : '继续支付(模拟)'}
|
>
|
||||||
</button>
|
收款码
|
||||||
|
</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}
|
) : null}
|
||||||
|
|
||||||
{!isOnSite && order.payStatus === 'PAID' ? (
|
{!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 selectedProduct = options?.products.find((p) => p.id === productId);
|
||||||
const deliveryLabel =
|
const deliveryLabel =
|
||||||
preview?.deliveryType === 'ON_SITE_PICKUP'
|
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">{paying ? '生成收款码中…' : '暂无收款码'}</p>
|
||||||
)}
|
)}
|
||||||
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
<p className="label-md text-muted" style={{ marginTop: 8 }}>
|
||||||
请使用微信扫码支付
|
请使用微信扫码支付,支付成功后自动跳转
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary btn-block"
|
className="btn btn-block"
|
||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 16 }}
|
||||||
disabled={paying}
|
disabled={paying}
|
||||||
onClick={() => void handleMockConfirm()}
|
onClick={() => void startPay(created.id, 'NATIVE')}
|
||||||
>
|
>
|
||||||
{paying ? '处理中…' : '模拟支付成功'}
|
{paying ? '生成中…' : '重新生成收款码'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
<p className="label-md text-muted">将调起微信支付(本地 Mock 可能直接入账)</p>
|
<p className="label-md text-muted">将调起微信支付,由合伙人微信完成代付</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary btn-block"
|
className="btn btn-primary btn-block"
|
||||||
@@ -406,15 +387,6 @@ export default function ProxyOrderPage() {
|
|||||||
>
|
>
|
||||||
{paying ? '支付中…' : '重新调起微信代付'}
|
{paying ? '支付中…' : '重新调起微信代付'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-block"
|
|
||||||
style={{ marginTop: 8 }}
|
|
||||||
disabled={paying}
|
|
||||||
onClick={() => void handleMockConfirm()}
|
|
||||||
>
|
|
||||||
模拟支付成功
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user