一、支付模块:电商的“心脏搭桥手术”
各位程序猿/媛们,想象一下:用户熬过商品浏览、拼手速抢购、填完复杂地址,最后却在支付页面转圈圈崩溃——这体验堪比相亲遇到前任,简直杀人诛心啊!
支付模块作为电商系统的“心脏搭桥手术”,它要负责:
- 资金动脉疏通:安全对接支付渠道
- 状态神经中枢:实时同步订单状态
- 防血栓机制:防止重复支付/超时未支付
- 术后康复:退款/异常处理
今天我们就用Vue3 + Element Plus,手把手造一个“既优雅又能打”的支付系统。
二、支付模块设计:先画靶子再开枪
2.1 状态流转图(程序员版“贪吃蛇”)
待支付 → 支付中 → 支付成功/失败
↓
超时关单
↓
库存回滚
2.2 核心文件结构
src/
├── components/
│ ├── Payment.vue # 支付方式选择
│ └── CountDown.vue # 倒计时组件
├── views/
│ └── Checkout.vue # 结算页面
├── stores/
│ └── payment.js # 支付状态管理
└── utils/
├── payment.js # 支付工具类
└── request.js # 封装请求
2.3 数据模型(Pinia版)
// stores/payment.js
export const usePaymentStore = defineStore('payment', {
state: () => ({
orderInfo: null, // 订单信息
payMethods: [
{ type: 'alipay', name: '支付宝', icon: '💰' },
{ type: 'wechat', name: '微信支付', icon: '💳' }
],
countDown: 900, // 15分钟倒计时
}),
actions: {
// 创建支付订单
async createOrder(orderData) {
const { data } = await api.createOrder(orderData)
this.orderInfo = data
this.startCountDown()
},
// 开启支付倒计时
startCountDown() {
const timer = setInterval(() => {
if (this.countDown <= 0) {
clearInterval(timer)
this.closeOrder()
return
}
this.countDown--
}, 1000)
}
}
})
三、支付页面实现:让用户“爽快掏钱”
3.1 支付方式选择(堪比选妃)
<template>
<div class="payment-methods">
<h3>选择支付方式</h3>
<div
v-for="method in payMethods"
:key="method.type"
class="method-card"
:class="{ active: selectedMethod === method.type }"
@click="selectedMethod = method.type"
>
<span class="icon">{{ method.icon }}</span>
<span>{{ method.name }}</span>
<el-radio :model-value="selectedMethod" :label="method.type" />
</div>
<!-- 倒计时组件 -->
<CountDown :time="countDown" @timeup="handleTimeUp" />
<!-- 支付按钮 -->
<el-button
type="primary"
size="large"
:loading="paying"
@click="handlePayment"
class="pay-btn">
{{ paying ? '支付中...' : `立即支付 ¥${amount}` }}
</el-button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { usePaymentStore } from '@/stores/payment'
const paymentStore = usePaymentStore()
const selectedMethod = ref('alipay')
const paying = ref(false)
const amount = computed(() => paymentStore.orderInfo?.amount || 0)
const handlePayment = async () => {
paying.value = true
try {
await paymentStore.requestPayment({
orderId: paymentStore.orderInfo.id,
method: selectedMethod.value
})
ElMessage.success('支付成功!')
} catch (error) {
ElMessage.error(error.message || '支付失败')
} finally {
paying.value = false
}
}
</script>
3.2 防重复提交(给手滑星人上保险)
// utils/payment.js
export const usePaymentLock = () => {
let isPaying = false
const withPaymentLock = async (fn) => {
if (isPaying) {
throw new Error('请不要重复点击支付按钮')
}
isPaying = true
try {
return await fn()
} finally {
// 2秒后解锁,防止连续快速点击
setTimeout(() => { isPaying = false }, 2000)
}
}
return { withPaymentLock }
}
四、支付接口对接:与第三方“谈恋爱”
4.1 支付请求封装(适配多渠道)
// utils/payment.js
export const paymentAPI = {
// 支付宝支付
alipay: async (orderId) => {
const { data } = await request.post('/pay/alipay', { orderId })
// 跳转到支付宝支付页面
window.location.href = data.payUrl
},
// 微信支付
wechat: async (orderId) => {
const { data } = await request.post('/pay/wechat', { orderId })
// 调用微信JSAPI
WeChatJSBridge.invoke(
'getBrandWCPayRequest',
data.payParams,
(res) => {
if (res.err_msg === "get_brand_wcpay_request:ok") {
console.log('支付成功')
}
}
)
}
}
4.2 轮询查询支付结果(比女朋友还执着)
// utils/payment.js
export const pollPaymentResult = (orderId, maxAttempts = 30) => {
return new Promise((resolve, reject) => {
let attempts = 0
const poll = async () => {
try {
const { data } = await request.get(`/orders/${orderId}/status`)
if (data.status === 'paid') {
resolve(data)
return
}
if (data.status === 'failed') {
reject(new Error('支付失败'))
return
}
attempts++
if (attempts >= maxAttempts) {
reject(new Error('查询超时'))
return
}
// 每2秒查询一次
setTimeout(poll, 2000)
} catch (error) {
reject(error)
}
}
poll()
})
}
五、支付成功处理:胜利的“凯旋门”
5.1 支付成功页面
<template>
<div class="success-page">
<el-result icon="success" title="支付成功!">
<template #extra>
<p>订单号:{{ orderId }}</p>
<p>支付金额:<span class="amount">¥{{ amount }}</span></p>
<p>预计24小时内发货,请保持手机畅通</p>
<div class="action-buttons">
<el-button @click="viewOrder">查看订单</el-button>
<el-button type="primary" @click="backToHome">返回首页</el-button>
</div>
</template>
</el-result>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const route = useRoute()
const orderId = route.query.orderId
const amount = route.query.amount
const viewOrder = () => {
router.push(`/orders/${orderId}`)
}
const backToHome = () => {
router.push('/')
}
</script>
5.2 支付异常处理(程序员的“救火指南”)
// utils/payment.js
export const handlePaymentError = (error) => {
const errorMap = {
NETWORK_ERROR: '网络异常,请检查网络连接',
INSUFFICIENT_BALANCE: '余额不足,请更换支付方式',
PAYMENT_TIMEOUT: '支付超时,请重新尝试',
SYSTEM_BUSY: '系统繁忙,请稍后再试'
}
const message = errorMap[error.code] || error.message || '支付遇到未知错误'
// 记录错误日志
console.error('支付错误:', error)
// 提示用户
ElMessage.error(message)
// 特殊错误处理
if (error.code === 'INSUFFICIENT_BALANCE') {
router.push('/recharge') // 跳转到充值页面
}
}
六、完整示例:可运行的支付demo
6.1 快速启动项目
# 克隆示例项目
git clone https://github.com/example/vue-payment-demo
cd vue-payment-demo
# 安装依赖
npm install
# 启动开发服务器
npm run dev
6.2 核心支付组件完整代码
由于篇幅限制,这里提供关键代码片段,完整项目请查看GitHub仓库:
<!-- components/PaymentFlow.vue -->
<template>
<div class="payment-flow">
<!-- 订单信息 -->
<OrderSummary :goods="orderGoods" />
<!-- 支付方式 -->
<PaymentMethods v-model="payMethod" />
<!-- 支付按钮 -->
<el-button
type="primary"
:loading="loading"
@click="handlePay"
class="pay-btn">
确认支付 ¥{{ totalAmount }}
</el-button>
<!-- 支付结果 -->
<PaymentResult
:visible="showResult"
:status="payStatus"
@close="handleResultClose" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { usePayment } from '@/composables/usePayment'
const {
payMethod,
loading,
payStatus,
showResult,
initiatePayment
} = usePayment()
const totalAmount = computed(() => 188.00)
const handlePay = async () => {
const success = await initiatePayment({
amount: totalAmount.value,
method: payMethod.value
})
if (success) {
// 支付成功处理
console.log('支付流程完成')
}
}
</script>
七、避坑指南:前人踩过的雷
- 时区问题:服务器时间 vs 本地时间,倒计时要以后端为准
- 金额精度:永远用分(整数)做计算,避免0.1+0.2≠0.3的悲剧
- 网络安全:敏感参数都要后端生成,前端只是“传话筒”
- 兼容性:iOS的Safari对支付跳转有特殊限制
八、结语
支付模块就像电商系统的“最后一公里”,设计得好,用户爽快掏钱;设计得烂,到嘴的鸭子都能飞。通过今天的实战,相信你已经掌握了:
✅ 支付状态流转设计
✅ 多支付渠道对接
✅ 防重复提交机制
✅ 异常情况处理
记住:好的支付体验,就是让用户“无感”地完成付款。就像优秀的服务员,需要时及时出现,结账时快速搞定,绝不拖泥带水。
279

被折叠的 条评论
为什么被折叠?



