Files
tyc-webview-v2/src/components/Payment.vue
2026-06-09 21:26:43 +08:00

278 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<van-popup v-model:show="show" position="bottom" class="flex flex-col justify-between p-6"
:style="{ height: '50%' }">
<div class="text-center">
<h3 class="text-lg font-bold">支付</h3>
</div>
<div class="text-center">
<div class="font-bold text-xl">{{ data.product_name }}</div>
<div class="text-3xl text-red-500 font-bold">
<!-- 显示原价和折扣价格 -->
<div v-if="discountPrice" class="line-through text-gray-500 mt-4"
:class="{ 'text-2xl': discountPrice }">
¥ {{ data.sell_price }}
</div>
<div>
¥
{{
discountPrice
? (data.sell_price * 0.2).toFixed(2)
: data.sell_price
}}
</div>
</div>
<!-- 仅在折扣时显示活动说明 -->
<div v-if="discountPrice" class="text-sm text-red-500 mt-1">
活动价2折优惠
</div>
</div>
<!-- 支付方式微信内仅微信推广 hideWechat 时仅支付宝非微信仅支付宝 -->
<div class="">
<van-cell-group inset>
<van-cell v-if="showWechatPay" title="微信支付" clickable @click="selectedPaymentMethod = 'wechat'">
<template #icon>
<van-icon size="24" name="wechat-pay" color="#1AAD19" class="mr-2" />
</template>
<template #right-icon>
<van-radio v-model="selectedPaymentMethod" name="wechat" />
</template>
</van-cell>
<van-cell v-if="showAlipayPay" title="支付宝支付" clickable @click="selectedPaymentMethod = 'alipay'">
<template #icon>
<van-icon size="24" name="alipay" color="#00A1E9" class="mr-2" />
</template>
<template #right-icon>
<van-radio v-model="selectedPaymentMethod" name="alipay" />
</template>
</van-cell>
<!-- 开发环境测试支付 -->
<van-cell v-if="isDev" title="测试支付(开发)" clickable @click="selectedPaymentMethod = 'test'">
<template #icon>
<van-icon size="24" name="passed" color="#07c160" class="mr-2" />
</template>
<template #right-icon>
<van-radio v-model="selectedPaymentMethod" name="test" />
</template>
</van-cell>
</van-cell-group>
</div>
<!-- 确认按钮 -->
<div class="">
<van-button class="w-full" round type="primary" @click="getPayment">确认支付</van-button>
</div>
</van-popup>
</template>
<script setup>
import { ref, watch, onMounted, computed } from "vue";
import { showToast } from "vant";
const isDev = import.meta.env.DEV;
const props = defineProps({
data: {
type: Object,
required: true,
},
id: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
/** 为 true 时不展示微信支付(例如 /agent/promotionInquire/ 代理推广查询) */
hideWechat: {
type: Boolean,
default: false,
},
});
const show = defineModel();
function isWechatBrowser() {
if (typeof navigator === "undefined") return false;
return /MicroMessenger/i.test(navigator.userAgent);
}
/** 推广强制隐藏微信;非微信环境不展示微信支付 */
const showWechatPay = computed(() => !props.hideWechat && isWechatBrowser());
/** 微信内不展示支付宝;推广 hideWechat 时在微信内只能走支付宝,仍展示 */
const showAlipayPay = computed(() => !isWechatBrowser() || props.hideWechat);
function defaultPaymentMethod() {
if (isDev) return "test";
if (props.hideWechat) return "alipay";
if (isWechatBrowser()) return "wechat";
return "alipay";
}
const selectedPaymentMethod = ref(defaultPaymentMethod());
function syncSelectedPaymentMethod() {
selectedPaymentMethod.value = defaultPaymentMethod();
}
onMounted(syncSelectedPaymentMethod);
watch(
[() => props.hideWechat, showWechatPay, showAlipayPay, show],
() => {
if (show.value) {
syncSelectedPaymentMethod();
}
},
{ flush: "post" }
);
const orderNo = ref("");
const router = useRouter();
const discountPrice = ref(false); // 是否应用折扣
/** 规范化为 WeixinJSBridge 需要的字符串字段 */
function normalizeWechatJsApiPayload(obj) {
if (!obj || typeof obj !== "object") return null;
const appId = obj.appId ?? obj.appid;
const timeStamp = obj.timeStamp ?? obj.timestamp;
const nonceStr = obj.nonceStr ?? obj.nonce_str;
const pkg = obj.package != null ? obj.package : undefined;
const signType = obj.signType ?? obj.sign_type;
const paySign = obj.paySign ?? obj.pay_sign;
const out = {
appId: appId != null && appId !== "" ? String(appId) : undefined,
timeStamp: timeStamp != null && timeStamp !== "" ? String(timeStamp) : undefined,
nonceStr: nonceStr != null && nonceStr !== "" ? String(nonceStr) : undefined,
package: pkg != null && pkg !== "" ? String(pkg) : undefined,
signType: signType != null && signType !== "" ? String(signType) : undefined,
paySign: paySign != null && paySign !== "" ? String(paySign) : undefined,
};
if (Object.values(out).some((v) => v == null)) return null;
return out;
}
/** 从接口 data 中取出 JSAPI 参数(兼容 snake_case、camelCase、扁平 */
function extractWechatJsApiPayload(apiInner) {
if (!apiInner || typeof apiInner !== "object") return null;
const nested =
apiInner.prepay_data ??
apiInner.prepayData ??
apiInner.request_payment ??
apiInner.requestPayment;
if (nested && typeof nested === "object") {
const fromNested = normalizeWechatJsApiPayload(nested);
if (fromNested) return fromNested;
}
return normalizeWechatJsApiPayload(apiInner);
}
/** 微信内 JSAPI 调起支付(等待 WeixinJSBridge 就绪;非微信环境超时后提示) */
function invokeWechatPay(payload, onResult) {
const run = () => {
try {
WeixinJSBridge.invoke("getBrandWCPayRequest", payload, onResult);
} catch (e) {
console.error(e);
showToast("无法调起微信支付,请稍后重试");
show.value = true;
}
};
if (typeof WeixinJSBridge !== "undefined") {
run();
return;
}
const timeoutId = setTimeout(() => {
showToast("请在微信内打开页面后使用微信支付");
show.value = true;
}, 4000);
document.addEventListener(
"WeixinJSBridgeReady",
function onReady() {
clearTimeout(timeoutId);
document.removeEventListener("WeixinJSBridgeReady", onReady);
run();
},
false
);
}
async function getPayment() {
show.value = false;
// 为保障您的个人信息与资金安全,请您务必知悉以下事项:
//
// 关于平台业务:本平台官方服务仅限于大数据报告查询,不涉及也从未开展“央行征信修复”、“贷款办理”或“征信洗白”等相关业务。请注意,本平台出具的报告仅供决策参考,不可作为任何官方征信凭证或贷款依据。
//
// 关于诈骗警示:任何自称与本平台合作,或以“内部渠道”、“百分百包下款”、“修复征信”等为由,诱导您进行支付的行为,均属欺诈。请您切勿相信,谨慎对待任何支付要求。
//
// 关于安全提示:请您时刻保持警惕,妥善保管个人敏感信息。如遇任何索款要求或可疑承诺,请务必首先通过我平台官方公布的联系方式进行核实,切勿轻信他人。
const { data, error } = await useApiFetch("/pay/payment")
.post({
id: props.id,
pay_method: selectedPaymentMethod.value,
pay_type: props.type,
})
.json();
// 业务错误时 afterFetch 已 showToast此处仅恢复支付弹窗便于重试
if (error.value || !data.value || data.value.code !== 200) {
show.value = true;
return;
}
const inner = data.value.data ?? {};
const prepayId = inner.prepay_id ?? inner.prepayId;
const orderNoFromApi = inner.order_no ?? inner.orderNo;
orderNo.value = orderNoFromApi;
// 开发环境测试支付:直接跳转结果页
if (selectedPaymentMethod.value === "test" && prepayId === "test_payment_success") {
router.push({
path: "/payment/result",
query: { orderNo: orderNoFromApi },
});
return;
}
if (selectedPaymentMethod.value === "alipay") {
const prepayUrl = prepayId;
if (!prepayUrl) {
showToast("未获取到支付宝支付链接");
show.value = true;
return;
}
const paymentForm = document.createElement("form");
paymentForm.method = "POST";
paymentForm.action = prepayUrl;
paymentForm.style.display = "none";
document.body.appendChild(paymentForm);
paymentForm.submit();
return;
}
const payload = extractWechatJsApiPayload(inner);
if (!payload) {
showToast(
"未获取到微信支付参数。请确认1后端已部署最新支付接口2请求头含 X-Platform: wxh53用户已绑定微信网页授权 openid。"
);
show.value = true;
return;
}
invokeWechatPay(payload, function (res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
router.push({
path: "/payment/result",
query: { orderNo: orderNoFromApi },
});
} else if (res.err_msg) {
showToast(res.err_msg.replace(/^get_brand_wcpay_request:/, "") || "支付未完成");
}
});
}
</script>
<style scoped></style>