fix(claim): 统一所有买家领取链接为 H5 订单号格式
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { buildOrderClaimUrl } from '../../claim/claim-service.js'
|
||||
import {
|
||||
buildDouyinMashidaBuyerClaimUrl,
|
||||
buildDouyinMashidaClaimMessage,
|
||||
isDouyinMashidaH5ClaimBase,
|
||||
renderDouyinMashidaMessageTemplate,
|
||||
resolveDouyinMashidaClaimUrl,
|
||||
} from './claim-link.js'
|
||||
import {
|
||||
createDefaultDouyinMashidaSourceConfig,
|
||||
@@ -45,22 +44,6 @@ test('抖音配置归一化:claimLinkBase 去尾斜杠,开关可关,模板
|
||||
assert.equal(config.claimMessageEnabled, false)
|
||||
})
|
||||
|
||||
test('买家领取链接 = claimLinkBase(去尾斜杠)+ / + token,绝不回退全局领取页', () => {
|
||||
assert.equal(
|
||||
resolveDouyinMashidaClaimUrl(
|
||||
'AbC123',
|
||||
buildConfig({ claimLinkBase: 'https://x.ngrok-free.app/claim/' }),
|
||||
),
|
||||
'https://x.ngrok-free.app/claim/AbC123',
|
||||
)
|
||||
|
||||
assert.equal(resolveDouyinMashidaClaimUrl('AbC123', buildConfig()), '')
|
||||
assert.equal(
|
||||
resolveDouyinMashidaClaimUrl('', buildConfig({ claimLinkBase: 'https://x/claim' })),
|
||||
'',
|
||||
)
|
||||
})
|
||||
|
||||
test('模板占位符替换:支持 link/sku/orderNo/productTitle,空值保留占位符', () => {
|
||||
assert.equal(
|
||||
renderDouyinMashidaMessageTemplate(
|
||||
@@ -105,16 +88,6 @@ test('发消息正文:无领取链接则不发(返回空串),默认模
|
||||
)
|
||||
})
|
||||
|
||||
test('领取链接基址形态:/h5 与 /h5/xxx 判为 H5 订单号形态,其它不是', () => {
|
||||
assert.equal(isDouyinMashidaH5ClaimBase('https://ks.khhao.com/h5'), true)
|
||||
assert.equal(isDouyinMashidaH5ClaimBase('https://ks.khhao.com/h5/'), true)
|
||||
assert.equal(isDouyinMashidaH5ClaimBase('https://ks.khhao.com/h5/anything'), true)
|
||||
assert.equal(isDouyinMashidaH5ClaimBase(' https://ks.khhao.com/claim '), false)
|
||||
assert.equal(isDouyinMashidaH5ClaimBase('https://ks.khhao.com/h5x'), false)
|
||||
assert.equal(isDouyinMashidaH5ClaimBase(''), false)
|
||||
assert.equal(isDouyinMashidaH5ClaimBase(undefined), false)
|
||||
})
|
||||
|
||||
test('买家领取消息链接:没有自动发货领取单元(人工单)一律返回空串 = 不发消息', () => {
|
||||
// 线上实测的缺陷场景:未命中映射的「套装-牛仔情诗」单回落人工,却收到了「已发货」领取链接
|
||||
assert.equal(
|
||||
@@ -126,19 +99,18 @@ test('买家领取消息链接:没有自动发货领取单元(人工单)
|
||||
'',
|
||||
)
|
||||
|
||||
// token 形态基址同样不发:人工单本来就没有 token,不能拿订单号去凑
|
||||
// 历史 /claim 基址同样不发:人工单本来就没有自动发货单元
|
||||
assert.equal(
|
||||
buildDouyinMashidaBuyerClaimUrl({
|
||||
claimLinkBase: 'https://ks.khhao.com/claim',
|
||||
orderNo: '6918490107300556291',
|
||||
hasAutoClaimUnit: false,
|
||||
tokenClaimUrl: '',
|
||||
}),
|
||||
'',
|
||||
)
|
||||
})
|
||||
|
||||
test('买家领取消息链接:有自动发货领取单元时按基址形态拼接', () => {
|
||||
test('买家领取消息链接:有自动发货领取单元时统一生成 /h5/订单号', () => {
|
||||
// /h5 形态:拼订单号(结尾斜杠、结尾多斜杠都容忍)
|
||||
assert.equal(
|
||||
buildDouyinMashidaBuyerClaimUrl({
|
||||
@@ -157,15 +129,14 @@ test('买家领取消息链接:有自动发货领取单元时按基址形态
|
||||
'https://ks.khhao.com/h5/6918490107300556291',
|
||||
)
|
||||
|
||||
// token 形态:只用 resolveOrderItemClaimLink 解析出的链接
|
||||
// 历史 /claim 基址也归一化到 /h5,不再回退 token 链接
|
||||
assert.equal(
|
||||
buildDouyinMashidaBuyerClaimUrl({
|
||||
claimLinkBase: 'https://ks.khhao.com/claim',
|
||||
orderNo: '6918490107300556291',
|
||||
hasAutoClaimUnit: true,
|
||||
tokenClaimUrl: 'https://ks.khhao.com/claim/AbC123',
|
||||
}),
|
||||
'https://ks.khhao.com/claim/AbC123',
|
||||
'https://ks.khhao.com/h5/6918490107300556291',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -176,7 +147,7 @@ test('买家领取消息链接:基址或订单号缺失时返回空串(不
|
||||
orderNo: '6918490107300556291',
|
||||
hasAutoClaimUnit: true,
|
||||
}),
|
||||
'',
|
||||
buildOrderClaimUrl('6918490107300556291'),
|
||||
)
|
||||
assert.equal(
|
||||
buildDouyinMashidaBuyerClaimUrl({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buildConfiguredClaimUrl } from '../../claim/claim-service.js'
|
||||
import { buildOrderClaimUrl } from '../../claim/claim-service.js'
|
||||
import {
|
||||
DEFAULT_CLAIM_MESSAGE_TEMPLATE,
|
||||
getDouyinMashidaSourceConfig,
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
/**
|
||||
* 抖音码速达的买家领取链接与发货消息拼装(纯函数,便于单测)。
|
||||
*
|
||||
* 领取链接 = `config.claimLinkBase`(去尾斜杠) + `/` + token。
|
||||
* ⚠️ 这里**只认** `claimLinkBase`,绝不回退 `runtimeConfig.orders.claimBaseUrl`
|
||||
* (那条口径的默认基址属于主站默认领取页,两者不能互相回退)。
|
||||
* 买家链接统一为 `/h5/<平台订单号>`;配置了更具体的领取站基址时沿用其站点。
|
||||
*/
|
||||
|
||||
export type DouyinMashidaClaimMessageVars = {
|
||||
@@ -20,25 +18,6 @@ export type DouyinMashidaClaimMessageVars = {
|
||||
productTitle?: string | undefined
|
||||
}
|
||||
|
||||
/** 买家领取链接;`claimLinkBase` 或 token 缺失时返回空串(调用方据此跳过发链接/发消息)。 */
|
||||
export function resolveDouyinMashidaClaimUrl(
|
||||
token: unknown,
|
||||
config: DouyinMashidaSourceConfig = getDouyinMashidaSourceConfig(),
|
||||
): string {
|
||||
return buildConfiguredClaimUrl(config.claimLinkBase, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取链接基址是否是**路径式 H5 订单号**形态(`https://<域名>/h5`、`.../h5/xxx`)。
|
||||
*
|
||||
* 这种基址拼的是**订单号**(`/h5/<订单号>`),不是 claim token;两种形态的拼接规则不同,
|
||||
* 判断只此一处,避免各调用方各写一份。
|
||||
*/
|
||||
export function isDouyinMashidaH5ClaimBase(claimLinkBase: unknown): boolean {
|
||||
const base = normalizeClaimLinkBase(claimLinkBase)
|
||||
return Boolean(base) && (base.endsWith('/h5') || base.includes('/h5/'))
|
||||
}
|
||||
|
||||
/**
|
||||
* 买家消息里真正要发出去的领取链接(**空串 = 这条消息不许发**)。
|
||||
*
|
||||
@@ -52,40 +31,21 @@ export function isDouyinMashidaH5ClaimBase(claimLinkBase: unknown): boolean {
|
||||
* 历史缺陷:`/h5` 基址原先走了一条捷径——只拼订单号、跳过履约任务校验,
|
||||
* 于是人工单也照样收到领取消息(线上实测:未命中映射的「套装-牛仔情诗」单被发了链接)。
|
||||
*
|
||||
* 拼接规则按基址形态分流:
|
||||
* - `/h5` 形态 → `claimLinkBase` + `/` + 订单号;
|
||||
* - 其它形态 → `resolveOrderItemClaimLink` 解析出的 token 链接。
|
||||
* 拼接规则:有自动发货领取单元时,所有来源统一发 `/h5/<平台订单号>`。
|
||||
* 历史配置的 `/#/claim`、`/claim` 等基址也会由公共 helper 归一化到 `/h5`。
|
||||
*/
|
||||
export function buildDouyinMashidaBuyerClaimUrl(input: {
|
||||
claimLinkBase: unknown
|
||||
/** 平台订单号(`/h5` 形态用) */
|
||||
/** 平台订单号 */
|
||||
orderNo: unknown
|
||||
/** 该订单项是否有自动发货领取单元(`resolveOrderItemClaimLink().taskId` 非空即为有) */
|
||||
hasAutoClaimUnit: boolean
|
||||
/** token 形态链接(`resolveOrderItemClaimLink().claimUrl`) */
|
||||
tokenClaimUrl?: unknown
|
||||
}): string {
|
||||
if (!input.hasAutoClaimUnit) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const base = normalizeClaimLinkBase(input.claimLinkBase)
|
||||
if (!base) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (isDouyinMashidaH5ClaimBase(base)) {
|
||||
return buildConfiguredClaimUrl(base, String(input.orderNo ?? '').trim())
|
||||
}
|
||||
|
||||
return String(input.tokenClaimUrl ?? '').trim()
|
||||
}
|
||||
|
||||
/** 基址归一化:去首尾空白与结尾斜杠。 */
|
||||
function normalizeClaimLinkBase(claimLinkBase: unknown): string {
|
||||
return String(claimLinkBase ?? '')
|
||||
.trim()
|
||||
.replace(/\/+$/, '')
|
||||
return buildOrderClaimUrl(input.orderNo, input.claimLinkBase || undefined)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -254,31 +254,28 @@ async function notifyBuyerClaimLink(input: {
|
||||
const claimLinkBase = String(input.config.claimLinkBase || '')
|
||||
.trim()
|
||||
.replace(/\/+$/, '')
|
||||
if (!claimLinkBase) {
|
||||
return
|
||||
}
|
||||
|
||||
const orderNo = resolveReceiptOrderId(input.order, input.orderItem)
|
||||
const receiptOrderNo = resolveReceiptOrderId(input.order, input.orderItem)
|
||||
const claimOrderNo = String(input.order.platform_order_id || receiptOrderNo).trim()
|
||||
|
||||
// 先核对履约任务:`resolveOrderItemClaimLink` 只有在该订单项存在带 token 的领取单元时才给 taskId。
|
||||
const claimLink = await resolveOrderItemClaimLink({
|
||||
orderId: input.order.id,
|
||||
orderNo: claimOrderNo,
|
||||
orderItemId: input.orderItem.id,
|
||||
claimLinkBase,
|
||||
})
|
||||
|
||||
const claimUrl = buildDouyinMashidaBuyerClaimUrl({
|
||||
claimLinkBase,
|
||||
orderNo,
|
||||
orderNo: claimOrderNo,
|
||||
hasAutoClaimUnit: Boolean(claimLink.taskId),
|
||||
tokenClaimUrl: claimLink.claimUrl,
|
||||
})
|
||||
|
||||
if (!claimUrl) {
|
||||
logIntegration('[douyin-mashida/notify]', '该订单项没有自动发货领取单元,跳过领取链接消息', {
|
||||
orderId: input.order.id,
|
||||
orderItemId: input.orderItem.id,
|
||||
orderNo,
|
||||
orderNo: claimOrderNo,
|
||||
claimLinkBase,
|
||||
taskCount: claimLink.taskCount,
|
||||
hasClaimUnit: Boolean(claimLink.taskId),
|
||||
@@ -290,19 +287,24 @@ async function notifyBuyerClaimLink(input: {
|
||||
config: input.config,
|
||||
claimUrl,
|
||||
sku: input.orderItem.sku_code,
|
||||
orderNo,
|
||||
orderNo: claimOrderNo,
|
||||
productTitle: input.orderItem.sku_name,
|
||||
})
|
||||
if (!content) {
|
||||
return
|
||||
}
|
||||
|
||||
await sendDouyinMashidaOrderMessage({ orderId: orderNo, content, config: input.config })
|
||||
await sendDouyinMashidaOrderMessage({
|
||||
orderId: receiptOrderNo,
|
||||
content,
|
||||
config: input.config,
|
||||
})
|
||||
|
||||
logIntegration('[douyin-mashida/notify]', '已把领取链接发给买家(order.message)', {
|
||||
orderId: input.order.id,
|
||||
orderItemId: input.orderItem.id,
|
||||
orderNo,
|
||||
orderNo: claimOrderNo,
|
||||
receiptOrderNo,
|
||||
taskId: claimLink.taskId,
|
||||
claimUrl,
|
||||
})
|
||||
|
||||
@@ -18,13 +18,11 @@ import {
|
||||
* - `uid` 是抖店商户号;`secret` 是用户秘钥(两者都由平台方分配)。
|
||||
* - `certLink` 是我们发给平台的固定兑换链接(`coupons.syncV2` 的 `cert_link`),
|
||||
* **必须做成配置项**:链接一旦同步给平台就收不回来,换域名时只影响新券。
|
||||
* 命中 affiliate-dash 映射的订单会优先用「买家领取链接」(`claimLinkBase` + token),
|
||||
* 此时 `certLink` 退化为兜底。
|
||||
* - `claimLinkBase` 是**买家领取页**基址,必须渲染 affiliate-dash 领取流程
|
||||
* (输 UID → 二维码 → 扫码绑定 → 比对「绑定 UID == 填的 UID」完成发货)。
|
||||
* 本仓库前端该页面是 `pages/claim/ClaimPage`,路径式 `https://<域名>/claim` 与
|
||||
* hash 式 `https://<域名>/#/claim` 都会渲染它;它**不是** `runtimeConfig.orders.claimBaseUrl`
|
||||
* (两条口径不能互相回退)。
|
||||
* 买家领取链接统一为 `/h5/<订单号>`;未配 `claimLinkBase` 时使用全局领取站,
|
||||
* `certLink` 仅在订单号或领取站不可用时兜底。
|
||||
* - `claimLinkBase` 是**买家领取页**基址;出站链接会在其站点下统一生成
|
||||
* `/h5/<平台订单号>`,历史 `/#/claim`、`/claim` 基址也会被归一化。
|
||||
* 本仓库前端该页面是 `pages/claim/ClaimPage`,走 affiliate-dash 领取流程。
|
||||
* - `handledTopics` 是本地处理白名单:白名单外的 topic 只落库留痕、不产生副作用。
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user