From cd239afd83ec0d9ae9cde793139f7a7adda35f85 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Tue, 27 May 2025 22:15:34 +0800 Subject: [PATCH] feat: pay cashier --- apps/web-antd/src/views/pay/cashier/index.vue | 295 +++++++++++++++--- .../src/views/pay/cashier/modules/form.vue | 155 --------- 2 files changed, 257 insertions(+), 193 deletions(-) delete mode 100644 apps/web-antd/src/views/pay/cashier/modules/form.vue diff --git a/apps/web-antd/src/views/pay/cashier/index.vue b/apps/web-antd/src/views/pay/cashier/index.vue index df15c9e2c..3fa92eea9 100644 --- a/apps/web-antd/src/views/pay/cashier/index.vue +++ b/apps/web-antd/src/views/pay/cashier/index.vue @@ -5,28 +5,58 @@ import { onMounted, ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { Page, useVbenModal } from '@vben/common-ui'; +import { useTabs } from '@vben/hooks'; import { formatDate } from '@vben/utils'; -import { Card, Descriptions, message } from 'ant-design-vue'; +import { + Button, + Card, + Descriptions, + Input, + message, + QRCode, +} from 'ant-design-vue'; -import { getOrder } from '#/api/pay/order'; -import { PayChannelEnum, PayOrderStatusEnum } from '#/utils/constants'; +import { getOrder, submitOrder } from '#/api/pay/order'; +import { + fenToYuan, + PayChannelEnum, + PayDisplayModeEnum, + PayOrderStatusEnum, +} from '#/utils'; import { channelsAlipay, channelsMock, channelsWechat } from './data'; -import Form from './modules/form.vue'; defineOptions({ name: 'PayCashier' }); -const [FormModal, formModalApi] = useVbenModal({ - connectedComponent: Form, +const [Modal, modalApi] = useVbenModal({ + showConfirmButton: false, destroyOnClose: true, }); -const id = ref(); // 支付单号 -const returnUrl = ref(); // 支付完的回调地址 const route = useRoute(); -const router = useRouter(); +const { push } = useRouter(); // 路由 +const { closeCurrentTab } = useTabs(); + +const id = ref(); // 支付单号 +const title = ref('支付订单'); +const returnUrl = ref(); // 支付完的回调地址 + const payOrder = ref(); +const interval = ref(undefined); // 定时任务,轮询是否完成支付 + +/** 展示形式:二维码 */ +const qrCode = ref({ + url: '', + visible: false, +}); + +/** 展示形式:条形码 */ +const barCode = ref({ + channelCode: '', + value: '', + visible: false, +}); /** 获得支付信息 */ async function getDetail() { @@ -38,57 +68,227 @@ async function getDetail() { // 1.1 未传递订单编号 if (!id.value) { message.error('未传递支付单号,无法查看对应的支付信息'); - // goReturnUrl('cancel') + goReturnUrl('cancel'); return; } const res = await getOrder(id.value); // 1.2 无法查询到支付信息 if (!res) { message.error('支付订单不存在,请检查!'); - // goReturnUrl('cancel') + goReturnUrl('cancel'); return; } // 1.3 如果已支付、或者已关闭,则直接跳转 if (res.status === PayOrderStatusEnum.SUCCESS.status) { message.success('支付成功'); - // goReturnUrl('success') + goReturnUrl('success'); return; } else if (res.status === PayOrderStatusEnum.CLOSED.status) { message.error('无法支付,原因:订单已关闭'); - // goReturnUrl('close') + goReturnUrl('close'); return; } payOrder.value = res; } function handlePay(channelCode: string) { - // 微信公众号、小程序支付,无法在 PC 网页中进行 - if (channelCode === PayChannelEnum.WX_PUB.code) { - message.error('微信公众号支付:不支持 PC 网站'); - return; + switch (channelCode) { + // 条形码支付,需要特殊处理 + case PayChannelEnum.ALIPAY_BAR.code: { + title.value = '“支付宝”条码支付'; + barCode.value = { + channelCode, + value: '', + visible: true, + }; + modalApi.open(); + break; + } + case PayChannelEnum.WX_BAR.code: { + title.value = '“微信”条码支付'; + barCode.value = { + channelCode, + value: '', + visible: true, + }; + modalApi.open(); + break; + } + // 微信公众号、小程序支付,无法在 PC 网页中进行 + case PayChannelEnum.WX_LITE.code: { + message.error('微信小程序:不支持 PC 网站'); + break; + } + case PayChannelEnum.WX_PUB.code: { + message.error('微信公众号支付:不支持 PC 网站'); + break; + } + default: { + submit(channelCode); + break; + } } - if (channelCode === PayChannelEnum.WX_LITE.code) { - message.error('微信小程序:不支持 PC 网站'); - return; - } - const data = { - id: id.value, - channelCode, - returnUrl: location.href, // 支付成功后,支付渠道跳转回当前页;再由当前页,跳转回 {@link returnUrl} 对应的地址 - }; - formModalApi.setData(data).open(); } -function success(data?: { channelCode: string; data?: any }) { - if (!data) { - clearQueryInterval(); +async function submit(channelCode: string) { + try { + const submitParam = { + id: id.value, + channelCode, + returnUrl: location.href, // 支付成功后,支付渠道跳转回当前页;再由当前页,跳转回 {@link returnUrl} 对应的地址 + ...buildSubmitParam(channelCode), + }; + const data = await submitOrder(submitParam); + // 直接返回已支付的情况,例如说扫码支付 + if (data.status === PayOrderStatusEnum.SUCCESS.status) { + clearQueryInterval(); + message.success('支付成功!'); + goReturnUrl('success'); + return; + } + + // 展示对应的界面 + switch (data.displayMode) { + case PayDisplayModeEnum.APP.mode: { + displayApp(channelCode); + break; + } + case PayDisplayModeEnum.QR_CODE.mode: { + displayQrCode(channelCode, data); + break; + } + case PayDisplayModeEnum.URL.mode: { + displayUrl(data); + break; + } + // No default + } + + // 打开轮询任务 + createQueryInterval(); + } finally { + // message.success('支付成功!') } - if (data.channelCode === PayChannelEnum.ALIPAY_BAR.code) { - message.success('支付宝条码支付'); - } else if (data.channelCode === PayChannelEnum.WX_BAR.code) { - message.success('微信条码支付'); +} + +/** 构建提交支付的额外参数 */ +function buildSubmitParam(channelCode: string) { + // ① 支付宝 BarCode 支付时,需要传递 authCode 条形码 + if (channelCode === PayChannelEnum.ALIPAY_BAR.code) { + return { + channelExtras: { + auth_code: barCode.value.value, + }, + }; + } + // ② 微信 BarCode 支付时,需要传递 authCode 条形码 + if (channelCode === PayChannelEnum.WX_BAR.code) { + return { + channelExtras: { + authCode: barCode.value.value, + }, + }; + } + return {}; +} + +/** 提交支付后,URL 的展示形式 */ +function displayUrl(data: any) { + location.href = data.displayContent; +} + +/** 提交支付后(扫码支付) */ +function displayQrCode(channelCode: string, data: any) { + title.value = '请使用手机浏览器“扫一扫”'; + if (channelCode === PayChannelEnum.ALIPAY_WAP.code) { + // 考虑到 WAP 测试,所以引导手机浏览器搞 + } else if (channelCode.indexOf('alipay_') === 0) { + title.value = '请使用支付宝“扫一扫”扫码支付'; + } else if (channelCode.indexOf('wx_') === 0) { + title.value = '请使用微信“扫一扫”扫码支付'; + } + qrCode.value = { + url: data.displayContent, + visible: true, + }; +} + +/** 提交支付后(App) */ +function displayApp(channelCode: string) { + if (channelCode === PayChannelEnum.ALIPAY_APP.code) { + message.error('支付宝 App 支付:无法在网页支付!'); + } + if (channelCode === PayChannelEnum.WX_APP.code) { + message.error('微信 App 支付:无法在网页支付!'); + } +} + +/** 轮询查询任务 */ +function createQueryInterval() { + if (interval.value) { + return; + } + interval.value = setInterval(async () => { + const data = await getOrder(id.value); + // 已支付 + if (data.status === PayOrderStatusEnum.SUCCESS.status) { + clearQueryInterval(); + message.success('支付成功!'); + goReturnUrl('success'); + } + // 已取消 + if (data.status === PayOrderStatusEnum.CLOSED.status) { + clearQueryInterval(); + message.error('支付已关闭!'); + goReturnUrl('close'); + } + }, 1000 * 2); +} + +/** 清空查询任务 */ +function clearQueryInterval() { + // 清空数据 + qrCode.value = { + url: '', + visible: false, + }; + barCode.value = { + channelCode: '', + value: '', + visible: false, + }; + // 清空任务 + clearInterval(interval.value); + interval.value = undefined; +} + +/** + * 回到业务的 URL + * + * @param payResult 支付结果 + * ① success:支付成功 + * ② cancel:取消支付 + * ③ close:支付已关闭 + */ +function goReturnUrl(payResult: string) { + // 清理任务 + clearQueryInterval(); + + // 未配置的情况下,只能关闭 + if (!returnUrl.value) { + closeCurrentTab(); + return; + } + + const url = returnUrl.value.includes('?') + ? `${returnUrl.value}&payResult=${payResult}` + : `${returnUrl.value}?payResult=${payResult}`; + // 如果有配置,且是 http 开头,则浏览器跳转 + if (returnUrl.value.indexOf('http') === 0) { + location.href = url; } else { - message.success('支付成功'); + closeCurrentTab(); + push({ path: url }); } } @@ -98,8 +298,6 @@ onMounted(async () => { diff --git a/apps/web-antd/src/views/pay/cashier/modules/form.vue b/apps/web-antd/src/views/pay/cashier/modules/form.vue deleted file mode 100644 index c75ebfdcb..000000000 --- a/apps/web-antd/src/views/pay/cashier/modules/form.vue +++ /dev/null @@ -1,155 +0,0 @@ - - -