Commit 0a792d771483ff1bb216d11541f0b97916e2f840
1 parent
607807d2
feat(payment):增加支付状态查询以及正式环境打包地址修改
Showing
4 changed files
with
172 additions
and
10 deletions
apps/web-payment/.env.production
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 'vconsole'; |
| 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<boolean>(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 () => { |
| 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) => { |
| 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) => { |
| 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 () => { |
| 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) => { |
| 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 () => { |
| 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 () => { |
| 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 () => { |
| 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 | }, | ... | ... |