Vue基础教程(207)网上购物商城开发实战之系统功能模块设计与实现中的支付模块:代码如诗:Vue支付模块实战全揭秘,从“剁手”到“到账”的奇幻漂流

一、支付模块:电商的“心脏搭桥手术”

各位程序猿/媛们,想象一下:用户熬过商品浏览、拼手速抢购、填完复杂地址,最后却在支付页面转圈圈崩溃——这体验堪比相亲遇到前任,简直杀人诛心啊!

支付模块作为电商系统的“心脏搭桥手术”,它要负责:

  • 资金动脉疏通:安全对接支付渠道
  • 状态神经中枢:实时同步订单状态
  • 防血栓机制:防止重复支付/超时未支付
  • 术后康复:退款/异常处理

今天我们就用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>
七、避坑指南:前人踩过的雷
  1. 时区问题:服务器时间 vs 本地时间,倒计时要以后端为准
  2. 金额精度:永远用分(整数)做计算,避免0.1+0.2≠0.3的悲剧
  3. 网络安全:敏感参数都要后端生成,前端只是“传话筒”
  4. 兼容性:iOS的Safari对支付跳转有特殊限制
八、结语

支付模块就像电商系统的“最后一公里”,设计得好,用户爽快掏钱;设计得烂,到嘴的鸭子都能飞。通过今天的实战,相信你已经掌握了:

✅ 支付状态流转设计
✅ 多支付渠道对接
✅ 防重复提交机制
✅ 异常情况处理

记住:好的支付体验,就是让用户“无感”地完成付款。就像优秀的服务员,需要时及时出现,结账时快速搞定,绝不拖泥带水。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值