2
0

fix:支付方式对接

This commit is contained in:
zhongzm
2025-01-21 21:53:55 +08:00
parent e0ea1e1764
commit 96d19e323f
7 changed files with 368 additions and 70 deletions

View File

@@ -0,0 +1,155 @@
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import { useI18n } from 'vue-i18n';
import { AlipayOutlined, WechatOutlined } from '@ant-design/icons-vue';
const { t } = useI18n();
interface Props {
visible: boolean;
orderInfo: {
orderId: string;
orderType: number; // 0: 购买套餐, 1: 余额充值
orderAmount: number;
};
}
const props = defineProps<Props>();
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
const orderTypeMap = {
0: t('page.order.packagePurchase'),
1: t('page.order.balanceRecharge')
} as const;
const handleConfirm = (paymentMethod: 'alipay' | 'wxpay') => {
emit('confirm', paymentMethod);
};
const handleCancel = () => {
emit('update:visible', false);
emit('cancel');
};
</script>
<template>
<a-modal
:visible="visible"
:title="t('page.order.confirmOrder')"
:footer="null"
@cancel="handleCancel"
width="460px"
:maskClosable="false"
>
<div class="order-info">
<div class="info-item">
<span class="label">{{ t('page.order.orderType') }}</span>
<span class="value">{{ orderTypeMap[orderInfo.orderType] }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.order.orderAmount') }}</span>
<span class="value highlight">¥{{ orderInfo.orderAmount.toFixed(2) }}</span>
</div>
<div class="info-item">
<span class="label">{{ t('page.order.orderId') }}</span>
<span class="value">{{ orderInfo.orderId }}</span>
</div>
</div>
<div class="payment-methods">
<h4>{{ t('page.order.selectPayment') }}</h4>
<div class="methods-container">
<div class="method-item" @click="handleConfirm('alipay')">
<AlipayOutlined class="payment-icon alipay-icon" />
<span>{{ t('page.order.alipay') }}</span>
</div>
<div class="method-item" @click="handleConfirm('wxpay')">
<WechatOutlined class="payment-icon wxpay-icon" />
<span>{{ t('page.order.wxpay') }}</span>
</div>
</div>
</div>
</a-modal>
</template>
<style scoped>
.order-info {
padding: 20px;
background: #f8f8f8;
border-radius: 8px;
margin-bottom: 24px;
}
.info-item {
display: flex;
margin-bottom: 16px;
line-height: 24px;
font-size: 14px;
}
.info-item:last-child {
margin-bottom: 0;
}
.label {
width: 84px;
color: #666;
}
.value {
flex: 1;
color: #333;
}
.value.highlight {
color: #ff4d4f;
font-size: 16px;
font-weight: 500;
}
.payment-methods {
padding: 0 4px;
}
.payment-methods h4 {
margin-bottom: 16px;
color: #333;
font-size: 14px;
font-weight: normal;
}
.methods-container {
display: flex;
gap: 12px;
}
.method-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border: 1px solid #e8e8e8;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.method-item:hover {
border-color: #1890ff;
background: #f0f5ff;
}
.payment-icon {
font-size: 24px;
margin-bottom: 8px;
}
.alipay-icon {
color: #1677ff;
}
.wxpay-icon {
color: #07c160;
}
</style>

View File

