后端迁移91卡券共享模块
This commit is contained in:
+22
-21
@@ -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 个用例通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user