# apigRechargeOnPay 云函数 ## 功能说明 微信支付成功后,由支付回调自动触发的 APIG 充值云函数。 通过 `pay_code2` 的 `fun_id` 参数绑定,支付成功后微信回调自动执行,无需前端调用 `saveRecharge`。 **解决的问题:** 用户支付后关闭页面导致余额不到账。 ## 触发方式 - 不需要前端主动调用 - 由 `pay_code2` 支付流程自动触发:前端请求 `pay_code2` 时传入 `fun_id`,微信支付成功回调后服务端自动执行该云函数 ## 接收参数(由微信回调 PayReturn 数据传入) 云函数通过 `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 服务端函数。直接复制下方代码到云函数平台即可。 ```javascript 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 }); } } ``` ## 云函数部署说明 - **云函数名称**: `apigRechargeOnPay` - **触发方式**: 由 `pay_code2` 的 `fun_id` 参数绑定,微信支付回调后自动执行 - **无需前端调用**,完全由后端处理 ### 部署步骤 1. 在 Parse Dashboard 或服务端创建云函数,代码如上 2. 获取云函数的 objectId 作为 `fun_id` 3. 前端 `pay_code2` 调用时传入 `fun_id: '<云函数objectId>'` ## 前端适配(apig-pay.html) 升级前: ```javascript // 前端发起支付 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(有页面关闭风险) ``` 升级后: ```javascript // 前端发起支付 — 加入 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) ``` ## 关键优势 1. **防丢单**: 充值由微信回调触发,用户关闭页面也能到账 2. **防重复**: 云函数内有防重复充值逻辑(检查 isPay + detail.addCount) 3. **可追溯**: detail 字段记录完整的充值链路信息 4. **前端简化**: 前端只需展示支付二维码和结果,无需处理充值逻辑