@@ -661,6 +661,17 @@ const local: any = {
balanceRecharge:'Banlance Recharge', balanceRecharge:'Banlance Recharge',
packageSubscription:'Package Subscription', packageSubscription:'Package Subscription',
}, },
order:{
confirmOrder:'Order Confirmation',
orderType:'Order type',
balanceRecharge:'Balance recharge',
packagePurchase:'Package processing',
orderAmount:'Amount',
orderId:'ID',
selectPayment:'Payment',
alipay:'Alipay',
wxpay:'WeChat Pay'
}
}, },
form: { form: {
required: 'Cannot be empty', required: 'Cannot be empty',

View File

@@ -663,6 +663,20 @@ const local:any = {
balanceRecharge:'余额充值', balanceRecharge:'余额充值',
packageSubscription:'套餐办理', packageSubscription:'套餐办理',
}, },
order:{
confirmOrder:'订单确认',
orderType:'订单类型',
balanceRecharge:'余额充值',
packagePurchase:'套餐办理',
orderAmount:'订单金额',
orderId:'订单ID',
selectPayment:'支付方式',
alipay:'支付宝支付',
wxpay:'微信支付'
},
package:{
}
}, },
form: { form: {
required: '不能为空', required: '不能为空',

View File

@@ -14,6 +14,7 @@ declare global {
const afterAll: typeof import('vitest')['afterAll'] const afterAll: typeof import('vitest')['afterAll']
const afterEach: typeof import('vitest')['afterEach'] const afterEach: typeof import('vitest')['afterEach']
const aliPayPcPay: typeof import('../service/api/payment')['aliPayPcPay'] const aliPayPcPay: typeof import('../service/api/payment')['aliPayPcPay']
const aliPayWapPay: typeof import('../service/api/payment')['aliPayWapPay']
const assert: typeof import('vitest')['assert'] const assert: typeof import('vitest')['assert']
const assign: typeof import('lodash-es')['assign'] const assign: typeof import('lodash-es')['assign']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
@@ -74,6 +75,7 @@ declare global {
const doGetDictList: typeof import('../service/api/dict')['doGetDictList'] const doGetDictList: typeof import('../service/api/dict')['doGetDictList']
const doGetMenuDetail: typeof import('../service/api/menu')['doGetMenuDetail'] const doGetMenuDetail: typeof import('../service/api/menu')['doGetMenuDetail']
const doGetMenuList: typeof import('../service/api/menu')['doGetMenuList'] const doGetMenuList: typeof import('../service/api/menu')['doGetMenuList']
const doGetOrderInfo: typeof import('../service/api/order')['doGetOrderInfo']
const doGetPostDetail: typeof import('../service/api/post')['doGetPostDetail'] const doGetPostDetail: typeof import('../service/api/post')['doGetPostDetail']
const doGetPostList: typeof import('../service/api/post')['doGetPostList'] const doGetPostList: typeof import('../service/api/post')['doGetPostList']
const doGetRoleList: typeof import('../service/api/role')['doGetRoleList'] const doGetRoleList: typeof import('../service/api/role')['doGetRoleList']
@@ -433,6 +435,7 @@ declare global {
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever'] const whenever: typeof import('@vueuse/core')['whenever']
const wxPayScanCode: typeof import('../service/api/payment')['wxPayScanCode']
} }
// for type re-export // for type re-export
declare global { declare global {

View File

@@ -77,6 +77,7 @@ declare module 'vue' {
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default'] LookForward: typeof import('./../components/custom/look-forward.vue')['default']
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default'] MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
OrderConfirmModal: typeof import('./../components/order-confirm/orderConfirmModal.vue')['default']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']

View File

@@ -7,6 +7,7 @@ import { message } from 'ant-design-vue';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import OrderConfirmModal from '@/components/order-confirm/orderConfirmModal.vue';
defineOptions({ defineOptions({
name: 'BalanceRecharge' name: 'BalanceRecharge'
@@ -82,6 +83,14 @@ onUnmounted(() => {
document.removeEventListener('click', handleClickOutside); document.removeEventListener('click', handleClickOutside);
}); });
// 添加订单确认弹窗相关状态
const showOrderModal = ref(false);
const currentOrderInfo = ref({
orderId: '',
orderType: 1, // 1 表示余额充值
orderAmount: 0
});
// 修改充值处理方法 // 修改充值处理方法
const handleRecharge = async () => { const handleRecharge = async () => {
// 验证金额 // 验证金额
@@ -90,55 +99,54 @@ const handleRecharge = async () => {
return; return;
} }
try {
await submitOrder({
type: 1,
orderAmount: paymentAmount.value
});
message.success('充值订单提交成功!');
} catch (error) {
message.error('充值失败,请重试!');
console.error('Failed to submit recharge order:', error);
}
};
// 测试:支付宝支付
const handleAliPay = async () => {
try { try {
const orderRes = await submitOrder({ const orderRes = await submitOrder({
type: 1, type: 1,
orderAmount: paymentAmount.value orderAmount: paymentAmount.value
}); });
// 更新订单信息并显示弹窗
currentOrderInfo.value = {
orderId: orderRes.data,
orderType: 1,
orderAmount: paymentAmount.value
};
showOrderModal.value = true;
} catch (error) {
message.error('创建订单失败,请重试!');
console.error('Failed to create order:', error);
}
};
// 处理支付方式选择
const handlePaymentConfirm = async (paymentMethod: 'alipay' | 'wxpay') => {
try {
if (paymentMethod === 'alipay') {
// 区分手机端和pc端支付 // 区分手机端和pc端支付
let res; let res;
if (appStore.isMobile) { if (appStore.isMobile) {
res = await aliPayWapPay({orderId: orderRes.data}); res = await aliPayWapPay({ orderId: currentOrderInfo.value.orderId });
} else { } else {
res = await aliPayPcPay({orderId: orderRes.data}); res = await aliPayPcPay({ orderId: currentOrderInfo.value.orderId });
} }
console.log(res);
const div = document.createElement("div"); const div = document.createElement("div");
div.innerHTML = res; // html code div.innerHTML = res;
document.body.appendChild(div); document.body.appendChild(div);
document.forms['punchout_form'].submit(); document.forms['punchout_form'].submit();
} else {
} catch (error) { const res = await wxPayScanCode({ orderId: currentOrderInfo.value.orderId });
message.error('充值失败,请重试!'); routerPushByKey('billing_wxpay', {
console.error('Failed to submit recharge order:', error); query: {
url: res.data,
orderId: currentOrderInfo.value.orderId
} }
};
// 微信支付
const handleWxPay = async () => {
try {
const orderRes = await submitOrder({
type: 1,
orderAmount: paymentAmount.value
}); });
const res = await wxPayScanCode({orderId: orderRes.data}); }
routerPushByKey('billing_wxpay', { query: { url: res.data, orderId: orderRes.data } })
} catch (error) { } catch (error) {
message.error('充值失败,请重试!'); message.error(t('page.order.paymentFailed'));
console.error('Failed to submit recharge order:', error); console.error('Payment failed:', error);
} finally {
showOrderModal.value = false;
} }
}; };
</script> </script>
@@ -193,27 +201,14 @@ const handleWxPay = async () => {
> >
¥{{ paymentAmount.toFixed(2) }} {{ t('page.carddata.pay') }} ¥{{ paymentAmount.toFixed(2) }} {{ t('page.carddata.pay') }}
</AButton> </AButton>
<!-- 测试支付宝支付 -->
<AButton
type="primary"
size="large"
block
:disabled="!paymentAmount || paymentAmount <= 0"
@click="handleAliPay"
>
AliPay
</AButton>
<!-- 测试微信支付 -->
<AButton
type="primary"
size="large"
block
:disabled="!paymentAmount || paymentAmount <= 0"
@click="handleWxPay"
>
WxPay
</AButton>
</div> </div>
<!-- 订单确认弹窗 -->
<OrderConfirmModal
v-model:visible="showOrderModal"
:order-info="currentOrderInfo"
@confirm="handlePaymentConfirm"
/>
</div> </div>
</template> </template>

View File

@@ -3,12 +3,22 @@ import { useI18n } from 'vue-i18n';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { fetchPackageList, submitOrder } from '@/service/api/auth'; import { fetchPackageList, submitOrder } from '@/service/api/auth';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import OrderConfirmModal from '@/components/order-confirm/orderConfirmModal.vue';
import { aliPayPcPay, wxPayScanCode } from '@/service/api/payment';
import { useRouterPush } from '@/hooks/common/router';
defineOptions({ defineOptions({
name: 'PackageSubscription' name: 'PackageSubscription'
}); });
const { t } = useI18n(); const { t } = useI18n();
interface RateLimit {
upLimitEnable: boolean;
downLimitEnable: boolean;
upLimit: number;
downLimit: number;
}
interface PackageOption { interface PackageOption {
id: string; id: string;
packageName: string; packageName: string;
@@ -24,6 +34,8 @@ interface PackageOption {
periodNum: number; periodNum: number;
periodType: number; periodType: number;
validityPeriod: string; validityPeriod: string;
rateLimitEnable: boolean;
rateLimits: RateLimit;
} }
// 添加有效期类型枚举 // 添加有效期类型枚举
@@ -86,6 +98,16 @@ const formatTrafficValidity = (num: number, type: number): string => {
return `${num}${unit}`; return `${num}${unit}`;
}; };
// 添加速率格式化函数
const formatSpeed = (speed: number): string => {
if (speed >= 1000000) {
return `${(speed / 1000000).toFixed(1)}Gbps`;
} else if (speed >= 1000) {
return `${(speed / 1000).toFixed(1)}Mbps`;
}
return `${speed}Kbps`;
};
const packageOptions = ref<PackageOption[]>([]); const packageOptions = ref<PackageOption[]>([]);
const selectedPackage = ref<PackageOption>({ const selectedPackage = ref<PackageOption>({
id: '1', id: '1',
@@ -101,7 +123,25 @@ const selectedPackage = ref<PackageOption>({
promotion: '', promotion: '',
periodNum: 0, periodNum: 0,
periodType: PERIOD_TYPE.MONTH, periodType: PERIOD_TYPE.MONTH,
validityPeriod: '0月' validityPeriod: '0月',
rateLimitEnable: false,
rateLimits: {
upLimitEnable: false,
downLimitEnable: false,
upLimit: 0,
downLimit: 0
}
});
// 添加路由跳转方法
const { routerPushByKey } = useRouterPush();
// 添加订单确认弹窗相关状态
const showOrderModal = ref(false);
const currentOrderInfo = ref({
orderId: '',
orderType: 0, // 0 表示购买套餐
orderAmount: 0
}); });
const fetchPackages = async () => { const fetchPackages = async () => {
@@ -116,13 +156,22 @@ const fetchPackages = async () => {
clientNumEnable: pkg.clientNumEnable, clientNumEnable: pkg.clientNumEnable,
traffic: Number(pkg.traffic), traffic: Number(pkg.traffic),
trafficEnable: pkg.trafficEnable, trafficEnable: pkg.trafficEnable,
trafficDisplay: pkg.trafficEnable ? `${formatTraffic(Number(pkg.traffic))}${formatTrafficValidity(Number(pkg.periodNum), Number(pkg.periodType))}内有效` : '无限制', trafficDisplay: pkg.trafficEnable ? formatTraffic(Number(pkg.traffic)) : '无限制',
durationEnable: pkg.durationEnable, durationEnable: Boolean(pkg.periodNum && pkg.periodType !== undefined),
isRecommended: pkg.isRecommended || false, isRecommended: pkg.isRecommended || false,
promotion: pkg.promotion || '', promotion: pkg.promotion || '',
periodNum: Number(pkg.periodNum), periodNum: Number(pkg.periodNum),
periodType: Number(pkg.periodType), periodType: Number(pkg.periodType),
validityPeriod: pkg.durationEnable ? formatValidityPeriod(Number(pkg.periodNum), Number(pkg.periodType)) : '无限制' validityPeriod: pkg.periodNum && pkg.periodType !== undefined
? formatValidityPeriod(Number(pkg.periodNum), Number(pkg.periodType))+'内有效'
: '无限制',
rateLimitEnable: pkg.rateLimitEnable,
rateLimits: {
upLimitEnable: pkg.rateLimits?.upLimitEnable || false,
downLimitEnable: pkg.rateLimits?.downLimitEnable || false,
upLimit: Number(pkg.rateLimits?.upLimit) || 0,
downLimit: Number(pkg.rateLimits?.downLimit) || 0
}
})); }));
if (packageOptions.value.length > 0) { if (packageOptions.value.length > 0) {
@@ -138,17 +187,50 @@ const selectPackage = (option: PackageOption) => {
selectedPackage.value = option; selectedPackage.value = option;
}; };
// 套餐办理方法 // 修改套餐办理方法
const handleSubmitOrder = async () => { const handleSubmitOrder = async () => {
try { try {
await submitOrder({ const orderRes = await submitOrder({
type: 0, type: 0,
packageId: selectedPackage.value.id packageId: selectedPackage.value.id
}); });
message.success('套餐办理成功!');
// 更新订单信息并显示弹窗
currentOrderInfo.value = {
orderId: orderRes.data,
orderType: 0,
orderAmount: selectedPackage.value.price
};
showOrderModal.value = true;
} catch (error) { } catch (error) {
message.error('套餐办理失败,请重试!'); message.error(t('page.order.createOrderFailed'));
console.error('Failed to submit order:', error); console.error('Failed to create order:', error);
}
};
// 处理支付方式选择
const handlePaymentConfirm = async (paymentMethod: 'alipay' | 'wxpay') => {
try {
if (paymentMethod === 'alipay') {
const res = await aliPayPcPay({ orderId: currentOrderInfo.value.orderId });
const div = document.createElement("div");
div.innerHTML = res;
document.body.appendChild(div);
document.forms['punchout_form'].submit();
} else {
const res = await wxPayScanCode({ orderId: currentOrderInfo.value.orderId });
routerPushByKey('billing_wxpay', {
query: {
url: res.data,
orderId: currentOrderInfo.value.orderId
}
});
}
} catch (error) {
message.error(t('page.order.paymentFailed'));
console.error('Payment failed:', error);
} finally {
showOrderModal.value = false;
} }
}; };
@@ -224,6 +306,22 @@ onMounted(async () => {
{{ selectedPackage.durationEnable ? selectedPackage.validityPeriod : '无限制' }} {{ selectedPackage.durationEnable ? selectedPackage.validityPeriod : '无限制' }}
</div> </div>
</div> </div>
<div class="detail-item">
<div class="label">速率限制</div>
<div class="value">
<template v-if="!selectedPackage.rateLimitEnable">
不限速
</template>
<template v-else>
<div>
上行{{ selectedPackage.rateLimits.upLimitEnable ? formatSpeed(selectedPackage.rateLimits.upLimit) : '-' }}
</div>
<div>
下行{{ selectedPackage.rateLimits.downLimitEnable ? formatSpeed(selectedPackage.rateLimits.downLimit) : '-' }}
</div>
</template>
</div>
</div>
<div class="bottom-bar"> <div class="bottom-bar">
<button <button
class="btn-primary" class="btn-primary"
@@ -235,6 +333,13 @@ onMounted(async () => {
</div> </div>
</div> </div>
</div> </div>
<!-- 订单确认弹窗 -->
<OrderConfirmModal
v-model:visible="showOrderModal"
:order-info="currentOrderInfo"
@confirm="handlePaymentConfirm"
/>
</div> </div>
</template> </template>
@@ -401,4 +506,18 @@ onMounted(async () => {
color: #666; color: #666;
margin-top: 4px; margin-top: 4px;
} }
.speed-limit {
font-size: 14px;
color: #666;
margin-top: 4px;
}
.value > div {
margin-bottom: 4px;
}
.value > div:last-child {
margin-bottom: 0;
}
</style> </style>