微信支付成功后,由支付回调自动触发的 APIG 充值云函数。
通过 pay_code2 的 fun_id 参数绑定,支付成功后微信回调自动执行,无需前端调用 saveRecharge。
解决的问题: 用户支付后关闭页面导致余额不到账。
pay_code2 支付流程自动触发:前端请求 pay_code2 时传入 fun_id,微信支付成功回调后服务端自动执行该云函数云函数通过 request.params.data 接收微信支付回调数据(PayReturn),关键字段:
| 字段 | 说明 |
|---|---|
| out_trade_no | 商户订单号(前端生成的 tradeNo) |
| total_fee | 支付金额(分) |
| result_code | 支付结果,"SUCCESS" 表示成功 |
| transaction_id | 微信支付交易号 |
1. 从回调数据提取 out_trade_no(即前端的 tradeNo)
2. 查询 APIGOrder 表 WHERE orderNum = out_trade_no → 找到对应订单
3. 校验订单未重复充值(detail.rechargedBy != 'apigRechargeOnPay')
4. 从 order.apig 获取 APIG objectId,从 order.detail.addCount 获取充值数
5. 查询 APIG 表获取当前余额
6. 更新 APIG.count = 当前余额 + detail.addCount
7. 更新 APIGOrder:isPay=true,合并 rechargedBy/rechargedAt/newCount 到 detail
8. 返回充值结果
重要: 此云函数由微信支付回调通过
fun_id自动触发,不要在前端调用。 云函数平台部署,非 Parse 服务端函数。直接复制下方代码到云函数平台即可。
async function handler(request, response) {
console.log('apigRechargeOnPay 开始');
// DEBUG: 打印关键属性
console.log('request.body:', JSON.stringify(request.body));
try {
// 1. 提取回调数据
// 服务端调用云函数时传: { id: fun_id, params: jsonData }
// 所以微信回调数据在 request.body.params 中
var payData = null;
var body = request.body;
// body 可能是字符串,先解析
if (typeof body === 'string') {
try { body = JSON.parse(body); } catch(e) { body = {}; }
}
// 优先从 body.params 提取(服务端实际发送路径)
if (body && body.params) {
var d = body.params;
if (typeof d === 'string') {
try { d = JSON.parse(d); } catch(e) { d = {}; }
}
if (d.out_trade_no) {
payData = d;
console.log('从 request.body.params 提取, out_trade_no:', d.out_trade_no);
}
}
// fallback: body.data(兼容旧版服务端)
if (!payData && body && body.data) {
var d2 = body.data;
if (typeof d2 === 'string') {
try { d2 = JSON.parse(d2); } catch(e) { d2 = {}; }
}
if (d2.out_trade_no) {
payData = d2;
console.log('从 request.body.data 提取, out_trade_no:', d2.out_trade_no);
}
}
// fallback: 直接在 body 中找(兼容手动测试等场景)
if (!payData && body && body.out_trade_no) {
payData = body;
console.log('从 request.body 直接提取, out_trade_no:', body.out_trade_no);
}
// fallback: request.params(兼容云函数平台手动测试)
if (!payData && request.params) {
var p = request.params;
if (typeof p === 'string') {
try { p = JSON.parse(p); } catch(e) { p = {}; }
}
if (p.out_trade_no) {
payData = p;
console.log('从 request.params 提取, out_trade_no:', p.out_trade_no);
}
}
if (!payData || !payData.out_trade_no) {
console.log('未找到out_trade_no, body:', JSON.stringify(body));
response.json({ code: 400, success: false, error: '未找到out_trade_no' });
return;
}
// 兼容数组格式(微信回调XML解析后字段可能是数组如 ["CU2026..."])
var outTradeNo = payData.out_trade_no;
if (Array.isArray(outTradeNo)) outTradeNo = outTradeNo[0];
var transactionId = payData.transaction_id || '';
if (Array.isArray(transactionId)) transactionId = transactionId[0] || '';
// 2. 查询 APIGOrder(字段: orderNum=商户单号, apig=APIG指针, detail=JSON含addCount)
var orderResult = await Psql.query(
'SELECT * FROM "APIGOrder" WHERE "orderNum" = $1 ORDER BY "createdAt" DESC LIMIT 1',
[outTradeNo]
);
if (!orderResult || orderResult.length === 0) {
console.log('未找到订单, orderNum:', outTradeNo);
response.json({ code: 404, success: false, error: '未找到订单: ' + outTradeNo });
return;
}
var order = orderResult[0];
var detail = order.detail;
if (typeof detail === 'string') {
try { detail = JSON.parse(detail); } catch(e) { detail = {}; }
}
if (!detail) detail = {};
console.log('订单:', order.objectId, 'apig:', order.apig, 'isPay:', order.isPay, 'detail:', JSON.stringify(detail));
// 3. 防重复: 已由云函数或 saveRecharge 处理过则跳过
if (detail.rechargedBy === 'apigRechargeOnPay') {
console.log('已由云函数充值,跳过');
response.json({ code: 200, success: true, msg: '已充值(云函数)' });
return;
}
if (order.isPay === true || order.isPay === 'true') {
console.log('订单已支付(isPay=true),可能已由saveRecharge处理,跳过');
response.json({ code: 200, success: true, msg: '已充值(isPay)' });
return;
}
// apig 可能是: "Vo3ROWEvDy" 或 {"objectId":"Vo3ROWEvDy",...} 或 "APIG$Vo3ROWEvDy"
var apigId = order.apig;
if (typeof apigId === 'object' && apigId && apigId.objectId) {
apigId = apigId.objectId;
} else if (typeof apigId === 'string' && apigId.indexOf('$') > -1) {
apigId = apigId.split('$')[1];
}
var addCount = parseInt(detail.addCount) || 0;
console.log('解析后 apigId:', apigId, 'addCount:', addCount);
if (!apigId || addCount <= 0) {
console.log('数据异常 apig原始值:', JSON.stringify(order.apig), 'apigId:', apigId, 'addCount:', addCount);
response.json({ code: 400, success: false, error: '订单数据异常' });
return;
}
// 4. 获取 APIGAuth(计费表)objectId
var apigAuthId = null;
if (detail.apigAuth) {
if (typeof detail.apigAuth === 'object' && detail.apigAuth.objectId) {
apigAuthId = detail.apigAuth.objectId;
} else if (typeof detail.apigAuth === 'string') {
// 可能是 "APIGAuth$T0iOotHcDX" 格式
apigAuthId = detail.apigAuth.indexOf('$') > -1 ? detail.apigAuth.split('$')[1] : detail.apigAuth;
}
}
console.log('apigAuthId:', apigAuthId);
if (!apigAuthId) {
console.log('未找到apigAuth, detail:', JSON.stringify(detail));
response.json({ code: 400, success: false, error: '订单缺少apigAuth信息' });
return;
}
// 5. 查询 APIGAuth 计费表余额
var authResult = await Psql.query(
'SELECT "objectId", "count", "used" FROM "APIGAuth" WHERE "objectId" = $1 LIMIT 1',
[apigAuthId]
);
if (!authResult || authResult.length === 0) {
console.log('未找到APIGAuth:', apigAuthId);
response.json({ code: 404, success: false, error: '未找到APIGAuth: ' + apigAuthId });
return;
}
var currentCount = parseInt(authResult[0].count) || 0;
var newCount = currentCount + addCount;
console.log('APIGAuth余额计算:', currentCount, '+', addCount, '=', newCount);
// 6. 更新 APIGAuth 计费表余额
await Psql.query(
'UPDATE "APIGAuth" SET "count" = $1, "updatedAt" = NOW() WHERE "objectId" = $2',
[newCount, apigAuthId]
);
console.log('APIGAuth余额已更新:', newCount);
// 7. 更新 APIGOrder: isPay=true, detail 加入充值标记
detail.newCount = newCount;
detail.transactionId = transactionId;
detail.rechargedAt = new Date().toISOString();
detail.rechargedBy = 'apigRechargeOnPay';
await Psql.query(
'UPDATE "APIGOrder" SET "isPay" = true, "detail" = $1::jsonb, "updatedAt" = NOW() WHERE "objectId" = $2',
[JSON.stringify(detail), order.objectId]
);
console.log('订单已更新:', order.objectId);
// 8. 返回
response.json({
code: 200, success: true, msg: '充值成功',
data: { orderId: order.objectId, apigAuthId: apigAuthId, oldCount: currentCount, newCount: newCount }
});
console.log('apigRechargeOnPay 完成');
} catch (error) {
console.error('apigRechargeOnPay 失败:', error.message, error.stack);
response.json({ code: 500, success: false, error: error.message });
}
}
apigRechargeOnPaypay_code2 的 fun_id 参数绑定,微信支付回调后自动执行fun_idpay_code2 调用时传入 fun_id: '<云函数objectId>'升级前:
// 前端发起支付
const payResp = await postJSON(PARSE_BASE + '/pay_code2', {
_ApplicationId: APP_ID,
company: PAY_COMPANY,
out_trade_no: tradeNo,
total_fee: +tier.price,
body: apig.title + ' 接口充值'
});
// ... 支付成功后前端调 saveRecharge(有页面关闭风险)
升级后:
// 前端发起支付 — 加入 fun_id
const payResp = await postJSON(PARSE_BASE + '/pay_code2', {
_ApplicationId: APP_ID,
company: PAY_COMPANY,
out_trade_no: tradeNo,
total_fee: +tier.price,
body: apig.title + ' 接口充值',
fun_id: FUN_ID // 云函数ID,微信回调自动触发充值
});
// ... 支付成功后仅刷新余额展示(无需前端调 saveRecharge)