新增 affiliate_dash 履约 executor(阶段 2)
- executor 常量/守卫 + affiliate-dash-executor(preparePaidTask/resolveDeliveryLink) - 履约业务实现:prepareAffiliateDashTask(幂等建单, client_order_no=task_no, data 透传 91单号/game_account) + syncAffiliateDashTaskStatus(状态合并: delivering→redeeming / delivered→核销→redeemed / ship_failed→retry_pending / cancelled→closed) - registry 注册 + 测试(212 全绿) - 真实建单联调通过(线上 skin.khhao.com,幂等不重复扣款) - 修复 POST /orders 响应 data.order 映射层级
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { buildClaimUrl } from '../../claim/claim-service.js'
|
||||
import { TASK_STATUS } from '../../../domain/task-status.js'
|
||||
import { prepareAffiliateDashTask } from '../affiliate-dash/index.js'
|
||||
import {
|
||||
FULFILLMENT_EXECUTOR_KEYS,
|
||||
type FulfillmentDeliveryLink,
|
||||
type FulfillmentExecutor,
|
||||
type FulfillmentPrepareDeps,
|
||||
} from './types.js'
|
||||
import type { TaskRow } from '../../../types/repository/rows.js'
|
||||
|
||||
export const affiliateDashExecutor: FulfillmentExecutor = {
|
||||
key: FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
preparePaidTask,
|
||||
resolveDeliveryLink,
|
||||
}
|
||||
|
||||
async function preparePaidTask(
|
||||
task: TaskRow,
|
||||
deps: FulfillmentPrepareDeps,
|
||||
): Promise<TaskRow | null> {
|
||||
try {
|
||||
let workingTask = task
|
||||
if (!task.primary_claim_token_id && !String(task.claim_token || '').trim()) {
|
||||
const claimToken = await deps.createTaskClaimToken(task.id)
|
||||
workingTask =
|
||||
(await deps.updateTask(task.id, {
|
||||
claim_token: claimToken.token,
|
||||
claim_expires_at: claimToken.expired_at,
|
||||
user_action_status: 'pending_claim',
|
||||
updated_at: deps.nowIso(),
|
||||
})) || task
|
||||
}
|
||||
|
||||
return await prepareAffiliateDashTask(workingTask)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'affiliate-dash 订单创建失败'
|
||||
const updatedTask = await deps.updateTask(task.id, {
|
||||
task_status: TASK_STATUS.MANUAL_REVIEW,
|
||||
user_action_status: 'not_required',
|
||||
last_error: message,
|
||||
result_code: 'affiliate_dash_prepare_failed',
|
||||
result_message: message,
|
||||
updated_at: deps.nowIso(),
|
||||
})
|
||||
await deps.notifyTaskAutoManualReview({
|
||||
task: updatedTask || task,
|
||||
reason: message,
|
||||
source: 'affiliate_dash_prepare_failed',
|
||||
})
|
||||
return updatedTask
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外(91 等)统一返回本站领取链接(方案 B:自建领取页,不返回 affiliate_dash H5)。
|
||||
*/
|
||||
async function resolveDeliveryLink(task: TaskRow): Promise<FulfillmentDeliveryLink | null> {
|
||||
const primaryToken = String(task.primary_claim_token || task.claim_token || '').trim()
|
||||
const expireTime = String(task.primary_claim_expires_at || task.claim_expires_at || '').trim()
|
||||
|
||||
if (!primaryToken) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
claimUrl: buildClaimUrl(primaryToken),
|
||||
expireTime,
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ function makeTask(executorKey: string): TaskRow {
|
||||
} as TaskRow
|
||||
}
|
||||
|
||||
test('getFulfillmentExecutor 映射 lewan / industry / feifei / manual', () => {
|
||||
test('getFulfillmentExecutor 映射 lewan / industry / feifei / affiliate_dash / manual', () => {
|
||||
assert.equal(
|
||||
getFulfillmentExecutor(FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD)?.key,
|
||||
FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD,
|
||||
@@ -34,6 +34,10 @@ test('getFulfillmentExecutor 映射 lewan / industry / feifei / manual', () => {
|
||||
getFulfillmentExecutor(FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI)?.key,
|
||||
FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI,
|
||||
)
|
||||
assert.equal(
|
||||
getFulfillmentExecutor(FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH)?.key,
|
||||
FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH,
|
||||
)
|
||||
assert.equal(
|
||||
getFulfillmentExecutor(FULFILLMENT_EXECUTOR_KEYS.MANUAL_DISPATCH)?.key,
|
||||
FULFILLMENT_EXECUTOR_KEYS.MANUAL_DISPATCH,
|
||||
@@ -41,6 +45,16 @@ test('getFulfillmentExecutor 映射 lewan / industry / feifei / manual', () => {
|
||||
assert.equal(getFulfillmentExecutor('unknown'), null)
|
||||
})
|
||||
|
||||
test('affiliate_dash executor 暴露 preparePaidTask 与 resolveDeliveryLink', () => {
|
||||
const executor = getFulfillmentExecutor(FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH)
|
||||
assert.ok(executor)
|
||||
assert.equal(typeof executor.preparePaidTask, 'function')
|
||||
assert.equal(typeof executor.resolveDeliveryLink, 'function')
|
||||
// affiliate_dash 不需要 lewan 专属动作
|
||||
assert.equal(executor.prepareBinding, undefined)
|
||||
assert.equal(executor.redeemTask, undefined)
|
||||
})
|
||||
|
||||
test('lewan executor 暴露完整履约动作', () => {
|
||||
const executor = getFulfillmentExecutor(FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD)
|
||||
assert.ok(executor)
|
||||
|
||||
@@ -2,6 +2,7 @@ import { isPaidPreparationStableStatus } from '../../../domain/task-status.js'
|
||||
import { kuaishouCloudExecutor } from './kuaishou-cloud-executor.js'
|
||||
import { kuaishouFeifeiExecutor } from './kuaishou-feifei-executor.js'
|
||||
import { manualDispatchExecutor } from './manual-executor.js'
|
||||
import { affiliateDashExecutor } from './affiliate-dash-executor.js'
|
||||
import {
|
||||
FULFILLMENT_EXECUTOR_KEYS,
|
||||
isManualDispatchExecutor,
|
||||
@@ -17,6 +18,7 @@ const EXECUTORS = new Map<string, FulfillmentExecutor>([
|
||||
[FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_CLOUD, kuaishouCloudExecutor],
|
||||
[FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_INDUSTRY, kuaishouCloudExecutor],
|
||||
[FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI, kuaishouFeifeiExecutor],
|
||||
[FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH, affiliateDashExecutor],
|
||||
[FULFILLMENT_EXECUTOR_KEYS.MANUAL_DISPATCH, manualDispatchExecutor],
|
||||
])
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export const FULFILLMENT_EXECUTOR_KEYS = {
|
||||
KUAISHOU_CLOUD: 'kuaishou_ct_assisted',
|
||||
KUAISHOU_INDUSTRY: 'kuaishou-industry',
|
||||
KUAISHOU_FEIFEI: 'kuaishou_feifei',
|
||||
AFFILIATE_DASH: 'affiliate_dash',
|
||||
} as const
|
||||
|
||||
export type FulfillmentExecutorKey =
|
||||
@@ -111,6 +112,10 @@ export function isKuaishouFeifeiExecutor(value: unknown): boolean {
|
||||
return normalizeExecutorKey(value) === FULFILLMENT_EXECUTOR_KEYS.KUAISHOU_FEIFEI
|
||||
}
|
||||
|
||||
export function isAffiliateDashExecutor(value: unknown): boolean {
|
||||
return normalizeExecutorKey(value) === FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH
|
||||
}
|
||||
|
||||
export function isManualDispatchExecutor(value: unknown): boolean {
|
||||
return normalizeExecutorKey(value) === FULFILLMENT_EXECUTOR_KEYS.MANUAL_DISPATCH
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user