Commit 0a792d771483ff1bb216d11541f0b97916e2f840

Authored by tianwu
1 parent 607807d2

feat(payment):增加支付状态查询以及正式环境打包地址修改

apps/web-payment/.env.production
1 1 VITE_BASE = /pages/
2 2  
3 3 # 接口地址
4   -VITE_GLOB_API_URL=https://cashier.test.gszdtop.com
  4 +VITE_GLOB_API_URL=https://cashier.pay.gszdtop.com
5 5  
6 6 # 是否开启压缩,可以设置为 none, brotli, gzip
7 7 VITE_COMPRESS=none
... ...
apps/web-payment/src/api/payment.ts
... ... @@ -78,3 +78,14 @@ export async function zrPaymentState(
78 78 `/payment/cashier/zrPaymentState?paymentId=${paymentId}&mode=${mode}`,
79 79 );
80 80 }
  81 +
  82 +/**
  83 + *
  84 + * @param tradeId
  85 + * @returns
  86 + */
  87 +export async function checkorder(tradeId: number | string) {
  88 + return requestClient.post<any>(
  89 + `payment/cashier/checkOrder?tradeId=${tradeId}`,
  90 + );
  91 +}
... ...
apps/web-payment/src/views/payment/index.vue
1 1 <script setup lang="ts">
2   -import { computed, ref } from 'vue';
  2 +import { computed, onUnmounted, ref } from 'vue';
