From 00d490a2cdc5e31cbf2a34440219f655956c3cc4 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Wed, 5 Aug 2026 16:02:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20affiliate=5Fdash=20?= =?UTF-8?q?=E6=A0=B8=E9=94=80=E6=97=B6=E6=9C=BA:=E5=BB=BA=E5=8D=95?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E5=8D=B3=E6=A0=B8=E9=94=80(=E5=8F=82?= =?UTF-8?q?=E8=80=83=20feifei=20=E7=BB=99=E7=94=A8=E6=88=B7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E5=8D=B3=E6=A0=B8=E9=94=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - prepareAffiliateDashTask 建单成功(91 卡信息到手、链接可发放)立即核销行业电子凭证 - 核销失败不阻塞下单与链接发放(consumeStatus=failed, delivered 回调兜底补核销) - sync delivered 分支 alreadyConsumed 短路,避免重复核销 - typecheck + 212 测试通过 --- .../fulfillment/affiliate-dash/index.ts | 46 +++++++++++++++++-- .../affiliate-dash对接.md | 17 +++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/apps/backend/src/services/fulfillment/affiliate-dash/index.ts b/apps/backend/src/services/fulfillment/affiliate-dash/index.ts index 26de4122..83926d97 100644 --- a/apps/backend/src/services/fulfillment/affiliate-dash/index.ts +++ b/apps/backend/src/services/fulfillment/affiliate-dash/index.ts @@ -15,6 +15,7 @@ import { } from '../../platforms/affiliate-dash/order-service.js' import { getAffiliateDashConfig } from '../../platforms/affiliate-dash/config.js' import { consumeKuaishouIndustryVouchersForTask } from '../../platforms/kuaishou-industry/voucher-service.js' +import { buildKuaishouIndustryVoucherContext } from '../../platforms/kuaishou-industry/voucher-binding-service.js' import type { TaskRow } from '../../../types/repository/rows.js' export function isAffiliateDashTask(task: Partial | null | undefined) { @@ -75,6 +76,38 @@ export async function prepareAffiliateDashTask(task: TaskRow) { clientOrderNo, syncedAt: now, }) + + // 核销时机优化:建单成功(91 卡信息到手、领取链接可发放)即核销行业电子凭证, + // 不再等 delivered 回调(参考 kuaishou-feifei「给用户链接即核销」)。 + // 核销失败不阻塞下单与链接发放:consumeStatus=failed,delivered 回调兜底补核销。 + const consumeResult = await consumeKuaishouIndustryVouchersForTask(task, { + source: 'affiliate_dash_order_created', + consumeTime: Date.now(), + }) + const consumedVoucher = consumeResult.consumed[0] || null + let nextIndustryVoucher = taskContext.kuaishouIndustryVoucher + if (consumeResult.vouchers.length > 0) { + nextFlow.consumeStatus = consumeResult.ok ? 'success' : 'failed' + if (consumedVoucher) { + nextIndustryVoucher = buildKuaishouIndustryVoucherContext( + consumedVoucher, + taskContext.kuaishouIndustryVoucher, + now, + ) + } + if (!consumeResult.ok) { + logIntegration('[affiliate-dash]', '建单成功但电子凭证核销失败,delivered 回调将兜底重试', { + taskId: task.id, + orderNo: order.orderNo, + voucherCount: consumeResult.vouchers.length, + failedCount: consumeResult.failed.length, + errorMessage: consumeResult.failed[0]?.errorMessage || '', + }, { level: 'warn' }) + } + } else { + nextFlow.consumeStatus = 'not_required' + } + const updatedTask = await updateTask(task.id, { task_status: TASK_STATUS.LINK_GENERATED, user_action_status: 'pending_claim', @@ -83,6 +116,7 @@ export async function prepareAffiliateDashTask(task: TaskRow) { context_json: JSON.stringify({ ...taskContext, affiliateDash: nextFlow, + kuaishouIndustryVoucher: nextIndustryVoucher, }), updated_at: now, }) @@ -153,10 +187,14 @@ export async function syncAffiliateDashTaskStatus(task: TaskRow) { lastError = '' break case 'delivered': { - const consumeResult = await consumeKuaishouIndustryVouchersForTask(task, { - source: 'affiliate_dash_delivered', - consumeTime: Date.now(), - }) + const alreadyConsumed = + nextFlow.consumeStatus === 'success' || nextFlow.consumeStatus === 'not_required' + const consumeResult = alreadyConsumed + ? { ok: true, vouchers: [], consumed: [], failed: [] } + : await consumeKuaishouIndustryVouchersForTask(task, { + source: 'affiliate_dash_delivered', + consumeTime: Date.now(), + }) if (consumeResult.ok || consumeResult.vouchers.length === 0) { nextTaskStatus = TASK_STATUS.REDEEMED deliveryStatus = 'delivered' diff --git a/docs/ affiliate_dash_发货平台/affiliate-dash对接.md b/docs/ affiliate_dash_发货平台/affiliate-dash对接.md index 9fe7d3f5..2be2ff92 100644 --- a/docs/ affiliate_dash_发货平台/affiliate-dash对接.md +++ b/docs/ affiliate_dash_发货平台/affiliate-dash对接.md @@ -523,3 +523,20 @@ order_site 处理要求: **验证**:前后端 typecheck 通过;后端全量 212 测试通过;claim 详情分流脚本验证(flowType=affiliate_dash、affiliateDash 块完整、product 正确、其他 flow 为 null)。 **领取闭环状态迁移(与 §8 状态机一致)**:输 UID → submitClaimUid(bind 触发,task `link_generated → waiting_binding`)→ 轮询详情(bind-result 刷新 bound)→ 提交发货(submitAffiliateDashClaim,task `waiting_binding → redeeming`)→ 回调 delivered → redeemed + 核销。 + +--- + +## 19. 核销时机优化(v2.4.1 · 已完成) + +**需求**:核销时机从「delivered 回调」提前到「给用户链接(91 卡信息到手、建单成功)」——参考 kuaishou-feifei 的「给用户链接即核销」模式。 + +**改动**(`apps/backend/src/services/fulfillment/affiliate-dash/index.ts`): + +| 位置 | 行为 | +| --- | --- | +| `prepareAffiliateDashTask` | 建单成功(orderNo 拿到、task → link_generated、91 侧即可取 claimUrl)后**立即**调用 `consumeKuaishouIndustryVouchersForTask`:有凭证 → 核销并写回 `kuaishouIndustryVoucher` 上下文(`consumeStatus=success`);无凭证 → `not_required`;**核销失败不阻塞**下单与链接发放(`consumeStatus=failed` + warn 日志,delivered 兜底) | +| `syncAffiliateDashTaskStatus`(delivered 分支) | `alreadyConsumed` 短路:`consumeStatus ∈ {success, not_required}` 时不再重复调核销,直接 redeemed;`failed/空` 才补核销(原逻辑保留为兜底) | + +**效果**:核销由「发货完成后(分钟~小时级)」提前到「建单成功(秒级)」;91 卡信息一经消耗即核销,delivered 回调只做状态收敛。已核销成功的凭证在 delivered 时 `resolveTaskVouchers` 返回空,自然走 redeemed。 + +**验证**:typecheck 通过;后端全量 212 测试通过。