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,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 'vconsole'; | @@ -17,6 +17,7 @@ import VConsole from 'vconsole'; | ||
| 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<boolean>(false); | @@ -74,6 +75,13 @@ const loading = ref<boolean>(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 () => { | @@ -189,6 +197,12 @@ const loadOpenId = async () => { | ||
| 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) => { | @@ -215,6 +229,12 @@ const handlepayBtnShowClick = async (pipeline: Pipeline) => { | ||
| 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) => { | @@ -253,6 +273,7 @@ const handlePasswordComplete = async (password: string) => { | ||
| 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 () => { | @@ -338,6 +359,12 @@ const queryPayment = async () => { | ||
| 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) => { | @@ -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 | 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 () => { | @@ -520,6 +603,9 @@ const init = async () => { | ||
| 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 () => { | @@ -529,6 +615,12 @@ const init = async () => { | ||
| 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 () => { | @@ -18,7 +18,7 @@ export default defineConfig(async () => { | ||
| 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 | }, |