WechatPayment.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <template>
  2. <!-- 支付加载提示 -->
  3. <view v-if="showPayLoading" class="pay-loading-mask">
  4. <view class="pay-loading-box">
  5. <view class="loading-spinner"></view>
  6. <text class="loading-text">处理支付中...</text>
  7. </view>
  8. </view>
  9. </template>
  10. <script setup>
  11. import { ref, getCurrentInstance } from "vue";
  12. import { createPaymentOrder, queryPaymentStatus } from "@/api/payment";
  13. import { generateCustomId } from "@/utils/util.js";
  14. // 组件实例
  15. const instance = getCurrentInstance();
  16. // 状态管理
  17. const orderNo = ref("");
  18. const orderId = ref("");
  19. const showPayLoading = ref(false);
  20. const paymentInProgress = ref(false);
  21. // 回调函数存储
  22. const callbacks = ref({
  23. onUniPayCreate: null,
  24. onUniPaySuccess: null,
  25. onUniPayCancel: null,
  26. onUniPayFail: null,
  27. });
  28. /**
  29. * 发起微信支付
  30. * @param {Object} options - 支付参数
  31. * @param {number} options.amount - 支付金额(分)
  32. * @param {string} options.description - 商品描述
  33. * @param {string} options.openId - 用户openId
  34. * @param {string} options.orderPrefix - 订单号前缀
  35. * @param {Function} options.onUniPayCreate - 订单创建成功回调
  36. * @param {Function} options.onUniPaySuccess - 支付成功回调
  37. * @param {Function} options.onUniPayCancel - 取消支付回调
  38. * @param {Function} options.onUniPayFail - 支付失败回调
  39. */
  40. const createUniPay = async (options) => {
  41. // 防止重复发起支付
  42. if (paymentInProgress.value) return;
  43. try {
  44. paymentInProgress.value = true;
  45. showPayLoading.value = true;
  46. // 保存回调函数
  47. Object.keys(callbacks.value).forEach((key) => {
  48. callbacks.value[key] =
  49. options[key] && typeof options[key] === "function"
  50. ? options[key]
  51. : () => {};
  52. });
  53. // 生成订单号(使用公共组件)
  54. orderNo.value = generateCustomId(options.orderPrefix || "WX");
  55. // 调用后端创建支付订单
  56. const { data: payRes } = await createPaymentOrder({
  57. amount: options.amount,
  58. description: options.description,
  59. openId: options.openId,
  60. orderNo: orderNo.value,
  61. });
  62. // 订单创建成功,触发回调
  63. callbacks.value.onUniPayCreate({
  64. out_trade_no: orderNo.value,
  65. ...payRes,
  66. });
  67. // 调起微信支付
  68. await uni.requestPayment({
  69. provider: "wxpay",
  70. timeStamp: payRes.timeStamp.toString(),
  71. nonceStr: payRes.nonceStr,
  72. package: payRes.package,
  73. signType: payRes.signType,
  74. paySign: payRes.paySign,
  75. success: () => {
  76. confirmPaymentStatus();
  77. },
  78. fail: (err) => {
  79. showPayLoading.value = false;
  80. paymentInProgress.value = false;
  81. if (err.errMsg && err.errMsg.includes("cancel")) {
  82. callbacks.value.onUniPayCancel(err);
  83. } else {
  84. callbacks.value.onUniPayFail(err);
  85. }
  86. },
  87. });
  88. } catch (err) {
  89. showPayLoading.value = false;
  90. paymentInProgress.value = false;
  91. console.error("支付流程异常:", err);
  92. callbacks.value.onUniPayFail(err);
  93. }
  94. };
  95. /**
  96. * 确认支付状态(轮询查询)
  97. */
  98. const confirmPaymentStatus = async () => {
  99. try {
  100. let checkCount = 0;
  101. const maxCheckCount = 10; // 最多查询10次
  102. const checkInterval = 1000; // 每次查询间隔1秒
  103. const checkStatus = async () => {
  104. // 超过最大查询次数,视为超时
  105. if (checkCount >= maxCheckCount) {
  106. showPayLoading.value = false;
  107. paymentInProgress.value = false;
  108. callbacks.value.onUniPayFail(
  109. new Error("支付结果查询超时,请稍后查看订单状态")
  110. );
  111. return;
  112. }
  113. checkCount++;
  114. try {
  115. // 查询支付状态
  116. const res = await queryPaymentStatus(orderNo.value);
  117. // 根据后端返回的支付状态进行处理
  118. if (res.code === 200) {
  119. showPayLoading.value = false;
  120. paymentInProgress.value = false;
  121. callbacks.value.onUniPaySuccess({
  122. ...res.data,
  123. out_trade_no: orderNo.value,
  124. });
  125. }
  126. } catch (err) {
  127. // 查询接口出错,重试
  128. console.error(`第${checkCount}次查询支付状态失败:`, err);
  129. setTimeout(checkStatus, checkInterval);
  130. }
  131. };
  132. // 开始第一次查询
  133. checkStatus();
  134. } catch (err) {
  135. showPayLoading.value = false;
  136. paymentInProgress.value = false;
  137. console.error("确认支付状态异常:", err);
  138. callbacks.value.onUniPayFail(err);
  139. }
  140. };
  141. // 对外暴露方法
  142. defineExpose({
  143. createUniPay,
  144. });
  145. </script>
  146. <style scoped>
  147. /* 支付加载提示样式 */
  148. .pay-loading-mask {
  149. position: fixed;
  150. top: 0;
  151. left: 0;
  152. right: 0;
  153. bottom: 0;
  154. background-color: rgba(0, 0, 0, 0.5);
  155. display: flex;
  156. justify-content: center;
  157. align-items: center;
  158. z-index: 9999;
  159. }
  160. .pay-loading-box {
  161. background-color: white;
  162. padding: 30rpx 40rpx;
  163. border-radius: 16rpx;
  164. display: flex;
  165. flex-direction: column;
  166. align-items: center;
  167. }
  168. .loading-spinner {
  169. width: 50rpx;
  170. height: 50rpx;
  171. border: 4rpx solid #eee;
  172. border-top-color: #07c160;
  173. border-radius: 50%;
  174. animation: spin 1s linear infinite;
  175. margin-bottom: 20rpx;
  176. }
  177. .loading-text {
  178. color: #333;
  179. font-size: 28rpx;
  180. }
  181. /* 旋转动画 */
  182. @keyframes spin {
  183. to {
  184. transform: rotate(360deg);
  185. }
  186. }
  187. </style>