import crypto from 'node:crypto' import { createHttpError } from '../../utils/http.js' import { assertOpen91Config } from './config.js' import { normalizeOpen91String } from './payload.js' type JsonObject = Record export function buildOpen91SignSource(params: JsonObject = {}, { secret } = assertOpen91Config()) { const entries = Object.entries(params) .filter(([key]) => key !== 'sign') .sort(([left], [right]) => { if (left === right) { return 0 } return left < right ? -1 : 1 }) const queryString = entries .map(([key, value]) => `${key}=${stringifyOpen91SignValue(value)}`) .join('&') return `${secret}${queryString}${secret}` } export function signOpen91Payload(params: JsonObject = {}, config = assertOpen91Config()) { return crypto .createHash('md5') .update(buildOpen91SignSource(params, config), 'utf8') .digest('hex') .toUpperCase() } export function verifyOpen91Signature(params: JsonObject = {}, config = assertOpen91Config()) { const expected = signOpen91Payload(params, config) const actual = normalizeOpen91String(params.sign).toUpperCase() return expected === actual } export function assertOpen91Signature(params: JsonObject = {}, config = assertOpen91Config()) { if (!verifyOpen91Signature(params, config)) { throw createHttpError('验签失败', { statusCode: 400, errorCode: 'open91_invalid_signature', }) } } export function encryptOpen91Cards(cards: unknown[] = [], secret = assertOpen91Config().secret) { const normalizedSecret = normalizeOpen91String(secret) if (normalizedSecret.length !== 32) { throw createHttpError('91卡券 cards 加密密钥长度必须为 32 个字符', { statusCode: 500, errorCode: 'open91_invalid_cards_secret_length', }) } const plainText = JSON.stringify(Array.isArray(cards) ? cards : []) const cipher = crypto.createCipheriv('aes-256-ecb', Buffer.from(normalizedSecret, 'utf8'), null) cipher.setAutoPadding(true) return `${cipher.update(plainText, 'utf8', 'base64')}${cipher.final('base64')}` } export function buildOpen91Cards(cards: unknown[] = [], config = assertOpen91Config()) { const encoding = normalizeOpen91String(config.cardsEncoding).toLowerCase() if (encoding && encoding !== 'aes-256-ecb-base64') { throw createHttpError(`暂不支持的 cardsEncoding: ${config.cardsEncoding}`, { statusCode: 500, errorCode: 'open91_unsupported_cards_encoding', }) } return encryptOpen91Cards(cards, config.secret) } export function stringifyOpen91SignValue(value: unknown) { if (typeof value === 'number' && Number.isFinite(value)) { return String(value) } if (typeof value === 'boolean') { return value ? 'true' : 'false' } if (value == null) { return '' } return String(value) }