e97c4b0a by 华明祺

feat(pay): 集成民生支付模块并优化登录流程

- 新增 pay.js 民生支付模块,使用 await-to-js 处理异步错误
- 优化登录流程,支持 openId 快速登录,避免重复授权
- 添加 payUrl 配置项
- 修复支付失败后错误跳转到成功页的问题
- goPay_per.vue 集成民生支付,正确处理支付结果
1 parent 9ad3fad9
......@@ -164,6 +164,10 @@ function getMyOwnMemberInfo() {
function wxLogin() {
const openId = uni.getStorageSync('openId')
if (openId) {
return pcLoginByOpenId(openId)
} else {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
......@@ -182,6 +186,7 @@ function wxLogin() {
}).then(res => {
return pcLoginByCode(res.code)
})
}
}
function pcLoginByCode(code) {
......@@ -189,8 +194,18 @@ function pcLoginByCode(code) {
url: `/loginByJsCode?jsCode=${code}`,
method: "POST"
}).then((res) => {
uni.setStorageSync('token', 'Bearer ' + res.data);
}).then(getWebInfo)
uni.setStorageSync('token', 'Bearer ' + res.data.token);
uni.setStorageSync('openId', res.data.openId);
})
}
function pcLoginByOpenId(openId) {
return request({
url: `/loginByOpenId?openId=${openId}`,
method: "POST"
}).then((res) => {
uni.setStorageSync('token', 'Bearer ' + res.data.token);
})
}
export {
......
/**
* 民生支付模块
* 封装民生银行支付流程,包括请求支付凭证、解密数据、调起微信支付等功能
* @module common/pay
*/
import config from '@/config.js';
import request from './request'
import to from 'await-to-js'
/** 错误消息映射表 */
const ERROR_MESSAGES = {
USER_CANCEL: '支付已取消',
NETWORK_ERROR: '网络异常,请检查网络连接',
INVALID_PARAMS: '参数错误',
DECRYPT_FAILED: '数据解密失败',
PARSE_FAILED: '数据解析失败',
PAY_INFO_MISSING: '支付信息缺失'
}
/**
* 民生支付主函数
* @async
* @param {string} orderId - 订单ID,用于错误处理和取消订单
* @param {string} encryptedData - 加密数据
* @returns {Promise<void>}
* @throws {Error} 支付过程中发生的错误
*/
async function minShengPay(orderId, encryptedData) {
uni.showLoading({
title: '生成支付...',
mask: true
})
// 参数校验
if (!orderId) {
console.error('minShengPay: orderId is required')
handlePaymentError(new Error(ERROR_MESSAGES.INVALID_PARAMS), null)
}
if (!encryptedData) {
handlePaymentError(new Error(ERROR_MESSAGES.INVALID_PARAMS), orderId)
}
// 1. 数据准备:将表单数据转换为 URL 编码格式
const encodedData = buildUrlEncodedData({
context: encryptedData
})
// 2. 请求支付凭证
const [reqErr, res] = await to(requestPaymentCredential(encodedData))
if (reqErr) {
handlePaymentError(reqErr, orderId)
}
// 3. 响应验证
if (!res?.data?.businessContext) {
handlePaymentError({
message: res?.data?.gateReturnMessage
}, orderId)
}
// 4. 解密并处理数据
const [decryptErr, decryptResult] = await to(decrypt({
bussinessContext: res.data.businessContext
}))
if (decryptErr || !decryptResult?.data) {
handlePaymentError(new Error(ERROR_MESSAGES.DECRYPT_FAILED), orderId)
}
// 5. 解析 JSON 数据
const parsedData = safeJsonParse(decryptResult.data)
if (!parsedData) {
handlePaymentError(new Error(ERROR_MESSAGES.PARSE_FAILED), orderId)
}
// 6. 验证支付信息
if (!parsedData.payInfo) {
handlePaymentError(new Error(ERROR_MESSAGES.PAY_INFO_MISSING), orderId)
}
// 7. 解析支付参数
const payParams = parsePayInfo(parsedData.payInfo)
uni.hideLoading()
// 8. 调起微信支付
const [payErr] = await to(invokeWechatPayment(payParams, orderId))
if (payErr) {
handlePaymentError(payErr, orderId)
}
}
/**
* 将对象转换为 URL 编码格式字符串
* @param {Object} data - 需要转换的数据对象
* @returns {string} URL 编码格式字符串
*/
function buildUrlEncodedData(data) {
return Object.entries(data)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
}
/**
* 请求支付凭证
* @async
* @param {string} encodedData - URL 编码格式的请求数据
* @returns {Promise<Object>} 支付凭证响应
*/
function requestPaymentCredential(encodedData) {
return uni.request({
url: config.payUrl,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: encodedData
})
}
/**
* 解密支付数据(待实现)
* @param {Object} params - 解密参数
* @param {string} params.bussinessContext - 业务上下文数据
* @returns {Promise<Object>} 解密后的数据
*/
function decrypt(params) {
return request({
url: `/api/decrypt`,
method: 'post',
params
})
}
/**
* 安全解析 JSON 字符串
* @param {string} jsonString - JSON 字符串
* @returns {Object|null} 解析后的对象,解析失败返回 null
*/
function safeJsonParse(jsonString) {
try {
return JSON.parse(jsonString)
} catch (e) {
console.error('JSON parse error:', e)
return null
}
}
/**
* 解析支付信息字符串
* 将 "key1=value1|key2=value2" 格式转换为对象
* @param {string} payInfoStr - 支付信息字符串,格式为 "key1=value1|key2=value2|..."
* @returns {Object} 解析后的支付参数对象
* @example
* // 返回 { appId: 'wx123', prepayId: 'abc' }
* parsePayInfo('appId=wx123|prepayId=abc|')
*/
function parsePayInfo(payInfoStr) {
if (!payInfoStr || typeof payInfoStr !== 'string') {
return {}
}
const result = {}
payInfoStr.split('|').forEach(item => {
if (!item) return
const index = item.indexOf('=')
if (index === -1) return
const key = item.substring(0, index)
const value = item.substring(index + 1)
result[key] = value
})
return result
}
/**
* 调起微信支付
* @param {Object} payParams - 微信支付参数
* @param {string} payParams.appId - 应用ID
* @param {string} payParams.nonceStr - 随机字符串
* @param {string} payParams.prepayId - 预支付ID
* @param {string} payParams.timeStamp - 时间戳
* @param {string} payParams.signType - 签名类型
* @param {string} payParams.paySign - 支付签名
* @param {string} orderId - 订单ID
* @returns {Promise<Object>} 支付结果
*/
function invokeWechatPayment(payParams, orderId) {
debugger
return new Promise((resolve, reject) => {
// 参数校验
const requiredFields = ['appId', 'nonceStr', 'prepayId', 'timeStamp', 'signType', 'paySign']
const missingFields = requiredFields.filter(field => !payParams?.[field])
if (missingFields.length > 0) {
reject(new Error(`缺少必要支付参数: ${missingFields.join(', ')}`))
return
}
uni.requestPayment({
appId: payParams.appId,
nonceStr: payParams.nonceStr,
package: `prepay_id=${payParams.prepayId}`,
timeStamp: payParams.timeStamp,
signType: payParams.signType,
paySign: payParams.paySign,
success: (res) => {
uni.showToast({
title: '支付成功',
duration: 2000,
complete: () => resolve(res)
})
},
fail: async (err) => {
debugger
// 用户取消支付
if (err.errMsg?.includes('cancel')) {
await handleUserCancel(orderId)
reject(new Error('USER_CANCEL'))
} else {
await handlePaymentFailure(orderId, err)
reject(err)
}
}
})
})
}
/**
* 处理用户取消支付
* @async
* @param {string} orderId - 订单ID
* @returns {Promise<void>}
*/
async function handleUserCancel(orderId) {
const [err] = await to(request({
url: 'cancelOrder',
method: 'get',
params: {
orderId
}
}))
if (err) {
console.error('取消订单失败:', err)
return
}
uni.showToast({
title: '支付取消',
icon: 'none',
duration: 2000
})
}
/**
* 处理支付失败
* @async
* @param {string} orderId - 订单ID
* @param {Error|Object} error - 错误对象
* @returns {Promise<void>}
*/
async function handlePaymentFailure(orderId, error) {
console.error(`支付失败 [订单: ${orderId}]:`, error)
// 可在此处添加支付失败后的业务逻辑,如上报错误等
}
/**
* 统一错误处理(处理后会抛出错误)
* @param {Error} error - 错误对象
* @param {string|null} orderId - 订单ID
* @throws {Error} 始终抛出传入的错误对象
*/
function handlePaymentError(error, orderId) {
uni.hideLoading()
const message = ERROR_MESSAGES[error?.message] || error?.message || '支付失败,请重试'
uni.showToast({
title: message,
icon: 'none'
})
// 记录错误日志,便于排查
console.error(`支付错误 [订单: ${orderId}]:`, error)
throw error
}
export {
minShengPay
}
\ No newline at end of file
// dev
const baseUrl_api = 'http://192.168.1.189:8787'
const payUrl = 'https://wxpay.cmbc.com.cn/mobilePlatform/appserver/lcbpPay.do'
// prod
// const baseUrl_api = 'https://research.wtwuxicenter.com/';
// const payUrl = 'https://epay.cmbc.com.cn/appweb/appserver/lcbpPay.do'
// staging 会员系统
// const baseUrl_api = "http://22yidpjzjifv.ngrok.xiaomiqiu123.top/stage-api/";
// const baseUrl_api = "https://ztx.itechtop.cn:8443/stage-api";
const baseUrl_api = 'http://192.168.1.189:8787'
// const baseUrl_api = 'https://tkcn.19wk.cn:8443/stage-api'
// const baseUrl_api = 'http://tk004.wxjylt.com/stage-api'
// const baseUrl_api = 'https://system.taekwondo.org.cn/stage-api'
export default {
baseUrl_api
baseUrl_api,
payUrl
}
\ No newline at end of file
......
......@@ -121,6 +121,7 @@
<script setup>
import {
onMounted,
ref
} from 'vue'
import * as api from '@/common/api.js'
......@@ -128,7 +129,11 @@
onLoad
} from '@dcloudio/uni-app'
import config from '@/config.js'
import {
wxLogin
} from '@/common/login.js';
import * as aes2 from '@/common/utils.js'
const current = ref(0)
const popup = ref(null)
const infoConfirm = ref(null)
......@@ -193,6 +198,7 @@
width: '500rpx',
height: '316rpx'
});
onLoad((option) => {
if (option.tab == '1') {
current.value = 1
......@@ -208,6 +214,13 @@
// getRegionsList()
})
onMounted(() => {
let openId = uni.getStorageSync('openId')
if (!openId) {
wxLogin()
}
})
function getRegionsList() {
api.regionsList().then(res => {
regionsList.value = res.data
......@@ -444,7 +457,6 @@
function changeIdcType(e) {
console.log(e)
// 切换证件照类型把当前页面数据清空
cardObj.value = {}
photoArr.value = {}
......
......@@ -61,6 +61,9 @@
} from '@dcloudio/uni-app';
import to from 'await-to-js'
import * as api from '@/common/api.js'
import {
minShengPay
} from '@/common/pay.js'
const form = ref({
payYear: 1
......@@ -120,7 +123,7 @@
// 显示 loading
uni.showLoading({
title: '支付中...',
title: '创建订单...',
mask: true
})
isPaying.value = true
......@@ -135,8 +138,9 @@
// 创建订单
const [orderErr, orderRes] = await to(api.insertSinglePay(postData))
if (orderErr) {
uni.hideLoading()
if (orderErr) {
isPaying.value = false
// uni.showToast({
// title: '创建订单失败',
......@@ -146,7 +150,6 @@
}
if (!orderRes.data?.orderId) {
uni.hideLoading()
isPaying.value = false
uni.showToast({
title: '订单创建异常',
......@@ -155,11 +158,16 @@
return
}
// 等待支付回调
await to(api.pcallBack2(orderRes.data.orderId))
uni.hideLoading()
// 调起支付
const [payErr] = await to(minShengPay(orderRes.data.orderId, orderRes.data.payResult.encryptedData))
isPaying.value = false
// 支付失败不跳转
if (payErr) {
return
}
// 支付成功,跳转页面
uni.redirectTo({
url: `/personal/sucPay?orderId=${orderRes.data.orderId}`
......
......@@ -162,7 +162,7 @@
onMounted(() => {
let webUserName = uni.getStorageSync('webUserName')
if (!webUserName) {
wxLogin()
wxLogin().then(getWebInfo)
}
})
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!