3 3 import { useRoute, useRouter } from 'vue-router';
4 4  
5 5 import { fenToYuan } from '@vben/utils';
... ... @@ -17,6 +17,7 @@ import VConsole from &#39;vconsole&#39;;
17 17 import wx from 'weixin-js-sdk';
18 18  
19 19 import {
  20 + checkorder,
20 21 getOpenId,
21 22 listUserCards,
22 23 orderInfo,
... ... @@ -74,6 +75,13 @@ const loading = ref&lt;boolean&gt;(false);
74 75 const paymentSuccess = ref<boolean>(false);
75 76 const payErrorDialog = ref<boolean>(false);
76 77 const errorMessage = ref<string>('支付失败');
  78 +const orderExpired = ref<boolean>(false);
  79 +const orderCheckTimer = ref<any>(null);
  80 +
  81 +// 订单检查相关配置
  82 +const ORDER_CHECK_INTERVAL = 15_000; // 每10秒检查一次
  83 +const ORDER_CHECK_MAX_COUNT = 180; // 最多检查180次(30分钟)
  84 +let orderCheckCount = 0;
77 85  
78 86 const handleShowConsole = () => {
79 87 const vConsole = new VConsole({ theme: 'dark' });
... ... @@ -189,6 +197,12 @@ const loadOpenId = async () =&gt; {
189 197  
190 198 // 方法
191 199 const handlepayBtnShowClick = async (pipeline: Pipeline) => {
  200 + // 如果订单已过期,阻止操作
  201 + if (orderExpired.value) {
  202 + ElMessage.error('订单已过期,请重新下单');
  203 + return;
  204 + }
  205 +
192 206 currentPaymentMethod.value = pipeline.pipelineId;
193 207 currentPayType.value = pipeline;
194 208 payBtnShow.value = false;
... ... @@ -215,6 +229,12 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) =&gt; {
215 229 };
216 230  
217 231 const handleCardSelect = (card: Card) => {
  232 + // 如果订单已过期,阻止操作
  233 + if (orderExpired.value) {
  234 + ElMessage.error('订单已过期,请重新下单');
  235 + return;
  236 + }
  237 +
218 238 selectedCard.value = card;
219 239 showCardDialog.value = false;
220 240 // ElMessage.success(`已选择 ${card.name} 的园区卡`);
... ... @@ -253,6 +273,7 @@ const handlePasswordComplete = async (password: string) =&gt; {
253 273 // });
254 274 } catch (error) {
255 275 errorMessage.value = error?.message || '支付失败';
  276 + showPasswordDialog.value = false;
256 277 payErrorDialog.value = true;
257 278 console.error(error);
258 279 } finally {
... ... @@ -338,6 +359,12 @@ const queryPayment = async () =&gt; {
338 359 };
339 360  
340 361 const handlePay = async () => {
  362 + // 如果订单已过期,阻止操作
  363 + if (orderExpired.value) {
  364 + ElMessage.error('订单已过期,请重新下单');
  365 + return;
  366 + }
  367 +
341 368 if (!currentPayType.value) {
342 369 ElMessage.warning('请先选择支付方式');
343 370 return;
... ... @@ -510,6 +537,62 @@ const checkPayResult = async (paymentId: number | string) =&gt; {
510 537 }
511 538 };
512 539  
  540 +// 停止订单检查轮询
  541 +const stopOrderCheck = () => {
  542 + if (orderCheckTimer.value) {
  543 + clearInterval(orderCheckTimer.value);
  544 + orderCheckTimer.value = null;
  545 + }
  546 + orderCheckCount = 0;
  547 +};
  548 +
  549 +// 检查订单是否过期
  550 +const checkOrderExpired = async () => {
  551 + // 如果已经标记为过期,则不再检查
  552 + if (orderExpired.value) {
  553 + stopOrderCheck();
  554 + return;
  555 + }
  556 +
  557 + // 超过最大检查次数,停止检查
  558 + if (orderCheckCount >= ORDER_CHECK_MAX_COUNT) {
  559 + console.log('订单检查次数已达上限');
  560 + stopOrderCheck();
  561 + return;
  562 + }
  563 +
  564 + try {
  565 + orderCheckCount++;
  566 + const data = await checkorder(orderInfoData.value.tradeId);
  567 + // 检查订单状态
  568 + } catch (error) {
  569 + if (String(error?.code) !== '200') {
  570 + orderExpired.value = true;
  571 + stopOrderCheck();
  572 +
  573 + // 重置所有支付相关状态
  574 + resetPaymentState();
  575 +
  576 + // 显示过期提示
  577 + errorMessage.value = '订单已过期,请重新下单';
  578 + payErrorDialog.value = true;
  579 + }
  580 + console.error('检查订单状态失败:', error);
  581 + // 检查失败不影响用户操作,继续轮询
  582 + }
  583 +};
  584 +
  585 +// 启动订单检查
  586 +const startOrderCheck = () => {
  587 + // 首先执行一次检查
  588 + checkOrderExpired();
  589 +
  590 + // 然后启动定时检查
  591 + orderCheckTimer.value = setInterval(() => {
  592 + checkOrderExpired();
  593 + }, ORDER_CHECK_INTERVAL);
  594 +};
  595 +
513 596 const init = async () => {
514 597 token.value = (route.query?.token as string) || '';
515 598 openId.value = (route.query?.openId as string) || '';
... ... @@ -520,6 +603,9 @@ const init = async () =&gt; {
520 603 try {
521 604 loadLoading.value = true;
522 605 await getOrderInfo();
  606 +
  607 + // 订单信息加载成功后,开始检查订单是否过期
  608 + startOrderCheck();
523 609 } catch (error) {
524 610 // ElMessage.error(error?.message || '获取订单信息失败');
525 611 errorMessage.value = error?.message || '获取订单信息失败';
... ... @@ -529,6 +615,12 @@ const init = async () =&gt; {
529 615 }
530 616 };
531 617  
  618 +// 组件卸载时清理定时器
  619 +onUnmounted(() => {
  620 + stopOrderCheck();
  621 + stopPollingPayResult();
  622 +});
  623 +
532 624 init();
533 625 </script>
534 626  
... ... @@ -572,8 +664,16 @@ init();
572 664 <p class="subtitle">快捷·安全</p>
573 665 </div>
574 666  
  667 + <!-- 订单过期提示横幅 -->
  668 + <div v-if="orderExpired" class="expired-banner">
  669 + <ElIcon :size="20" color="#f59e0b">
  670 + <WarningFilled />
  671 + </ElIcon>
  672 + <span>订单已过期,请重新下单</span>
  673 + </div>
  674 +
575 675 <!-- 订单金额卡片 -->
576   - <div class="amount-card">
  676 + <div class="amount-card" :class="{ 'is-expired': orderExpired }">
577 677 <div class="amount-content">
578 678 <p class="amount-label">订单金额</p>
579 679 <p class="amount-value">¥{{ displayAmount }}</p>
... ... @@ -582,7 +682,7 @@ init();
582 682 </div>
583 683  
584 684 <!-- 支付方式选择 -->
585   - <div class="payment-section">
  685 + <div class="payment-section" :class="{ 'is-expired': orderExpired }">
586 686 <div class="section-header">
587 687 <span class="section-title">选择支付方式</span>
588 688 </div>
... ... @@ -592,7 +692,10 @@ init();
592 692 v-for="pipeline in paymentPipelines"
593 693 :key="pipeline.pipelineId"
594 694 class="payment-item"
595   - :class="{ active: currentPaymentMethod === pipeline.pipelineId }"
  695 + :class="{
  696 + active: currentPaymentMethod === pipeline.pipelineId,
  697 + disabled: orderExpired,
  698 + }"
596 699 @click="handlepayBtnShowClick(pipeline)"
597 700 >
598 701 <div class="payment-item-left">
... ... @@ -662,6 +765,7 @@ init();
662 765 <ElRadio
663 766 :model-value="currentPaymentMethod"
664 767 :label="pipeline.pipelineId"
  768 + :disabled="orderExpired"
665 769 size="large"
666 770 />
667 771 </div>
... ... @@ -675,14 +779,20 @@ init();
675 779 <!-- 底部支付按钮 -->
676 780 <div class="payment-footer">
677 781 <ElButton
678   - :disabled="!payBtnShow"
  782 + :disabled="!payBtnShow || orderExpired"
679 783 class="pay-button"
680 784 type="primary"
681 785 size="large"
682 786 :loading="loading"
683 787 @click="handlePay"
684 788 >
685   - {{ loading ? '处理中...' : `确认支付 ¥${displayAmount}` }}
  789 + {{
  790 + orderExpired
  791 + ? '订单已过期'
  792 + : loading
  793 + ? '处理中...'
  794 + : `确认支付 ¥${displayAmount}`
  795 + }}
686 796 </ElButton>
687 797 </div>
688 798 </div>
... ... @@ -743,7 +853,9 @@ init();
743 853 >
744 854 <div class="color-[#333] items-center">
745 855 <img src="/fail.png" class="m-auto block h-[64px] w-[64px]" />
746   - <div class="py-[20px] text-center text-[20px] font-bold">支付失败</div>
  856 + <div class="py-[20px] text-center text-[20px] font-bold">
  857 + {{ orderExpired ? '订单已过期' : '支付失败' }}
  858 + </div>
747 859 <div class="w-full overflow-y-auto text-left text-[18px] leading-6">
748 860 {{ errorMessage }}
749 861 </div>
... ... @@ -866,6 +978,38 @@ init();
866 978 );
867 979 }
868 980  
  981 +// 订单过期横幅样式
  982 +.expired-banner {
  983 + display: flex;
  984 + gap: 8px;
  985 + align-items: center;
  986 + justify-content: center;
  987 + padding: 12px 16px;
  988 + margin-bottom: 20px;
  989 + font-size: 14px;
  990 + font-weight: 600;
  991 + color: #78350f;
  992 + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
  993 + border: 1px solid #fbbf24;
  994 + border-radius: 8px;
  995 + animation: slideUp 0.5s ease-out;
  996 +}
  997 +
  998 +// 过期状态样式
  999 +.is-expired {
  1000 + position: relative;
  1001 + pointer-events: none;
  1002 + opacity: 0.6;
  1003 +
  1004 + &::after {
  1005 + position: absolute;
  1006 + inset: 0;
  1007 + content: '';
  1008 + background: rgb(255 255 255 / 30%);
  1009 + border-radius: inherit;
  1010 + }
  1011 +}
  1012 +
869 1013 // 错误提示容器
870 1014 .error-container {
871 1015 display: flex;
... ... @@ -1057,7 +1201,7 @@ init();
1057 1201 margin-bottom: 0;
1058 1202 }
1059 1203  
1060   - &:hover {
  1204 + &:hover:not(.disabled) {
1061 1205 border-color: #fecaca;
1062 1206 }
1063 1207  
... ... @@ -1065,6 +1209,11 @@ init();
1065 1209 border-color: #ea4200;
1066 1210 }
1067 1211  
  1212 + &.disabled {
  1213 + cursor: not-allowed;
  1214 + opacity: 0.5;
  1215 + }
  1216 +
1068 1217 .payment-item-left {
1069 1218 display: flex;
1070 1219 gap: 16px;
... ... @@ -1163,6 +1312,8 @@ init();
1163 1312  
1164 1313 &:disabled {
1165 1314 cursor: not-allowed;
  1315 + background: #9ca3af;
  1316 + box-shadow: none;
1166 1317 opacity: 0.5;
1167 1318 }
1168 1319 }
... ...
apps/web-payment/vite.config.mts
... ... @@ -18,7 +18,7 @@ export default defineConfig(async () =&gt; {
18 18 changeOrigin: true,
19 19 rewrite: (path) => path.replace(/^\/api/, ''),
20 20 // mock代理目标地址
21   - target: 'http://cashier.test.gszdtop.com',
  21 + target: 'https://cashier.test.gszdtop.com',
22 22 ws: true,
23 23 },
24 24 },
... ...