92 lines
2.8 KiB
TypeScript
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)
|
|
}
|