refactor(douyin-mashida)!: 纠偏——删掉自建的皮肤通道,改回复用 affiliate_dash
上一轮我把皮肤交付做成了「另建一套执行器 + 独立领取接口 + 只提交账号就结束」, 跟买家实际要走的过程不一致(真正的完成信号是「扫码绑定后的 UID == 买家填的 UID」), 而且核销还触发了老通知逻辑,给买家**又发了一条 /redeem** —— 买家收到两条链接。 本次按用户要求改回**完全复用仓库现成的 affiliate_dash 通道**(= 旧的快手皮肤自动发货)。 **删掉** - 执行器 `douyin_mashida_skin`(含 registry/types/planner/bootstrap/路由白名单注册) - `services/claim/claim-skin-service.ts`、`routes/claim-skin.ts`、`/api/v1/claim-skin` 挂载 - 迁移 096 的 `claim_submission_json` 全链路(task-repo / rows / inputs / task-detail / admin-read-service 的 claimSubmission),并新增 **097 兜底迁移**把该列 DROP 掉 - 顺带清掉上一轮专为已删接口加的死代码(utils/http.ts 的 buildMessageSuccessPayload 等) **改回复用** - `resolveOrderItemClaimLink`:通用解析(只看订单项、不看执行器)→ `buildConfiguredClaimUrl(claimLinkBase, token)`;多履约单元取 task id 最小并告警; **绝不回退** hash 形态的 `buildClaimUrl` - `cert_link` 与付款后那条**唯一**消息共用同一个解析结果 - **核销后不再给抖音买家发第二条消息**(`shouldNotifyBuyerAfterVerify` 只放行非抖音 provider) - 买家侧走既有流程:输 UID → 二维码 → 扫码绑定 → 比对 UID → delivered/REDEEMED; 在 delivered 分支 best-effort 核销平台券(幂等、失败只 warn,不拖慢买家响应) - 后台回显链接改按 **order.provider** 判断(抖音用 `claimLinkBase`) **SQL 守卫**:允许「删除历史迁移」,但强制要求「同时新增兜底迁移」配对 (修改历史迁移仍禁止),并把改动来源换成 `git status --porcelain`(否则未跟踪的新迁移看不见) `npm run check`:tests 671 / pass 668 / fail 0 / skipped 3(含新增的物品图回退用例)
This commit is contained in:
@@ -4,7 +4,7 @@ import test from 'node:test'
|
||||
import {
|
||||
buildDouyinMashidaClaimMessage,
|
||||
renderDouyinMashidaMessageTemplate,
|
||||
resolveDouyinMashidaSkinClaimUrl,
|
||||
resolveDouyinMashidaClaimUrl,
|
||||
} from './claim-link.js'
|
||||
import {
|
||||
createDefaultDouyinMashidaSourceConfig,
|
||||
@@ -43,18 +43,18 @@ test('抖音配置归一化:claimLinkBase 去尾斜杠,开关可关,模板
|
||||
assert.equal(config.claimMessageEnabled, false)
|
||||
})
|
||||
|
||||
test('皮肤领取链接 = claimLinkBase(去尾斜杠)+ / + token,绝不回退 hash 版领取页', () => {
|
||||
test('买家领取链接 = claimLinkBase(去尾斜杠)+ / + token,绝不回退全局领取页', () => {
|
||||
assert.equal(
|
||||
resolveDouyinMashidaSkinClaimUrl(
|
||||
resolveDouyinMashidaClaimUrl(
|
||||
'AbC123',
|
||||
buildConfig({ claimLinkBase: 'https://x.ngrok-free.app/claim/' }),
|
||||
),
|
||||
'https://x.ngrok-free.app/claim/AbC123',
|
||||
)
|
||||
|
||||
assert.equal(resolveDouyinMashidaSkinClaimUrl('AbC123', buildConfig()), '')
|
||||
assert.equal(resolveDouyinMashidaClaimUrl('AbC123', buildConfig()), '')
|
||||
assert.equal(
|
||||
resolveDouyinMashidaSkinClaimUrl('', buildConfig({ claimLinkBase: 'https://x/claim' })),
|
||||
resolveDouyinMashidaClaimUrl('', buildConfig({ claimLinkBase: 'https://x/claim' })),
|
||||
'',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buildSkinClaimUrl } from '../../claim/claim-service.js'
|
||||
import { buildConfiguredClaimUrl } from '../../claim/claim-service.js'
|
||||
import {
|
||||
DEFAULT_CLAIM_MESSAGE_TEMPLATE,
|
||||
getDouyinMashidaSourceConfig,
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
} from './source-config-service.js'
|
||||
|
||||
/**
|
||||
* 抖音码速达「皮肤自动发货」的领取链接与发货消息拼装(纯函数,便于单测)。
|
||||
* 抖音码速达的买家领取链接与发货消息拼装(纯函数,便于单测)。
|
||||
*
|
||||
* 领取链接 = `config.claimLinkBase`(去尾斜杠) + `/` + token。
|
||||
* ⚠️ 这里**只认** `claimLinkBase`,绝不回退 `runtimeConfig.orders.claimBaseUrl`
|
||||
* (那是 hash 形式的打手领取页,语义完全不同)。
|
||||
* (那条口径的默认基址属于主站默认领取页,两者不能互相回退)。
|
||||
*/
|
||||
|
||||
export type DouyinMashidaClaimMessageVars = {
|
||||
@@ -20,12 +20,12 @@ export type DouyinMashidaClaimMessageVars = {
|
||||
productTitle?: string | undefined
|
||||
}
|
||||
|
||||
/** 皮肤领取链接;`claimLinkBase` 或 token 缺失时返回空串(调用方据此跳过发链接/发消息)。 */
|
||||
export function resolveDouyinMashidaSkinClaimUrl(
|
||||
/** 买家领取链接;`claimLinkBase` 或 token 缺失时返回空串(调用方据此跳过发链接/发消息)。 */
|
||||
export function resolveDouyinMashidaClaimUrl(
|
||||
token: unknown,
|
||||
config: DouyinMashidaSourceConfig = getDouyinMashidaSourceConfig(),
|
||||
): string {
|
||||
return buildSkinClaimUrl(config.claimLinkBase, token)
|
||||
return buildConfiguredClaimUrl(config.claimLinkBase, token)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ export function renderDouyinMashidaMessageTemplate(
|
||||
})
|
||||
}
|
||||
|
||||
/** 皮肤订单付款后发给买家的正文;`claimUrl` 为空返回空串(不发消息)。 */
|
||||
/** 付款成功后发给买家的领取正文;`claimUrl` 为空返回空串(不发消息)。 */
|
||||
export function buildDouyinMashidaClaimMessage(input: {
|
||||
config: DouyinMashidaSourceConfig
|
||||
claimUrl: unknown
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
buildDouyinMashidaSourceEvent,
|
||||
resolveDouyinMashidaParentOrderId,
|
||||
} from './order-mapper.js'
|
||||
import { resolveOrderItemSkinClaimLink } from '../../fulfillment/executors/douyin-mashida-skin-executor.js'
|
||||
import { resolveOrderItemClaimLink } from '../../claim/claim-service.js'
|
||||
import { buildDouyinMashidaClaimMessage } from './claim-link.js'
|
||||
import {
|
||||
getDouyinMashidaSourceConfig,
|
||||
@@ -192,12 +192,12 @@ async function handlePaidMessage(input: {
|
||||
await ensureCertificateForOrderItem({ order, orderItem, config: input.config })
|
||||
certificateCount += 1
|
||||
|
||||
// 皮肤自动发货:发券成功后按模板把领取链接发给买家。
|
||||
// 发券成功后按模板把领取链接发给买家。
|
||||
// best-effort:通知失败只记 warn,绝不影响建单/发券主流程(平台会重推时也不会重复发券)。
|
||||
await notifyBuyerSkinClaimLink({ order, orderItem, config: input.config }).catch((error) => {
|
||||
await notifyBuyerClaimLink({ order, orderItem, config: input.config }).catch((error) => {
|
||||
logIntegration(
|
||||
'[douyin-mashida/notify]',
|
||||
'皮肤领取链接通知买家失败(建单/发券不受影响)',
|
||||
'领取链接通知买家失败(建单/发券不受影响)',
|
||||
{
|
||||
orderId: order.id,
|
||||
orderItemId: orderItem.id,
|
||||
@@ -229,12 +229,16 @@ async function handlePaidMessage(input: {
|
||||
}
|
||||
|
||||
/**
|
||||
* 皮肤自动发货:付款建单 + 发券成功后,把领取链接按模板通过 `order.message` 发给买家。
|
||||
* 付款建单 + 发券成功后,把买家领取链接按模板通过 `order.message` 发给买家。
|
||||
*
|
||||
* 只在「该订单项确实有皮肤履约任务 + claimLinkBase 已配置 + 开关打开」时才发;
|
||||
* 非皮肤订单静默跳过(不打扰现有兑换流程)。
|
||||
* 这是买家**唯一**会收到的那条领取链接(核销后不再补发第二条,见 redeem/certificate-service.ts):
|
||||
* 买家点链接 → 输 UID → 出二维码 → 扫码绑定 → 系统比对「绑定 UID == 填的 UID」→ 发货完成
|
||||
* (完全复用现成的 affiliate_dash 通道)。
|
||||
*
|
||||
* 只在「该订单项确实有履约任务 claim token + claimLinkBase 已配置 + 开关打开」时才发;
|
||||
* 拿不到链接的订单(例如未命中映射、走人工的订单)静默跳过,不打扰现有兑换流程。
|
||||
*/
|
||||
async function notifyBuyerSkinClaimLink(input: {
|
||||
async function notifyBuyerClaimLink(input: {
|
||||
order: OrderRow
|
||||
orderItem: OrderItemRow
|
||||
config: DouyinMashidaSourceConfig
|
||||
@@ -246,19 +250,19 @@ async function notifyBuyerSkinClaimLink(input: {
|
||||
return
|
||||
}
|
||||
|
||||
const skinLink = await resolveOrderItemSkinClaimLink({
|
||||
const claimLink = await resolveOrderItemClaimLink({
|
||||
orderId: input.order.id,
|
||||
orderItemId: input.orderItem.id,
|
||||
config: input.config,
|
||||
claimLinkBase: input.config.claimLinkBase,
|
||||
})
|
||||
if (!skinLink.claimUrl) {
|
||||
if (!claimLink.claimUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
const orderNo = resolveReceiptOrderId(input.order, input.orderItem)
|
||||
const content = buildDouyinMashidaClaimMessage({
|
||||
config: input.config,
|
||||
claimUrl: skinLink.claimUrl,
|
||||
claimUrl: claimLink.claimUrl,
|
||||
sku: input.orderItem.sku_code,
|
||||
orderNo,
|
||||
productTitle: input.orderItem.sku_name,
|
||||
@@ -269,11 +273,11 @@ async function notifyBuyerSkinClaimLink(input: {
|
||||
|
||||
await sendDouyinMashidaOrderMessage({ orderId: orderNo, content, config: input.config })
|
||||
|
||||
logIntegration('[douyin-mashida/notify]', '已把皮肤领取链接发给买家(order.message)', {
|
||||
logIntegration('[douyin-mashida/notify]', '已把领取链接发给买家(order.message)', {
|
||||
orderId: input.order.id,
|
||||
orderItemId: input.orderItem.id,
|
||||
orderNo,
|
||||
taskId: skinLink.pickedTaskId,
|
||||
taskId: claimLink.taskId,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,16 @@ import { sanitizeWellFormedUnicode } from '../../../utils/json.js'
|
||||
export const DOUYIN_MASHIDA_PROVIDER = 'douyin_mashida'
|
||||
export const DOUYIN_MASHIDA_PLATFORM = 'doudian'
|
||||
|
||||
/**
|
||||
* 是否抖音码速达订单(provider 口径)。
|
||||
*
|
||||
* 履约/领取链接的判定一律用它,**不要**按 executor_key 判断:
|
||||
* 抖音皮肤/礼包单已改走现成的 `affiliate_dash` 通道,按执行器判断会失效。
|
||||
*/
|
||||
export function isDouyinMashidaProvider(provider: unknown): boolean {
|
||||
return String(provider || '').trim() === DOUYIN_MASHIDA_PROVIDER
|
||||
}
|
||||
|
||||
/** 内部订单状态只有 4 个值(order-service.ts:432)——抖店状态按「多对一」降级。 */
|
||||
export const DOUYIN_MASHIDA_ORDER_STATUS_MAP: Record<
|
||||
string,
|
||||
|
||||
@@ -18,9 +18,13 @@ import {
|
||||
* - `uid` 是抖店商户号;`secret` 是用户秘钥(两者都由平台方分配)。
|
||||
* - `certLink` 是我们发给平台的固定兑换链接(`coupons.syncV2` 的 `cert_link`),
|
||||
* **必须做成配置项**:链接一旦同步给平台就收不回来,换域名时只影响新券。
|
||||
* 皮肤自动发货订单会优先用「本站领取链接」(`claimLinkBase` + token),此时 `certLink` 退化为兜底。
|
||||
* - `claimLinkBase` 是皮肤领取页的**路径式**基址(如 `https://x.ngrok-free.app/claim`);
|
||||
* 它**不是**打手领取页(`runtimeConfig.orders.claimBaseUrl` 那个 hash 版),两者不能互相回退。
|
||||
* 命中 affiliate-dash 映射的订单会优先用「买家领取链接」(`claimLinkBase` + token),
|
||||
* 此时 `certLink` 退化为兜底。
|
||||
* - `claimLinkBase` 是**买家领取页**基址,必须渲染 affiliate-dash 领取流程
|
||||
* (输 UID → 二维码 → 扫码绑定 → 比对「绑定 UID == 填的 UID」完成发货)。
|
||||
* 本仓库前端该页面是 `pages/claim/ClaimPage`,路径式 `https://<域名>/claim` 与
|
||||
* hash 式 `https://<域名>/#/claim` 都会渲染它;它**不是** `runtimeConfig.orders.claimBaseUrl`
|
||||
* (两条口径不能互相回退)。
|
||||
* - `handledTopics` 是本地处理白名单:白名单外的 topic 只落库留痕、不产生副作用。
|
||||
*/
|
||||
|
||||
@@ -32,7 +36,7 @@ export const DOUYIN_MASHIDA_DEFAULT_BASE_URL = 'https://dy-openplatform-api.wanm
|
||||
/** 兑换成功后 order.message 的默认正文(可在后台覆盖)。 */
|
||||
export const DEFAULT_AFTER_VERIFY_MESSAGE = '您的订单已兑换成功'
|
||||
|
||||
/** 皮肤自动发货:付款后用该模板把领取链接发给买家(可在后台覆盖)。 */
|
||||
/** 付款后用该模板把买家领取链接发给他(可在后台覆盖)。 */
|
||||
export const DEFAULT_CLAIM_MESSAGE_TEMPLATE = '您的订单已发货,请点击链接领取:{link}'
|
||||
|
||||
export const DOUYIN_MASHIDA_DEFAULT_HANDLED_TOPICS = [
|
||||
@@ -46,13 +50,13 @@ export type DouyinMashidaSourceConfig = {
|
||||
baseUrl: string
|
||||
uid: string
|
||||
secret: string
|
||||
/** 固定兑换链接(cert_link),如 https://ks.khhao.com/redeem;皮肤订单无领取链接时兜底用它 */
|
||||
/** 固定兑换链接(cert_link),如 https://ks.khhao.com/redeem;无买家领取链接时兜底用它 */
|
||||
certLink: string
|
||||
/** 皮肤领取页基址(路径式,如 https://x.ngrok-free.app/claim),留空则不生成皮肤领取链接 */
|
||||
/** 买家领取页基址(affiliate-dash 领取流程,如 https://<域名>/claim),留空则不生成/不发领取链接 */
|
||||
claimLinkBase: string
|
||||
/** 皮肤自动发货:付款成功发给买家的 message 模板,支持 {link}/{sku}/{orderNo}/{productTitle} */
|
||||
/** 付款成功发给买家的 message 模板,支持 {link}/{sku}/{orderNo}/{productTitle} */
|
||||
messageTemplate: string
|
||||
/** 皮肤自动发货「付款后发消息」开关,默认开 */
|
||||
/** 「付款后发领取链接」开关,默认开 */
|
||||
claimMessageEnabled: boolean
|
||||
/** order.message 的 message_type:1-订单状态 2-订单发货 4-图片 5-视频 */
|
||||
messageType: number
|
||||
|
||||
Reference in New Issue
Block a user