|
|
@@ -0,0 +1,368 @@
|
|
|
+<template>
|
|
|
+ <view class="pay-container">
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <view v-if="loading" class="loading-wrap flex-center">
|
|
|
+ <uni-load-more type="loading" color="#f8c008"></uni-load-more>
|
|
|
+ <text class="loading-text">正在初始化支付...</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 订单信息 & 支付区域 -->
|
|
|
+ <view v-else-if="orderInfo" class="pay-content">
|
|
|
+ <view class="order-card">
|
|
|
+ <view class="order-item price-highlight">
|
|
|
+ <text class="label">支付金额:</text>
|
|
|
+ <view class="value price">
|
|
|
+ <text class="currency">¥</text>
|
|
|
+ <text class="amount-text">{{ orderInfo.amount }}</text>
|
|
|
+ <text class="unit">元</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="order-item" v-if="orderInfo.description">
|
|
|
+ <text class="label">订单描述:</text>
|
|
|
+ <text class="value">{{ orderInfo.description }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="order-item" v-if="orderInfo.orderNo">
|
|
|
+ <text class="label">订单号:</text>
|
|
|
+ <text class="value">{{ orderInfo.orderNo }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 支付按钮 -->
|
|
|
+ <view class="pay-btn-wrap flex-center" @click="handlePay">
|
|
|
+ <button class="pay-btn" :disabled="paying">
|
|
|
+ <text v-if="!paying" class="btn-text">立即支付</text>
|
|
|
+ <text v-else class="btn-text">支付中...</text>
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 异常提示 -->
|
|
|
+ <view v-else class="error-wrap flex-center">
|
|
|
+ <text class="error-text">{{ errorMsg }}</text>
|
|
|
+ <button class="back-btn" @click="goBackToWebview">返回上一页</button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 微信支付组件-->
|
|
|
+ <WechatPayment ref="wechatPaymentRef" />
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref } from "vue";
|
|
|
+import { onLoad } from "@dcloudio/uni-app";
|
|
|
+import { useAppStore } from "@/stores/app";
|
|
|
+import { getUserOpenId } from "@/api/user";
|
|
|
+import { Calc } from "@/utils/util";
|
|
|
+import { goldPrincipalCompleteOrder } from "@/api/payment";
|
|
|
+import { recyclePreOrderSuccessAPI } from "@/api/functions";
|
|
|
+import WechatPayment from "@/components/payment/WechatPayment.vue";
|
|
|
+
|
|
|
+// 状态管理
|
|
|
+const appStore = useAppStore();
|
|
|
+const wechatPaymentRef = ref(null);
|
|
|
+
|
|
|
+// 页面核心数据
|
|
|
+const loading = ref(true);
|
|
|
+const paying = ref(false);
|
|
|
+const amount = ref(0);
|
|
|
+const orderNo = ref(0);
|
|
|
+const description = ref("");
|
|
|
+const returnUrl = ref("");
|
|
|
+const orderPrefix = ref("");
|
|
|
+const orderInfo = ref(null);
|
|
|
+const errorMsg = ref("");
|
|
|
+
|
|
|
+onLoad(async (options) => {
|
|
|
+ try {
|
|
|
+ amount.value = Number(options.amount) || 0.01;
|
|
|
+ description.value = decodeURIComponent(options.description || "");
|
|
|
+ orderPrefix.value = options.orderPrefix || "H5";
|
|
|
+ orderNo.value = options.orderNo || "";
|
|
|
+ returnUrl.value = options.returnUrl || "";
|
|
|
+
|
|
|
+ if (isNaN(amount.value) || amount.value <= 0) {
|
|
|
+ throw new Error("支付金额异常(需大于0分)");
|
|
|
+ }
|
|
|
+ if (!description.value && !orderNo.value) {
|
|
|
+ throw new Error("订单描述或订单号其中一项不能为空");
|
|
|
+ }
|
|
|
+ if (!returnUrl.value) {
|
|
|
+ throw new Error("支付完成返回地址不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ orderInfo.value = {
|
|
|
+ amount: Calc.div(amount.value, 100).truncate().valueOf(),
|
|
|
+ description: description.value,
|
|
|
+ orderNo: orderNo.value,
|
|
|
+ };
|
|
|
+ loading.value = false;
|
|
|
+ } catch (err) {
|
|
|
+ loading.value = false;
|
|
|
+ errorMsg.value = err.message || "页面参数异常";
|
|
|
+ console.error("页面初始化失败:", err);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const handlePay = async () => {
|
|
|
+ if (paying.value) return;
|
|
|
+ if (!appStore.isLogin) {
|
|
|
+ uni.showToast({ title: "请先登录", icon: "none" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const userOpenId = appStore.$userInfo?.openId;
|
|
|
+ if (!userOpenId) {
|
|
|
+ uni.showToast({ title: "用户信息异常,请重新登录", icon: "none" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ paying.value = true;
|
|
|
+ try {
|
|
|
+ wechatPaymentRef.value.createUniPay({
|
|
|
+ amount: amount.value,
|
|
|
+ description: description.value,
|
|
|
+ openId: userOpenId,
|
|
|
+ orderPrefix: orderPrefix.value,
|
|
|
+ orderNo: orderNo.value,
|
|
|
+
|
|
|
+ onUniPayCreate: async (payRes) => {
|
|
|
+ console.log("支付订单创建成功:", payRes);
|
|
|
+ },
|
|
|
+
|
|
|
+ onUniPaySuccess: async (payStatusRes) => {
|
|
|
+ console.log("支付成功:", payStatusRes);
|
|
|
+ await processOrderBizChange(payStatusRes);
|
|
|
+ paying.value = false;
|
|
|
+ uni.showToast({
|
|
|
+ title: "您已支付成功,将返回订单列表",
|
|
|
+ icon: "success",
|
|
|
+ });
|
|
|
+ setTimeout(() => {
|
|
|
+ goBackToWebview();
|
|
|
+ }, 1300);
|
|
|
+ },
|
|
|
+
|
|
|
+ onUniPayCancel: () => {
|
|
|
+ paying.value = false;
|
|
|
+ uni.showToast({ title: "你已取消支付", icon: "none" });
|
|
|
+ },
|
|
|
+
|
|
|
+ onUniPayFail: (err) => {
|
|
|
+ paying.value = false;
|
|
|
+ const errMsg = err || "支付失败,请重试";
|
|
|
+ uni.showToast({ title: errMsg, icon: "none" });
|
|
|
+ console.error("支付失败:", err);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } catch (err) {
|
|
|
+ paying.value = false;
|
|
|
+ uni.showToast({ title: "支付接口调用失败", icon: "none" });
|
|
|
+ console.error("支付调用异常:", err);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const processOrderBizChange = async (payStatusRes) => {
|
|
|
+ try {
|
|
|
+ switch (payStatusRes.orderType) {
|
|
|
+ case "recyle":
|
|
|
+ await recyclePreOrderSuccessAPI({
|
|
|
+ orderNo: payStatusRes.outTradeNo,
|
|
|
+ payType: "routine",
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case "tl":
|
|
|
+ await goldPrincipalCompleteOrder({
|
|
|
+ orderNo: payStatusRes.outTradeNo,
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case "svip":
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ uni.showToast({ title: error || "业务订单状态更改失败", icon: "none" });
|
|
|
+ console.error("业务订单状态更改失败:", error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const goBackToWebview = () => {
|
|
|
+ if (!returnUrl.value) {
|
|
|
+ uni.showToast({ title: "返回地址异常", icon: "none" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let finalUrl = returnUrl.value;
|
|
|
+ try {
|
|
|
+ if (finalUrl.includes("%")) {
|
|
|
+ finalUrl = decodeURIComponent(finalUrl);
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.warn("URL解码失败,使用原始URL:", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.redirectTo({
|
|
|
+ url: `/pages/webview/index?path=${returnUrl.value}`,
|
|
|
+ });
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+$primary: #f8c008;
|
|
|
+$primary-dark: #e6b007;
|
|
|
+$primary-light: #fff5cc;
|
|
|
+$primary-disabled: #fad966;
|
|
|
+
|
|
|
+.pay-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background-color: #fafafa;
|
|
|
+ padding: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+// 加载状态
|
|
|
+.loading-wrap {
|
|
|
+ flex-direction: column;
|
|
|
+ height: 60vh;
|
|
|
+ .loading-text {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 订单信息卡片
|
|
|
+.pay-content {
|
|
|
+ .order-card {
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 40rpx 32rpx;
|
|
|
+ margin-bottom: 80rpx;
|
|
|
+ box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.04);
|
|
|
+ border: 1px solid $primary-light;
|
|
|
+
|
|
|
+ .order-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 28rpx;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .label {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #777;
|
|
|
+ width: 160rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .value {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
+ word-break: break-all;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 🔥 高亮金额区域
|
|
|
+ .price-highlight {
|
|
|
+ padding: 12rpx 0;
|
|
|
+ margin: 12rpx 0 32rpx;
|
|
|
+
|
|
|
+ .value.price {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 6rpx;
|
|
|
+
|
|
|
+ .currency {
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: $primary;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 核心金额数字(最大、最亮)
|
|
|
+ .amount-text {
|
|
|
+ font-size: 48rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: $primary;
|
|
|
+ letter-spacing: 1rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .unit {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: $primary;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-left: 4rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 支付按钮
|
|
|
+.pay-btn-wrap {
|
|
|
+ .pay-btn {
|
|
|
+ width: 100%;
|
|
|
+ height: 96rpx;
|
|
|
+ line-height: 96rpx;
|
|
|
+ background-color: $primary;
|
|
|
+ border: none;
|
|
|
+ border-radius: 48rpx;
|
|
|
+ box-shadow: 0 8rpx 24rpx rgba($primary, 0.25);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+
|
|
|
+ .btn-text {
|
|
|
+ font-size: 34rpx;
|
|
|
+ color: #fff;
|
|
|
+ font-weight: 600;
|
|
|
+ letter-spacing: 2rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:not(:disabled):active {
|
|
|
+ background-color: $primary-dark;
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba($primary, 0.2);
|
|
|
+ transform: translateY(2rpx);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ background-color: $primary-disabled;
|
|
|
+ box-shadow: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 异常提示
|
|
|
+.error-wrap {
|
|
|
+ flex-direction: column;
|
|
|
+ height: 60vh;
|
|
|
+ .error-text {
|
|
|
+ font-size: 30rpx;
|
|
|
+ color: #e64340;
|
|
|
+ margin-bottom: 40rpx;
|
|
|
+ text-align: center;
|
|
|
+ padding: 0 30rpx;
|
|
|
+ line-height: 44rpx;
|
|
|
+ }
|
|
|
+ .back-btn {
|
|
|
+ width: 280rpx;
|
|
|
+ height: 76rpx;
|
|
|
+ line-height: 76rpx;
|
|
|
+ background-color: #fff;
|
|
|
+ border: 1px solid $primary;
|
|
|
+ border-radius: 38rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: $primary;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background-color: $primary-light;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.flex-center {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+</style>
|