wechat_miniprogram_pay.js
12.1 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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
const Router = require('koa-router');
const Promise = require('bluebird');
const tenpay = require('tenpay');
const config = require('config');
const mathjs = require('mathjs');
const route = new Router();
const payApi = new tenpay(config.get('wechatPay'), true);
const utils = require('../../utils/utils');
const { getVipOrder, updateVipOrder } = require('../../service/pay_vip');
const { logger } = utils;
async function updateProductStock(connection, cartInfos, tradeNo) {
for (let i = 0; i < cartInfos.length; i++) {
const cartInfo = JSON.parse(cartInfos[i].cart_info);
if (cartInfo.seckill_id) {
// 更新秒杀库存和销售量
const updateSeckill = await connection.queryAsync(
'UPDATE ct_store_seckill SET stock=stock-1,sales=sales+1 WHERE id=?',
[cartInfo.seckill_id]
);
if (!updateSeckill.affectedRows) {
throw new Error(
`更新秒杀库存失败,【秒杀编号】:${
cartInfo.seckill_id
},【updateSeckill】:${JSON.stringify(updateSeckill)}`
);
}
logger.info(
`【支付通知】【订单编号】:${tradeNo},更新秒杀库存成功,【秒杀编号】:${cartInfo.seckill_id}`
);
} else {
if (
cartInfo &&
cartInfo.productInfo &&
cartInfo.productInfo.attrInfo &&
cartInfo.productInfo.attrInfo.unique
) {
const updateAttrValue = await connection.queryAsync(
'UPDATE ct_store_product_attr_value SET stock=stock-?,sales=sales+? WHERE product_id=? AND `unique`=?',
[
cartInfo.cart_num,
cartInfo.cart_num,
cartInfo.productInfo.id,
cartInfo.productInfo.attrInfo.unique
]
);
if (!updateAttrValue.affectedRows) {
throw new Error(
`更新产品属性表失败,【updateAttrValue】:${JSON.stringify(updateAttrValue)}`
);
}
logger.info(
`【支付通知】【订单编号】:${tradeNo},更新产品属性表成功,【产品编号】:${cartInfo.productInfo.id},【unique】:${cartInfo.productInfo.attrInfo.unique},【cart_num】:${cartInfo.cart_num}`
);
} else {
const updateProduct = await connection.queryAsync(
'UPDATE ct_store_product SET stock=stock-?,sales=sales+? WHERE id=?',
[cartInfo.cart_num, cartInfo.cart_num, cartInfo.productInfo.id]
);
if (!updateProduct.affectedRows) {
throw new Error(
`更新产品表失败,【updateProduct】:${JSON.stringify(updateProduct)},【产品编号】:${
cartInfo.productInfo.id
},【cart_num】:${cartInfo.cart_num}`
);
}
logger.info(
`【支付通知】【订单编号】:${tradeNo},更新产品表成功,【产品编号】:${cartInfo.productInfo.id},【cart_num】:${cartInfo.cart_num}`
);
}
}
}
}
async function paySuccess(connection, info) {
const orders = await connection.queryAsync('SELECT * FROM ct_store_order WHERE order_id=?', [
info.out_trade_no
]);
if (orders.length) {
const order = orders[0];
// 事务开始
await connection.beginTransactionAsync();
try {
// demo环境实付金额设置为1分钱
const payPrice = config.get('demo.payPrice') || order.pay_price;
// 更改订单信息
const updateOrderRes = await connection.queryAsync(
'UPDATE ct_store_order SET paid=1,pay_type=?,pay_time=?,pay_price=?,trade_no=? WHERE order_id=?',
['wxpaymini', +new Date() / 1000, payPrice, info.transaction_id, info.out_trade_no]
);
if (updateOrderRes.affectedRows) {
// 查询购物车信息
const cartInfos = await connection.queryAsync(
'SELECT cart_info,`unique` FROM ct_store_order_cart_info WHERE oid=?',
[order.id]
);
// 更新产品库存
await updateProductStock(connection, cartInfos, info.out_trade_no);
// 更新用户购买次数
await connection.queryAsync('UPDATE ct_user SET pay_count=pay_count+1 WHERE uid=?', [
order.uid
]);
// 添加用户付款状态
await connection.queryAsync(
'INSERT INTO ct_store_order_status(oid,change_type,change_message,change_time) VALUES(?,?,?,?)',
[order.id, 'pay_success', '用户付款成功', +new Date() / 1000]
);
// 提交事务
await connection.commitAsync();
logger.info(`【支付通知】【订单编号】:${info.out_trade_no},事务处理成功`);
return true;
} else {
throw new Error(`更改订单信息出错,【updateOrderRes】:${JSON.stringify(updateOrderRes)}`);
}
} catch (e) {
logger.error(
`【支付通知】【订单编号】:${info.out_trade_no},【事务处理出错】:${e.message}`
);
await connection.rollbackAsync();
}
} else {
throw new Error('未找到订单');
}
}
route.post('/wxpay/js/notify/pay', payApi.middleware('pay'), async ctx => {
const connection = Promise.promisifyAll(await ctx.pool.getConnectionAsync());
const info = ctx.request.weixin;
// 更新回调记录表
const strInfo = JSON.stringify(info);
await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
strInfo,
utils.formatTime(new Date())
]);
const logTradeNo = `【订单编号】:${info.out_trade_no}`;
if (
info.result_code.toUpperCase() === 'SUCCESS' &&
info.return_code.toUpperCase() === 'SUCCESS'
) {
try {
// 获取订单
const resCount = await connection.queryAsync(
'SELECT COUNT(1) AS n FROM ct_store_order WHERE order_id=? AND paid=1',
[info.out_trade_no]
);
if (resCount && resCount[0]['n']) {
logger.warn(
`【支付通知】${logTradeNo},该订单已支付,resCount:${JSON.stringify(resCount)}`
);
ctx.reply('');
} else {
const success = await paySuccess(connection, info);
if (success) {
ctx.reply('');
} else {
ctx.reply('FAIL');
}
}
} catch (e) {
logger.error(`【支付通知】${logTradeNo} 【出错】:${e.message}`);
await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
`wxmp_${e.message}`,
utils.formatTime(new Date())
]);
ctx.reply('FAIL');
} finally {
// 释放数据库链接
connection.release();
}
} else {
// 支付未成功
logger.error(`【支付通知】${logTradeNo} 【微信端业务处理错误】:${JSON.stringify(info)}`);
ctx.reply('');
}
});
route.post('/wxpay/js/notify/refund', payApi.middleware('refund'), async ctx => {
const connection = Promise.promisifyAll(await ctx.pool.getConnectionAsync());
const info = { return_code: ctx.request.weixin.return_code, ...ctx.request.weixin.req_info };
const tradeNo = info.out_trade_no;
const refundTime = +new Date(info.success_time) / 1000;
const logTradeNo = `【订单编号】:${tradeNo}`;
if (info.return_code.toUpperCase() === 'SUCCESS') {
try {
// 更新回调记录表
const strInfo = JSON.stringify(info);
await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
strInfo,
utils.formatTime(new Date())
]);
// 查询订单是否存在
const result = await connection.queryAsync(
'SELECT id,pay_price,refund_price,refund_status FROM ct_store_order WHERE order_id=?',
[tradeNo]
);
if (result && result.length) {
const order = result[0];
const refundPrice = order.refund_price;
// 在途金额
const transitFee = (await ctx.redis.get(`wechat:refund:transit:${tradeNo}`)) || '0.00';
// 在途金额金额+已退款金额
const refundTotalFee = mathjs.evaluate(`${refundPrice}+${transitFee}/100`);
//退款金额正常
if (mathjs.evaluate(`${refundTotalFee}<=${order.pay_price}`)) {
let updateRefundRes,
refundStatus = order.refund_status;
if (mathjs.evaluate(`${refundTotalFee}==${transitFee}/100`)) {
refundStatus = 2;
}
logger.info(
`【退款通知】${logTradeNo},【更新退款参数】:${JSON.stringify([
refundStatus,
refundTotalFee,
refundTime,
tradeNo
])}`
);
updateRefundRes = await connection.queryAsync(
'UPDATE ct_store_order SET refund_status=?,refund_price=?,refund_reason_time=? WHERE order_id=?',
[refundStatus, refundTotalFee, refundTime, tradeNo]
);
if (updateRefundRes.affectedRows) {
// 更新在途金额
await ctx.redis.decrby(`wechat:refund:transit:${tradeNo}`, info.settlement_refund_fee);
// 清理缓存数据
if (refundStatus == 2) {
await ctx.redis.del(`wechat:refund:transit:${tradeNo}`);
await ctx.redis.del(`wechat:refund:suffix:${tradeNo}`);
}
// 退款成功
ctx.reply('');
logger.info(`【退款通知】${logTradeNo},退款成功`);
} else {
// 更新退款信息失败
throw new Error('更新退款信息失败');
}
} else {
// 退款金额超出付款总额
throw new Error('退款金额超出付款总额');
}
} else {
throw new Error('订单不存在');
}
} catch (e) {
// 退款记录入库错误
logger.error(
`【退款通知】${logTradeNo},【错误】:${e.message} 【业务处理错误】:${JSON.stringify(
info
)}`
);
await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
`wxmp_${e.message}`,
utils.formatTime(new Date())
]);
ctx.reply('FAIL');
} finally {
// 释放数据库链接
connection.release();
}
} else {
// 退款未成功
logger.error(`【退款通知】${logTradeNo} 【微信端业务处理错误】:${JSON.stringify(info)}`);
ctx.reply('');
}
});
route.post('/wxpay/js/notify/pay_vip', payApi.middleware('pay'), async ctx => {
const connection = Promise.promisifyAll(await ctx.pool.getConnectionAsync());
const info = ctx.request.weixin;
// 更新回调记录表
const strInfo = JSON.stringify(info);
await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
strInfo,
utils.formatTime(new Date())
]);
const logUidAndTradeNo = `【用户编号】:${info.attach},【订单编号】:${info.out_trade_no}`;
logger.info(`【支付食卡会员通知】${logUidAndTradeNo}`);
if (
info.result_code.toUpperCase() === 'SUCCESS' &&
info.return_code.toUpperCase() === 'SUCCESS'
) {
try {
// 用户id
const uId = parseInt(info.attach);
// 获取订单
const resCount = await getVipOrder(connection, uId, '支付食卡会员通知');
if (!resCount) {
ctx.reply('');
} else {
// 更新结果
const success = updateVipOrder(connection, uId, resCount[0]);
// 提交事务
await connection.commitAsync();
if (success) {
logger.info(`【支付食卡会员通知】${logUidAndTradeNo} 【成功】`);
ctx.reply('');
} else {
ctx.reply('FAIL');
}
}
} catch (e) {
// 回滚事务
await connection.rollbackAsync();
logger.error(`【支付食卡会员通知】${logUidAndTradeNo} 【出错】:${e.message}`);
await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
`wxmp_${e.message}`,
utils.formatTime(new Date())
]);
ctx.reply('FAIL');
} finally {
// 释放数据库链接
connection.release();
}
} else {
// 支付未成功
logger.error(
`【支付食卡会员通知】${logUidAndTradeNo} 【微信端业务处理错误】:${JSON.stringify(info)}`
);
ctx.reply('');
}
});
module.exports = route;