Files
device-voice-h5/components/RenewalPaymentPopup.vue
luo 34ca1c5eca
All checks were successful
构建并部署前端到生产环境 / build-and-deploy (push) Successful in 59s
fix: 立即续费不跳转
2026-07-28 16:02:40 +08:00

375 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>
<up-popup :show="show" mode="center" @close="close">
<view class="renewal-popup">
<view class="popup-header">
<view class="popup-title">立即续费</view>
<view class="popup-close" @tap="close">×</view>
</view>
<view class="package-summary">
<view class="summary-label">续费套餐</view>
<view class="summary-name">{{ packageNamesText }}</view>
</view>
<view class="payment-methods">
<view
v-for="method in paymentMethodOptions"
:key="method.value"
class="method-item"
:class="{ active: paymentMethod === method.value }"
@tap="selectPaymentMethod(method.value)"
>
<view class="method-left">
<view v-if="method.value === 'alipay'" class="method-icon method-badge method-badge-alipay"></view>
<image v-else-if="method.value === 'wallet'" class="method-icon" src="/static/wallet.png" mode="aspectFit"></image>
<view v-else class="method-icon method-badge"></view>
<text class="method-name">{{ method.label }}</text>
</view>
<view class="method-radio" :class="{ checked: paymentMethod === method.value }"></view>
</view>
<view v-if="paymentMethodOptions.length === 0" class="method-empty">暂无可用支付方式</view>
</view>
<view class="popup-footer">
<button class="btn-apple btn-secondary" @tap="close">取消</button>
<button class="btn-apple btn-primary" :disabled="submitting" @tap="confirmPay">
{{ submitting ? '处理中...' : '确认续费' }}
</button>
</view>
</view>
</up-popup>
</template>
<script setup>
import { ref, computed } from 'vue';
import { assetApi, orderApi } from '@/api/index.js';
import {
handlePaymentError,
isValidAlipayPaymentLink,
isValidWechatPayConfig,
openAlipayPayment,
PAYMENT_REFRESH_TARGETS,
showPaymentToast,
wechatH5Pay
} from '@/utils/payment.js';
import { formatMoney } from '@/utils/display.js';
import { getDefaultPaymentMethod, getPaymentMethodOptions, normalizePaymentMethods } from '@/utils/payment-methods.js';
const props = defineProps({
identifier: { type: String, default: '' },
paymentRefreshTarget: { type: String, default: PAYMENT_REFRESH_TARGETS.ORDER_LIST }
});
const emit = defineEmits(['completed']);
const show = ref(false);
const submitting = ref(false);
const packageIds = ref([]);
const packageNames = ref([]);
const paymentMethod = ref('alipay');
const allowedPaymentMethods = ref([]);
const assetInfo = ref({});
const paymentMethodOptions = computed(() => getPaymentMethodOptions(allowedPaymentMethods.value));
const packageNamesText = computed(() => packageNames.value.filter(Boolean).join('、') || '当前套餐');
const hasPreparedPaymentData = (paymentData) => {
return isValidWechatPayConfig(paymentData?.pay_config) ||
isValidAlipayPaymentLink(paymentData?.payment_link);
};
const close = () => {
if (!submitting.value) show.value = false;
};
const loadAssetInfo = async () => {
assetInfo.value = await assetApi.getInfo(props.identifier);
allowedPaymentMethods.value = normalizePaymentMethods(assetInfo.value.allowed_payment_methods);
paymentMethod.value = getDefaultPaymentMethod(allowedPaymentMethods.value);
};
const open = async (ids, names = []) => {
const validIds = [...new Set((Array.isArray(ids) ? ids : [ids])
.map((value) => Number(value))
.filter((value) => Number.isInteger(value) && value > 0))];
if (!validIds.length) {
uni.showToast({ title: '当前暂无可续费套餐', icon: 'none' });
return;
}
if (!props.identifier) {
uni.showToast({ title: '未找到当前资产', icon: 'none' });
return;
}
packageIds.value = validIds;
packageNames.value = Array.isArray(names) ? names.filter(Boolean) : [];
try {
await loadAssetInfo();
} catch (error) {
console.error('加载续费支付方式失败', error);
uni.showToast({ title: error.msg || error.message || '加载支付方式失败', icon: 'none' });
return;
}
if (assetInfo.value.effective_realname_policy === 'before_order' &&
assetInfo.value.realname_required && Number(assetInfo.value.real_name_status) !== 1) {
uni.showModal({
title: '需要实名认证',
content: '当前资产下单前需要完成实名认证',
confirmText: '去认证',
cancelText: '取消',
success: ({ confirm }) => {
if (confirm) uni.navigateTo({ url: '/pages/auth/auth' });
}
});
return;
}
if (!paymentMethodOptions.value.length) {
uni.showToast({ title: '暂无可用支付方式', icon: 'none' });
return;
}
show.value = true;
};
const selectPaymentMethod = (method) => {
if (paymentMethodOptions.value.some((item) => item.value === method)) {
paymentMethod.value = method;
}
};
const confirmOrderStatus = async (orderId) => {
try {
const detail = await orderApi.getDetail(orderId);
const status = detail?.payment_status ?? detail?.order?.payment_status;
showPaymentToast(status === 2, status === 2 ? '支付成功' : '支付结果确认中');
if (status === 2) emit('completed');
} catch (error) {
console.error('确认续费订单状态失败', error);
showPaymentToast(false, '支付结果确认失败,请稍后查看订单');
}
};
const handlePreparedPayment = async (paymentData, orderId = null, isRecharge = false) => {
if (isValidWechatPayConfig(paymentData?.pay_config)) {
try {
await wechatH5Pay(paymentData.pay_config);
if (orderId) {
await confirmOrderStatus(orderId);
} else {
showPaymentToast(true, isRecharge ? '充值成功,套餐将自动购买' : '支付成功');
emit('completed');
}
} catch (error) {
handlePaymentError(error);
}
return;
}
if (isValidAlipayPaymentLink(paymentData?.payment_link)) {
await openAlipayPayment(paymentData.payment_link, props.paymentRefreshTarget);
return;
}
uni.showToast({ title: '支付参数获取失败', icon: 'none' });
};
const handleRechargeOrder = async (orderResult) => {
const recharge = orderResult.recharge;
if (recharge.status === 2 || recharge.status === 3) {
uni.showToast({ title: `充值订单${recharge.status_name || '不可支付'},请重新下单`, icon: 'none' });
return;
}
if (recharge.status === 1 && !hasPreparedPaymentData(orderResult)) {
uni.showModal({
title: '提示',
content: '检测到您有待支付的充值订单,是否前往我的钱包查看?',
confirmText: '去查看',
cancelText: '取消',
success: ({ confirm }) => {
if (confirm) uni.navigateTo({ url: '/pages/my-wallet/my-wallet' });
}
});
return;
}
if (!hasPreparedPaymentData(orderResult)) {
uni.showToast({ title: '支付参数获取失败', icon: 'none' });
return;
}
const packageName = packageNamesText.value;
const rechargeAmount = orderResult.linked_package_info?.force_recharge_amount || recharge.amount;
uni.showModal({
title: '需要充值',
content: `续费${packageName}需要先充值¥${formatMoney(rechargeAmount)},充值成功后将自动购买套餐`,
confirmText: '去充值',
cancelText: '取消',
success: async ({ confirm }) => {
if (confirm) await handlePreparedPayment(orderResult, null, true);
}
});
};
const confirmPay = async () => {
if (submitting.value || !packageIds.value.length) return;
submitting.value = true;
uni.showLoading({ title: '创建订单...', mask: true });
try {
const orderResult = await orderApi.create(props.identifier, packageIds.value, paymentMethod.value);
if (orderResult?.order_type === 'recharge' && orderResult.recharge) {
uni.hideLoading();
show.value = false;
await handleRechargeOrder(orderResult);
return;
}
const orderId = orderResult?.order?.order_id;
if (!orderId) {
uni.hideLoading();
show.value = false;
uni.showToast({ title: '订单创建失败', icon: 'none' });
return;
}
uni.showLoading({ title: '准备支付...', mask: true });
const payResult = await orderApi.pay(orderId, paymentMethod.value);
uni.hideLoading();
show.value = false;
if (paymentMethod.value === 'wallet') {
await confirmOrderStatus(orderId);
return;
}
await handlePreparedPayment(payResult, orderId);
} catch (error) {
uni.hideLoading();
show.value = false;
console.error('创建续费订单或支付失败', error);
uni.showToast({ title: error.msg || error.message || '操作失败,请稍后重试', icon: 'none' });
} finally {
submitting.value = false;
}
};
defineExpose({ open });
</script>
<style scoped lang="scss">
.renewal-popup {
width: 620rpx;
max-width: calc(100vw - 80rpx);
padding: 32rpx;
box-sizing: border-box;
background: #fff;
border-radius: 24rpx;
}
.popup-header,
.popup-footer,
.method-left {
display: flex;
align-items: center;
}
.popup-header {
justify-content: space-between;
}
.popup-title {
color: var(--text-primary);
font-size: 34rpx;
font-weight: 600;
}
.popup-close {
width: 52rpx;
height: 52rpx;
color: var(--text-tertiary);
font-size: 44rpx;
line-height: 46rpx;
text-align: center;
}
.package-summary {
margin-top: 28rpx;
padding: 22rpx;
background: var(--bg-secondary);
border-radius: 14rpx;
}
.summary-label {
color: var(--text-tertiary);
font-size: 24rpx;
}
.summary-name {
margin-top: 8rpx;
color: var(--text-primary);
font-size: 30rpx;
font-weight: 600;
line-height: 1.5;
}
.payment-methods {
margin-top: 24rpx;
}
.method-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22rpx 0;
border-bottom: 1rpx solid var(--border-light);
&.active .method-name { color: var(--primary); }
}
.method-left { gap: 16rpx; }
.method-icon {
width: 44rpx;
height: 44rpx;
border-radius: 10rpx;
}
.method-badge {
display: flex;
align-items: center;
justify-content: center;
background: #07c160;
color: #fff;
font-size: 24rpx;
font-weight: 600;
}
.method-badge-alipay { background: #1677ff; }
.method-name { color: var(--text-primary); font-size: 28rpx; }
.method-radio {
width: 32rpx;
height: 32rpx;
border: 2rpx solid var(--border-light);
border-radius: 50%;
&.checked {
border: 8rpx solid var(--primary);
}
}
.method-empty {
padding: 28rpx 0;
color: var(--text-tertiary);
font-size: 26rpx;
text-align: center;
}
.popup-footer {
gap: 20rpx;
margin-top: 28rpx;
button {
flex: 1;
margin: 0;
}
}
</style>