From affba7e924ef1b2a4b14d9cfae6d744691a39894 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 7 Jul 2026 15:46:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dfeifei=E9=A2=86=E5=8F=96?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E7=9B=B4=E8=BF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/admin/admin-read-service.ts | 16 ++++-- .../fulfillment/kuaishou-feifei/index.test.ts | 27 +++++++++ .../fulfillment/kuaishou-feifei/index.ts | 57 ++++++++++--------- .../services/open-91/order-query-service.ts | 7 ++- 4 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 apps/backend/src/services/fulfillment/kuaishou-feifei/index.test.ts diff --git a/apps/backend/src/services/admin/admin-read-service.ts b/apps/backend/src/services/admin/admin-read-service.ts index a74a0236..73bd5147 100644 --- a/apps/backend/src/services/admin/admin-read-service.ts +++ b/apps/backend/src/services/admin/admin-read-service.ts @@ -5,6 +5,7 @@ import { getOrderById, listOrders } from '../../repositories/order-repo.js' import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js' import { listTaskEventsByTaskId } from '../../repositories/task-event-repo.js' import { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-config-service.js' +import { resolveKuaishouFeifeiClaimUrl } from '../fulfillment/kuaishou-feifei/index.js' import { createHttpError } from '../../utils/http.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.js' import { @@ -189,7 +190,10 @@ export async function getAdminTaskDetail( const taskContext = parseTaskContext(task) const taskState = parseTaskState(task) const viewerContext = createAdminViewerContext(session) - const claimUrl = claimToken ? buildClaimUrl(claimToken.token) : '' + const directClaimUrl = String(task.executor_key || '').trim() === 'kuaishou_feifei' + ? resolveKuaishouFeifeiClaimUrl(taskContext.kuaishouFeifei) + : '' + const claimUrl = directClaimUrl || (claimToken ? buildClaimUrl(claimToken.token) : '') const screenshotUrl = await resolveAdminTaskScreenshotUrl(task, viewerContext) const cloudSourceLabelMap = buildCloudSourceLabelMap() const kuaishouCloudFulfillment = @@ -227,12 +231,12 @@ export async function getAdminTaskDetail( quantity: orderItem.quantity, } : null, - claimToken: claimToken + claimToken: claimToken || directClaimUrl ? { - primaryClaimTokenId: claimToken.id, - token: viewerContext.canViewSensitiveTaskData ? claimToken.token : '', - status: claimToken.status, - expiredAt: claimToken.expired_at, + primaryClaimTokenId: claimToken?.id || 0, + token: viewerContext.canViewSensitiveTaskData ? (claimToken?.token || '') : '', + status: claimToken?.status || 'external', + expiredAt: claimToken?.expired_at || '', claimUrl, } : null, diff --git a/apps/backend/src/services/fulfillment/kuaishou-feifei/index.test.ts b/apps/backend/src/services/fulfillment/kuaishou-feifei/index.test.ts new file mode 100644 index 00000000..d9469ef8 --- /dev/null +++ b/apps/backend/src/services/fulfillment/kuaishou-feifei/index.test.ts @@ -0,0 +1,27 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { resolveKuaishouFeifeiClaimUrl } from './index.js' + +test('resolveKuaishouFeifeiClaimUrl prefers feifei h5 url over local claim url', () => { + assert.equal( + resolveKuaishouFeifeiClaimUrl({ + claimUrl: 'https://ks.khhao.com/#/claim/local-token', + h5: { + entryUrl: 'https://feifei.example.com/h5/exchange', + rechargeUrl: 'https://feifei.example.com/h5/bind?code=abc', + }, + }), + 'https://feifei.example.com/h5/bind?code=abc', + ) +}) + +test('resolveKuaishouFeifeiClaimUrl does not expose local claim token fallback', () => { + assert.equal( + resolveKuaishouFeifeiClaimUrl({ + claimUrl: 'local-token', + h5: {}, + }), + '', + ) +}) diff --git a/apps/backend/src/services/fulfillment/kuaishou-feifei/index.ts b/apps/backend/src/services/fulfillment/kuaishou-feifei/index.ts index 382ed031..f09fab23 100644 --- a/apps/backend/src/services/fulfillment/kuaishou-feifei/index.ts +++ b/apps/backend/src/services/fulfillment/kuaishou-feifei/index.ts @@ -4,7 +4,6 @@ import { createHttpError } from '../../../utils/http.js' import { parseTaskContext } from '../../../utils/task-json.js' import { nowIso } from '../../../utils/time.js' import { TASK_STATUS } from '../../../domain/task-status.js' -import { buildClaimUrl, createTaskClaimToken } from '../../claim/claim-service.js' import { createKuaishouFeifeiOrder, queryKuaishouFeifeiOrder, @@ -29,20 +28,18 @@ export async function prepareKuaishouFeifeiTask(task: TaskRow) { const now = nowIso() const taskContext = parseTaskContext(task) const flow = normalizeKuaishouFeifeiFlow(taskContext.kuaishouFeifei) - const claimLinkState = await ensureTaskClaimLink(task) - if (flow.orderNo && flow.h5.rechargeUrl) { + const existingClaimUrl = resolveKuaishouFeifeiClaimUrl(flow) + if (flow.orderNo && existingClaimUrl) { const nextContext = { ...taskContext, kuaishouFeifei: { ...flow, - claimUrl: claimLinkState.claimUrl, + claimUrl: existingClaimUrl, }, } return updateTask(task.id, { task_status: TASK_STATUS.LINK_GENERATED, - claim_token: claimLinkState.token || task.claim_token || '', - claim_expires_at: claimLinkState.expiredAt || task.claim_expires_at || null, user_action_status: 'pending_claim', context_json: JSON.stringify(nextContext), updated_at: now, @@ -62,15 +59,21 @@ export async function prepareKuaishouFeifeiTask(task: TaskRow) { productCode: flow.productCode, platformBuyNum: 1, }) + const feifeiClaimUrl = resolveKuaishouFeifeiOrderClaimUrl(order) + if (!feifeiClaimUrl) { + throw createHttpError('kuaishou-feifei 未返回领取链接', { + statusCode: 502, + errorCode: 'kuaishou_feifei_missing_claim_url', + }) + } + const nextFlow = mergeKuaishouFeifeiOrder(flow, order, { platformOrderNo, - claimUrl: claimLinkState.claimUrl, + claimUrl: feifeiClaimUrl, syncedAt: now, }) const updatedTask = await updateTask(task.id, { task_status: TASK_STATUS.LINK_GENERATED, - claim_token: claimLinkState.token || task.claim_token || '', - claim_expires_at: claimLinkState.expiredAt || task.claim_expires_at || null, user_action_status: 'pending_claim', result_code: 'kuaishou_feifei_order_created', result_message: order.rechargeStatusLabel || 'kuaishou-feifei 订单已创建', @@ -122,7 +125,7 @@ export async function syncKuaishouFeifeiTaskStatus(task: TaskRow) { let lastError = task.last_error const nextFlow = mergeKuaishouFeifeiOrder(flow, order, { syncedAt: now, - claimUrl: flow.claimUrl, + claimUrl: resolveKuaishouFeifeiOrderClaimUrl(order) || resolveKuaishouFeifeiClaimUrl(flow), }) if (order.rechargeStatus === 30) { @@ -197,6 +200,16 @@ export function normalizeKuaishouFeifeiFlow(value: unknown) { } } +export function resolveKuaishouFeifeiClaimUrl(value: unknown) { + const flow = normalizeKuaishouFeifeiFlow(value) + const h5ClaimUrl = flow.h5.rechargeUrl || flow.h5.entryUrl + if (h5ClaimUrl) { + return h5ClaimUrl + } + + return isLocalClaimUrl(flow.claimUrl) ? '' : flow.claimUrl +} + function mergeKuaishouFeifeiOrder( flow: ReturnType, order: Awaited>, @@ -225,25 +238,13 @@ function mergeKuaishouFeifeiOrder( } } -async function ensureTaskClaimLink(task: TaskRow) { - const tokenStatus = String(task?.primary_claim_token_status || '').trim() - const token = String(task?.primary_claim_token || task?.claim_token || '').trim() - const expiredAt = task?.primary_claim_expires_at || task?.claim_expires_at || null +function resolveKuaishouFeifeiOrderClaimUrl(order: Awaited>) { + return String(order.h5.rechargeUrl || order.h5.entryUrl || '').trim() +} - if (tokenStatus === 'active' && token && expiredAt && new Date(expiredAt).getTime() > Date.now()) { - return { - token, - expiredAt, - claimUrl: buildClaimUrl(token), - } - } - - const claimToken = await createTaskClaimToken(task.id) - return { - token: claimToken.token, - expiredAt: claimToken.expired_at, - claimUrl: claimToken.claimUrl, - } +function isLocalClaimUrl(value: unknown) { + const url = String(value || '').trim() + return !/^https?:\/\//i.test(url) || url.includes('/#/claim/') } function buildFeifeiPlatformOrderNo(task: TaskRow) { diff --git a/apps/backend/src/services/open-91/order-query-service.ts b/apps/backend/src/services/open-91/order-query-service.ts index 758d9407..23ffad58 100644 --- a/apps/backend/src/services/open-91/order-query-service.ts +++ b/apps/backend/src/services/open-91/order-query-service.ts @@ -2,6 +2,7 @@ import { findLatestOrderByPlatformOrderId } from '../../repositories/order-repo. import { listTasksByOrderId } from '../../repositories/task-repo.js' import { buildClaimUrl } from '../claim/claim-service.js' import { ensureTaskClaimLink } from '../fulfillment/kuaishou-cloud/index.js' +import { resolveKuaishouFeifeiClaimUrl } from '../fulfillment/kuaishou-feifei/index.js' import { logIntegration } from '../../utils/logger.js' import { OPEN_91_MANUAL_FAILED_STATUS, @@ -88,7 +89,11 @@ export async function queryOpen91Order(payload: JsonObject = {}, { requestId = ' let expireTime: unknown = task.primary_claim_expires_at || task.claim_expires_at || '' const primaryToken = String(task.primary_claim_token || task.claim_token || '').trim() - if (primaryToken) { + if (String(task.executor_key || '').trim() === 'kuaishou_feifei') { + const context = parseJsonObject(task.context_json) + claimUrl = resolveKuaishouFeifeiClaimUrl(context.kuaishouFeifei) + expireTime = '' + } else if (primaryToken) { claimUrl = buildClaimUrl(primaryToken) } else if (String(task.executor_key || '').trim() === 'kuaishou_ct_assisted') { try {