H5 页面在微信浏览器里调用微信支付 demo

Vue 3 + TypeScript + Element Plus 版本

javascript 复制代码
<template>
  <div class="payment-container">
    <el-form 
      ref="paymentFormRef" 
      :model="formData" 
      :rules="formRules" 
      label-position="top" 
      class="payment-form"
      @submit.prevent="handleSubmit"
    >
      <!-- 固定金额选项 -->
      <div class="amount-options">
        <div class="amount-row">
          <div
            v-for="amount in fixedAmounts.slice(0, 3)"
            :key="amount.value"
            class="amount-item"
            :class="{ 'amount-item-active': selectedAmount === amount.value && !isCustomAmount }"
            @click="selectFixedAmount(amount.value)"
          >
            {{ amount.label }}
          </div>
        </div>
        <div class="amount-row">
          <div
            v-for="amount in fixedAmounts.slice(3)"
            :key="amount.value"
            class="amount-item"
            :class="{ 'amount-item-active': selectedAmount === amount.value && !isCustomAmount }"
            @click="selectFixedAmount(amount.value)"
          >
            {{ amount.label }}
          </div>
        </div>
      </div>

      <!-- 自定义金额 -->
      <div class="custom-amount-section">
        <div
          class="amount-item custom-label"
          :class="{ 'amount-item-active': isCustomAmount }"
          @click="focusCustomAmount"
        >
          其他金额
        </div>
        <el-form-item prop="customAmount" class="custom-input-item">
          <el-input
            v-model="formData.customAmount"
            type="number"
            placeholder="请输入金额"
            :class="{ 'custom-input-active': isCustomAmount }"
            @focus="focusCustomAmount"
            @input="handleAmountInput"
          >
            <template #append>元</template>
          </el-input>
        </el-form-item>
      </div>

      <!-- 提交按钮 -->
      <el-form-item class="submit-item">
        <el-button
          type="primary"
          :loading="loading"
          @click="handleSubmit"
        >
          立即支付
        </el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import axios from 'axios'

// 微信支付相关类型定义
interface WeixinPayParams {
  appId: string
  timeStamp: string
  nonceStr: string
  package: string
  signType: string
  paySign: string
}

interface PayResponseData {
  code: number
  msg: string
  data?: WeixinPayParams
}

interface FixedAmount {
  value: number
  label: string
}

// 表单数据
const formData = reactive({
  customAmount: ''
})

