'use strict'; const crypto = require('crypto'); const superagent = require('superagent'); const aesEncryptToBase64 = (plainText, key, iv) => { const cipher = crypto.createCipheriv('aes-128-cbc', key, iv); let encrypted = cipher.update(String(plainText), 'utf8', 'base64'); encrypted += cipher.final('base64'); return encrypted; }; const aesDecryptFromBase64 = (cipherTextBase64, key, iv) => { const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); let decrypted = decipher.update(String(cipherTextBase64), 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; }; const parseJsonSafe = (value, fallback = null) => { try { if (typeof value === 'string') return JSON.parse(value); if (value && typeof value === 'object') return value; return fallback; } catch (_) { return fallback; } }; const buildConfig = (app) => { const conf = app?.fs?.config?.aideduct || {}; const key = String(conf.aesKey || '').trim(); const iv = String(conf.aesIv || '').trim(); if (Buffer.byteLength(key) !== 16) { throw new Error('aideduct.aesKey 长度必须为 16 字节'); } if (Buffer.byteLength(iv) !== 16) { throw new Error('aideduct.aesIv 长度必须为 16 字节'); } return { baseUrl: String(conf.baseUrl || '').replace(/\/+$/, ''), key, iv, timeout: Number(conf.timeout || 10000), }; }; const createClient = (app) => { const conf = buildConfig(app); const postEncrypted = async (path, plainBody) => { if (!conf.baseUrl) { throw new Error('aideduct.baseUrl 未配置'); } const plainText = JSON.stringify(plainBody || {}); const encryptedData = aesEncryptToBase64(plainText, conf.key, conf.iv); const url = `${conf.baseUrl}/${String(path || '').replace(/^\/+/, '')}`; const res = await superagent .post(url) .type('form') .send({ data: encryptedData }) .timeout(conf.timeout); const body = parseJsonSafe(res?.body, parseJsonSafe(res?.text, {})) || {}; if (Number(body.code) !== 200) { throw new Error(body.msg || '飞尚接口调用失败'); } if (typeof body.data !== 'string') { return body.data || {}; } const decrypted = aesDecryptFromBase64(body.data, conf.key, conf.iv); const result = parseJsonSafe(decrypted, null); if (!result || typeof result !== 'object') { throw new Error('飞尚接口解密后数据格式错误'); } return result; }; const requireUserId = (userId) => { const normalizedUserId = String(userId || '').trim(); if (!normalizedUserId) throw new Error('缺少 user_id'); return normalizedUserId; }; const requireAmount = (amount) => { const normalizedAmount = Number(amount); if (!Number.isFinite(normalizedAmount) || normalizedAmount <= 0) { throw new Error('amount 必须为大于 0 的数字'); } return normalizedAmount; }; const requireRequestId = (requestId) => { const normalizedRequestId = String(requestId || '').trim(); if (!normalizedRequestId) throw new Error('缺少 request_id'); return normalizedRequestId; }; const getBalance = async ({ userId }) => { const normalizedUserId = requireUserId(userId); const plainRes = await postEncrypted('/getBalance', { user_id: normalizedUserId, }); if (Number(plainRes.code) !== 200) { throw new Error(plainRes.msg || '余额查询失败'); } const data = plainRes.data || {}; return { code: Number(plainRes.code), msg: String(plainRes.msg || ''), data: { balance: Number(data.balance || 0), frozen_balance: Number(data.frozen_balance || 0), total_balance: Number(data.total_balance || 0), }, }; }; const getUserInfo = async ({ userId }) => { const normalizedUserId = requireUserId(userId); const plainRes = await postEncrypted('/getuserinfo', { user_id: normalizedUserId, }); if (Number(plainRes.code) !== 200) { throw new Error(plainRes.msg || '用户信息查询失败'); } const data = plainRes.data || {}; return { code: Number(plainRes.code), msg: String(plainRes.msg || ''), data: { user: String(data.user || '').trim(), email: String(data.email || '').trim(), fullname: String(data.fullname || '').trim(), create_time: String(data.create_time || '').trim(), }, }; }; const preDeduct = async ({ userId, amount, requestId }) => { const plainRes = await postEncrypted('/preDeduct', { user_id: requireUserId(userId), amount: requireAmount(amount), request_id: requireRequestId(requestId), }); if (Number(plainRes.code) !== 200) { throw new Error(plainRes.msg || '预扣费失败'); } return { code: Number(plainRes.code), msg: String(plainRes.msg || ''), data: plainRes.data || {}, }; }; const confirmDeduct = async ({ userId, amount, requestId }) => { const plainRes = await postEncrypted('/confirmDeduct', { user_id: requireUserId(userId), amount: requireAmount(amount), request_id: requireRequestId(requestId), }); if (Number(plainRes.code) !== 200) { throw new Error(plainRes.msg || '确认扣费失败'); } return { code: Number(plainRes.code), msg: String(plainRes.msg || ''), data: plainRes.data || {}, }; }; const rollbackDeduct = async ({ userId, amount, requestId }) => { const plainRes = await postEncrypted('/rollbackDeduct', { user_id: requireUserId(userId), amount: requireAmount(amount), request_id: requireRequestId(requestId), }); if (Number(plainRes.code) !== 200) { throw new Error(plainRes.msg || '回退扣费失败'); } return { code: Number(plainRes.code), msg: String(plainRes.msg || ''), data: plainRes.data || {}, }; }; const rechargeBalance = async ({ userId, amount, requestId }) => { const plainRes = await postEncrypted('/rechargeBalance', { user_id: requireUserId(userId), amount: requireAmount(amount), request_id: requireRequestId(requestId), }); if (Number(plainRes.code) !== 200) { throw new Error(plainRes.msg || '充值失败'); } return { code: Number(plainRes.code), msg: String(plainRes.msg || ''), data: plainRes.data || {}, }; }; return { getBalance, getUserInfo, preDeduct, confirmDeduct, rollbackDeduct, rechargeBalance, aesEncryptToBase64: (plainText) => aesEncryptToBase64(plainText, conf.key, conf.iv), aesDecryptFromBase64: (cipherTextBase64) => aesDecryptFromBase64(cipherTextBase64, conf.key, conf.iv), }; }; module.exports = async (app) => { app.fs = app.fs || {}; app.fs.aideductService = createClient(app); };