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>
相关推荐
还有多久拿退休金1 小时前
一张栈的图,治好你面试答不出 script 阻塞的病
前端·javascript
zithern_juejin1 小时前
原型与原型链
javascript
虎头金猫2 小时前
管理飞牛OS还在点点点?我用Ansible给它装了个远程遥控器
微信·开源·ansible·aigc·智能家居·开源软件·ai编程
008爬虫实战录3 小时前
【码上爬】 题十二:如来神掌 困难, JSVMP加密,使用代理补环境
前端·javascript·node.js
threelab4 小时前
Three.js 数学函数着色器 | 三维可视化 / AI 提示词
javascript·人工智能·着色器
ZC跨境爬虫5 小时前
跟着 MDN 学CSS day_3:(为一个传记页面添加样式)
前端·javascript·css·ui·音视频·html5
夜雪闻竹5 小时前
sql.js WASM 实战:浏览器里跑 SQLite
javascript·sql·wasm
爱喝铁观音的谷力景辉5 小时前
在Cesium中实现带箭头方向路线样式的技术详解
javascript·cesium
Qhappy5 小时前
AI逆向实战:从零还原某航空App的AES加密
javascript·后端
安妮的小熊呢6 小时前
CRMEB开源商城系统 & 标准版系统(PHP)开发规范
开发语言·javascript·php