行业电子凭证接入
- 新增 4 个回调接口:send-code/destroy-code/query-code/consume-code - 支持快手开放平台 MD5/HMAC_SHA256 签名验签 - send-code 回调自动创建 order/order_items/fulfillment_tasks/profiles - consume-code 回调与既有 flow.consume 结构兼容 - isKuaishouCloudTask 同时匹配 kuaishou-industry executor_key - claim 页面自动验证行业凭证 task,跳过核销码输入 - 退回时行业 task 跳过快手核销 API - 新增 KUASHOU_INDUSTRY_* 环境变量配置
This commit is contained in:
@@ -46,7 +46,9 @@ export function createDefaultRuntimeConfig(projectRoot: string): RuntimeConfig {
|
||||
},
|
||||
kuaishouIndustry: {
|
||||
appKey: "",
|
||||
appSecret: "",
|
||||
signSecret: "",
|
||||
messageSecret: "",
|
||||
provider: "kuaishou-industry",
|
||||
platform: "kuaishou",
|
||||
shopId: "kuaishou-industry",
|
||||
|
||||
@@ -229,11 +229,21 @@ export const ENV_OVERRIDES: readonly EnvOverride[] = [
|
||||
"kuaishouIndustry",
|
||||
"appKey",
|
||||
]),
|
||||
stringEnv("KUASHOU_INDUSTRY_APP_SECRET", [
|
||||
"platforms",
|
||||
"kuaishouIndustry",
|
||||
"appSecret",
|
||||
]),
|
||||
stringEnv("KUASHOU_INDUSTRY_SIGN_SECRET", [
|
||||
"platforms",
|
||||
"kuaishouIndustry",
|
||||
"signSecret",
|
||||
]),
|
||||
stringEnv("KUASHOU_INDUSTRY_MESSAGE_SECRET", [
|
||||
"platforms",
|
||||
"kuaishouIndustry",
|
||||
"messageSecret",
|
||||
]),
|
||||
stringEnv("KUASHOU_INDUSTRY_PROVIDER", [
|
||||
"platforms",
|
||||
"kuaishouIndustry",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createRateLimitMiddleware } from '../middleware/rate-limit.js'
|
||||
import { handleSendCode } from '../services/platforms/kuaishou-industry/send-code-service.js'
|
||||
import { handleDestroyCode } from '../services/platforms/kuaishou-industry/destroy-code-service.js'
|
||||
import { handleQueryCode } from '../services/platforms/kuaishou-industry/query-code-service.js'
|
||||
import { handleConsumeCode } from '../services/platforms/kuaishou-industry/consume-code-service.js'
|
||||
import { buildIndustryErrorResponse } from '../services/platforms/kuaishou-industry/response.js'
|
||||
import { createRequestId, logIntegration } from '../utils/logger.js'
|
||||
|
||||
@@ -117,6 +118,39 @@ router.post('/query-code', industryRateLimit, async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/consume-code', industryRateLimit, async (req, res) => {
|
||||
const requestId = createRequestId('ksind')
|
||||
const startedAt = Date.now()
|
||||
|
||||
logIntegration('[kuaishou-industry/consume-code]', '收到快手行业电子凭证核销回调', {
|
||||
requestId,
|
||||
method: req.method,
|
||||
originalUrl: req.originalUrl,
|
||||
ip: req.ip,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
})
|
||||
|
||||
try {
|
||||
const params = mergeRequestParams(req)
|
||||
const result = await handleConsumeCode(params)
|
||||
logIntegration('[kuaishou-industry/consume-code]', '核销处理完成', {
|
||||
requestId,
|
||||
durationMs: Date.now() - startedAt,
|
||||
result,
|
||||
})
|
||||
res.status(200).json(result)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '系统异常'
|
||||
logIntegration('[kuaishou-industry/consume-code]', '核销处理失败', {
|
||||
requestId,
|
||||
durationMs: Date.now() - startedAt,
|
||||
error,
|
||||
}, { level: 'error' })
|
||||
res.status(200).json(buildIndustryErrorResponse(4010003, message))
|
||||
}
|
||||
})
|
||||
|
||||
function mergeRequestParams(req: any): Record<string, any> {
|
||||
const query = req.query || {}
|
||||
const body = req.body || {}
|
||||
|
||||
@@ -47,7 +47,13 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
|
||||
const context = await getClaimContext(token)
|
||||
const now = nowIso()
|
||||
|
||||
if (String(context.task.executor_key || '').trim() !== 'kuaishou_ct_assisted') {
|
||||
const executorKey = String(context.task.executor_key || '').trim()
|
||||
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
return verifyIndustryeVoucherTicket(context, now)
|
||||
}
|
||||
|
||||
if (executorKey !== 'kuaishou_ct_assisted') {
|
||||
throw createHttpError('当前领取链接不是快手 Cloud 客户领取流程', {
|
||||
statusCode: 409,
|
||||
errorCode: 'claim_not_kuaishou_cloud',
|
||||
@@ -237,6 +243,96 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
|
||||
return getKuaishouCloudClaimDetail(token)
|
||||
}
|
||||
|
||||
async function verifyIndustryeVoucherTicket(
|
||||
context: Awaited<ReturnType<typeof getClaimContext>>,
|
||||
now: string,
|
||||
) {
|
||||
const taskContext = parseTaskContext(context.task)
|
||||
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
||||
|
||||
if (flow.consume.status === 'success') {
|
||||
return getKuaishouCloudClaimDetail(context.claimToken.token)
|
||||
}
|
||||
|
||||
const industryContext = typeof taskContext === 'object' ? taskContext : {}
|
||||
const token = String(industryContext.token || '').trim()
|
||||
const certExpireType = Number(industryContext.certExpireType || 0)
|
||||
const certActualStartTime = Number(industryContext.certActualStartTime || 0)
|
||||
const certActualEndTime = Number(industryContext.certActualEndTime || 0)
|
||||
|
||||
const nextContext = {
|
||||
...taskContext,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
ticket: {
|
||||
...flow.ticket,
|
||||
code: '',
|
||||
status: 'verified',
|
||||
capturedAt: flow.ticket.capturedAt || now,
|
||||
capturedBy: flow.ticket.capturedBy || { source: 'send_code_callback' },
|
||||
verifiedAt: now,
|
||||
oid: context.order.platform_order_id,
|
||||
formToken: token,
|
||||
leftCount: 0,
|
||||
goodsTitle: context.orderItem.sku_name || context.orderItem.sku_code || '',
|
||||
},
|
||||
consume: {
|
||||
...flow.consume,
|
||||
status: 'pending',
|
||||
shopId: context.order.shop_id,
|
||||
shopName: context.order.shop_name,
|
||||
autoConsumeEnabled: true,
|
||||
},
|
||||
certInfo: {
|
||||
certExpireType,
|
||||
certActualStartTime,
|
||||
certActualEndTime,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const taskWithTicket = (await updateTask(context.task.id, {
|
||||
claim_token: context.claimToken.token,
|
||||
claim_expires_at: context.claimToken.expired_at,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
claimed_at: context.task.claimed_at || now,
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
user_action_status: 'pending_claim',
|
||||
last_error: '',
|
||||
updated_at: now,
|
||||
})) || {
|
||||
...context.task,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
claimed_at: context.task.claimed_at || now,
|
||||
task_status: TASK_STATUS.WAITING_BINDING,
|
||||
user_action_status: 'pending_claim',
|
||||
last_error: '',
|
||||
updated_at: now,
|
||||
}
|
||||
|
||||
const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment)
|
||||
if (preparedFlow.binding.prepareStatus !== 'ready' || !preparedFlow.binding.bindUrl) {
|
||||
await prepareKuaishouCloudFulfillmentTask(taskWithTicket, {
|
||||
source: 'send_code_callback',
|
||||
actor: { source: 'send_code_callback' },
|
||||
})
|
||||
}
|
||||
|
||||
await createTaskEvent(
|
||||
context.task.id,
|
||||
'kuaishou_cloud_ticket_verified',
|
||||
{
|
||||
ticketCodeMasked: '',
|
||||
source: 'send_code_callback',
|
||||
industryVoucher: true,
|
||||
verifiedAt: now,
|
||||
},
|
||||
now,
|
||||
)
|
||||
|
||||
return getKuaishouCloudClaimDetail(context.claimToken.token)
|
||||
}
|
||||
|
||||
export async function getKuaishouCloudClaimGuideAssetPath(filename: unknown) {
|
||||
const normalized = String(filename || '').trim()
|
||||
if (!ALLOWED_GUIDE_FILES.has(normalized)) {
|
||||
@@ -261,8 +357,18 @@ export async function getKuaishouCloudClaimDetail(token: unknown) {
|
||||
const context = await getClaimContext(token)
|
||||
let task = context.task
|
||||
|
||||
const executorKey = String(task.executor_key || '').trim()
|
||||
|
||||
if (executorKey === 'kuaishou-industry') {
|
||||
const flow = normalizeKuaishouCloudFlow(parseTaskContext(task).kuaishouCloudFulfillment)
|
||||
if (flow.consume.status !== 'success') {
|
||||
const now = nowIso()
|
||||
await verifyIndustryeVoucherTicket(context, now).catch(() => null)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' &&
|
||||
executorKey === 'kuaishou_ct_assisted' &&
|
||||
!isKuaishouCloudMockTask(task)
|
||||
) {
|
||||
task = (await syncKuaishouCloudRoleInfo(task)) || task
|
||||
|
||||
@@ -5,9 +5,16 @@ export const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
|
||||
|
||||
export type JsonObject = Record<string, any>
|
||||
|
||||
const KUAISHOU_CLOUD_EXECUTOR_KEYS = new Set(['kuaishou_ct_assisted', 'kuaishou-industry'])
|
||||
|
||||
export function isKuaishouCloudTask(task: unknown) {
|
||||
const source = task && typeof task === 'object' ? (task as JsonObject) : {}
|
||||
return String(source.executor_key || '').trim() === 'kuaishou_ct_assisted'
|
||||
return KUAISHOU_CLOUD_EXECUTOR_KEYS.has(String(source.executor_key || '').trim())
|
||||
}
|
||||
|
||||
export function isIndustryEVoucherTask(task: unknown) {
|
||||
const source = task && typeof task === 'object' ? (task as JsonObject) : {}
|
||||
return String(source.executor_key || '').trim() === 'kuaishou-industry'
|
||||
}
|
||||
|
||||
export function normalizeKuaishouCloudFlow(value: unknown) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from "../../platforms/kuaishou-eticket/source-config-service.js";
|
||||
import {
|
||||
isKuaishouCloudTask,
|
||||
isIndustryEVoucherTask,
|
||||
maskCode,
|
||||
maskPhone,
|
||||
normalizeKuaishouCloudFlow,
|
||||
@@ -793,10 +794,15 @@ export async function returnKuaishouCloudFulfillmentTask(
|
||||
let nextResultCode = "kuaishou_cloud_completed";
|
||||
let nextResultMessage = "cloudtentacles 发货、退号并完成快手核销";
|
||||
const consumeAlreadyCompleted = flow.consume.status === "success";
|
||||
const isIndustryTask = isIndustryEVoucherTask(task)
|
||||
|
||||
if (consumeAlreadyCompleted) {
|
||||
consumeStatus = "success";
|
||||
consumedAt = flow.consume.consumedAt || now;
|
||||
} else if (isIndustryTask) {
|
||||
consumeStatus = "success";
|
||||
consumedAt = flow.consume.consumedAt || now;
|
||||
consumeErrorMessage = "行业电子凭证核销由平台回调处理,已跳过主动核销";
|
||||
} else if (!order) {
|
||||
consumeStatus = "failed";
|
||||
consumeErrorMessage = "任务关联订单不存在,无法执行快手核销";
|
||||
|
||||
@@ -15,7 +15,9 @@ export function getKuaishouIndustryConfig(overrides: Partial<KuaishouIndustryRun
|
||||
|
||||
return {
|
||||
appKey: String(config.appKey || '').trim(),
|
||||
appSecret: String(config.appSecret || '').trim(),
|
||||
signSecret: String(config.signSecret || '').trim(),
|
||||
messageSecret: String(config.messageSecret || '').trim(),
|
||||
provider: String(config.provider || KUISHOU_INDUSTRY_PROVIDER).trim() || KUISHOU_INDUSTRY_PROVIDER,
|
||||
platform: String(config.platform || KUISHOU_INDUSTRY_PLATFORM).trim() || KUISHOU_INDUSTRY_PLATFORM,
|
||||
shopId: String(config.shopId || KUISHOU_INDUSTRY_PROVIDER).trim() || KUISHOU_INDUSTRY_PROVIDER,
|
||||
@@ -27,6 +29,13 @@ export function getKuaishouIndustryConfig(overrides: Partial<KuaishouIndustryRun
|
||||
export function assertKuaishouIndustryConfig() {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
if (!config.appKey) {
|
||||
throw createHttpError('快手行业电子凭证 appKey 未配置', {
|
||||
statusCode: 500,
|
||||
errorCode: 'kuaishou_industry_missing_app_key',
|
||||
})
|
||||
}
|
||||
|
||||
if (!config.signSecret) {
|
||||
throw createHttpError('快手行业电子凭证 signSecret 未配置', {
|
||||
statusCode: 500,
|
||||
@@ -36,3 +45,18 @@ export function assertKuaishouIndustryConfig() {
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
export function assertMatchingAppKey(incomingAppKey: string) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
if (!config.appKey) {
|
||||
return
|
||||
}
|
||||
|
||||
if (incomingAppKey !== config.appKey) {
|
||||
throw createHttpError('appkey 不匹配', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_industry_app_key_mismatch',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js'
|
||||
import { listTasksByOrderId, updateTask } from '../../../repositories/task-repo.js'
|
||||
import { normalizeTimestampIso } from '../../../utils/time.js'
|
||||
import { TASK_STATUS } from '../../../domain/task-status.js'
|
||||
import {
|
||||
KUISHOU_INDUSTRY_PLATFORM,
|
||||
getKuaishouIndustryConfig,
|
||||
assertMatchingAppKey,
|
||||
} from './config.js'
|
||||
import { normalizeConsumeCodePayload, assertConsumeCodePayload } from './payload.js'
|
||||
import { assertKuaishouIndustrySignature } from './crypto.js'
|
||||
import {
|
||||
buildIndustrySuccessResponse,
|
||||
buildIndustryErrorResponse,
|
||||
} from './response.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function handleConsumeCode(rawBody: JsonObject = {}) {
|
||||
const config = getKuaishouIndustryConfig()
|
||||
|
||||
assertKuaishouIndustrySignature(rawBody, config)
|
||||
|
||||
const params = normalizeConsumeCodePayload(rawBody)
|
||||
assertConsumeCodePayload(params, config)
|
||||
assertMatchingAppKey(params.appKey)
|
||||
|
||||
const now = normalizeTimestampIso(new Date().toISOString())
|
||||
const normalizedOid = params.oid
|
||||
|
||||
const order = await findLatestOrderByPlatformOrderId({
|
||||
provider: config.provider,
|
||||
platform: KUISHOU_INDUSTRY_PLATFORM,
|
||||
platformOrderId: normalizedOid,
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
return buildIndustryErrorResponse(4012002, `订单不存在: ${normalizedOid}`)
|
||||
}
|
||||
|
||||
const tasks = await listTasksByOrderId(order.id)
|
||||
const matchedIds = new Set(params.etickets.map((e) => String(e.id)))
|
||||
let consumedCount = 0
|
||||
|
||||
for (const task of tasks) {
|
||||
const taskId = String(task.id || '')
|
||||
if (!matchedIds.has(taskId)) {
|
||||
continue
|
||||
}
|
||||
|
||||
let contextJson: JsonObject = {}
|
||||
try {
|
||||
contextJson = typeof task.context_json === 'string'
|
||||
? JSON.parse(task.context_json)
|
||||
: (task.context_json || {})
|
||||
} catch {
|
||||
contextJson = {}
|
||||
}
|
||||
|
||||
const flow = contextJson.kuaishouCloudFulfillment || {}
|
||||
const existingConsumes = Array.isArray(flow.consumeDetails)
|
||||
? contextJson.consumeDetails as JsonObject[]
|
||||
: Array.isArray(contextJson.consumeDetails)
|
||||
? contextJson.consumeDetails as JsonObject[]
|
||||
: []
|
||||
|
||||
const consumeDetail = {
|
||||
serialNum: params.seriallNum,
|
||||
consumeType: params.consumeType,
|
||||
consumeTime: params.consumeTime,
|
||||
appointmentTime: params.appointmentTime || undefined,
|
||||
storeName: params.storeName || undefined,
|
||||
storeAddress: params.storeAddress || undefined,
|
||||
expressCode: params.expressCode || undefined,
|
||||
expressNo: params.expressNo || undefined,
|
||||
consumePoiId: params.consumePoiId || undefined,
|
||||
eticketType: params.eticketType || undefined,
|
||||
ext: params.ext || undefined,
|
||||
}
|
||||
|
||||
const nextContext = {
|
||||
...contextJson,
|
||||
kuaishouCloudFulfillment: {
|
||||
...flow,
|
||||
consume: {
|
||||
...(flow.consume || {}),
|
||||
status: 'success',
|
||||
consumedAt: now,
|
||||
consumeDetail,
|
||||
},
|
||||
},
|
||||
consumeDetails: [...existingConsumes, consumeDetail],
|
||||
}
|
||||
|
||||
const taskStatus = String(task.task_status || '')
|
||||
const isDispatched = taskStatus === TASK_STATUS.DISPATCHED_PENDING_RETURN
|
||||
|
||||
const updatePayload: Record<string, unknown> = {
|
||||
task_status: isDispatched ? TASK_STATUS.COMPLETED : taskStatus,
|
||||
result_code: params.status,
|
||||
result_message: `核销方式: ${params.consumeType}`,
|
||||
context_json: JSON.stringify(nextContext),
|
||||
updated_at: now,
|
||||
}
|
||||
|
||||
if (isDispatched) {
|
||||
updatePayload.delivery_status = 'delivered'
|
||||
}
|
||||
|
||||
await updateTask(taskId, updatePayload as any)
|
||||
|
||||
consumedCount++
|
||||
}
|
||||
|
||||
if (consumedCount === 0) {
|
||||
return buildIndustryErrorResponse(4012005, `未找到匹配的卡券: ${params.etickets.map((e) => e.id).join(',')}`)
|
||||
}
|
||||
|
||||
return buildIndustrySuccessResponse({
|
||||
oid: normalizedOid,
|
||||
consumedCount,
|
||||
})
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-repo.js'
|
||||
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
||||
import { updateTask, getTaskById } from '../../../repositories/task-repo.js'
|
||||
import { updateTask } from '../../../repositories/task-repo.js'
|
||||
import { normalizeTimestampIso } from '../../../utils/time.js'
|
||||
import {
|
||||
KUISHOU_INDUSTRY_PLATFORM,
|
||||
getKuaishouIndustryConfig,
|
||||
assertMatchingAppKey,
|
||||
} from './config.js'
|
||||
import { normalizeDestroyCodePayload, assertDestroyCodePayload } from './payload.js'
|
||||
import { assertKuaishouIndustrySignature } from './crypto.js'
|
||||
@@ -22,6 +23,7 @@ export async function handleDestroyCode(rawBody: JsonObject = {}) {
|
||||
|
||||
const params = normalizeDestroyCodePayload(rawBody)
|
||||
assertDestroyCodePayload(params, config)
|
||||
assertMatchingAppKey(params.appKey)
|
||||
|
||||
const now = normalizeTimestampIso(new Date().toISOString())
|
||||
const normalizedOid = params.oid
|
||||
|
||||
@@ -125,6 +125,75 @@ export function assertQueryCodePayload(payload: ReturnType<typeof normalizeQuery
|
||||
assertCommonPayload(payload, config)
|
||||
}
|
||||
|
||||
export function normalizeConsumeCodePayload(raw: JsonObject = {}) {
|
||||
const param = parseParamField(raw)
|
||||
return {
|
||||
appKey: normalizeIndustryString(raw.appkey),
|
||||
version: normalizeIndustryString(raw.version),
|
||||
timestamp: normalizeIndustryTimestamp(raw.timestamp),
|
||||
signMethod: normalizeIndustryString(raw.signMethod || 'MD5'),
|
||||
sign: normalizeIndustryString(raw.sign),
|
||||
accessToken: normalizeIndustryString(raw.access_token),
|
||||
method: normalizeIndustryString(raw.method),
|
||||
paramRaw: normalizeIndustryString(raw.param),
|
||||
|
||||
oid: normalizeIndustryString(param.oid),
|
||||
etickets: normalizeConsumeEtickets(param.etickets),
|
||||
status: normalizeIndustryString(param.status),
|
||||
consumeType: normalizeIndustryString(param.consumeType),
|
||||
consumeTime: normalizeIndustryLong(param.consumeTime),
|
||||
storeName: normalizeIndustryString(param.storeName),
|
||||
storeAddress: normalizeIndustryString(param.storeAddress),
|
||||
expressNo: normalizeIndustryString(param.expressNo),
|
||||
expressCode: normalizeIndustryString(param.expressCode),
|
||||
appointmentTime: normalizeIndustryString(param.appointmentTime),
|
||||
eticketType: normalizeIndustryString(param.eticketType),
|
||||
ext: normalizeIndustryString(param.ext),
|
||||
token: normalizeIndustryString(param.token),
|
||||
seriallNum: normalizeIndustryString(param.seriallNum),
|
||||
consumePoiId: normalizeIndustryLong(param.consumePoiId),
|
||||
}
|
||||
}
|
||||
|
||||
export function assertConsumeCodePayload(payload: ReturnType<typeof normalizeConsumeCodePayload>, config = assertKuaishouIndustryConfig()) {
|
||||
if (!payload.oid) {
|
||||
throw createHttpError('缺少 oid', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_industry_missing_oid',
|
||||
})
|
||||
}
|
||||
|
||||
if (!Array.isArray(payload.etickets) || payload.etickets.length === 0) {
|
||||
throw createHttpError('缺少 etickets', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_industry_missing_etickets',
|
||||
})
|
||||
}
|
||||
|
||||
if (!payload.status) {
|
||||
throw createHttpError('缺少 status', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_industry_missing_status',
|
||||
})
|
||||
}
|
||||
|
||||
if (!payload.consumeType) {
|
||||
throw createHttpError('缺少 consumeType', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_industry_missing_consume_type',
|
||||
})
|
||||
}
|
||||
|
||||
if (!payload.seriallNum) {
|
||||
throw createHttpError('缺少 seriallNum', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_industry_missing_serial_num',
|
||||
})
|
||||
}
|
||||
|
||||
assertCommonPayload(payload, config)
|
||||
}
|
||||
|
||||
function assertCommonPayload(
|
||||
payload: { appKey: string, version: string, timestamp: number, signMethod: string, sign: string, method: string, accessToken: string, paramRaw: string },
|
||||
config = assertKuaishouIndustryConfig(),
|
||||
@@ -192,3 +261,16 @@ function normalizeDestroyEtickets(value: unknown) {
|
||||
goodsValue: normalizeIndustryLong(item?.goodsValue),
|
||||
}))
|
||||
}
|
||||
|
||||
function normalizeConsumeEtickets(value: unknown) {
|
||||
if (!Array.isArray(value)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return value.map((item) => ({
|
||||
id: normalizeIndustryString(item?.id),
|
||||
code: normalizeIndustryString(item?.code),
|
||||
num: normalizeIndustryInteger(item?.num),
|
||||
goodsValue: normalizeIndustryLong(item?.goodsValue),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { TaskRow } from '../../../types/repository/rows.js'
|
||||
import {
|
||||
KUISHOU_INDUSTRY_PLATFORM,
|
||||
getKuaishouIndustryConfig,
|
||||
assertMatchingAppKey,
|
||||
} from './config.js'
|
||||
import { normalizeQueryCodePayload, assertQueryCodePayload } from './payload.js'
|
||||
import { assertKuaishouIndustrySignature } from './crypto.js'
|
||||
@@ -23,6 +24,7 @@ export async function handleQueryCode(rawBody: JsonObject = {}) {
|
||||
|
||||
const params = normalizeQueryCodePayload(rawBody)
|
||||
assertQueryCodePayload(params, config)
|
||||
assertMatchingAppKey(params.appKey)
|
||||
|
||||
const normalizedOid = params.oid
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
KUISHOU_INDUSTRY_PROVIDER,
|
||||
KUISHOU_INDUSTRY_PLATFORM,
|
||||
getKuaishouIndustryConfig,
|
||||
assertMatchingAppKey,
|
||||
} from './config.js'
|
||||
import { normalizeSendCodePayload, assertSendCodePayload } from './payload.js'
|
||||
import { assertKuaishouIndustrySignature } from './crypto.js'
|
||||
@@ -29,6 +30,7 @@ export async function handleSendCode(rawBody: JsonObject = {}) {
|
||||
|
||||
const params = normalizeSendCodePayload(rawBody)
|
||||
assertSendCodePayload(params, config)
|
||||
assertMatchingAppKey(params.appKey)
|
||||
|
||||
const now = normalizeTimestampIso(new Date().toISOString())
|
||||
const normalizedOid = params.oid
|
||||
|
||||
@@ -79,7 +79,9 @@ export type RuntimeConfig = {
|
||||
};
|
||||
kuaishouIndustry: {
|
||||
appKey: string;
|
||||
appSecret: string;
|
||||
signSecret: string;
|
||||
messageSecret: string;
|
||||
provider: string;
|
||||
platform: string;
|
||||
shopId: string;
|
||||
|
||||
Reference in New Issue
Block a user