拆分91卡券共享模块
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user