Files
order_site/apps/backend/src/services/open-91/crypto.ts
T
2026-05-26 09:03:03 +08:00

92 lines
2.8 KiB
TypeScript

import crypto from 'node:crypto'
import { createHttpError } from '../../utils/http.js'
import { assertOpen91Config } from './config.js'
import { normalizeOpen91String } from './payload.js'
type JsonObject = Record<string, any>
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)
}