846 lines
25 KiB
Vue
846 lines
25 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="payment">
|
|||
|
|
<u-navbar
|
|||
|
|
title="缴费"
|
|||
|
|
:border-bottom="false"
|
|||
|
|
back-icon-color="#333"
|
|||
|
|
title-color="#333"
|
|||
|
|
:background="{ background: '#FFFFFF' }"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<view class="payment-content">
|
|||
|
|
<!-- 缴费金额输入 -->
|
|||
|
|
<view class="amount-section">
|
|||
|
|
<view class="section-title">缴费金额</view>
|
|||
|
|
<view class="amount-input-wrapper">
|
|||
|
|
<text class="currency">¥</text>
|
|||
|
|
<input
|
|||
|
|
class="amount-input"
|
|||
|
|
type="digit"
|
|||
|
|
v-model="amount"
|
|||
|
|
placeholder="请输入缴费金额"
|
|||
|
|
placeholder-class="placeholder"
|
|||
|
|
/>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 缴费说明 -->
|
|||
|
|
<view class="remark-section">
|
|||
|
|
<view class="section-title">缴费说明(选填)</view>
|
|||
|
|
<textarea
|
|||
|
|
class="remark-input"
|
|||
|
|
v-model="remark"
|
|||
|
|
placeholder="请输入缴费说明"
|
|||
|
|
placeholder-class="placeholder"
|
|||
|
|
maxlength="100"
|
|||
|
|
/>
|
|||
|
|
<text class="remark-count">{{ remark.length }}/100</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 支付方式 -->
|
|||
|
|
<view class="payway-section">
|
|||
|
|
<view class="section-title">支付方式</view>
|
|||
|
|
<view class="payway-list">
|
|||
|
|
<view
|
|||
|
|
class="payway-item"
|
|||
|
|
v-for="item in payWayList"
|
|||
|
|
:key="item.pay_way"
|
|||
|
|
:class="{ active: selectedPayWay === item.pay_way }"
|
|||
|
|
@click="selectPayWay(item.pay_way)"
|
|||
|
|
>
|
|||
|
|
<view class="payway-info">
|
|||
|
|
<u-icon
|
|||
|
|
:name="getPayWayIcon(item.pay_way)"
|
|||
|
|
size="48"
|
|||
|
|
:color="getPayWayColor(item.pay_way)"
|
|||
|
|
/>
|
|||
|
|
<text class="payway-name">{{ item.name }}</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="payway-check">
|
|||
|
|
<u-icon
|
|||
|
|
v-if="selectedPayWay === item.pay_way"
|
|||
|
|
name="checkmark-circle-fill"
|
|||
|
|
size="40"
|
|||
|
|
color="#3B82F6"
|
|||
|
|
/>
|
|||
|
|
<view v-else class="check-circle"></view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 底部提交按钮 -->
|
|||
|
|
<view class="footer">
|
|||
|
|
<view class="total-amount">
|
|||
|
|
<text class="label">合计:</text>
|
|||
|
|
<text class="currency">¥</text>
|
|||
|
|
<text class="amount">{{ amount || '0.00' }}</text>
|
|||
|
|
</view>
|
|||
|
|
<view
|
|||
|
|
class="submit-btn"
|
|||
|
|
:class="{ disabled: !canSubmit }"
|
|||
|
|
@click="handleSubmit"
|
|||
|
|
>
|
|||
|
|
立即缴费
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 支付密码弹窗 -->
|
|||
|
|
<u-popup v-model="showPasswordPopup" mode="center" border-radius="20">
|
|||
|
|
<view class="password-popup">
|
|||
|
|
<view class="popup-title">请输入支付密码</view>
|
|||
|
|
<u-message-input
|
|||
|
|
mode="box"
|
|||
|
|
:maxlength="6"
|
|||
|
|
:focus="true"
|
|||
|
|
v-model="password"
|
|||
|
|
@finish="onPasswordFinish"
|
|||
|
|
/>
|
|||
|
|
<view class="popup-cancel" @click="showPasswordPopup = false">
|
|||
|
|
取消
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</u-popup>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, computed, onMounted } from 'vue'
|
|||
|
|
import { getPayWay, prepay, getPayResult, alipayJspay, wechatJspay, unifiedTradeQuery } from '@/api/pay'
|
|||
|
|
import { useUserStore } from '@/stores/user'
|
|||
|
|
import config from '@/config'
|
|||
|
|
|
|||
|
|
const userStore = useUserStore()
|
|||
|
|
|
|||
|
|
// 支付配置
|
|||
|
|
const paymentConfig = config.payment
|
|||
|
|
|
|||
|
|
// 数据
|
|||
|
|
const amount = ref('')
|
|||
|
|
const remark = ref('')
|
|||
|
|
const payWayList = ref<any[]>([])
|
|||
|
|
const selectedPayWay = ref('')
|
|||
|
|
const showPasswordPopup = ref(false)
|
|||
|
|
const password = ref('')
|
|||
|
|
const orderId = ref('')
|
|||
|
|
|
|||
|
|
// 计算属性
|
|||
|
|
const canSubmit = computed(() => {
|
|||
|
|
const numAmount = parseFloat(amount.value)
|
|||
|
|
return numAmount > 0 && selectedPayWay.value
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 获取支付方式 - 使用内置的开放银行支付方式
|
|||
|
|
const getPayWayData = async () => {
|
|||
|
|
// 直接设置开放银行支持的支付方式
|
|||
|
|
payWayList.value = [
|
|||
|
|
{
|
|||
|
|
pay_way: 'wechat',
|
|||
|
|
name: '微信支付'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
pay_way: 'alipay',
|
|||
|
|
name: '支付宝支付'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
selectedPayWay.value = 'wechat'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 选择支付方式
|
|||
|
|
const selectPayWay = (payWay: string) => {
|
|||
|
|
selectedPayWay.value = payWay
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取支付方式图标
|
|||
|
|
const getPayWayIcon = (payWay: string) => {
|
|||
|
|
const iconMap: Record<string, string> = {
|
|||
|
|
'wechat': 'weixin-fill',
|
|||
|
|
'alipay': 'zhifubao-circle-fill',
|
|||
|
|
'balance': 'rmb-circle-fill'
|
|||
|
|
}
|
|||
|
|
return iconMap[payWay] || 'rmb-circle-fill'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取支付方式颜色
|
|||
|
|
const getPayWayColor = (payWay: string) => {
|
|||
|
|
const colorMap: Record<string, string> = {
|
|||
|
|
'wechat': '#07C160',
|
|||
|
|
'alipay': '#1677FF',
|
|||
|
|
'balance': '#FF6B00'
|
|||
|
|
}
|
|||
|
|
return colorMap[payWay] || '#999'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提交缴费
|
|||
|
|
const handleSubmit = () => {
|
|||
|
|
if (!canSubmit.value) return
|
|||
|
|
|
|||
|
|
const numAmount = parseFloat(amount.value)
|
|||
|
|
if (numAmount <= 0) {
|
|||
|
|
uni.showToast({ title: '请输入有效的缴费金额', icon: 'none' })
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 余额支付需要输入密码
|
|||
|
|
if (selectedPayWay.value === 'balance') {
|
|||
|
|
showPasswordPopup.value = true
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
doPay()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 密码输入完成
|
|||
|
|
const onPasswordFinish = () => {
|
|||
|
|
showPasswordPopup.value = false
|
|||
|
|
doPay()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行支付
|
|||
|
|
const doPay = async () => {
|
|||
|
|
try {
|
|||
|
|
uni.showLoading({ title: '支付中...' })
|
|||
|
|
|
|||
|
|
const params: any = {
|
|||
|
|
pay_way: selectedPayWay.value,
|
|||
|
|
amount: parseFloat(amount.value),
|
|||
|
|
remark: remark.value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (selectedPayWay.value === 'balance') {
|
|||
|
|
params.password = password.value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const res = await prepay(params)
|
|||
|
|
|
|||
|
|
if (res && res.order_id) {
|
|||
|
|
orderId.value = res.order_id
|
|||
|
|
|
|||
|
|
// 微信支付 - 开放银行微信支付
|
|||
|
|
if (selectedPayWay.value === 'wechat' && res.pay_params) {
|
|||
|
|
await wechatJspayPay(res.pay_params)
|
|||
|
|
}
|
|||
|
|
// 支付宝支付 - 开放银行服务窗支付
|
|||
|
|
else if (selectedPayWay.value === 'alipay' && res.pay_params) {
|
|||
|
|
await alipayJspayPay(res.pay_params)
|
|||
|
|
}
|
|||
|
|
// 余额支付直接成功
|
|||
|
|
else {
|
|||
|
|
uni.hideLoading()
|
|||
|
|
goToResult(true)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
throw new Error('支付参数错误')
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
uni.hideLoading()
|
|||
|
|
uni.showToast({ title: error.message || '支付失败', icon: 'none' })
|
|||
|
|
password.value = ''
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 微信支付
|
|||
|
|
const wechatPay = (payParams: any) => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'wxpay',
|
|||
|
|
...payParams,
|
|||
|
|
success: () => {
|
|||
|
|
resolve(true)
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
reject(err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
if (payParams.mweb_url) {
|
|||
|
|
window.location.href = payParams.mweb_url
|
|||
|
|
} else if (payParams.jsapi_params) {
|
|||
|
|
// JSAPI支付
|
|||
|
|
const { appId, timeStamp, nonceStr, package: packageStr, signType, paySign } = payParams.jsapi_params
|
|||
|
|
if (typeof WeixinJSBridge !== 'undefined') {
|
|||
|
|
WeixinJSBridge.invoke(
|
|||
|
|
'getBrandWCPayRequest',
|
|||
|
|
{ appId, timeStamp, nonceStr, package: packageStr, signType, paySign },
|
|||
|
|
(res: any) => {
|
|||
|
|
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
|||
|
|
resolve(true)
|
|||
|
|
checkPayResult()
|
|||
|
|
} else {
|
|||
|
|
reject(res)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'wxpay',
|
|||
|
|
orderInfo: payParams,
|
|||
|
|
success: () => {
|
|||
|
|
resolve(true)
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
reject(err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 支付宝支付
|
|||
|
|
const alipay = (payParams: any) => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// #ifdef H5
|
|||
|
|
if (payParams.form) {
|
|||
|
|
const div = document.createElement('div')
|
|||
|
|
div.innerHTML = payParams.form
|
|||
|
|
document.body.appendChild(div)
|
|||
|
|
div.querySelector('form')?.submit()
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'alipay',
|
|||
|
|
orderInfo: payParams.order_str,
|
|||
|
|
success: () => {
|
|||
|
|
resolve(true)
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
reject(err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// 开放银行支付宝服务窗支付
|
|||
|
|
const alipayJspayPay = async (payParams: any) => {
|
|||
|
|
try {
|
|||
|
|
// 构建开放银行支付请求参数
|
|||
|
|
const params = {
|
|||
|
|
version: '3.0',
|
|||
|
|
charset: 'UTF-8',
|
|||
|
|
service: 'pay.alipay.jspay',
|
|||
|
|
mch_id: paymentConfig.mch_id, // 商户号
|
|||
|
|
out_trade_no: orderId.value,
|
|||
|
|
total_fee: String(Math.round(parseFloat(amount.value) * 100)), // 金额转换为分
|
|||
|
|
body: remark.value || '缴费支付',
|
|||
|
|
mch_create_ip: '127.0.0.1', // 需要获取真实IP
|
|||
|
|
notify_url: paymentConfig.alipay.notify_url,
|
|||
|
|
buyer_logon_id: '', // 买家支付宝账号(可选)
|
|||
|
|
'terminal_info.terminal_type': '11', // 终端类型
|
|||
|
|
'terminal_info.terminal_id': paymentConfig.terminal_id,
|
|||
|
|
'terminal_info.app_version': '1.000000'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const res = await alipayJspay(params)
|
|||
|
|
|
|||
|
|
// 检查返回结果
|
|||
|
|
if (res && res.status === '0' && res.result_code === '0') {
|
|||
|
|
// 解析 pay_info
|
|||
|
|
const payInfo = JSON.parse(res.pay_info || '{}')
|
|||
|
|
|
|||
|
|
if (payInfo.tradeNO) {
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// APP 环境使用 tradeNO 唤起支付宝
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'alipay',
|
|||
|
|
orderInfo: {
|
|||
|
|
tradeNO: payInfo.tradeNO
|
|||
|
|
},
|
|||
|
|
success: () => {
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
console.error('支付宝支付失败:', err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5 支付宝服务窗支付
|
|||
|
|
// 方式1:使用 pay_url 跳转
|
|||
|
|
if (res.pay_url) {
|
|||
|
|
window.location.href = res.pay_url
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
// 方式2:使用 tradeNO 唤起支付宝
|
|||
|
|
if (payInfo.tradeNO && typeof AlipayJSBridge !== 'undefined') {
|
|||
|
|
AlipayJSBridge.call('tradePay', {
|
|||
|
|
tradeNO: payInfo.tradeNO
|
|||
|
|
}, (result: any) => {
|
|||
|
|
if (result.resultCode === '9000') {
|
|||
|
|
checkPayResult()
|
|||
|
|
} else {
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
} else {
|
|||
|
|
throw new Error('获取支付参数失败')
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
throw new Error(res.err_msg || res.message || '支付请求失败')
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error('开放银行支付错误:', error)
|
|||
|
|
uni.showToast({ title: error.message || '支付请求失败', icon: 'none' })
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 开放银行微信支付(JSPay)
|
|||
|
|
const wechatJspayPay = async (payParams: any) => {
|
|||
|
|
try {
|
|||
|
|
// 构建开放银行微信支付请求参数
|
|||
|
|
const params: any = {
|
|||
|
|
version: '3.0',
|
|||
|
|
charset: 'UTF-8',
|
|||
|
|
service: 'pay.weixin.jspay',
|
|||
|
|
mch_id: paymentConfig.mch_id, // 商户号
|
|||
|
|
out_trade_no: orderId.value,
|
|||
|
|
total_fee: String(Math.round(parseFloat(amount.value) * 100)), // 金额转换为分
|
|||
|
|
body: remark.value || '缴费支付',
|
|||
|
|
mch_create_ip: '127.0.0.1', // 需要获取真实IP
|
|||
|
|
notify_url: paymentConfig.wechat.notify_url,
|
|||
|
|
'terminal_info.terminal_type': '11', // 终端类型
|
|||
|
|
'terminal_info.terminal_id': paymentConfig.terminal_id,
|
|||
|
|
'terminal_info.app_version': '1.000000'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 公众号/小程序支付需要 sub_appid 和 sub_openid
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
params.is_minipg = '1' // 小程序支付标识
|
|||
|
|
params.sub_appid = paymentConfig.wechat.sub_appid // 小程序 appid
|
|||
|
|
// 获取用户 openid
|
|||
|
|
const openid = userStore.userInfo?.openid || ''
|
|||
|
|
if (openid) {
|
|||
|
|
params.sub_openid = openid
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5 公众号支付 - 用户在微信内打开网页
|
|||
|
|
params.is_raw = '1' // 原生态 JS 支付
|
|||
|
|
params.sub_appid = paymentConfig.wechat.sub_appid // 公众号 appid
|
|||
|
|
// 获取用户 openid(如果在微信内)
|
|||
|
|
const openid = userStore.userInfo?.openid || ''
|
|||
|
|
if (openid) {
|
|||
|
|
params.sub_openid = openid
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
const res = await wechatJspay(params)
|
|||
|
|
|
|||
|
|
// 检查返回结果
|
|||
|
|
if (res && res.status === '0' && res.result_code === '0') {
|
|||
|
|
const payInfo = res.pay_info
|
|||
|
|
|
|||
|
|
if (payInfo) {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
// 小程序支付 - 解析 XML 格式的 pay_info
|
|||
|
|
const payParams = parseXmlPayInfo(payInfo)
|
|||
|
|
if (payParams) {
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'wxpay',
|
|||
|
|
...payParams,
|
|||
|
|
success: () => {
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
console.error('微信支付失败:', err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
throw new Error('解析支付参数失败')
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef H5
|
|||
|
|
// H5 公众号支付 - 解析 XML 格式的 pay_info
|
|||
|
|
const payParams = parseXmlPayInfo(payInfo)
|
|||
|
|
if (payParams && typeof WeixinJSBridge !== 'undefined') {
|
|||
|
|
WeixinJSBridge.invoke(
|
|||
|
|
'getBrandWCPayRequest',
|
|||
|
|
payParams,
|
|||
|
|
(res: any) => {
|
|||
|
|
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
|||
|
|
checkPayResult()
|
|||
|
|
} else {
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
} else if (payParams) {
|
|||
|
|
// 使用 uni.requestPayment
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'wxpay',
|
|||
|
|
...payParams,
|
|||
|
|
success: () => {
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
console.error('微信支付失败:', err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
throw new Error('解析支付参数失败')
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// APP 环境
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'wxpay',
|
|||
|
|
orderInfo: payInfo,
|
|||
|
|
success: () => {
|
|||
|
|
checkPayResult()
|
|||
|
|
},
|
|||
|
|
fail: (err: any) => {
|
|||
|
|
console.error('微信支付失败:', err)
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
// #endif
|
|||
|
|
} else {
|
|||
|
|
throw new Error('获取支付参数失败')
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
throw new Error(res.err_msg || res.message || '支付请求失败')
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
console.error('开放银行微信支付错误:', error)
|
|||
|
|
uni.showToast({ title: error.message || '支付请求失败', icon: 'none' })
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 解析 XML 格式的支付参数
|
|||
|
|
const parseXmlPayInfo = (xmlStr: string) => {
|
|||
|
|
try {
|
|||
|
|
// 简单的 XML 解析
|
|||
|
|
const getValue = (xml: string, tag: string) => {
|
|||
|
|
const regex = new RegExp(`<${tag}><!\[CDATA\[(.*?)\]\]></${tag}>|<${tag}>(.*?)</${tag}>`, 'i')
|
|||
|
|
const match = xml.match(regex)
|
|||
|
|
return match ? (match[1] || match[2]) : ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
appId: getValue(xmlStr, 'appId'),
|
|||
|
|
timeStamp: getValue(xmlStr, 'timeStamp'),
|
|||
|
|
nonceStr: getValue(xmlStr, 'nonceStr'),
|
|||
|
|
package: getValue(xmlStr, 'package'),
|
|||
|
|
signType: getValue(xmlStr, 'signType') || 'RSA',
|
|||
|
|
paySign: getValue(xmlStr, 'paySign')
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('解析 XML 失败:', error)
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询支付结果 - 使用开放银行订单查询接口
|
|||
|
|
const checkPayResult = async () => {
|
|||
|
|
try {
|
|||
|
|
uni.showLoading({ title: '查询中...' })
|
|||
|
|
|
|||
|
|
// 构建订单查询参数
|
|||
|
|
const params = {
|
|||
|
|
version: '3.0',
|
|||
|
|
service: 'unified.trade.query',
|
|||
|
|
mch_id: paymentConfig.mch_id, // 商户号
|
|||
|
|
out_trade_no: orderId.value
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const res = await unifiedTradeQuery(params)
|
|||
|
|
|
|||
|
|
uni.hideLoading()
|
|||
|
|
|
|||
|
|
// 检查查询结果
|
|||
|
|
if (res && res.status === '0' && res.result_code === '0') {
|
|||
|
|
// trade_state: SUCCESS-支付成功, REFUND-转入退款, NOTPAY-未支付,
|
|||
|
|
// CLOSED-已关闭, REVOKED-已撤销, USERPAYING-用户支付中, PAYERROR-支付失败
|
|||
|
|
const tradeState = res.trade_state
|
|||
|
|
|
|||
|
|
if (tradeState === 'SUCCESS') {
|
|||
|
|
// 支付成功
|
|||
|
|
goToResult(true)
|
|||
|
|
} else if (tradeState === 'NOTPAY' || tradeState === 'USERPAYING') {
|
|||
|
|
// 未支付或支付中,可以轮询查询
|
|||
|
|
uni.showToast({ title: '支付处理中,请稍后...', icon: 'none' })
|
|||
|
|
// 3秒后再次查询
|
|||
|
|
setTimeout(() => {
|
|||
|
|
checkPayResult()
|
|||
|
|
}, 3000)
|
|||
|
|
} else {
|
|||
|
|
// 其他状态视为失败
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 查询失败,使用备用查询方式
|
|||
|
|
console.error('开放银行订单查询失败:', res)
|
|||
|
|
await fallbackCheckPayResult()
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
uni.hideLoading()
|
|||
|
|
console.error('查询支付结果错误:', error)
|
|||
|
|
// 使用备用查询方式
|
|||
|
|
await fallbackCheckPayResult()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 备用查询方式 - 使用原系统接口
|
|||
|
|
const fallbackCheckPayResult = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await getPayResult({ order_id: orderId.value })
|
|||
|
|
if (res && res.pay_status === 1) {
|
|||
|
|
goToResult(true)
|
|||
|
|
} else {
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
goToResult(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 跳转到结果页
|
|||
|
|
const goToResult = (success: boolean) => {
|
|||
|
|
uni.redirectTo({
|
|||
|
|
url: `/pages/payment_result/payment_result?status=${success ? 'success' : 'fail'}&order_id=${orderId.value}`
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
getPayWayData()
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
.payment {
|
|||
|
|
min-height: 100vh;
|
|||
|
|
background-color: #F5F7FA;
|
|||
|
|
padding-bottom: calc(180rpx + env(safe-area-inset-bottom));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.payment-content {
|
|||
|
|
padding: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #666;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 金额输入区域
|
|||
|
|
.amount-section {
|
|||
|
|
background: #FFFFFF;
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
padding: 30rpx;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
|
|||
|
|
.amount-input-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
border-bottom: 2rpx solid #E5E7EB;
|
|||
|
|
padding: 20rpx 0;
|
|||
|
|
|
|||
|
|
.currency {
|
|||
|
|
font-size: 60rpx;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #333;
|
|||
|
|
margin-right: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.amount-input {
|
|||
|
|
flex: 1;
|
|||
|
|
font-size: 60rpx;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #333;
|
|||
|
|
height: 80rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.placeholder {
|
|||
|
|
font-size: 40rpx;
|
|||
|
|
color: #999;
|
|||
|
|
font-weight: normal;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 备注区域
|
|||
|
|
.remark-section {
|
|||
|
|
background: #FFFFFF;
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
padding: 30rpx;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
position: relative;
|
|||
|
|
|
|||
|
|
.remark-input {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 160rpx;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #333;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.placeholder {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #999;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.remark-count {
|
|||
|
|
position: absolute;
|
|||
|
|
right: 30rpx;
|
|||
|
|
bottom: 20rpx;
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
color: #999;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 支付方式区域
|
|||
|
|
.payway-section {
|
|||
|
|
background: #FFFFFF;
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
padding: 30rpx;
|
|||
|
|
|
|||
|
|
.payway-list {
|
|||
|
|
.payway-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
padding: 30rpx 0;
|
|||
|
|
border-bottom: 2rpx solid #F3F4F6;
|
|||
|
|
|
|||
|
|
&:last-child {
|
|||
|
|
border-bottom: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&.active {
|
|||
|
|
.payway-name {
|
|||
|
|
color: #333;
|
|||
|
|
font-weight: 500;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.payway-info {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
|
|||
|
|
.payway-name {
|
|||
|
|
font-size: 30rpx;
|
|||
|
|
color: #666;
|
|||
|
|
margin-left: 20rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.payway-check {
|
|||
|
|
.check-circle {
|
|||
|
|
width: 40rpx;
|
|||
|
|
height: 40rpx;
|
|||
|
|
border: 2rpx solid #D1D5DB;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 底部区域
|
|||
|
|
.footer {
|
|||
|
|
position: fixed;
|
|||
|
|
left: 0;
|
|||
|
|
right: 0;
|
|||
|
|
bottom: 0;
|
|||
|
|
background: #FFFFFF;
|
|||
|
|
padding: 20rpx 30rpx;
|
|||
|
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
|||
|
|
|
|||
|
|
.total-amount {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: baseline;
|
|||
|
|
|
|||
|
|
.label {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #666;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.currency {
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
color: #FF6B00;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.amount {
|
|||
|
|
font-size: 48rpx;
|
|||
|
|
color: #FF6B00;
|
|||
|
|
font-weight: 600;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.submit-btn {
|
|||
|
|
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
|
|||
|
|
color: #FFFFFF;
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
font-weight: 500;
|
|||
|
|
padding: 24rpx 60rpx;
|
|||
|
|
border-radius: 40rpx;
|
|||
|
|
|
|||
|
|
&:active {
|
|||
|
|
opacity: 0.9;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&.disabled {
|
|||
|
|
background: #D1D5DB;
|
|||
|
|
color: #9CA3AF;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 密码弹窗
|
|||
|
|
.password-popup {
|
|||
|
|
width: 600rpx;
|
|||
|
|
padding: 40rpx;
|
|||
|
|
background: #FFFFFF;
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
|
|||
|
|
.popup-title {
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #333;
|
|||
|
|
text-align: center;
|
|||
|
|
margin-bottom: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.popup-cancel {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #999;
|
|||
|
|
text-align: center;
|
|||
|
|
margin-top: 40rpx;
|
|||
|
|
padding: 20rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|