后端迁移91卡券共享模块

This commit is contained in:
yml
2026-05-21 15:52:36 +08:00
parent f43101c080
commit 9983d15bba
2 changed files with 30 additions and 21 deletions
@@ -9,10 +9,10 @@ export const OPEN_91_SUCCESS_MESSAGE = '接口调用成功'
export const OPEN_91_DEFAULT_FAIL_CODE = 1204 export const OPEN_91_DEFAULT_FAIL_CODE = 1204
export const OPEN_91_COST_EXCEED_FAIL_CODE = 1220 export const OPEN_91_COST_EXCEED_FAIL_CODE = 1220
type JsonObject = Record<string, any>
export function getOpen91Config() { export function getOpen91Config() {
const config = /** @type {import('../../types/runtime-config.js').RuntimeConfig['platforms']['ninetyone']} */ ( const config = (runtimeConfig.platforms?.ninetyone || {}) as JsonObject
runtimeConfig.platforms?.ninetyone || {}
)
return { return {
userId: String(config.userId || '').trim(), userId: String(config.userId || '').trim(),
@@ -49,7 +49,7 @@ export function normalizeOpen91String(value) {
return String(value || '').trim() return String(value || '').trim()
} }
export function normalizeOpen91CreatePayload(payload = {}) { export function normalizeOpen91CreatePayload(payload: JsonObject = {}) {
return { return {
orderNo: normalizeOpen91String(payload.orderNo), orderNo: normalizeOpen91String(payload.orderNo),
productNo: normalizeOpen91String(payload.productNo), productNo: normalizeOpen91String(payload.productNo),
@@ -62,7 +62,7 @@ export function normalizeOpen91CreatePayload(payload = {}) {
} }
} }
export function normalizeOpen91QueryPayload(payload = {}) { export function normalizeOpen91QueryPayload(payload: JsonObject = {}) {
return { return {
orderNo: normalizeOpen91String(payload.orderNo), orderNo: normalizeOpen91String(payload.orderNo),
timestamp: normalizeOpen91Timestamp(payload.timestamp), timestamp: normalizeOpen91Timestamp(payload.timestamp),
@@ -71,7 +71,7 @@ export function normalizeOpen91QueryPayload(payload = {}) {
} }
} }
export function assertOpen91CreatePayload(payload, config = assertOpen91Config()) { export function assertOpen91CreatePayload(payload: JsonObject, config = assertOpen91Config()) {
if (!payload.orderNo) { if (!payload.orderNo) {
throw createHttpError('缺少 orderNo', { throw createHttpError('缺少 orderNo', {
statusCode: 400, statusCode: 400,
@@ -96,7 +96,7 @@ export function assertOpen91CreatePayload(payload, config = assertOpen91Config()
assertOpen91CommonPayload(payload, config) assertOpen91CommonPayload(payload, config)
} }
export function assertOpen91QueryPayload(payload, config = assertOpen91Config()) { export function assertOpen91QueryPayload(payload: JsonObject, config = assertOpen91Config()) {
if (!payload.orderNo) { if (!payload.orderNo) {
throw createHttpError('缺少 orderNo', { throw createHttpError('缺少 orderNo', {
statusCode: 400, statusCode: 400,
@@ -107,7 +107,7 @@ export function assertOpen91QueryPayload(payload, config = assertOpen91Config())
assertOpen91CommonPayload(payload, config) assertOpen91CommonPayload(payload, config)
} }
function assertOpen91CommonPayload(payload, config) { function assertOpen91CommonPayload(payload: JsonObject, config: JsonObject) {
if (!payload.timestamp) { if (!payload.timestamp) {
throw createHttpError('缺少 timestamp', { throw createHttpError('缺少 timestamp', {
statusCode: 400, statusCode: 400,
@@ -137,7 +137,7 @@ function assertOpen91CommonPayload(payload, config) {
} }
} }
export function assertOpen91Timestamp(timestamp, toleranceSeconds = assertOpen91Config().timestampToleranceSeconds) { export function assertOpen91Timestamp(timestamp: unknown, toleranceSeconds = assertOpen91Config().timestampToleranceSeconds) {
const nowSeconds = Math.floor(Date.now() / 1000) const nowSeconds = Math.floor(Date.now() / 1000)
if (Math.abs(nowSeconds - Number(timestamp)) > Math.max(1, Number(toleranceSeconds) || 600)) { if (Math.abs(nowSeconds - Number(timestamp)) > Math.max(1, Number(toleranceSeconds) || 600)) {
throw createHttpError('timestamp 已过期', { throw createHttpError('timestamp 已过期', {
@@ -147,7 +147,7 @@ export function assertOpen91Timestamp(timestamp, toleranceSeconds = assertOpen91
} }
} }
export function buildOpen91SignSource(params = {}, { secret } = assertOpen91Config()) { export function buildOpen91SignSource(params: JsonObject = {}, { secret } = assertOpen91Config()) {
const entries = Object.entries(params) const entries = Object.entries(params)
.filter(([key]) => key !== 'sign') .filter(([key]) => key !== 'sign')
.sort(([left], [right]) => { .sort(([left], [right]) => {
@@ -164,7 +164,7 @@ export function buildOpen91SignSource(params = {}, { secret } = assertOpen91Conf
return `${secret}${queryString}${secret}` return `${secret}${queryString}${secret}`
} }
export function signOpen91Payload(params = {}, config = assertOpen91Config()) { export function signOpen91Payload(params: JsonObject = {}, config = assertOpen91Config()) {
return crypto return crypto
.createHash('md5') .createHash('md5')
.update(buildOpen91SignSource(params, config), 'utf8') .update(buildOpen91SignSource(params, config), 'utf8')
@@ -172,13 +172,13 @@ export function signOpen91Payload(params = {}, config = assertOpen91Config()) {
.toUpperCase() .toUpperCase()
} }
export function verifyOpen91Signature(params = {}, config = assertOpen91Config()) { export function verifyOpen91Signature(params: JsonObject = {}, config = assertOpen91Config()) {
const expected = signOpen91Payload(params, config) const expected = signOpen91Payload(params, config)
const actual = normalizeOpen91String(params.sign).toUpperCase() const actual = normalizeOpen91String(params.sign).toUpperCase()
return expected === actual return expected === actual
} }
export function assertOpen91Signature(params = {}, config = assertOpen91Config()) { export function assertOpen91Signature(params: JsonObject = {}, config = assertOpen91Config()) {
if (!verifyOpen91Signature(params, config)) { if (!verifyOpen91Signature(params, config)) {
throw createHttpError('验签失败', { throw createHttpError('验签失败', {
statusCode: 400, statusCode: 400,
@@ -203,7 +203,7 @@ export function encryptOpen91Cards(cards = [], secret = assertOpen91Config().sec
return `${cipher.update(plainText, 'utf8', 'base64')}${cipher.final('base64')}` return `${cipher.update(plainText, 'utf8', 'base64')}${cipher.final('base64')}`
} }
export function buildOpen91Cards(cards = [], config = assertOpen91Config()) { export function buildOpen91Cards(cards: unknown[] = [], config = assertOpen91Config()) {
const encoding = normalizeOpen91String(config.cardsEncoding).toLowerCase() const encoding = normalizeOpen91String(config.cardsEncoding).toLowerCase()
if (encoding && encoding !== 'aes-256-ecb-base64') { if (encoding && encoding !== 'aes-256-ecb-base64') {
throw createHttpError(`暂不支持的 cardsEncoding: ${config.cardsEncoding}`, { throw createHttpError(`暂不支持的 cardsEncoding: ${config.cardsEncoding}`, {
@@ -215,7 +215,7 @@ export function buildOpen91Cards(cards = [], config = assertOpen91Config()) {
return encryptOpen91Cards(cards, config.secret) return encryptOpen91Cards(cards, config.secret)
} }
export function buildOpen91SuccessResponse(data, message = OPEN_91_SUCCESS_MESSAGE) { export function buildOpen91SuccessResponse(data: unknown, message = OPEN_91_SUCCESS_MESSAGE) {
return { return {
code: 200, code: 200,
message, message,
@@ -223,7 +223,7 @@ export function buildOpen91SuccessResponse(data, message = OPEN_91_SUCCESS_MESSA
} }
} }
export function buildOpen91ErrorResponse(message, code = 500) { export function buildOpen91ErrorResponse(message: unknown, code = 500) {
return { return {
code, code,
message: normalizeOpen91String(message) || '系统错误', message: normalizeOpen91String(message) || '系统错误',
@@ -231,7 +231,7 @@ export function buildOpen91ErrorResponse(message, code = 500) {
} }
} }
export function buildOpen91OutTradeNo(order) { export function buildOpen91OutTradeNo(order: JsonObject) {
const orderId = Number(order?.id || 0) const orderId = Number(order?.id || 0)
return orderId > 0 ? `OS${String(orderId).padStart(10, '0')}` : '' return orderId > 0 ? `OS${String(orderId).padStart(10, '0')}` : ''
} }
@@ -241,7 +241,7 @@ export function buildOpen91OrderCost(value = 0) {
return Number.isFinite(amount) ? amount.toFixed(4) : '0.0000' return Number.isFinite(amount) ? amount.toFixed(4) : '0.0000'
} }
export function buildOpen91CardItem({ claimUrl = '', expireTime = '' } = {}) { export function buildOpen91CardItem({ claimUrl = '', expireTime = '' }: { claimUrl?: unknown, expireTime?: unknown } = {}) {
const normalizedClaimUrl = normalizeOpen91String(claimUrl) const normalizedClaimUrl = normalizeOpen91String(claimUrl)
return { return {
cardNo: normalizedClaimUrl, cardNo: normalizedClaimUrl,
@@ -251,8 +251,7 @@ export function buildOpen91CardItem({ claimUrl = '', expireTime = '' } = {}) {
} }
} }
/** @param {unknown} value */ export function formatOpen91ExpireTime(value: unknown = '') {
export function formatOpen91ExpireTime(value = '') {
const normalized = normalizeOpen91String(value) const normalized = normalizeOpen91String(value)
if (!normalized) { if (!normalized) {
return '' return ''
@@ -282,7 +281,9 @@ export function formatOpen91ExpireTime(value = '') {
return `${byType.year}-${byType.month}-${byType.day} ${byType.hour}:${byType.minute}:${byType.second}` return `${byType.year}-${byType.month}-${byType.day} ${byType.hour}:${byType.minute}:${byType.second}`
} }
export function resolveOpen91QueryState({ tasks = [], readyTaskIds = [] } = {}) { export function resolveOpen91QueryState(
{ tasks = [], readyTaskIds = [] }: { tasks?: JsonObject[], readyTaskIds?: unknown[] } = {},
) {
const normalizedTasks = Array.isArray(tasks) ? tasks : [] const normalizedTasks = Array.isArray(tasks) ? tasks : []
const readyTaskIdSet = new Set((Array.isArray(readyTaskIds) ? readyTaskIds : []).map((item) => Number(item))) const readyTaskIdSet = new Set((Array.isArray(readyTaskIds) ? readyTaskIds : []).map((item) => Number(item)))
@@ -545,6 +545,14 @@
- `npm run typecheck` - `npm run typecheck`
- `npm run build` - `npm run build`
- `npm test` 共 139 个用例通过 - `npm test` 共 139 个用例通过
123. 91卡券 Open API 共享模块迁移到 `.ts`
- `src/services/open-91/shared.ts`
124. 91 卡券配置读取、请求归一化、签名 / 验签、cards AES 加密、卡密响应构造与查询状态解析已进入 TS 编译链路;动态配置与请求 payload 补齐对象类型
125. Docker 内验证通过:
- `src/services/open-91/shared.test.js` 共 6 个用例通过
- `npm run typecheck`
- `npm run build`
- `npm test` 共 139 个用例通过
## 下一步建议 ## 下一步建议