e97c4b0a by 华明祺

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

- 新增 pay.js 民生支付模块,使用 await-to-js 处理异步错误
- 优化登录流程,支持 openId 快速登录,避免重复授权
- 添加 payUrl 配置项
- 修复支付失败后错误跳转到成功页的问题
- goPay_per.vue 集成民生支付,正确处理支付结果
1 parent 9ad3fad9
...@@ -164,24 +164,29 @@ function getMyOwnMemberInfo() { ...@@ -164,24 +164,29 @@ function getMyOwnMemberInfo() {
164 164
165 165
166 function wxLogin() { 166 function wxLogin() {
167 return new Promise((resolve, reject) => { 167 const openId = uni.getStorageSync('openId')
168 uni.login({ 168 if (openId) {
169 provider: 'weixin', 169 return pcLoginByOpenId(openId)
170 success: (res) => { 170 } else {
171 resolve(res) 171 return new Promise((resolve, reject) => {
172 }, 172 uni.login({
173 fail: (res) => { 173 provider: 'weixin',
174 uni.showToast({ 174 success: (res) => {
175 title: '获取用户信息失败', 175 resolve(res)
176 icon: 'none', 176 },
177 duration: 2000 177 fail: (res) => {
178 }) 178 uni.showToast({
179 reject(res) 179 title: '获取用户信息失败',
180 } 180 icon: 'none',
181 duration: 2000
182 })
183 reject(res)
184 }
185 })
186 }).then(res => {
187 return pcLoginByCode(res.code)
181 }) 188 })
182 }).then(res => { 189 }
183 return pcLoginByCode(res.code)
184 })
185 } 190 }
186 191
187 function pcLoginByCode(code) { 192 function pcLoginByCode(code) {
...@@ -189,8 +194,18 @@ function pcLoginByCode(code) { ...@@ -189,8 +194,18 @@ function pcLoginByCode(code) {
189 url: `/loginByJsCode?jsCode=${code}`, 194 url: `/loginByJsCode?jsCode=${code}`,
190 method: "POST" 195 method: "POST"
191 }).then((res) => { 196 }).then((res) => {
192 uni.setStorageSync('token', 'Bearer ' + res.data); 197 uni.setStorageSync('token', 'Bearer ' + res.data.token);
193 }).then(getWebInfo) 198 uni.setStorageSync('openId', res.data.openId);
199 })
200 }
201
202 function pcLoginByOpenId(openId) {
203 return request({
204 url: `/loginByOpenId?openId=${openId}`,
205 method: "POST"
206 }).then((res) => {
207 uni.setStorageSync('token', 'Bearer ' + res.data.token);
208 })
194 } 209 }
195 210
196 export { 211 export {
......
1 /**
2 * 民生支付模块
3 * 封装民生银行支付流程,包括请求支付凭证、解密数据、调起微信支付等功能
4 * @module common/pay
5 */
6
7 import config from '@/config.js';
8 import request from './request'
9 import to from 'await-to-js'
10
11 /** 错误消息映射表 */
12 const ERROR_MESSAGES = {
13 USER_CANCEL: '支付已取消',
14 NETWORK_ERROR: '网络异常,请检查网络连接',
15 INVALID_PARAMS: '参数错误',
16 DECRYPT_FAILED: '数据解密失败',
17 PARSE_FAILED: '数据解析失败',
18 PAY_INFO_MISSING: '支付信息缺失'
19 }
20
21 /**
22 * 民生支付主函数
23 * @async
24 * @param {string} orderId - 订单ID,用于错误处理和取消订单
25 * @param {string} encryptedData - 加密数据
26 * @returns {Promise<void>}
27 * @throws {Error} 支付过程中发生的错误
28 */
29 async function minShengPay(orderId, encryptedData) {
30 uni.showLoading({
31 title: '生成支付...',
32 mask: true
33 })
34
35 // 参数校验
36 if (!orderId) {
37 console.error('minShengPay: orderId is required')
38 handlePaymentError(new Error(ERROR_MESSAGES.INVALID_PARAMS), null)
39 }
40
41 if (!encryptedData) {
42 handlePaymentError(new Error(ERROR_MESSAGES.INVALID_PARAMS), orderId)
43 }
44
45 // 1. 数据准备:将表单数据转换为 URL 编码格式
46 const encodedData = buildUrlEncodedData({
47 context: encryptedData
48 })
49
50 // 2. 请求支付凭证
51 const [reqErr, res] = await to(requestPaymentCredential(encodedData))
52 if (reqErr) {
53 handlePaymentError(reqErr, orderId)
54 }
55
56 // 3. 响应验证
57 if (!res?.data?.businessContext) {
58 handlePaymentError({
59 message: res?.data?.gateReturnMessage
60 }, orderId)
61 }
62
63 // 4. 解密并处理数据
64 const [decryptErr, decryptResult] = await to(decrypt({
65 bussinessContext: res.data.businessContext
66 }))
67
68 if (decryptErr || !decryptResult?.data) {
69 handlePaymentError(new Error(ERROR_MESSAGES.DECRYPT_FAILED), orderId)
70 }
71
72 // 5. 解析 JSON 数据
73 const parsedData = safeJsonParse(decryptResult.data)
74 if (!parsedData) {
75 handlePaymentError(new Error(ERROR_MESSAGES.PARSE_FAILED), orderId)
76 }
77
78 // 6. 验证支付信息
79 if (!parsedData.payInfo) {
80 handlePaymentError(new Error(ERROR_MESSAGES.PAY_INFO_MISSING), orderId)
81 }
82
83 // 7. 解析支付参数
84 const payParams = parsePayInfo(parsedData.payInfo)
85
86 uni.hideLoading()
87 // 8. 调起微信支付
88 const [payErr] = await to(invokeWechatPayment(payParams, orderId))
89 if (payErr) {
90 handlePaymentError(payErr, orderId)
91 }
92 }
93
94 /**
95 * 将对象转换为 URL 编码格式字符串
96 * @param {Object} data - 需要转换的数据对象
97 * @returns {string} URL 编码格式字符串
98 */
99 function buildUrlEncodedData(data) {
100 return Object.entries(data)
101 .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
102 .join('&')
103 }
104
105 /**
106 * 请求支付凭证
107 * @async
108 * @param {string} encodedData - URL 编码格式的请求数据
109 * @returns {Promise<Object>} 支付凭证响应
110 */
111 function requestPaymentCredential(encodedData) {
112 return uni.request({
113 url: config.payUrl,
114 method: 'POST',
115 header: {
116 'Content-Type': 'application/x-www-form-urlencoded'
117 },
118 data: encodedData
119 })
120 }
121
122 /**
123 * 解密支付数据(待实现)
124 * @param {Object} params - 解密参数
125 * @param {string} params.bussinessContext - 业务上下文数据
126 * @returns {Promise<Object>} 解密后的数据
127 */
128 function decrypt(params) {
129 return request({
130 url: `/api/decrypt`,
131 method: 'post',
132 params
133 })
134 }
135
136 /**
137 * 安全解析 JSON 字符串
138 * @param {string} jsonString - JSON 字符串
139 * @returns {Object|null} 解析后的对象,解析失败返回 null
140 */
141 function safeJsonParse(jsonString) {
142 try {
143 return JSON.parse(jsonString)
144 } catch (e) {
145 console.error('JSON parse error:', e)
146 return null
147 }
148 }
149
150 /**
151 * 解析支付信息字符串
152 * 将 "key1=value1|key2=value2" 格式转换为对象
153 * @param {string} payInfoStr - 支付信息字符串,格式为 "key1=value1|key2=value2|..."
154 * @returns {Object} 解析后的支付参数对象
155 * @example
156 * // 返回 { appId: 'wx123', prepayId: 'abc' }
157 * parsePayInfo('appId=wx123|prepayId=abc|')
158 */
159 function parsePayInfo(payInfoStr) {
160 if (!payInfoStr || typeof payInfoStr !== 'string') {
161 return {}
162 }
163
164 const result = {}
165
166 payInfoStr.split('|').forEach(item => {
167 if (!item) return
168
169 const index = item.indexOf('=')
170 if (index === -1) return
171
172 const key = item.substring(0, index)
173 const value = item.substring(index + 1)
174 result[key] = value
175 })
176
177 return result
178 }
179
180 /**
181 * 调起微信支付
182 * @param {Object} payParams - 微信支付参数
183 * @param {string} payParams.appId - 应用ID
184 * @param {string} payParams.nonceStr - 随机字符串
185 * @param {string} payParams.prepayId - 预支付ID
186 * @param {string} payParams.timeStamp - 时间戳
187 * @param {string} payParams.signType - 签名类型
188 * @param {string} payParams.paySign - 支付签名
189 * @param {string} orderId - 订单ID
190 * @returns {Promise<Object>} 支付结果
191 */
192 function invokeWechatPayment(payParams, orderId) {
193 debugger
194 return new Promise((resolve, reject) => {
195 // 参数校验
196 const requiredFields = ['appId', 'nonceStr', 'prepayId', 'timeStamp', 'signType', 'paySign']
197 const missingFields = requiredFields.filter(field => !payParams?.[field])
198
199 if (missingFields.length > 0) {
200 reject(new Error(`缺少必要支付参数: ${missingFields.join(', ')}`))
201 return
202 }
203
204 uni.requestPayment({
205 appId: payParams.appId,
206 nonceStr: payParams.nonceStr,
207 package: `prepay_id=${payParams.prepayId}`,
208 timeStamp: payParams.timeStamp,
209 signType: payParams.signType,
210 paySign: payParams.paySign,
211 success: (res) => {
212 uni.showToast({
213 title: '支付成功',
214 duration: 2000,
215 complete: () => resolve(res)
216 })
217 },
218 fail: async (err) => {
219 debugger
220 // 用户取消支付
221 if (err.errMsg?.includes('cancel')) {
222 await handleUserCancel(orderId)
223 reject(new Error('USER_CANCEL'))
224 } else {
225 await handlePaymentFailure(orderId, err)
226 reject(err)
227 }
228 }
229 })
230 })
231 }
232
233 /**
234 * 处理用户取消支付
235 * @async
236 * @param {string} orderId - 订单ID
237 * @returns {Promise<void>}
238 */
239 async function handleUserCancel(orderId) {
240 const [err] = await to(request({
241 url: 'cancelOrder',
242 method: 'get',
243 params: {
244 orderId
245 }
246 }))
247
248 if (err) {
249 console.error('取消订单失败:', err)
250 return
251 }
252
253 uni.showToast({
254 title: '支付取消',
255 icon: 'none',
256 duration: 2000
257 })
258 }
259
260 /**
261 * 处理支付失败
262 * @async
263 * @param {string} orderId - 订单ID
264 * @param {Error|Object} error - 错误对象
265 * @returns {Promise<void>}
266 */
267 async function handlePaymentFailure(orderId, error) {
268 console.error(`支付失败 [订单: ${orderId}]:`, error)
269 // 可在此处添加支付失败后的业务逻辑,如上报错误等
270 }
271
272 /**
273 * 统一错误处理(处理后会抛出错误)
274 * @param {Error} error - 错误对象
275 * @param {string|null} orderId - 订单ID
276 * @throws {Error} 始终抛出传入的错误对象
277 */
278 function handlePaymentError(error, orderId) {
279 uni.hideLoading()
280
281 const message = ERROR_MESSAGES[error?.message] || error?.message || '支付失败,请重试'
282
283 uni.showToast({
284 title: message,
285 icon: 'none'
286 })
287
288 // 记录错误日志,便于排查
289 console.error(`支付错误 [订单: ${orderId}]:`, error)
290
291 throw error
292 }
293
294 export {
295 minShengPay
296 }
...\ No newline at end of file ...\ No newline at end of file
1 // dev
2 const baseUrl_api = 'http://192.168.1.189:8787'
3 const payUrl = 'https://wxpay.cmbc.com.cn/mobilePlatform/appserver/lcbpPay.do'
4
1 // prod 5 // prod
2 // const baseUrl_api = 'https://research.wtwuxicenter.com/'; 6 // const baseUrl_api = 'https://research.wtwuxicenter.com/';
7 // const payUrl = 'https://epay.cmbc.com.cn/appweb/appserver/lcbpPay.do'
3 8
4
5 // staging 会员系统
6 // const baseUrl_api = "http://22yidpjzjifv.ngrok.xiaomiqiu123.top/stage-api/";
7 // const baseUrl_api = "https://ztx.itechtop.cn:8443/stage-api";
8 const baseUrl_api = 'http://192.168.1.189:8787'
9 // const baseUrl_api = 'https://tkcn.19wk.cn:8443/stage-api'
10 // const baseUrl_api = 'http://tk004.wxjylt.com/stage-api'
11
12 // const baseUrl_api = 'https://system.taekwondo.org.cn/stage-api'
13 export default { 9 export default {
14 baseUrl_api 10 baseUrl_api,
11 payUrl
15 } 12 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -121,6 +121,7 @@ ...@@ -121,6 +121,7 @@
121 121
122 <script setup> 122 <script setup>
123 import { 123 import {
124 onMounted,
124 ref 125 ref
125 } from 'vue' 126 } from 'vue'
126 import * as api from '@/common/api.js' 127 import * as api from '@/common/api.js'
...@@ -128,7 +129,11 @@ ...@@ -128,7 +129,11 @@
128 onLoad 129 onLoad
129 } from '@dcloudio/uni-app' 130 } from '@dcloudio/uni-app'
130 import config from '@/config.js' 131 import config from '@/config.js'
132 import {
133 wxLogin
134 } from '@/common/login.js';
131 import * as aes2 from '@/common/utils.js' 135 import * as aes2 from '@/common/utils.js'
136
132 const current = ref(0) 137 const current = ref(0)
133 const popup = ref(null) 138 const popup = ref(null)
134 const infoConfirm = ref(null) 139 const infoConfirm = ref(null)
...@@ -193,6 +198,7 @@ ...@@ -193,6 +198,7 @@
193 width: '500rpx', 198 width: '500rpx',
194 height: '316rpx' 199 height: '316rpx'
195 }); 200 });
201
196 onLoad((option) => { 202 onLoad((option) => {
197 if (option.tab == '1') { 203 if (option.tab == '1') {
198 current.value = 1 204 current.value = 1
...@@ -208,6 +214,13 @@ ...@@ -208,6 +214,13 @@
208 // getRegionsList() 214 // getRegionsList()
209 }) 215 })
210 216
217 onMounted(() => {
218 let openId = uni.getStorageSync('openId')
219 if (!openId) {
220 wxLogin()
221 }
222 })
223
211 function getRegionsList() { 224 function getRegionsList() {
212 api.regionsList().then(res => { 225 api.regionsList().then(res => {
213 regionsList.value = res.data 226 regionsList.value = res.data
...@@ -444,7 +457,6 @@ ...@@ -444,7 +457,6 @@
444 457
445 458
446 function changeIdcType(e) { 459 function changeIdcType(e) {
447 console.log(e)
448 // 切换证件照类型把当前页面数据清空 460 // 切换证件照类型把当前页面数据清空
449 cardObj.value = {} 461 cardObj.value = {}
450 photoArr.value = {} 462 photoArr.value = {}
......
...@@ -61,6 +61,9 @@ ...@@ -61,6 +61,9 @@
61 } from '@dcloudio/uni-app'; 61 } from '@dcloudio/uni-app';
62 import to from 'await-to-js' 62 import to from 'await-to-js'
63 import * as api from '@/common/api.js' 63 import * as api from '@/common/api.js'
64 import {
65 minShengPay
66 } from '@/common/pay.js'
64 67
65 const form = ref({ 68 const form = ref({
66 payYear: 1 69 payYear: 1
...@@ -120,7 +123,7 @@ ...@@ -120,7 +123,7 @@
120 123
121 // 显示 loading 124 // 显示 loading
122 uni.showLoading({ 125 uni.showLoading({
123 title: '支付中...', 126 title: '创建订单...',
124 mask: true 127 mask: true
125 }) 128 })
126 isPaying.value = true 129 isPaying.value = true
...@@ -135,8 +138,9 @@ ...@@ -135,8 +138,9 @@
135 138
136 // 创建订单 139 // 创建订单
137 const [orderErr, orderRes] = await to(api.insertSinglePay(postData)) 140 const [orderErr, orderRes] = await to(api.insertSinglePay(postData))
141 uni.hideLoading()
142
138 if (orderErr) { 143 if (orderErr) {
139 uni.hideLoading()
140 isPaying.value = false 144 isPaying.value = false
141 // uni.showToast({ 145 // uni.showToast({
142 // title: '创建订单失败', 146 // title: '创建订单失败',
...@@ -146,7 +150,6 @@ ...@@ -146,7 +150,6 @@
146 } 150 }
147 151
148 if (!orderRes.data?.orderId) { 152 if (!orderRes.data?.orderId) {
149 uni.hideLoading()
150 isPaying.value = false 153 isPaying.value = false
151 uni.showToast({ 154 uni.showToast({
152 title: '订单创建异常', 155 title: '订单创建异常',
...@@ -155,11 +158,16 @@ ...@@ -155,11 +158,16 @@
155 return 158 return
156 } 159 }
157 160
158 // 等待支付回调 161 // 调起支付
159 await to(api.pcallBack2(orderRes.data.orderId)) 162 const [payErr] = await to(minShengPay(orderRes.data.orderId, orderRes.data.payResult.encryptedData))
160 uni.hideLoading() 163
161 isPaying.value = false 164 isPaying.value = false
162 165
166 // 支付失败不跳转
167 if (payErr) {
168 return
169 }
170
163 // 支付成功,跳转页面 171 // 支付成功,跳转页面
164 uni.redirectTo({ 172 uni.redirectTo({
165 url: `/personal/sucPay?orderId=${orderRes.data.orderId}` 173 url: `/personal/sucPay?orderId=${orderRes.data.orderId}`
......
...@@ -162,7 +162,7 @@ ...@@ -162,7 +162,7 @@
162 onMounted(() => { 162 onMounted(() => {
163 let webUserName = uni.getStorageSync('webUserName') 163 let webUserName = uni.getStorageSync('webUserName')
164 if (!webUserName) { 164 if (!webUserName) {
165 wxLogin() 165 wxLogin().then(getWebInfo)
166 } 166 }
167 }) 167 })
168 168
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!