pay.js
7.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/**
* 民生支付模块
* 封装民生银行支付流程,包括请求支付凭证、解密数据、调起微信支付等功能
* @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<string>}
* @throws {Error} 支付过程中发生的错误
*/
async function minShengPay(orderId, encryptedData) {
uni.showLoading({
title: '生成支付...',
mask: true
})
console.log(1111)
// 参数校验
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, paySuccess] = await to(invokeWechatPayment(payParams, orderId))
if (payErr) {
handlePaymentError(payErr, orderId)
}
if (paySuccess) {
return 'OK'
}
}
/**
* 将对象转换为 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,
timeout: 60000 // 60秒超时,与业务请求保持一致
})
}
/**
* 解密支付数据(待实现)
* @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) {
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) => {
resolve(res)
uni.showToast({
title: '支付成功',
duration: 2000,
})
},
fail: async (err) => {
// 用户取消支付
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
}