// 表单验证规则
const formRules: FormRules = {
  customAmount: [
    { 
      validator: (rule: any, value: string, callback: any) => {
        if (isCustomAmount.value && (!value || Number(value) <= 0)) {
          callback(new Error('请输入有效的金额'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ]
}

// 响应式数据
const paymentFormRef = ref<FormInstance>()
const loading = ref(false)
const selectedAmount = ref<number>(50)
const isCustomAmount = ref(false)

// 固定金额选项
const fixedAmounts: FixedAmount[] = [
  { value: 50, label: '50元' },
  { value: 100, label: '100元' },
  { value: 200, label: '200元' },
  { value: 300, label: '300元' },
  { value: 500, label: '500元' }
]

// 选择固定金额
const selectFixedAmount = (amount: number) => {
  selectedAmount.value = amount
  isCustomAmount.value = false
  formData.customAmount = ''
}

// 聚焦自定义金额输入框
const focusCustomAmount = () => {
  isCustomAmount.value = true
  selectedAmount.value = 0
}

// 处理金额输入
const handleAmountInput = (value: string) => {
  // 限制只能输入数字和小数点
  const cleaned = value.replace(/[^\d.]/g, '')
  if (cleaned !== value) {
    formData.customAmount = cleaned
  }
  
  // 限制小数点后最多三位
  const parts = cleaned.split('.')
  if (parts.length > 1 && parts[1].length > 3) {
    formData.customAmount = `${parts[0]}.${parts[1].slice(0, 3)}`
  }
  
  if (cleaned) {
    isCustomAmount.value = true
    selectedAmount.value = Number(cleaned)
  }
}

// 获取当前支付金额
const getPaymentAmount = (): number => {
  if (isCustomAmount.value && formData.customAmount) {
    return Number(formData.customAmount)
  }
  return selectedAmount.value
}

// 微信支付函数
const onBridgeReady = (payData: WeixinPayParams) => {
  const wechat = (window as any).WeixinJSBridge
  if (!wechat) {
    ElMessage.error('微信支付环境初始化失败')
    return
  }

  wechat.invoke(
    'getBrandWCPayRequest',
    {
      appId: payData.appId,
      timeStamp: payData.timeStamp.toString(),
      nonceStr: payData.nonceStr,
      package: payData.package,
      signType: payData.signType,
      paySign: payData.paySign
    },
    (res: any) => {
      if (res.err_msg === 'get_brand_wcpay_request:ok') {
        ElMessage.success('支付成功')
        // 支付成功后的处理逻辑
      } else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
        ElMessage.warning('支付已取消')
      } else {
        ElMessage.error(`支付失败: ${res.err_msg}`)
      }
    }
  )
}

// 初始化微信支付
const initWechatPayment = () => {
  if (typeof (window as any).WeixinJSBridge === 'undefined') {
    if (document.addEventListener) {
      document.addEventListener(
        'WeixinJSBridgeReady',
        () => {},
        false
      )
    } else if ((document as any).attachEvent) {
      ;(document as any).attachEvent('WeixinJSBridgeReady', () => {})
      ;(document as any).attachEvent('onWeixinJSBridgeReady', () => {})
    }
  }
}

// 提交表单
const handleSubmit = async () => {
  const amount = getPaymentAmount()
  
  if (amount <= 0) {
    ElMessage.warning('请选择或输入支付金额')
    return
  }

  try {
    loading.value = true
    
    // 获取微信code(这里假设code存储在localStorage)
    const code = localStorage.getItem('wx-code')
    if (!code) {
      ElMessage.warning('请重新授权获取用户信息')
      return
    }

    // 调用支付接口
    const response = await axios.post<PayResponseData>('/h5/jsapi/pay', {
      code,
      money: amount
    })

    if (response.data.code === 0 && response.data.data) {
      onBridgeReady(response.data.data)
    } else {
      ElMessage.error(response.data.msg || '支付失败')
    }
  } catch (error: any) {
    console.error('支付请求失败:', error)
    ElMessage.error(error.message || '网络错误,请重试')
  } finally {
    loading.value = false
  }
}

// 组件挂载时初始化
onMounted(() => {
  initWechatPayment()
  
  // 验证是否有微信环境
  if (!/(micromessenger)/i.test(navigator.userAgent)) {
    ElMessage.warning('请在微信客户端中打开')
  }
})
</script>

<style scoped>
.payment-container {
  padding: 20px;
  background-color: #ffffff;
  min-height: 100vh;
}

.payment-form {
  max-width: 400px;
  margin: 0 auto;
}

.amount-options {
  margin-bottom: 20px;
}

.amount-row {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-bottom: 20px;
}

.amount-item {
  width: 100px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s;
  font-size: 16px;
  color: #606266;
  user-select: none;
}

.amount-item:hover {
  border-color: #409eff;
  color: #409eff;
}

.amount-item-active {
  border-color: #409eff !important;
  color: #409eff !important;
  background-color: #ecf5ff;
}

.custom-amount-section {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-bottom: 40px;
}

.custom-label {
  width: 100px;
}

.custom-input-item {
  margin: 0;
}

.custom-input-item :deep(.el-input) {
  width: 120px;
}

.custom-input-active :deep(.el-input__inner) {
  border-color: #409eff;
}

.submit-item {
  text-align: center;
}

.submit-item :deep(.el-button) {
  width: 200px;
  height: 44px;
  font-size: 16px;
}
</style>
相关推荐
奔跑的web.9 小时前
TypeScript 装饰器入门核心用法
前端·javascript·vue.js·typescript
阿蒙Amon10 小时前
TypeScript学习-第1章:入门
javascript·学习·typescript
winfredzhang10 小时前
实战复盘:如何用 HTML+JS+AI 打造一款“影迹”智能影视管理系统
javascript·html·json·加载·搜索·保存·电影接口
pas13610 小时前
37-mini-vue 解析插值
前端·javascript·vue.js
雨季66612 小时前
构建 OpenHarmony 简易文字行数统计器:用字符串分割实现纯文本结构感知
开发语言·前端·javascript·flutter·ui·dart
雨季66612 小时前
Flutter 三端应用实战:OpenHarmony 简易倒序文本查看器开发指南
开发语言·javascript·flutter·ui
小北方城市网12 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
2401_8920005212 小时前
Flutter for OpenHarmony 猫咪管家App实战 - 添加支出实现
前端·javascript·flutter
天马379812 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
天天向上102413 小时前
vue3 实现el-table 部分行不让勾选
前端·javascript·vue.js