'use strict'; const { randomBytes } = require('crypto'); const DEFAULT_PARSE_FUNCTIONS_URL = 'https://server.fmode.cn/parse/functions'; const DEFAULT_PAY_COMPANY = '1AiWpTEDH9'; const DEFAULT_NEW_API_CALLBACK = 'WbyPrzMI3A'; function createTradeNo(prefix = 'VOC') { const stamp = new Date().toISOString().replace(/[-:.TZ]/g, ''); return `${prefix}${stamp}${randomBytes(4).toString('hex')}`; } function buildRechargePaymentRequest({ amount, title = 'VOC 数据调用充值', tradeNo, sessionToken } = {}) { const numericAmount = Number(amount); if (!Number.isFinite(numericAmount) || numericAmount <= 0) { throw new Error('充值金额必须大于 0'); } const outTradeNo = tradeNo || createTradeNo(); const parseFunctionsUrl = String(process.env.FMODE_PARSE_FUNCTIONS_URL || DEFAULT_PARSE_FUNCTIONS_URL).replace(/\/$/, ''); const body = { company: process.env.FMODE_PAY_COMPANY || DEFAULT_PAY_COMPANY, out_trade_no: outTradeNo, total_fee: Number(numericAmount.toFixed(2)), body: String(title).slice(0, 80), fun_id: process.env.FMODE_NEWAPI_RECHARGE_CALLBACK_FUN_ID || DEFAULT_NEW_API_CALLBACK }; return { endpoint: `${parseFunctionsUrl}/pay_code2`, method: 'POST', headers: sessionToken ? { 'X-Parse-Session-Token': sessionToken } : {}, body, tradeNo: outTradeNo, amountCny: body.total_fee, polling: { endpoint: `${parseFunctionsUrl}/order_status2`, method: 'POST', body: { out_trade_no: outTradeNo, company: body.company } } }; } async function requestPaymentQr({ amount, title, sessionToken, tradeNo, fetchImpl = fetch } = {}) { const request = buildRechargePaymentRequest({ amount, title, sessionToken, tradeNo }); const response = await fetchImpl(request.endpoint, { method: request.method, headers: { Accept: 'application/json', 'Content-Type': 'application/json', ...request.headers }, body: JSON.stringify(request.body) }); const payload = await response.json().catch(() => ({})); const codeUrl = Array.isArray(payload?.code_url) ? payload.code_url[0] : payload?.code_url; if (!response.ok || !codeUrl) { const error = new Error(payload?.error || payload?.message || `pay_code2 HTTP ${response.status}`); error.httpStatus = response.status; throw error; } const nonceStr = payload?.nonce_str || ''; return { ...request, paymentMode: 'qr', qrCodeUrl: String(codeUrl), nonceStr, polling: { ...request.polling, body: { ...request.polling.body, ...(nonceStr ? { nonce_str: nonceStr } : {}) } } }; } module.exports = { DEFAULT_PARSE_FUNCTIONS_URL, createTradeNo, buildRechargePaymentRequest, requestPaymentQr };