Commit 0a792d771483ff1bb216d11541f0b97916e2f840

Authored by tianwu
1 parent 607807d2

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

apps/web-payment/.env.production
1 VITE_BASE = /pages/ 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 # 是否开启压缩,可以设置为 none, brotli, gzip 6 # 是否开启压缩,可以设置为 none, brotli, gzip
7 VITE_COMPRESS=none 7 VITE_COMPRESS=none
apps/web-payment/src/api/payment.ts
@@ -78,3 +78,14 @@ export async function zrPaymentState( @@ -78,3 +78,14 @@ export async function zrPaymentState(
78 `/payment/cashier/zrPaymentState?paymentId=${paymentId}&mode=${mode}`, 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 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { computed, ref } from 'vue'; 2 +import { computed, onUnmounted, ref } from 'vue';
3 import { useRoute, useRouter } from 'vue-router'; 3 import { useRoute, useRouter } from 'vue-router';
4 4
5 import { fenToYuan } from '@vben/utils'; 5 import { fenToYuan } from '@vben/utils';
@@ -17,6 +17,7 @@ import VConsole from &#39;vconsole&#39;; @@ -17,6 +17,7 @@ import VConsole from &#39;vconsole&#39;;
17 import wx from 'weixin-js-sdk'; 17 import wx from 'weixin-js-sdk';
18 18
19 import { 19 import {
  20 + checkorder,
20 getOpenId, 21 getOpenId,
21 listUserCards, 22 listUserCards,
22 orderInfo, 23 orderInfo,
@@ -74,6 +75,13 @@ const loading = ref&lt;boolean&gt;(false); @@ -74,6 +75,13 @@ const loading = ref&lt;boolean&gt;(false);
74 const paymentSuccess = ref<boolean>(false); 75 const paymentSuccess = ref<boolean>(false);
75 const payErrorDialog = ref<boolean>(false); 76 const payErrorDialog = ref<boolean>(false);
76 const errorMessage = ref<string>('支付失败'); 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 const handleShowConsole = () => { 86 const handleShowConsole = () => {
79 const vConsole = new VConsole({ theme: 'dark' }); 87 const vConsole = new VConsole({ theme: 'dark' });
@@ -189,6 +197,12 @@ const loadOpenId = async () =&gt; { @@ -189,6 +197,12 @@ const loadOpenId = async () =&gt; {
189 197
190 // 方法 198 // 方法
191 const handlepayBtnShowClick = async (pipeline: Pipeline) => { 199 const handlepayBtnShowClick = async (pipeline: Pipeline) => {
  200 + // 如果订单已过期,阻止操作
  201 + if (orderExpired.value) {
  202 + ElMessage.error('订单已过期,请重新下单');
  203 + return;
  204 + }
  205 +
192 currentPaymentMethod.value = pipeline.pipelineId; 206 currentPaymentMethod.value = pipeline.pipelineId;
193 currentPayType.value = pipeline; 207 currentPayType.value = pipeline;
194 payBtnShow.value = false; 208 payBtnShow.value = false;
@@ -215,6 +229,12 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) =&gt; { @@ -215,6 +229,12 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) =&gt; {
215 }; 229 };
216 230
217 const handleCardSelect = (card: Card) => { 231 const handleCardSelect = (card: Card) => {
  232 + // 如果订单已过期,阻止操作
  233 + if (orderExpired.value) {
  234 + ElMessage.error('订单已过期,请重新下单');
  235 + return;
  236 + }
  237 +
218 selectedCard.value = card; 238 selectedCard.value = card;
219 showCardDialog.value = false; 239 showCardDialog.value = false;
220 // ElMessage.success(`已选择 ${card.name} 的园区卡`); 240 // ElMessage.success(`已选择 ${card.name} 的园区卡`);
@@ -253,6 +273,7 @@ const handlePasswordComplete = async (password: string) =&gt; { @@ -253,6 +273,7 @@ const handlePasswordComplete = async (password: string) =&gt; {
253 // }); 273 // });
254 } catch (error) { 274 } catch (error) {
255 errorMessage.value = error?.message || '支付失败'; 275 errorMessage.value = error?.message || '支付失败';
  276 + showPasswordDialog.value = false;
256 payErrorDialog.value = true; 277 payErrorDialog.value = true;
257 console.error(error); 278 console.error(error);
258 } finally { 279 } finally {
@@ -338,6 +359,12 @@ const queryPayment = async () =&gt; { @@ -338,6 +359,12 @@ const queryPayment = async () =&gt; {
338 }; 359 };
339 360
340 const handlePay = async () => { 361 const handlePay = async () => {
  362 + // 如果订单已过期,阻止操作
  363 + if (orderExpired.value) {
  364 + ElMessage.error('订单已过期,请重新下单');
  365 + return;
  366 + }
  367 +
341 if (!currentPayType.value) { 368 if (!currentPayType.value) {
342 ElMessage.warning('请先选择支付方式'); 369 ElMessage.warning('请先选择支付方式');
343 return; 370 return;
@@ -510,6 +537,62 @@ const checkPayResult = async (paymentId: number | string) =&gt; { @@ -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 const init = async () => { 596 const init = async () => {
514 token.value = (route.query?.token as string) || ''; 597 token.value = (route.query?.token as string) || '';
515 openId.value = (route.query?.openId as string) || ''; 598 openId.value = (route.query?.openId as string) || '';
@@ -520,6 +603,9 @@ const init = async () =&gt; { @@ -520,6 +603,9 @@ const init = async () =&gt; {
520 try { 603 try {
521 loadLoading.value = true; 604 loadLoading.value = true;
522 await getOrderInfo(); 605 await getOrderInfo();
  606 +
  607 + // 订单信息加载成功后,开始检查订单是否过期
  608 + startOrderCheck();
523 } catch (error) { 609 } catch (error) {
524 // ElMessage.error(error?.message || '获取订单信息失败'); 610 // ElMessage.error(error?.message || '获取订单信息失败');
525 errorMessage.value = error?.message || '获取订单信息失败'; 611 errorMessage.value = error?.message || '获取订单信息失败';
@@ -529,6 +615,12 @@ const init = async () =&gt; { @@ -529,6 +615,12 @@ const init = async () =&gt; {
529 } 615 }
530 }; 616 };
531 617
  618 +// 组件卸载时清理定时器
  619 +onUnmounted(() => {
  620 + stopOrderCheck();
  621 + stopPollingPayResult();
  622 +});
  623 +
532 init(); 624 init();
533 </script> 625 </script>
534 626
@@ -572,8 +664,16 @@ init(); @@ -572,8 +664,16 @@ init();
572 <p class="subtitle">快捷·安全</p> 664 <p class="subtitle">快捷·安全</p>
573 </div> 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 <div class="amount-content"> 677 <div class="amount-content">
578 <p class="amount-label">订单金额</p> 678 <p class="amount-label">订单金额</p>
579 <p class="amount-value">¥{{ displayAmount }}</p> 679 <p class="amount-value">¥{{ displayAmount }}</p>
@@ -582,7 +682,7 @@ init(); @@ -582,7 +682,7 @@ init();
582 </div> 682 </div>
583 683
584 <!-- 支付方式选择 --> 684 <!-- 支付方式选择 -->
585 - <div class="payment-section"> 685 + <div class="payment-section" :class="{ 'is-expired': orderExpired }">
586 <div class="section-header"> 686 <div class="section-header">
587 <span class="section-title">选择支付方式</span> 687 <span class="section-title">选择支付方式</span>
588 </div> 688 </div>
@@ -592,7 +692,10 @@ init(); @@ -592,7 +692,10 @@ init();
592 v-for="pipeline in paymentPipelines" 692 v-for="pipeline in paymentPipelines"
593 :key="pipeline.pipelineId" 693 :key="pipeline.pipelineId"
594 class="payment-item" 694 class="payment-item"
595 - :class="{ active: currentPaymentMethod === pipeline.pipelineId }" 695 + :class="{
  696 + active: currentPaymentMethod === pipeline.pipelineId,
  697 + disabled: orderExpired,
  698 + }"
596 @click="handlepayBtnShowClick(pipeline)" 699 @click="handlepayBtnShowClick(pipeline)"
597 > 700 >
598 <div class="payment-item-left"> 701 <div class="payment-item-left">
@@ -662,6 +765,7 @@ init(); @@ -662,6 +765,7 @@ init();
662 <ElRadio 765 <ElRadio
663 :model-value="currentPaymentMethod" 766 :model-value="currentPaymentMethod"
664 :label="pipeline.pipelineId" 767 :label="pipeline.pipelineId"
  768 + :disabled="orderExpired"
665 size="large" 769 size="large"
666 /> 770 />
667 </div> 771 </div>
@@ -675,14 +779,20 @@ init(); @@ -675,14 +779,20 @@ init();
675 <!-- 底部支付按钮 --> 779 <!-- 底部支付按钮 -->
676 <div class="payment-footer"> 780 <div class="payment-footer">
677 <ElButton 781 <ElButton
678 - :disabled="!payBtnShow" 782 + :disabled="!payBtnShow || orderExpired"
679 class="pay-button" 783 class="pay-button"
680 type="primary" 784 type="primary"
681 size="large" 785 size="large"
682 :loading="loading" 786 :loading="loading"
683 @click="handlePay" 787 @click="handlePay"
684 > 788 >
685 - {{ loading ? '处理中...' : `确认支付 ¥${displayAmount}` }} 789 + {{
  790 + orderExpired
  791 + ? '订单已过期'
  792 + : loading
  793 + ? '处理中...'
  794 + : `确认支付 ¥${displayAmount}`
  795 + }}
686 </ElButton> 796 </ElButton>
687 </div> 797 </div>
688 </div> 798 </div>
@@ -743,7 +853,9 @@ init(); @@ -743,7 +853,9 @@ init();
743 > 853 >
744 <div class="color-[#333] items-center"> 854 <div class="color-[#333] items-center">
745 <img src="/fail.png" class="m-auto block h-[64px] w-[64px]" /> 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 <div class="w-full overflow-y-auto text-left text-[18px] leading-6"> 859 <div class="w-full overflow-y-auto text-left text-[18px] leading-6">
748 {{ errorMessage }} 860 {{ errorMessage }}
749 </div> 861 </div>
@@ -866,6 +978,38 @@ init(); @@ -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 .error-container { 1014 .error-container {
871 display: flex; 1015 display: flex;
@@ -1057,7 +1201,7 @@ init(); @@ -1057,7 +1201,7 @@ init();
1057 margin-bottom: 0; 1201 margin-bottom: 0;
1058 } 1202 }
1059 1203
1060 - &:hover { 1204 + &:hover:not(.disabled) {
1061 border-color: #fecaca; 1205 border-color: #fecaca;
1062 } 1206 }
1063 1207
@@ -1065,6 +1209,11 @@ init(); @@ -1065,6 +1209,11 @@ init();
1065 border-color: #ea4200; 1209 border-color: #ea4200;
1066 } 1210 }
1067 1211
  1212 + &.disabled {
  1213 + cursor: not-allowed;
  1214 + opacity: 0.5;
  1215 + }
  1216 +
1068 .payment-item-left { 1217 .payment-item-left {
1069 display: flex; 1218 display: flex;
1070 gap: 16px; 1219 gap: 16px;
@@ -1163,6 +1312,8 @@ init(); @@ -1163,6 +1312,8 @@ init();
1163 1312
1164 &:disabled { 1313 &:disabled {
1165 cursor: not-allowed; 1314 cursor: not-allowed;
  1315 + background: #9ca3af;
  1316 + box-shadow: none;
1166 opacity: 0.5; 1317 opacity: 0.5;
1167 } 1318 }
1168 } 1319 }
apps/web-payment/vite.config.mts
@@ -18,7 +18,7 @@ export default defineConfig(async () =&gt; { @@ -18,7 +18,7 @@ export default defineConfig(async () =&gt; {
18 changeOrigin: true, 18 changeOrigin: true,
19 rewrite: (path) => path.replace(/^\/api/, ''), 19 rewrite: (path) => path.replace(/^\/api/, ''),
20 // mock代理目标地址 20 // mock代理目标地址
21 - target: 'http://cashier.test.gszdtop.com', 21 + target: 'https://cashier.test.gszdtop.com',
22 ws: true, 22 ws: true,
23 }, 23 },
24 }, 24 },