| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- 'use strict';
- const { randomBytes } = require('crypto');
- const DEFAULT_PARSE_FUNCTIONS_URL = 'https://server.fmode.cn/parse/functions';
- const DEFAULT_RECHARGE_ORDER_URL = 'https://server.fmode.cn/api/fmode/recharge/order';
- 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 = {
- _ApplicationId: 'ncloudmaster',
- 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-Application-Id': 'ncloudmaster',
- 'X-Parse-Session-Token': sessionToken
- } : { 'X-Parse-Application-Id': 'ncloudmaster' },
- body,
- tradeNo: outTradeNo,
- amountCny: body.total_fee,
- polling: {
- endpoint: `${parseFunctionsUrl}/order_status2`,
- method: 'POST',
- body: {
- _ApplicationId: 'ncloudmaster',
- out_trade_no: outTradeNo,
- company: body.company
- }
- }
- };
- }
- function buildRechargeOrderRequest({ amount, tradeNo, sessionToken, title = 'VOC 数据调用充值' } = {}) {
- const numericAmount = Number(amount);
- if (!Number.isFinite(numericAmount) || numericAmount <= 0) {
- throw new Error('充值金额必须大于 0');
- }
- if (!sessionToken) throw new Error('创建充值订单需要 Session Token');
- const outTradeNo = tradeNo || createTradeNo();
- const endpoint = String(process.env.FMODE_RECHARGE_ORDER_URL || DEFAULT_RECHARGE_ORDER_URL).replace(/\/$/, '');
- return {
- endpoint,
- method: 'POST',
- headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json'
- },
- body: {
- sessionToken,
- amount: Number(numericAmount.toFixed(2)),
- tradeNo: outTradeNo,
- detail: { product: 'voc-newapi-balance', source: 'voc-skill', title: String(title).slice(0, 80) }
- },
- tradeNo: outTradeNo,
- amountCny: Number(numericAmount.toFixed(2))
- };
- }
- async function requestPaymentQr({ amount, title, sessionToken, tradeNo, fetchImpl = fetch } = {}) {
- let request = buildRechargePaymentRequest({ amount, title, sessionToken, tradeNo });
- if (sessionToken) {
- const orderRequest = buildRechargeOrderRequest({ amount, title, sessionToken, tradeNo: request.tradeNo });
- const orderResponse = await fetchImpl(orderRequest.endpoint, {
- method: orderRequest.method,
- headers: orderRequest.headers,
- body: JSON.stringify(orderRequest.body)
- });
- const orderPayload = await orderResponse.json().catch(() => ({}));
- if (!orderResponse.ok || Number(orderPayload?.code) !== 200 || !orderPayload?.data?.tradeNo) {
- const error = new Error(orderPayload?.mess || orderPayload?.message || `recharge/order HTTP ${orderResponse.status}`);
- error.httpStatus = orderResponse.status;
- throw error;
- }
- request = buildRechargePaymentRequest({
- amount,
- title,
- sessionToken,
- tradeNo: String(orderPayload.data.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(() => ({}));
- // Parse Cloud functions wrap successful results in `result`; older proxies
- // returned the same fields at the top level. Accept both response shapes.
- const paymentPayload = payload?.result || payload?.data || payload || {};
- const rawCodeUrl = paymentPayload.code_url ?? payload?.code_url;
- const codeUrl = Array.isArray(rawCodeUrl) ? rawCodeUrl[0] : rawCodeUrl;
- 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 = paymentPayload.nonce_str || 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,
- DEFAULT_RECHARGE_ORDER_URL,
- createTradeNo,
- buildRechargeOrderRequest,
- buildRechargePaymentRequest,
- requestPaymentQr
- };
|