添加快手 Cloud 换绑角色功能

This commit is contained in:
yml
2026-05-30 11:23:52 +08:00
parent 681ff44cbc
commit 1c50569ea9
22 changed files with 1537 additions and 953 deletions
@@ -26,6 +26,7 @@ import {
maskCode,
normalizeKuaishouCloudFlow,
prepareKuaishouCloudFulfillmentTask,
rebindKuaishouCloudTaskRole,
refreshKuaishouCloudTaskRoleInfo,
} from '../fulfillment/kuaishou-cloud/index.js'
import { buildClaimDetailPayload, getClaimContext } from './kuaishou-cloud-claim-context.js'
@@ -59,7 +60,9 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
}
const mockTicketCode = isKuaishouEticketMockTicketCode(ticketCode)
let shopId = String(flow.consume.shopId || context.order.shop_id || (mockTicketCode ? 'mock' : '')).trim()
let shopId = String(
flow.consume.shopId || context.order.shop_id || (mockTicketCode ? 'mock' : ''),
).trim()
let shopName = String(flow.consume.shopName || context.order.shop_name || '').trim()
let detailResult
@@ -134,13 +137,16 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
} else {
const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment)
if (preparedFlow.binding.prepareStatus !== 'ready' || !preparedFlow.binding.bindUrl) {
await prepareKuaishouCloudFulfillmentTask({
...context.task,
context_json: JSON.stringify(nextContext),
}, {
source: 'claim_page_ticket_verified',
actor: { source: 'claim_page' },
})
await prepareKuaishouCloudFulfillmentTask(
{
...context.task,
context_json: JSON.stringify(nextContext),
},
{
source: 'claim_page_ticket_verified',
actor: { source: 'claim_page' },
},
)
} else {
await updateTask(context.task.id, {
claim_token: context.claimToken.token,
@@ -159,13 +165,18 @@ export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: Js
}
}
await createTaskEvent(context.task.id, 'kuaishou_cloud_ticket_verified', {
ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode),
shopId,
shopName,
goodsTitle: String(detailResult.goods?.itemTitle || '').trim(),
mock: mockTicketCode,
}, now)
await createTaskEvent(
context.task.id,
'kuaishou_cloud_ticket_verified',
{
ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode),
shopId,
shopName,
goodsTitle: String(detailResult.goods?.itemTitle || '').trim(),
mock: mockTicketCode,
},
now,
)
return getKuaishouCloudClaimDetail(token)
}
@@ -195,10 +206,10 @@ export async function getKuaishouCloudClaimDetail(token: unknown) {
let task = context.task
if (
String(task.executor_key || '').trim() === 'kuaishou_ct_assisted'
&& !isKuaishouCloudMockTask(task)
String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' &&
!isKuaishouCloudMockTask(task)
) {
task = await syncKuaishouCloudRoleInfo(task) || task
task = (await syncKuaishouCloudRoleInfo(task)) || task
}
return buildClaimDetailPayload({
@@ -209,6 +220,24 @@ export async function getKuaishouCloudClaimDetail(token: unknown) {
})
}
export async function rebindKuaishouCloudClaimRole(token: unknown) {
const context = await getClaimContext(token)
if (String(context.task.executor_key || '').trim() !== 'kuaishou_ct_assisted') {
throw createHttpError('当前领取链接不是快手 Cloud 客户领取流程', {
statusCode: 409,
errorCode: 'claim_not_kuaishou_cloud',
})
}
await rebindKuaishouCloudTaskRole(context.task, {
source: 'claim_page_rebind_role',
actor: { source: 'claim_page' },
})
return getKuaishouCloudClaimDetail(token)
}
function parseTaskContext(task: Partial<TaskRow> | null | undefined): JsonObject {
const rawValue = task?.context_json
if (!rawValue) {
@@ -289,10 +318,13 @@ async function queryKuaishouEticketConsumeDetailWithShopFallback({
}
}
throw createHttpError(lastError instanceof Error ? lastError.message : '核销码校验失败,请确认是否复制完整', {
statusCode: 409,
errorCode: 'claim_kuaishou_cloud_ticket_invalid',
})
throw createHttpError(
lastError instanceof Error ? lastError.message : '核销码校验失败,请确认是否复制完整',
{
statusCode: 409,
errorCode: 'claim_kuaishou_cloud_ticket_invalid',
},
)
}
function resolveKuaishouEticketDetailCandidateShops({
@@ -333,9 +365,7 @@ function isUsableKuaishouEticketShopConfig(
shopConfig: KuaishouEticketShopConfig | null | undefined,
): shopConfig is KuaishouEticketShopConfig {
return Boolean(
shopConfig &&
shopConfig.enabled !== false &&
String(shopConfig.cookie || '').trim(),
shopConfig && shopConfig.enabled !== false && String(shopConfig.cookie || '').trim(),
)
}
@@ -373,11 +403,11 @@ export async function confirmKuaishouCloudClaimRole(token: unknown) {
const refreshed = mockMode
? { task: context.task }
: await refreshKuaishouCloudTaskRoleInfo(context.task, {
source: 'claim_page_role_confirm',
actor: { source: 'claim_page' },
recordEvent: false,
forceProbe: true,
})
source: 'claim_page_role_confirm',
actor: { source: 'claim_page' },
recordEvent: false,
forceProbe: true,
})
const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment)
if (flow.ticket.status !== 'verified') {
@@ -404,11 +434,16 @@ export async function confirmKuaishouCloudClaimRole(token: unknown) {
updated_at: now,
})
await createTaskEvent(context.task.id, 'kuaishou_cloud_role_confirmed', {
vnPhone: flow.binding.vnPhone,
roleName: flow.binding.roleName,
roleId: flow.binding.roleId,
}, now)
await createTaskEvent(
context.task.id,
'kuaishou_cloud_role_confirmed',
{
vnPhone: flow.binding.vnPhone,
roleName: flow.binding.roleName,
roleId: flow.binding.roleId,
},
now,
)
return getKuaishouCloudClaimDetail(token)
}
@@ -474,10 +509,15 @@ export async function redeemKuaishouCloudClaim(token: unknown) {
updated_at: nowIso(),
})
await createTaskEvent(lockedTask.id, 'kuaishou_cloud_redeem_failed', {
source: 'claim_page_redeem',
errorMessage: message,
}, nowIso())
await createTaskEvent(
lockedTask.id,
'kuaishou_cloud_redeem_failed',
{
source: 'claim_page_redeem',
errorMessage: message,
},
nowIso(),
)
throw error
}
@@ -490,10 +530,10 @@ function isKuaishouCloudMockTask(task: Partial<TaskRow> | null | undefined) {
}
function isKuaishouCloudMockContext(context: JsonObject = {}) {
const flow = context.kuaishouCloudFulfillment
&& typeof context.kuaishouCloudFulfillment === 'object'
? context.kuaishouCloudFulfillment
: {}
const flow =
context.kuaishouCloudFulfillment && typeof context.kuaishouCloudFulfillment === 'object'
? context.kuaishouCloudFulfillment
: {}
const mock = flow.mock && typeof flow.mock === 'object' ? flow.mock : context.mock
return Boolean(mock && typeof mock === 'object' && mock.enabled === true)
@@ -502,8 +542,9 @@ function isKuaishouCloudMockContext(context: JsonObject = {}) {
function buildMockVerifiedKuaishouCloudFlow(value: unknown, timestamp: string) {
const flow = normalizeKuaishouCloudFlow(value)
const source = flow as JsonObject
const bindUrl = flow.binding.bindUrl
|| `https://example.com/mock-kuaishou-cloud-bind?task=mock&ts=${encodeURIComponent(timestamp)}`
const bindUrl =
flow.binding.bindUrl ||
`https://example.com/mock-kuaishou-cloud-bind?task=mock&ts=${encodeURIComponent(timestamp)}`
const vnPhone = flow.binding.vnPhone || '13800000000'
const roleName = flow.binding.roleName || flow.role.name || '测试角色'
const roleId = flow.binding.roleId || flow.role.rid || '10001'
@@ -590,8 +631,13 @@ async function completeMockKuaishouCloudClaimTask(task: TaskRow, timestamp: stri
updated_at: timestamp,
})
await createTaskEvent(task.id, 'kuaishou_cloud_mock_redeemed', {
source: 'claim_page_mock',
deliveryItems: flow.deliveryItems,
}, timestamp)
await createTaskEvent(
task.id,
'kuaishou_cloud_mock_redeemed',
{
source: 'claim_page_mock',
deliveryItems: flow.deliveryItems,
},
timestamp,
)
}