From ffbc19a9db0e4e1696f5650aedc0b7d93bdf68f9 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 9 Jul 2026 21:03:39 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=BF=AB=E6=89=8B=E5=B0=8F?= =?UTF-8?q?=E5=BA=97=E4=BB=A5=E5=8F=8A=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/README.md | 2 +- .../data/kuaishou-eticket-source.example.json | 13 - .../src/routes/admin/platform-config.ts | 2 - .../admin/platform-config/kuaishou-eticket.ts | 153 ------- apps/backend/src/routes/admin/tasks.ts | 4 - apps/backend/src/routes/claims.ts | 22 +- .../cloudtentacles/context.test.ts | 47 --- .../platform-config/cloudtentacles/context.ts | 26 -- .../cloudtentacles/mappers.test.ts | 25 -- .../platform-config/cloudtentacles/mappers.ts | 20 - .../kuaishou-eticket-service.ts | 80 ---- .../admin/write/kuaishou-cloud-actions.ts | 141 ++----- .../claim/kuaishou-cloud-claim-context.ts | 8 +- .../claim/kuaishou-cloud-claim-service.ts | 372 +----------------- .../executors/kuaishou-cloud-executor.ts | 2 +- .../kuaishou-cloud/task-finalization.ts | 151 +++---- .../notification/domain-notifications.ts | 33 -- .../kuaishou-eticket/consume-service.test.ts | 121 ------ .../kuaishou-eticket/consume-service.ts | 273 ------------- .../platforms/kuaishou-eticket/helpers.ts | 106 ----- .../platforms/kuaishou-eticket/http-client.ts | 200 ---------- .../kuaishou-eticket/info-service.test.ts | 32 -- .../kuaishou-eticket/info-service.ts | 61 --- .../kuaishou-eticket/mock-ticket-service.ts | 123 ------ .../kuaishou-eticket/source-config-service.ts | 158 -------- apps/backend/src/types/admin/route-inputs.ts | 10 - apps/backend/src/types/admin/write-inputs.ts | 42 -- .../src/pages/admin/AdminTaskDetailPage.tsx | 12 +- .../admin/platform/AdminPlatformShopsPage.tsx | 256 ------------ apps/frontend/src/pages/claim/ClaimPage.tsx | 137 +------ .../services/admin/platform-config/index.ts | 1 - .../admin/platform-config/kuaishou-eticket.ts | 60 --- apps/frontend/src/services/admin/tasks.ts | 9 +- apps/frontend/src/services/claim.ts | 9 - apps/frontend/src/styles/main.css | 27 -- apps/frontend/src/types/admin/index.ts | 5 - .../src/types/admin/platform-config/index.ts | 8 - .../admin/platform-config/kuaishou-eticket.ts | 83 ---- 38 files changed, 110 insertions(+), 2724 deletions(-) delete mode 100644 apps/backend/data/kuaishou-eticket-source.example.json delete mode 100644 apps/backend/src/routes/admin/platform-config/kuaishou-eticket.ts delete mode 100644 apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/consume-service.test.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/consume-service.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/helpers.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/info-service.test.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/info-service.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/mock-ticket-service.ts delete mode 100644 apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts delete mode 100644 apps/frontend/src/services/admin/platform-config/kuaishou-eticket.ts delete mode 100644 apps/frontend/src/types/admin/platform-config/kuaishou-eticket.ts diff --git a/apps/backend/README.md b/apps/backend/README.md index 7e520eb0..83aabdd5 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -2,7 +2,7 @@ 快手轻量后端服务。 -后端负责订单入库、履约任务编排、kuaishou-lewan / 91 卡券 / 快手小店核销配置、后台管理接口和数据库迁移。默认入口就是快手轻量后端。 +后端负责订单入库、履约任务编排、kuaishou-lewan / 91 卡券 / 行业电子凭证配置、后台管理接口和数据库迁移。默认入口就是快手轻量后端。 ## 快速启动 diff --git a/apps/backend/data/kuaishou-eticket-source.example.json b/apps/backend/data/kuaishou-eticket-source.example.json deleted file mode 100644 index 74ea81eb..00000000 --- a/apps/backend/data/kuaishou-eticket-source.example.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "enabled": true, - "baseUrl": "https://s.kwaixiaodian.com", - "shops": [ - { - "shopId": "", - "kshopName": "", - "cookie": "", - "userAvatar": "", - "enabled": false - } - ] -} diff --git a/apps/backend/src/routes/admin/platform-config.ts b/apps/backend/src/routes/admin/platform-config.ts index 4a02d62d..a97aec39 100644 --- a/apps/backend/src/routes/admin/platform-config.ts +++ b/apps/backend/src/routes/admin/platform-config.ts @@ -4,7 +4,6 @@ import { requireAdminRoles } from "./session.js"; import cloudtentaclesRouter from "./platform-config/cloudtentacles.js"; import kuaishouIndustryRouter from "./platform-config/kuaishou-industry.js"; import kuaishouFeifeiRouter from "./platform-config/kuaishou-feifei.js"; -import kuaishouEticketRouter from "./platform-config/kuaishou-eticket.js"; import ninetyoneRouter from "./platform-config/ninetyone.js"; import notificationsRouter from "./platform-config/notifications.js"; import fulfillmentRoutingRouter from "./platform-config/fulfillment-routing.js"; @@ -14,7 +13,6 @@ const router = Router(); router.use("/platform-config", requireAdminRoles(["admin"])); router.use("/platform-config", notificationsRouter); router.use("/platform-config", kuaishouIndustryRouter); -router.use("/platform-config", kuaishouEticketRouter); router.use("/platform-config", kuaishouFeifeiRouter); router.use("/platform-config", ninetyoneRouter); router.use("/platform-config", fulfillmentRoutingRouter); diff --git a/apps/backend/src/routes/admin/platform-config/kuaishou-eticket.ts b/apps/backend/src/routes/admin/platform-config/kuaishou-eticket.ts deleted file mode 100644 index 656d34c0..00000000 --- a/apps/backend/src/routes/admin/platform-config/kuaishou-eticket.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Router } from "express"; - -import { - consumeAdminKuaishouEticket, - getAdminKuaishouEticketSourceConfig, - queryAdminKuaishouEticketDetail, - queryAdminKuaishouEticketShopInfo, - updateAdminKuaishouEticketSourceConfig, -} from "../../../services/admin/platform-config/kuaishou-eticket-service.js"; -import type { - AdminKuaishouEticketConsumeRouteBody, - AdminKuaishouEticketDetailQueryRouteBody, - AdminKuaishouEticketShopInfoRouteBody, - AdminKuaishouEticketSourceConfigRouteBody, -} from "../../../types/admin/route-inputs.js"; -import { createJsonHandler } from "../session.js"; -import type { JsonRecord } from "../../../types/json.js"; - -const router = Router(); - -router.get( - "/kuaishou-eticket-source", - createJsonHandler(() => getAdminKuaishouEticketSourceConfig(), { - successMessage: "ok", - errorMessage: "读取快手小店核销配置失败", - scope: "[admin/platform-config/kuaishou-eticket-source]", - }) -); - -router.post( - "/kuaishou-eticket-source", - createJsonHandler( - (req) => - updateAdminKuaishouEticketSourceConfig( - req.body as AdminKuaishouEticketSourceConfigRouteBody - ), - { - successMessage: "快手小店核销配置已保存", - errorMessage: "保存快手小店核销配置失败", - scope: "[admin/platform-config/kuaishou-eticket-source]", - audit: (_req, data) => { - const result = data as JsonRecord; - return { - action: "platform_kuaishou_eticket_source_updated", - targetType: "platform_config", - targetId: "kuaishou_eticket_source", - data: { - filePath: String(result.filePath || "").trim(), - enabled: Boolean(result.source?.enabled), - shopCount: Array.isArray(result.source?.shops) - ? result.source.shops.length - : 0, - }, - }; - }, - } - ) -); - -router.post( - "/kuaishou-eticket/query-detail", - createJsonHandler( - (req) => - queryAdminKuaishouEticketDetail( - req.body as AdminKuaishouEticketDetailQueryRouteBody - ), - { - successMessage: "快手小店核销信息查询成功", - errorMessage: "查询快手小店核销信息失败", - scope: "[admin/platform-config/kuaishou-eticket/query-detail]", - audit: (req, data) => { - const body = req.body as AdminKuaishouEticketDetailQueryRouteBody; - const result = data as JsonRecord; - return { - action: "platform_kuaishou_eticket_query_detail", - targetType: "platform_config", - targetId: - String(result.eTicketId || body.eTicketId || "").trim() || - "kuaishou_eticket", - data: { - result: Number(result.result || 0), - ok: Boolean(result.ok), - alreadyConsumed: Boolean(result.alreadyConsumed), - }, - }; - }, - } - ) -); - -router.post( - "/kuaishou-eticket/query-shop-info", - createJsonHandler( - (req) => - queryAdminKuaishouEticketShopInfo( - req.body as AdminKuaishouEticketShopInfoRouteBody - ), - { - successMessage: "快手小店店铺信息查询成功", - errorMessage: "查询快手小店店铺信息失败", - scope: "[admin/platform-config/kuaishou-eticket/query-shop-info]", - audit: (req, data) => { - const body = req.body as AdminKuaishouEticketShopInfoRouteBody; - const result = data as JsonRecord; - return { - action: "platform_kuaishou_eticket_query_shop_info", - targetType: "platform_config", - targetId: - String(result.shop?.shopId || body.shopId || "").trim() || - "kuaishou_eticket_shop_info", - data: { - result: Number(result.result || 0), - ok: Boolean(result.ok), - kshopName: String(result.shop?.kshopName || "").trim(), - }, - }; - }, - } - ) -); - -router.post( - "/kuaishou-eticket/consume", - createJsonHandler( - (req) => - consumeAdminKuaishouEticket( - req.body as AdminKuaishouEticketConsumeRouteBody - ), - { - successMessage: "快手小店核销请求已执行", - errorMessage: "执行快手小店核销失败", - scope: "[admin/platform-config/kuaishou-eticket/consume]", - audit: (req, data) => { - const body = req.body as AdminKuaishouEticketConsumeRouteBody; - const result = data as JsonRecord; - return { - action: "platform_kuaishou_eticket_consume", - targetType: "platform_config", - targetId: - String(result.eTicketId || body.eTicketId || "").trim() || - "kuaishou_eticket", - data: { - result: Number(result.result || 0), - consumed: Boolean(result.consumed), - alreadyConsumed: Boolean(result.alreadyConsumed), - }, - }; - }, - } - ) -); - -export default router; diff --git a/apps/backend/src/routes/admin/tasks.ts b/apps/backend/src/routes/admin/tasks.ts index f2601cb1..d1bfd28e 100644 --- a/apps/backend/src/routes/admin/tasks.ts +++ b/apps/backend/src/routes/admin/tasks.ts @@ -14,7 +14,6 @@ import { } from '../../services/admin/write/kuaishou-cloud-actions.js' import { createJsonHandler, requireAdminRoles } from './session.js' import type { - AdminTaskKuaishouCloudDispatchRouteBody, AdminTaskKuaishouIndustryConsumeRouteBody, AdminTaskRouteParams, AdminTaskRouteQuery, @@ -108,7 +107,6 @@ router.post( (req) => dispatchAdminTaskKuaishouCloudFulfillment( getTaskId(req), - req.body as AdminTaskKuaishouCloudDispatchRouteBody, req.adminSession || null, ), { @@ -117,7 +115,6 @@ router.post( scope: '[admin/tasks/:taskId/kuaishou-cloud/dispatch]', audit: (req, data) => { const result = data as AdminTaskActionResponse - const body = req.body as AdminTaskKuaishouCloudDispatchRouteBody return { action: 'task_kuaishou_cloud_dispatch', @@ -128,7 +125,6 @@ router.post( taskNo: result.task.taskNo, status: result.task.status, deliveryStatus: result.task.deliveryStatus, - ticketCodeProvided: Boolean(String(body.ticketCode || '').trim()), }, } }, diff --git a/apps/backend/src/routes/claims.ts b/apps/backend/src/routes/claims.ts index 9e866c55..ec18afde 100644 --- a/apps/backend/src/routes/claims.ts +++ b/apps/backend/src/routes/claims.ts @@ -4,12 +4,10 @@ import { createRateLimitMiddleware, getParamRateLimitKey } from '../middleware/r import { confirmKuaishouCloudClaimRole, getKuaishouCloudClaimDetail, - getKuaishouCloudClaimGuideAssetPath, rebindKuaishouCloudClaimRole, redeemKuaishouCloudClaim, - verifyKuaishouCloudClaimTicket, } from '../services/claim/kuaishou-cloud-claim-service.js' -import { buildNotFoundPayload, createRouteFileHandler, createRouteHandler } from '../utils/http.js' +import { buildNotFoundPayload, createRouteHandler } from '../utils/http.js' const router = Router() const claimReadRateLimit = createRateLimitMiddleware({ @@ -34,16 +32,6 @@ router.get( }), ) -router.post( - '/:token/kuaishou-cloud/verify-ticket', - claimWriteRateLimit, - createRouteHandler((req) => verifyKuaishouCloudClaimTicket(req.params.token, req.body), { - successMessage: '核销码提交并核销成功', - errorMessage: '提交核销码失败', - scope: '[claims/:token/kuaishou-cloud/verify-ticket]', - }), -) - router.post( '/:token/kuaishou-cloud/confirm-role', claimWriteRateLimit, @@ -74,14 +62,6 @@ router.post( }), ) -router.get( - '/assets/kuaishou-cloud/:filename', - createRouteFileHandler((req) => getKuaishouCloudClaimGuideAssetPath(req.params.filename), { - errorMessage: '读取指引图片失败', - scope: '[claims/assets/kuaishou-cloud/:filename]', - }), -) - router.use((req, res) => { res.status(404).json(buildNotFoundPayload(req)) }) diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/context.test.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/context.test.ts index 16495888..6d37b1f1 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/context.test.ts +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles/context.test.ts @@ -6,7 +6,6 @@ import { isPlainObject, pickFirstNonEmpty, resolveCloudtentaclesAdminContext, - resolveKuaishouEticketAdminContext, } from './context.js' test('pickFirstNonEmpty returns first trimmed non-empty string value', () => { @@ -21,52 +20,6 @@ test('isPlainObject only accepts non-array objects', () => { assert.equal(isPlainObject('x'), false) }) -test('resolveKuaishouEticketAdminContext prefers explicit payload values', () => { - const context = resolveKuaishouEticketAdminContext( - { - baseUrl: ' https://override.example.com ', - shopId: ' shop-2 ', - cookie: ' cookie-2 ', - }, - { - savedSource: { baseUrl: 'https://saved.example.com' }, - findShopConfig(requestedShopId) { - assert.equal(requestedShopId, 'shop-2') - return { shopId: 'shop-2', cookie: 'saved-cookie-2' } - }, - getFirstAvailableShop() { - throw new Error('should not use first available shop') - }, - }, - ) - - assert.deepEqual(context, { - baseUrl: 'https://override.example.com', - cookie: 'cookie-2', - shopId: 'shop-2', - shop: { shopId: 'shop-2', cookie: 'saved-cookie-2' }, - }) -}) - -test('resolveKuaishouEticketAdminContext falls back to first available shop', () => { - const context = resolveKuaishouEticketAdminContext( - {}, - { - savedSource: { baseUrl: ' https://saved.example.com ' }, - getFirstAvailableShop() { - return { shopId: 'shop-1', cookie: 'cookie-1' } - }, - }, - ) - - assert.deepEqual(context, { - baseUrl: 'https://saved.example.com', - cookie: 'cookie-1', - shopId: 'shop-1', - shop: { shopId: 'shop-1', cookie: 'cookie-1' }, - }) -}) - test('resolveCloudtentaclesAdminContext merges payload source and persisted session', () => { const context = resolveCloudtentaclesAdminContext( { diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/context.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/context.ts index 80bf3f03..c583632e 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/context.ts +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles/context.ts @@ -15,32 +15,6 @@ export function isPlainObject(value: unknown): value is JsonObject { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } -export function resolveKuaishouEticketAdminContext(payload: JsonObject = {}, options: JsonObject = {}) { - const savedSource = options.savedSource || {}; - const findShopConfig = - (options.findShopConfig || (() => null)) as (shopId: string, source: JsonObject) => unknown; - const getFirstAvailableShop = - (options.getFirstAvailableShop || (() => null)) as (source: JsonObject) => unknown; - const defaultBaseUrl = String( - options.defaultBaseUrl || "https://s.kwaixiaodian.com" - ).trim(); - const requestedShopId = String(payload.shopId || "").trim(); - const configuredShop = (requestedShopId - ? findShopConfig(requestedShopId, savedSource) - : getFirstAvailableShop(savedSource)) as JsonObject | null; - - return { - baseUrl: pickFirstNonEmpty([ - payload.baseUrl, - savedSource.baseUrl, - defaultBaseUrl, - ]), - cookie: pickFirstNonEmpty([payload.cookie, configuredShop?.cookie]), - shopId: pickFirstNonEmpty([requestedShopId, configuredShop?.shopId]), - shop: configuredShop, - }; -} - import { getCloudtentaclesSourceByKey } from "../../../platforms/cloudtentacles/source-config-service.js"; import { getCloudtentaclesSessionStateByKey } from "../../../platforms/cloudtentacles/session-state-service.js"; import { diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.test.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.test.ts index eefbcc05..8dc39268 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.test.ts +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.test.ts @@ -3,7 +3,6 @@ import assert from 'node:assert/strict' import { mapAdminCloudtentaclesSession, - mapAdminKuaishouEticketSourceConfig, maskPhone, maskSecret, } from './mappers.js' @@ -20,30 +19,6 @@ test('maskPhone hides middle digits for mobile numbers', () => { assert.equal(maskPhone(''), '') }) -test('mapAdminKuaishouEticketSourceConfig maps shops and masks cookies', () => { - assert.deepEqual( - mapAdminKuaishouEticketSourceConfig( - { enabled: true, baseUrl: ' https://s.kwaixiaodian.com ' }, - [{ shopId: ' 1 ', kshopName: ' A店 ', cookie: 'cookie-abcdef123456', enabled: true }], - ), - { - enabled: true, - baseUrl: 'https://s.kwaixiaodian.com', - shops: [ - { - shopId: '1', - kshopName: 'A店', - cookie: 'cookie-abcdef123456', - cookieMasked: 'cookie****123456', - hasCookie: true, - userAvatar: '', - enabled: true, - }, - ], - }, - ) -}) - test('mapAdminCloudtentaclesSession derives masked fields and token presence', () => { assert.deepEqual( mapAdminCloudtentaclesSession({ diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.ts b/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.ts index 494f4b3a..0c852ad4 100644 --- a/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.ts +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles/mappers.ts @@ -17,26 +17,6 @@ export function maskPhone(value: unknown) { return maskPhoneValue(value, { maskShort: false }); } -export function mapAdminKuaishouEticketShopItem(item: JsonObject = {}) { - return { - shopId: String(item.shopId || "").trim(), - kshopName: String(item.kshopName || "").trim(), - cookie: String(item.cookie || "").trim(), - cookieMasked: maskSecret(item.cookie), - hasCookie: Boolean(String(item.cookie || "").trim()), - userAvatar: String(item.userAvatar || "").trim(), - enabled: item.enabled !== false, - }; -} - -export function mapAdminKuaishouEticketSourceConfig(config: JsonObject = {}, shops: JsonObject[] = []) { - return { - enabled: config.enabled !== false, - baseUrl: String(config.baseUrl || "").trim(), - shops: shops.map((item) => mapAdminKuaishouEticketShopItem(item)), - }; -} - export function mapAdminCloudtentaclesSourceConfig(config: JsonObject = {}) { return { key: String(config.key || "").trim(), diff --git a/apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.ts b/apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.ts deleted file mode 100644 index bbc209d8..00000000 --- a/apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - consumeKuaishouEticket, - queryKuaishouEticketConsumeDetail, -} from '../../platforms/kuaishou-eticket/consume-service.js' -import { - findKuaishouEticketShopConfig, - getFirstAvailableKuaishouEticketShop, - getKuaishouEticketSourceConfig, - getKuaishouEticketSourceFilePath, - listKuaishouEticketShopConfigs, - saveKuaishouEticketSourceConfig, -} from '../../platforms/kuaishou-eticket/source-config-service.js' -import { queryKuaishouEticketStableInfo } from '../../platforms/kuaishou-eticket/info-service.js' -import { resolveKuaishouEticketAdminContext } from './cloudtentacles/context.js' -import { mapAdminKuaishouEticketSourceConfig } from './cloudtentacles/mappers.js' - -type JsonObject = Record - -function resolveAdminKuaishouEticketContext(payload: JsonObject = {}) { - return resolveKuaishouEticketAdminContext(payload, { - savedSource: getKuaishouEticketSourceConfig(), - findShopConfig: findKuaishouEticketShopConfig, - getFirstAvailableShop: getFirstAvailableKuaishouEticketShop, - }) -} - -export function getAdminKuaishouEticketSourceConfig() { - const config = getKuaishouEticketSourceConfig() - - return { - filePath: getKuaishouEticketSourceFilePath(), - source: mapAdminKuaishouEticketSourceConfig(config, listKuaishouEticketShopConfigs(config)), - } -} - -export function updateAdminKuaishouEticketSourceConfig(payload: JsonObject = {}) { - const saved = saveKuaishouEticketSourceConfig({ - enabled: payload.enabled !== false, - baseUrl: String(payload.baseUrl || '').trim() || 'https://s.kwaixiaodian.com', - shops: Array.isArray(payload.shops) ? payload.shops : [], - }) - - return { - filePath: getKuaishouEticketSourceFilePath(), - source: mapAdminKuaishouEticketSourceConfig(saved, listKuaishouEticketShopConfigs(saved)), - } -} - -export async function queryAdminKuaishouEticketDetail(payload: JsonObject = {}) { - const context = resolveAdminKuaishouEticketContext(payload) - - return queryKuaishouEticketConsumeDetail({ - baseUrl: context.baseUrl, - cookie: context.cookie, - eTicketId: String(payload.eTicketId || '').trim(), - }) -} - -export async function queryAdminKuaishouEticketShopInfo(payload: JsonObject = {}) { - const context = resolveAdminKuaishouEticketContext(payload) - - return queryKuaishouEticketStableInfo({ - baseUrl: context.baseUrl, - cookie: context.cookie, - }) -} - -export async function consumeAdminKuaishouEticket(payload: JsonObject = {}) { - const context = resolveAdminKuaishouEticketContext(payload) - - return consumeKuaishouEticket({ - baseUrl: context.baseUrl, - cookie: context.cookie, - eTicketId: String(payload.eTicketId || '').trim(), - oid: String(payload.oid || '').trim(), - formToken: String(payload.formToken || '').trim(), - num: payload.num, - storeId: String(payload.storeId || '').trim(), - }) -} diff --git a/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts b/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts index c7a58ad6..9e98bb27 100644 --- a/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts +++ b/apps/backend/src/services/admin/write/kuaishou-cloud-actions.ts @@ -17,18 +17,12 @@ import { backCloudtentaclesVirtualNumber, getCloudtentaclesBindInfo, } from '../../platforms/cloudtentacles/virtual-number-service.js' -import { consumeKuaishouEticket } from '../../platforms/kuaishou-eticket/consume-service.js' -import { isKuaishouEticketMockTicketCode } from '../../platforms/kuaishou-eticket/mock-ticket-service.js' import { resendKuaishouIndustryVoucherSendCallback } from '../../platforms/kuaishou-industry/send-code-service.js' import { consumeKuaishouIndustryVoucher } from '../../platforms/kuaishou-industry/voucher-service.js' import { attachKuaishouIndustryVoucherToTask, bindKuaishouIndustryVouchersToOrderTasks, } from '../../platforms/kuaishou-industry/voucher-binding-service.js' -import { - getKuaishouEticketSourceConfig, - resolveKuaishouEticketShopConfig, -} from '../../platforms/kuaishou-eticket/source-config-service.js' import { createHttpError } from '../../../utils/http.js' import { nowIso } from '../../../utils/time.js' import { TASK_STATUS } from '../../../domain/task-status.js' @@ -58,7 +52,6 @@ import type { AdminViewerSessionInput, } from '../../../types/admin/read-inputs.js' import type { - AdminTaskKuaishouCloudDispatchInput, AdminTaskKuaishouIndustryConsumeInput, } from '../../../types/admin/write-inputs.js' import type { AdminTaskActionResponse } from '../../../types/admin/write-models.js' @@ -246,7 +239,6 @@ export async function prepareAdminTaskKuaishouCloudFulfillment( export async function dispatchAdminTaskKuaishouCloudFulfillment( taskId: AdminEntityIdInput, - payload: AdminTaskKuaishouCloudDispatchInput = {}, session: AdminViewerSessionInput | null = null, ): Promise { const task = await getRequiredTask(taskId) @@ -281,11 +273,15 @@ export async function dispatchAdminTaskKuaishouCloudFulfillment( }) } - const ticketCode = String(payload.ticketCode || '').trim() const persistedTicketCode = String(flow.ticket.code || '').trim() + const voucherContext = normalizeAdminTaskIndustryVoucherContext(taskContext.kuaishouIndustryVoucher) + const industryVoucherCode = String( + voucherContext.voucherCode || voucherContext.eticketId || '', + ).trim() + const resolvedTicketCode = persistedTicketCode || industryVoucherCode - if (!persistedTicketCode && !ticketCode) { - throw createHttpError('客户还没有在领取页提交核销码,暂时不能直接发货', { + if (!resolvedTicketCode) { + throw createHttpError('旧快手小店核销流程已停用,请改用行业电子凭证处理', { statusCode: 409, errorCode: 'admin_task_kuaishou_cloud_missing_ticket_code', }) @@ -322,16 +318,9 @@ export async function dispatchAdminTaskKuaishouCloudFulfillment( ...syncedFlow, ticket: { ...syncedFlow.ticket, - code: ticketCode || persistedTicketCode, - capturedAt: ticketCode ? now : syncedFlow.ticket.capturedAt, - capturedBy: - ticketCode && session - ? { - userId: Number(session.userId || 0) || 0, - username: String(session.username || '').trim(), - role: String(session.role || '').trim(), - } - : syncedFlow.ticket.capturedBy, + code: resolvedTicketCode, + capturedAt: resolvedTicketCode ? (syncedFlow.ticket.capturedAt || now) : syncedFlow.ticket.capturedAt, + capturedBy: syncedFlow.ticket.capturedBy, }, dispatch: { ...syncedFlow.dispatch, @@ -366,7 +355,7 @@ export async function dispatchAdminTaskKuaishouCloudFulfillment( task.id, 'kuaishou_cloud_dispatched', { - ticketCodeMasked: maskCode(ticketCode || persistedTicketCode), + ticketCodeMasked: maskCode(resolvedTicketCode), skuId: syncedFlow.binding.skuId, vnId: syncedFlow.binding.vnId, vnPhoneMasked: maskPhone(syncedFlow.binding.vnPhone), @@ -548,77 +537,18 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment( id: flow.binding.vnId, }) - const order = await getOrderById(task.order_id) const ticketCode = String(flow.ticket.code || '').trim() - const shopId = String(flow.consume.shopId || order?.shop_id || '').trim() - const shopName = String(flow.consume.shopName || order?.shop_name || '').trim() - const mockTicketCode = isKuaishouEticketMockTicketCode(ticketCode) - const eticketSource = getKuaishouEticketSourceConfig() - const shopConfig = resolveKuaishouEticketShopConfig({ - shopId, - shopName, - }) - let consumeStatus = 'pending' - let consumeErrorMessage = '' - let consumedAt = null - let nextTaskStatus = 'completed' - let nextResultCode = 'kuaishou_cloud_completed' - let nextResultMessage = 'cloudtentacles 发货、退号并完成快手核销' const consumeAlreadyCompleted = flow.consume.status === 'success' - - if (consumeAlreadyCompleted) { - consumeStatus = 'success' - consumedAt = flow.consume.consumedAt || now - } else if (!order) { - consumeStatus = 'failed' - consumeErrorMessage = '任务关联订单不存在,无法执行快手核销' - } else if (!ticketCode) { - consumeStatus = 'failed' - consumeErrorMessage = '客户未提交有效核销码,无法执行快手核销' - } else if (mockTicketCode) { - const consumeResult = await consumeKuaishouEticket({ - eTicketId: ticketCode, - oid: String(flow.ticket.oid || '').trim(), - formToken: String(flow.ticket.formToken || '').trim(), - }) - consumeStatus = consumeResult.consumed ? 'success' : 'failed' - consumedAt = consumeResult.consumed ? now : null - consumeErrorMessage = String(consumeResult.errorMessage || '').trim() - } else if ( - !shopConfig || - shopConfig.enabled === false || - !String(shopConfig.cookie || '').trim() - ) { - consumeStatus = 'failed' - consumeErrorMessage = '订单对应快手小店缺少可用 Cookie,无法执行快手核销' - } else { - try { - const consumeResult = await consumeKuaishouEticket({ - baseUrl: eticketSource.baseUrl, - cookie: shopConfig.cookie, - eTicketId: ticketCode, - oid: String(flow.ticket.oid || '').trim(), - formToken: String(flow.ticket.formToken || '').trim(), - }) - - if (consumeResult.consumed) { - consumeStatus = 'success' - consumedAt = now - } else { - consumeStatus = 'failed' - consumeErrorMessage = String(consumeResult.errorMessage || '快手核销失败').trim() - } - } catch (error) { - consumeStatus = 'failed' - consumeErrorMessage = error instanceof Error ? error.message : '快手核销失败' - } - } - - if (consumeStatus !== 'success') { - nextTaskStatus = 'manual_review' - nextResultCode = 'kuaishou_cloud_consume_failed' - nextResultMessage = consumeErrorMessage || '号码已退还,但快手核销未完成,请人工处理' - } + const consumeStatus = consumeAlreadyCompleted ? 'success' : 'skipped' + let consumeErrorMessage = '' + const consumedAt = consumeAlreadyCompleted ? flow.consume.consumedAt || now : null + const nextTaskStatus = 'completed' + const nextResultCode = consumeAlreadyCompleted + ? 'kuaishou_cloud_completed' + : 'kuaishou_cloud_completed_without_eticket_consume' + const nextResultMessage = consumeAlreadyCompleted + ? 'cloudtentacles 发货、退号并完成电子凭证核销' + : 'cloudtentacles 发货、退号并收口' const nextContext = { ...taskContext, @@ -639,8 +569,8 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment( consume: { ...flow.consume, status: consumeStatus, - shopId: shopId || flow.consume.shopId, - shopName, + shopId: flow.consume.shopId, + shopName: flow.consume.shopName, autoConsumeEnabled: flow.consume.autoConsumeEnabled === true, consumedAt, errorMessage: consumeErrorMessage, @@ -653,7 +583,7 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment( delivery_status: 'delivered', result_code: nextResultCode, result_message: nextResultMessage, - redeemed_at: consumeStatus === 'success' ? now : task.redeemed_at, + redeemed_at: now, last_error: consumeErrorMessage, context_json: JSON.stringify(nextContext), updated_at: now, @@ -669,18 +599,19 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment( now, ) + const consumeEventType = consumeAlreadyCompleted + ? 'kuaishou_cloud_consume_already_completed' + : 'kuaishou_cloud_consume_skipped' + await createTaskEvent( task.id, - consumeAlreadyCompleted - ? 'kuaishou_cloud_consume_already_completed' - : consumeStatus === 'success' - ? 'kuaishou_cloud_consumed' - : 'kuaishou_cloud_consume_failed', + consumeEventType, { ticketCodeMasked: maskCode(ticketCode), - shopId, - shopName, + shopId: flow.consume.shopId, + shopName: flow.consume.shopName, consumeStatus, + consumeMode: 'legacy_writeoff_disabled', errorMessage: consumeErrorMessage, }, now, @@ -961,7 +892,7 @@ async function getRequiredIndustryVoucherForTask(task: TaskRow): Promise { + return value && typeof value === 'object' && !Array.isArray(value) + ? value as Record + : {} +} + async function resolveIndustryVouchersForOrder(platformOrderId: string): Promise { const normalizedOid = String(platformOrderId || '').trim() if (!normalizedOid) { diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts index 683159e2..9b381f8a 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-context.ts @@ -11,8 +11,6 @@ import { buildClaimUrl } from './claim-service.js' import type { ClaimTokenRow, OrderItemRow, OrderRow, TaskRow } from '../../types/repository/rows.js' export const CLAIM_TERMINAL_STATUSES = new Set([TASK_STATUS.EXPIRED, TASK_STATUS.CLOSED]) -export const KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH = '/kuaishou-cloud-guide' - type JsonObject = Record type ClaimContext = { claimToken: ClaimTokenRow @@ -355,11 +353,7 @@ export function mapClaimKuaishouCloudFulfillment(task: TaskRow, order: OrderRow) flowType: 'kuaishou_cloud', shopId: String(order?.shop_id || '').trim(), shopName: String(order?.shop_name || '').trim(), - guideImages: [ - `${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/1.png`, - `${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/2.png`, - `${KUAISHOU_CLOUD_CLAIM_GUIDE_BASE_PATH}/3.png`, - ], + guideImages: [], ticket: { code: String(ticket.code || '').trim(), status: String(ticket.status || 'pending').trim() || 'pending', diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts index 3adb2a22..74f96edb 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.ts @@ -1,7 +1,3 @@ -import fs from 'node:fs' -import path from 'node:path' - -import { PROJECT_ROOT } from '../../config/runtime.js' import { createTaskEvent } from '../../repositories/task-event-repo.js' import { getTaskById, updateTask, updateTaskStatusIfCurrent } from '../../repositories/task-repo.js' import { createHttpError } from '../../utils/http.js' @@ -13,22 +9,10 @@ import { isKuaishouCloudRoleConfirmSettledStatus, normalizeTaskStatus, } from '../../domain/task-status.js' -import { - getKuaishouEticketSourceConfig, - listKuaishouEticketShopConfigs, - resolveKuaishouEticketShopConfig, - type KuaishouEticketShopConfig, -} from '../platforms/kuaishou-eticket/source-config-service.js' -import { - consumeKuaishouEticket, - queryKuaishouEticketConsumeDetail, -} from '../platforms/kuaishou-eticket/consume-service.js' -import { isKuaishouEticketMockTicketCode } from '../platforms/kuaishou-eticket/mock-ticket-service.js' import { dispatchKuaishouCloudFulfillmentTask, hasKuaishouCloudCustomerRole, hasKuaishouCloudDefaultRoleSnapshot, - maskCode, normalizeKuaishouCloudFlow, prepareKuaishouCloudFulfillmentTask, rebindKuaishouCloudTaskRole, @@ -39,215 +23,9 @@ import { buildClaimDetailPayload, getClaimContext } from './kuaishou-cloud-claim import { syncKuaishouCloudRoleInfo } from './kuaishou-cloud-sync-service.js' import type { TaskRow } from '../../types/repository/rows.js' -const KUAISHOU_CLOUD_GUIDE_DIR = path.resolve(PROJECT_ROOT, '../../tems/imgs') -const ALLOWED_GUIDE_FILES = new Set(['1.png', '2.png', '3.png']) - type JsonObject = Record type ClaimDetailPayload = ReturnType -export async function verifyKuaishouCloudClaimTicket(token: unknown, payload: JsonObject = {}) { - const context = await getClaimContext(token) - const now = nowIso() - - const executorKey = String(context.task.executor_key || '').trim() - - if (executorKey === 'kuaishou-industry') { - return verifyIndustryVoucherTicket(context, now) - } - - if (executorKey !== 'kuaishou_ct_assisted') { - throw createHttpError('当前领取链接不是 kuaishou-lewan 客户领取流程', { - statusCode: 409, - errorCode: 'claim_not_kuaishou_cloud', - }) - } - - const flow = normalizeKuaishouCloudFlow(parseTaskContext(context.task).kuaishouCloudFulfillment) - if (hasUsableIndustryVoucher(parseTaskContext(context.task))) { - return verifyIndustryVoucherTicket(context, now) - } - - const ticketCode = String(payload.ticketCode || payload.eTicketId || '').trim() - if (!ticketCode) { - throw createHttpError('请先粘贴快手小店核销码', { - statusCode: 400, - errorCode: 'claim_kuaishou_cloud_missing_ticket_code', - }) - } - - const persistedTicketCode = String(flow.ticket.code || '').trim() - if (flow.consume.status === 'success') { - if (!persistedTicketCode || persistedTicketCode === ticketCode) { - return getKuaishouCloudClaimDetail(token) - } - - throw createHttpError('当前领取链接已完成核销,不能更换核销码', { - statusCode: 409, - errorCode: 'claim_kuaishou_cloud_ticket_already_consumed', - }) - } - - const mockTicketCode = isKuaishouEticketMockTicketCode(ticketCode) - 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 - let shopCookie = '' - let consumeBaseUrl = '' - - if (mockTicketCode) { - detailResult = await queryKuaishouEticketConsumeDetail({ - eTicketId: ticketCode, - goodsTitle: context.orderItem.sku_name || context.orderItem.sku_code, - }) - } else { - const matched = await queryKuaishouEticketConsumeDetailWithShopFallback({ - ticketCode, - shopId, - shopName, - fallbackShopName: String(context.order.shop_name || '').trim(), - }) - - detailResult = matched.detailResult - shopId = matched.shopConfig.shopId || shopId - shopName = matched.shopConfig.kshopName || shopName - shopCookie = matched.shopConfig.cookie - consumeBaseUrl = getKuaishouEticketSourceConfig().baseUrl - } - - if (!detailResult.ok || detailResult.alreadyConsumed || !detailResult.detail) { - throw createHttpError(detailResult.errorMessage || '核销码校验失败,请确认是否复制完整', { - statusCode: 409, - errorCode: detailResult.alreadyConsumed - ? 'claim_kuaishou_cloud_ticket_already_consumed' - : 'claim_kuaishou_cloud_ticket_invalid', - }) - } - - const consumePayload = { - baseUrl: consumeBaseUrl, - cookie: shopCookie, - eTicketId: detailResult.eTicketId || ticketCode, - oid: String(detailResult.detail.oid || '').trim(), - formToken: String(detailResult.detail.formToken || '').trim(), - num: Number(detailResult.detail.leftCount || 0) || 1, - } - const consumeResult = await consumeKuaishouEticket(consumePayload) - if (!consumeResult.consumed) { - throw createHttpError(consumeResult.errorMessage || '核销失败,请确认核销码状态后重试', { - statusCode: 409, - errorCode: 'claim_kuaishou_cloud_consume_failed', - }) - } - - const taskContext = parseTaskContext(context.task) - const nextContext = { - ...taskContext, - kuaishouCloudFulfillment: { - ...flow, - ticket: { - ...flow.ticket, - code: detailResult.eTicketId || ticketCode, - status: 'verified', - capturedAt: flow.ticket.capturedAt || now, - capturedBy: flow.ticket.capturedBy || { - source: 'claim_page', - }, - verifiedAt: now, - oid: String(detailResult.detail.oid || '').trim(), - formToken: String(detailResult.detail.formToken || '').trim(), - leftCount: Number(detailResult.detail.leftCount || 0) || 0, - goodsTitle: String(detailResult.goods?.itemTitle || '').trim(), - }, - consume: { - ...flow.consume, - status: 'success', - shopId, - shopName, - consumedAt: now, - errorMessage: '', - }, - }, - } - - if (isKuaishouCloudMockContext(nextContext)) { - const mockFlow = buildMockVerifiedKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment, now) - await updateTask(context.task.id, { - claim_token: context.claimToken.token, - claim_expires_at: context.claimToken.expired_at, - context_json: JSON.stringify({ - ...nextContext, - kuaishouCloudFulfillment: mockFlow, - }), - claimed_at: context.task.claimed_at || now, - task_status: TASK_STATUS.WAITING_BINDING, - user_action_status: 'pending_claim', - last_error: '', - updated_at: now, - }) - } else { - const preparedFlow = normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment) - const taskWithConsumedTicket = (await updateTask(context.task.id, { - claim_token: context.claimToken.token, - claim_expires_at: context.claimToken.expired_at, - context_json: JSON.stringify(nextContext), - claimed_at: context.task.claimed_at || now, - task_status: TASK_STATUS.WAITING_BINDING, - user_action_status: 'pending_claim', - last_error: '', - updated_at: now, - })) || { - ...context.task, - context_json: JSON.stringify(nextContext), - claimed_at: context.task.claimed_at || now, - task_status: TASK_STATUS.WAITING_BINDING, - user_action_status: 'pending_claim', - last_error: '', - updated_at: now, - } - - if (preparedFlow.binding.prepareStatus !== 'ready' || !preparedFlow.binding.bindUrl) { - await prepareKuaishouCloudFulfillmentTask(taskWithConsumedTicket, { - source: 'claim_page_ticket_verified', - actor: { source: 'claim_page' }, - }) - } - } - - await createTaskEvent( - context.task.id, - 'kuaishou_cloud_ticket_verified', - { - ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode), - shopId, - shopName, - goodsTitle: String(detailResult.goods?.itemTitle || '').trim(), - mock: mockTicketCode, - consumedAt: now, - consumeAlreadyCompleted: consumeResult.alreadyConsumed === true, - }, - now, - ) - - await createTaskEvent( - context.task.id, - 'kuaishou_cloud_consumed', - { - source: 'claim_page_ticket_submit', - ticketCodeMasked: maskCode(detailResult.eTicketId || ticketCode), - shopId, - shopName, - consumeStatus: 'success', - consumedAt: now, - mock: mockTicketCode, - }, - now, - ) - - return getKuaishouCloudClaimDetail(token) -} - async function verifyIndustryVoucherTicket( context: Awaited>, now: string, @@ -363,26 +141,6 @@ async function verifyIndustryVoucherTicket( return getKuaishouCloudClaimDetail(context.claimToken.token) } -export async function getKuaishouCloudClaimGuideAssetPath(filename: unknown) { - const normalized = String(filename || '').trim() - if (!ALLOWED_GUIDE_FILES.has(normalized)) { - throw createHttpError('指引图片不存在', { - statusCode: 404, - errorCode: 'claim_kuaishou_cloud_asset_not_found', - }) - } - - const filePath = path.resolve(KUAISHOU_CLOUD_GUIDE_DIR, normalized) - if (!filePath.startsWith(KUAISHOU_CLOUD_GUIDE_DIR) || !fs.existsSync(filePath)) { - throw createHttpError('指引图片不存在', { - statusCode: 404, - errorCode: 'claim_kuaishou_cloud_asset_not_found', - }) - } - - return filePath -} - export async function getKuaishouCloudClaimDetail(token: unknown): Promise { const context = await getClaimContext(token) let task = context.task @@ -500,134 +258,6 @@ function normalizeIndustryVoucherContext(value: unknown): JsonObject { } } -async function queryKuaishouEticketConsumeDetailWithShopFallback({ - ticketCode, - shopId, - shopName, - fallbackShopName = '', -}: { - ticketCode: string - shopId: string - shopName: string - fallbackShopName?: string -}) { - const eticketSource = getKuaishouEticketSourceConfig() - const shopConfigs = resolveKuaishouEticketDetailCandidateShops({ - shopId, - shopName, - fallbackShopName, - }) - - if (shopConfigs.length === 0) { - throw createHttpError('还没有配置可用的快手小店 Cookie,请联系客服处理', { - statusCode: 409, - errorCode: 'claim_kuaishou_cloud_shop_cookie_missing', - }) - } - - let lastResult: JsonObject | null = null - let lastError: unknown = null - - for (const shopConfig of shopConfigs) { - try { - const detailResult = await queryKuaishouEticketConsumeDetail({ - baseUrl: eticketSource.baseUrl, - cookie: shopConfig.cookie, - eTicketId: ticketCode, - }) - - lastResult = detailResult - if (detailResult.ok || detailResult.alreadyConsumed) { - return { - shopConfig, - detailResult, - } - } - } catch (error) { - lastError = error - } - } - - if (lastResult) { - const fallbackShopConfig = shopConfigs[0] - if (!fallbackShopConfig) { - throw createHttpError('还没有配置可用的快手小店 Cookie,请联系客服处理', { - statusCode: 409, - errorCode: 'claim_kuaishou_cloud_shop_cookie_missing', - }) - } - - return { - shopConfig: fallbackShopConfig, - detailResult: lastResult, - } - } - - throw createHttpError( - lastError instanceof Error ? lastError.message : '核销码校验失败,请确认是否复制完整', - { - statusCode: 409, - errorCode: 'claim_kuaishou_cloud_ticket_invalid', - }, - ) -} - -function resolveKuaishouEticketDetailCandidateShops({ - shopId, - shopName, - fallbackShopName = '', -}: { - shopId: string - shopName: string - fallbackShopName?: string -}) { - const candidates: KuaishouEticketShopConfig[] = [] - const configuredShop = resolveKuaishouEticketShopConfig({ - shopId, - shopName: shopName || fallbackShopName, - }) - - if (isUsableKuaishouEticketShopConfig(configuredShop)) { - candidates.push(configuredShop) - } - - for (const shopConfig of listKuaishouEticketShopConfigs()) { - if (!isUsableKuaishouEticketShopConfig(shopConfig)) { - continue - } - - if (candidates.some((item) => isSameKuaishouEticketShopConfig(item, shopConfig))) { - continue - } - - candidates.push(shopConfig) - } - - return candidates -} - -function isUsableKuaishouEticketShopConfig( - shopConfig: KuaishouEticketShopConfig | null | undefined, -): shopConfig is KuaishouEticketShopConfig { - return Boolean( - shopConfig && shopConfig.enabled !== false && String(shopConfig.cookie || '').trim(), - ) -} - -function isSameKuaishouEticketShopConfig( - left: KuaishouEticketShopConfig, - right: KuaishouEticketShopConfig, -) { - const leftShopId = String(left.shopId || '').trim() - const rightShopId = String(right.shopId || '').trim() - - if (leftShopId && rightShopId) { - return leftShopId === rightShopId - } - - return String(left.kshopName || '').trim() === String(right.kshopName || '').trim() -} - export async function confirmKuaishouCloudClaimRole(token: unknown) { const context = await getClaimContext(token) const now = nowIso() @@ -656,7 +286,7 @@ export async function confirmKuaishouCloudClaimRole(token: unknown) { const flow = normalizeKuaishouCloudFlow(parseTaskContext(refreshed.task).kuaishouCloudFulfillment) if (flow.ticket.status !== 'verified') { - throw createHttpError('请先验证核销码', { + throw createHttpError('请先完成电子凭证确认', { statusCode: 409, errorCode: 'claim_kuaishou_cloud_ticket_not_verified', }) diff --git a/apps/backend/src/services/fulfillment/executors/kuaishou-cloud-executor.ts b/apps/backend/src/services/fulfillment/executors/kuaishou-cloud-executor.ts index 55f16afb..9551e4ae 100644 --- a/apps/backend/src/services/fulfillment/executors/kuaishou-cloud-executor.ts +++ b/apps/backend/src/services/fulfillment/executors/kuaishou-cloud-executor.ts @@ -45,7 +45,7 @@ async function preparePaidTask( claim_token: claimToken.token, claim_expires_at: claimToken.expired_at, user_action_status: 'pending_claim', - last_error: task.last_error || '领取链接已生成,等待客户提交核销码', + last_error: task.last_error || '领取链接已生成,等待电子凭证流程推进', updated_at: now, }) } diff --git a/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts b/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts index da7f975a..02a0d56f 100644 --- a/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts +++ b/apps/backend/src/services/fulfillment/kuaishou-cloud/task-finalization.ts @@ -1,13 +1,9 @@ -import { getOrderById } from "../../../repositories/order-repo.js"; import { createTaskEvent } from "../../../repositories/task-event-repo.js"; import { updateTask } from "../../../repositories/task-repo.js"; import { createHttpError } from "../../../utils/http.js"; import { nowIso } from "../../../utils/time.js"; import { TASK_STATUS, normalizeTaskStatus, type TaskStatus } from "../../../domain/task-status.js"; -import { - notifyKuaishouCloudAssetNotEnough, - notifyKuaishouCloudConsumeFailed, -} from "../../notification/domain-notifications.js"; +import { notifyKuaishouCloudAssetNotEnough } from "../../notification/domain-notifications.js"; import { buyCloudtentaclesSku, getCloudtentaclesAsset, @@ -16,13 +12,7 @@ import { } from "../../platforms/cloudtentacles/catalog-service.js"; import { getCloudtentaclesKnapsack } from "../../platforms/cloudtentacles/knapsack-service.js"; import { backCloudtentaclesVirtualNumber } from "../../platforms/cloudtentacles/virtual-number-service.js"; -import { consumeKuaishouEticket } from "../../platforms/kuaishou-eticket/consume-service.js"; -import { isKuaishouEticketMockTicketCode } from "../../platforms/kuaishou-eticket/mock-ticket-service.js"; import { consumeKuaishouIndustryVouchersForTask } from "../../platforms/kuaishou-industry/voucher-service.js"; -import { - getKuaishouEticketSourceConfig, - resolveKuaishouEticketShopConfig, -} from "../../platforms/kuaishou-eticket/source-config-service.js"; import { isKuaishouCloudTask, isIndustryEVoucherTask, @@ -95,10 +85,19 @@ export async function dispatchKuaishouCloudFulfillmentTask( ); } - const ticketCode = String(options.ticketCode || "").trim(); const persistedTicketCode = String(flow.ticket.code || "").trim(); - if (!persistedTicketCode && !ticketCode) { - throw createHttpError("客户还没有提交有效核销码,暂时不能继续兑换", { + const voucherContext = isPlainObject(taskContext.kuaishouIndustryVoucher) + ? taskContext.kuaishouIndustryVoucher + : {}; + const industryVoucherCode = String( + voucherContext.voucherCode || voucherContext.eticketId || "" + ).trim(); + const hasIndustryVoucherForDispatch = + Boolean(industryVoucherCode) || isIndustryEVoucherTask(task); + const resolvedTicketCode = persistedTicketCode || industryVoucherCode; + + if (!resolvedTicketCode && !hasIndustryVoucherForDispatch) { + throw createHttpError("旧快手小店核销流程已停用,请改用行业电子凭证处理", { statusCode: 409, errorCode: "kuaishou_cloud_missing_ticket_code", }); @@ -173,9 +172,11 @@ export async function dispatchKuaishouCloudFulfillmentTask( ...syncedFlow, ticket: { ...syncedFlow.ticket, - code: ticketCode || persistedTicketCode, - capturedAt: ticketCode ? now : syncedFlow.ticket.capturedAt, - capturedBy: ticketCode && actor ? actor : syncedFlow.ticket.capturedBy, + code: resolvedTicketCode, + capturedAt: resolvedTicketCode + ? syncedFlow.ticket.capturedAt || now + : syncedFlow.ticket.capturedAt, + capturedBy: syncedFlow.ticket.capturedBy, }, dispatch: { ...syncedFlow.dispatch, @@ -222,7 +223,7 @@ export async function dispatchKuaishouCloudFulfillmentTask( "kuaishou_cloud_dispatched", { source: String(options.source || "system").trim() || "system", - ticketCodeMasked: maskCode(ticketCode || persistedTicketCode), + ticketCodeMasked: maskCode(resolvedTicketCode), skuId: syncedFlow.binding.skuId, vnId: syncedFlow.binding.vnId, vnPhoneMasked: maskPhone(syncedFlow.binding.vnPhone), @@ -785,34 +786,25 @@ export async function returnKuaishouCloudFulfillmentTask( id: flow.binding.vnId, }); - const order = await getOrderById(task.order_id); const ticketCode = String(flow.ticket.code || "").trim(); - const shopId = String(flow.consume.shopId || order?.shop_id || "").trim(); - const shopName = String( - flow.consume.shopName || order?.shop_name || "" - ).trim(); - const mockTicketCode = isKuaishouEticketMockTicketCode(ticketCode); - const eticketSource = getKuaishouEticketSourceConfig(); - const shopConfig = resolveKuaishouEticketShopConfig({ - shopId, - shopName, - }); - let consumeStatus = "pending"; let consumeErrorMessage = ""; let consumedAt = null; let nextTaskStatus: TaskStatus = TASK_STATUS.COMPLETED; let nextResultCode = "kuaishou_cloud_completed"; - let nextResultMessage = "cloudtentacles 发货、退号并完成快手核销"; const consumeAlreadyCompleted = flow.consume.status === "success"; const hasIndustryVoucher = hasKuaishouIndustryVoucherContext(taskContext); const isIndustryTask = isIndustryEVoucherTask(task); + const shouldConsumeIndustryVoucher = hasIndustryVoucher || isIndustryTask; + let nextResultMessage = shouldConsumeIndustryVoucher + ? "cloudtentacles 发货、退号并完成电子凭证核销" + : "cloudtentacles 发货、退号并收口"; let industryVoucherContextPatch: JsonObject | null = null; if (consumeAlreadyCompleted) { consumeStatus = "success"; consumedAt = flow.consume.consumedAt || now; - } else if (hasIndustryVoucher || isIndustryTask) { + } else if (shouldConsumeIndustryVoucher) { const industryResult = hasIndustryVoucher ? await consumeKuaishouIndustryVouchersForTask(task, { source: String(options.source || "system_auto_finalize").trim() || "system_auto_finalize", @@ -844,59 +836,20 @@ export async function returnKuaishouCloudFulfillmentTask( consumeErrorMessage = industryResult.failed[0]?.errorMessage || "电子凭证核销回调失败,请人工处理"; } - } else if (!order) { - consumeStatus = "failed"; - consumeErrorMessage = "任务关联订单不存在,无法执行快手核销"; - } else if (!ticketCode) { - consumeStatus = "failed"; - consumeErrorMessage = "客户未提交有效核销码,无法执行快手核销"; - } else if (mockTicketCode) { - const consumeResult = await consumeKuaishouEticket({ - eTicketId: ticketCode, - oid: String(flow.ticket.oid || "").trim(), - formToken: String(flow.ticket.formToken || "").trim(), - }); - consumeStatus = consumeResult.consumed ? "success" : "failed"; - consumedAt = consumeResult.consumed ? now : null; - consumeErrorMessage = String(consumeResult.errorMessage || "").trim(); - } else if ( - !shopConfig || - shopConfig.enabled === false || - !String(shopConfig.cookie || "").trim() - ) { - consumeStatus = "failed"; - consumeErrorMessage = "订单对应快手小店缺少可用 Cookie,无法执行快手核销"; } else { - try { - const consumeResult = await consumeKuaishouEticket({ - baseUrl: eticketSource.baseUrl, - cookie: shopConfig.cookie, - eTicketId: ticketCode, - oid: String(flow.ticket.oid || "").trim(), - formToken: String(flow.ticket.formToken || "").trim(), - }); - - if (consumeResult.consumed) { - consumeStatus = "success"; - consumedAt = now; - } else { - consumeStatus = "failed"; - consumeErrorMessage = String( - consumeResult.errorMessage || "快手核销失败" - ).trim(); - } - } catch (error) { - consumeStatus = "failed"; - consumeErrorMessage = - error instanceof Error ? error.message : "快手核销失败"; - } + consumeStatus = "skipped"; + consumeErrorMessage = ""; } if (consumeStatus !== "success") { - nextTaskStatus = TASK_STATUS.MANUAL_REVIEW; - nextResultCode = "kuaishou_cloud_consume_failed"; - nextResultMessage = - consumeErrorMessage || "号码已退还,但快手核销未完成,请人工处理"; + if (shouldConsumeIndustryVoucher) { + nextTaskStatus = TASK_STATUS.MANUAL_REVIEW; + nextResultCode = "kuaishou_cloud_consume_failed"; + nextResultMessage = + consumeErrorMessage || "号码已退还,但电子凭证核销未完成,请人工处理"; + } else { + nextResultCode = "kuaishou_cloud_completed_without_eticket_consume"; + } } const nextContext = { @@ -922,8 +875,8 @@ export async function returnKuaishouCloudFulfillmentTask( consume: { ...flow.consume, status: consumeStatus, - shopId: shopId || flow.consume.shopId, - shopName, + shopId: flow.consume.shopId, + shopName: flow.consume.shopName, autoConsumeEnabled: flow.consume.autoConsumeEnabled === true, consumedAt, errorMessage: consumeErrorMessage, @@ -936,7 +889,7 @@ export async function returnKuaishouCloudFulfillmentTask( delivery_status: "delivered", result_code: nextResultCode, result_message: nextResultMessage, - redeemed_at: consumeStatus === "success" ? now : task.redeemed_at, + redeemed_at: consumeStatus === "failed" ? task.redeemed_at : now, last_error: consumeErrorMessage, context_json: JSON.stringify(nextContext), updated_at: now, @@ -954,36 +907,30 @@ export async function returnKuaishouCloudFulfillmentTask( now ); + const consumeEventType = consumeAlreadyCompleted + ? "kuaishou_cloud_consume_already_completed" + : consumeStatus === "success" + ? "kuaishou_cloud_consumed" + : consumeStatus === "skipped" + ? "kuaishou_cloud_consume_skipped" + : "kuaishou_cloud_consume_failed"; + await createTaskEvent( task.id, - consumeAlreadyCompleted - ? "kuaishou_cloud_consume_already_completed" - : consumeStatus === "success" - ? "kuaishou_cloud_consumed" - : "kuaishou_cloud_consume_failed", + consumeEventType, { source: String(options.source || "system").trim() || "system", ticketCodeMasked: maskCode(ticketCode), - shopId, - shopName, + shopId: flow.consume.shopId, + shopName: flow.consume.shopName, consumeStatus, + consumeMode: shouldConsumeIndustryVoucher ? "industry_voucher" : "legacy_writeoff_disabled", errorMessage: consumeErrorMessage, actor, }, now ); - if (consumeStatus !== "success") { - await notifyKuaishouCloudConsumeFailed({ - task: updatedTask, - order, - ticketCodeMasked: maskCode(ticketCode), - shopId, - shopName, - errorMessage: consumeErrorMessage, - }); - } - return { task: updatedTask, flow: normalizeKuaishouCloudFlow( diff --git a/apps/backend/src/services/notification/domain-notifications.ts b/apps/backend/src/services/notification/domain-notifications.ts index 069a52db..bad11428 100644 --- a/apps/backend/src/services/notification/domain-notifications.ts +++ b/apps/backend/src/services/notification/domain-notifications.ts @@ -48,14 +48,6 @@ type TaskNotificationPayload = { source?: unknown errorMessage?: unknown } -type KuaishouCloudConsumeFailedPayload = { - task?: unknown - order?: unknown - ticketCodeMasked?: unknown - shopId?: unknown - shopName?: unknown - errorMessage?: unknown -} type ClaimRedeemNeedsAttentionPayload = { task?: unknown order?: unknown @@ -216,31 +208,6 @@ export function notifyKuaishouCloudBindUrlRefreshFailed({ }) } -export function notifyKuaishouCloudConsumeFailed({ - task = {}, - order = {}, - ticketCodeMasked = '', - shopId = '', - shopName = '', - errorMessage = '', -}: KuaishouCloudConsumeFailedPayload = {}) { - const taskRecord = toRecord(task) - const orderRecord = toRecord(order) - - return notifyInternalSafely({ - title: '快手核销失败,需人工处理', - body: [ - formatTaskLine(taskRecord), - `订单:${String(orderRecord.platform_order_id || taskRecord.platform_order_id || '').trim() || '-'}`, - `券码:${String(ticketCodeMasked || '').trim() || '-'}`, - `店铺:${String(shopName || shopId || '').trim() || '-'}`, - `原因:${String(errorMessage || taskRecord.last_error || '').trim() || '-'}`, - ].join('\n'), - category: 'kuaishou_cloud_consume_failed', - cooldownKey: ['kuaishou_cloud_consume_failed', taskRecord.id || ''].join(':'), - }) -} - export function notifyTaskAutoManualReview({ task = {}, reason = '', diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/consume-service.test.ts b/apps/backend/src/services/platforms/kuaishou-eticket/consume-service.test.ts deleted file mode 100644 index 8fb34abe..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/consume-service.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' - -import { - consumeKuaishouEticket, - mapKuaishouEticketDetailResult, - normalizeKuaishouEticketApiResult, - queryKuaishouEticketConsumeDetail, - resolveKuaishouEticketConsumePayload, -} from './consume-service.js' - -test('normalizeKuaishouEticketApiResult recognizes already consumed response', () => { - const result = normalizeKuaishouEticketApiResult({ - result: 21, - error_msg: '券码已核销无法再次核销,请核实后重试', - requestId: 'abc', - }) - - assert.equal(result.ok, false) - assert.equal(result.result, 21) - assert.equal(result.errorMessage, '券码已核销无法再次核销,请核实后重试') - assert.equal(result.requestId, 'abc') -}) - -test('mapKuaishouEticketDetailResult maps detail payload fields', () => { - const result = mapKuaishouEticketDetailResult({ - result: 1, - error_msg: '成功', - data: { - eTicket: { - eTicketId: 'F2E886B6CBF8E5F5', - oid: '2612200246413561', - formToken: '1777716306154', - leftCount: 1, - totalCount: 1, - status: 'CONSUME_ORDER_CREATED', - }, - goods: { - itemTitle: '测试链接五排', - skuDesc: '测试1', - }, - }, - }, { - baseUrl: 'https://s.kwaixiaodian.com', - eTicketId: 'F2E886B6CBF8E5F5', - }) - - assert.equal(result.ok, true) - assert.equal(result.detail?.oid, '2612200246413561') - assert.equal(result.detail?.formToken, '1777716306154') - assert.equal(result.detail?.leftCount, 1) - assert.equal(result.goods?.itemTitle, '测试链接五排') -}) - -test('resolveKuaishouEticketConsumePayload falls back to detail result', () => { - const payload = resolveKuaishouEticketConsumePayload({ - eTicketId: 'F2E886B6CBF8E5F5', - }, { - detail: { - eTicketId: 'F2E886B6CBF8E5F5', - oid: '2612200246413561', - formToken: '1777716306154', - leftCount: 2, - }, - }) - - assert.deepEqual(payload, { - eTicketId: 'F2E886B6CBF8E5F5', - num: 2, - storeId: '0', - oid: '2612200246413561', - formToken: '1777716306154', - }) -}) - -test('queryKuaishouEticketConsumeDetail accepts mock ticket code outside production', async () => { - const originalNodeEnv = process.env.NODE_ENV - process.env.NODE_ENV = 'development' - - try { - const result = await queryKuaishouEticketConsumeDetail({ - eTicketId: 'MOCK-CLAIM-001', - goodsTitle: '套装-浪漫天命', - }) - - assert.equal(result.ok, true) - assert.equal(result.eTicketId, 'MOCK-CLAIM-001') - assert.equal(result.detail?.leftCount, 1) - assert.equal(result.goods?.itemTitle, '套装-浪漫天命') - } finally { - if (originalNodeEnv === undefined) { - delete process.env.NODE_ENV - } else { - process.env.NODE_ENV = originalNodeEnv - } - } -}) - -test('consumeKuaishouEticket consumes mock ticket code outside production', async () => { - const originalNodeEnv = process.env.NODE_ENV - process.env.NODE_ENV = 'development' - - try { - const result = await consumeKuaishouEticket({ - eTicketId: 'MOCK-CLAIM-002', - oid: 'MOCK-OID-002', - formToken: 'MOCK-FORM-002', - }) - - assert.equal(result.consumed, true) - assert.equal(result.eTicketId, 'MOCK-CLAIM-002') - assert.equal(result.oid, 'MOCK-OID-002') - assert.equal(result.formToken, 'MOCK-FORM-002') - } finally { - if (originalNodeEnv === undefined) { - delete process.env.NODE_ENV - } else { - process.env.NODE_ENV = originalNodeEnv - } - } -}) diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/consume-service.ts b/apps/backend/src/services/platforms/kuaishou-eticket/consume-service.ts deleted file mode 100644 index 6102f9ad..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/consume-service.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { createHttpError } from '../../../utils/http.js' -import { kuaishouEticketRequest } from './http-client.js' -import { resolveKuaishouEticketConfig } from './helpers.js' -import { - buildMockKuaishouEticketConsumeResult, - buildMockKuaishouEticketDetailResult, - isKuaishouEticketMockTicketCode, -} from './mock-ticket-service.js' - -type JsonObject = Record - -type KuaishouEticketDetailPayload = { - eTicketId: string - oid: string - formToken: string - leftCount: number - [key: string]: unknown -} - -type KuaishouEticketDetailResult = { - detail: KuaishouEticketDetailPayload | null - [key: string]: any -} - -export async function queryKuaishouEticketConsumeDetail(payload: JsonObject = {}) { - if (isKuaishouEticketMockTicketCode(payload.eTicketId)) { - return buildMockKuaishouEticketDetailResult({ - baseUrl: String(payload.baseUrl || '').trim(), - eTicketId: String(payload.eTicketId || '').trim(), - goodsTitle: String(payload.goodsTitle || '').trim(), - }) - } - - const context = resolveKuaishouEticketActionContext(payload) - const response = await kuaishouEticketRequest(context.detailPath, { - baseUrl: context.baseUrl, - cookie: context.cookie, - timeoutMs: context.timeoutMs, - body: { - eTicketId: context.eTicketId, - }, - }) - - return mapKuaishouEticketDetailResult(response.payload, { - baseUrl: context.baseUrl, - eTicketId: context.eTicketId, - }) -} - -export async function consumeKuaishouEticket(payload: JsonObject = {}) { - if (isKuaishouEticketMockTicketCode(payload.eTicketId)) { - return buildMockKuaishouEticketConsumeResult({ - baseUrl: String(payload.baseUrl || '').trim(), - eTicketId: String(payload.eTicketId || '').trim(), - oid: String(payload.oid || '').trim(), - formToken: String(payload.formToken || '').trim(), - num: payload.num, - storeId: String(payload.storeId || '').trim(), - }) - } - - const context = resolveKuaishouEticketActionContext(payload) - const detailResult = shouldResolveConsumeDetail(payload) - ? await queryKuaishouEticketConsumeDetail({ - baseUrl: context.baseUrl, - cookie: context.cookie, - eTicketId: context.eTicketId, - timeoutMs: context.timeoutMs, - }) - : null - const consumePayload = resolveKuaishouEticketConsumePayload(payload, detailResult) - - const response = await kuaishouEticketRequest(context.consumePath, { - baseUrl: context.baseUrl, - cookie: context.cookie, - timeoutMs: context.timeoutMs, - body: consumePayload, - }) - - return mapKuaishouEticketConsumeResult(response.payload, { - baseUrl: context.baseUrl, - eTicketId: consumePayload.eTicketId, - oid: consumePayload.oid, - formToken: consumePayload.formToken, - num: consumePayload.num, - storeId: consumePayload.storeId, - detailResult, - }) -} - -export function mapKuaishouEticketDetailResult(payload: JsonObject = {}, context: JsonObject = {}) { - const normalized = normalizeKuaishouEticketApiResult(payload) - const detail = mapKuaishouEticketDetailPayload(payload?.data?.eTicket) - const goods = mapKuaishouEticketGoodsPayload(payload?.data?.goods) - - return { - baseUrl: String(context.baseUrl || '').trim(), - eTicketId: String(context.eTicketId || detail?.eTicketId || '').trim(), - ok: normalized.ok, - alreadyConsumed: normalized.result === 21, - result: normalized.result, - errorMessage: normalized.errorMessage, - requestId: normalized.requestId, - serverTimestamp: normalized.serverTimestamp, - detail, - goods, - raw: payload && typeof payload === 'object' ? payload : {}, - } -} - -export function mapKuaishouEticketConsumeResult(payload: JsonObject = {}, context: JsonObject = {}) { - const normalized = normalizeKuaishouEticketApiResult(payload) - - return { - baseUrl: String(context.baseUrl || '').trim(), - eTicketId: String(context.eTicketId || '').trim(), - oid: String(context.oid || '').trim(), - formToken: String(context.formToken || '').trim(), - num: normalizePositiveInteger(context.num, 1), - storeId: String(context.storeId || '0').trim() || '0', - ok: normalized.ok, - consumed: normalized.result === 1 || normalized.result === 21, - alreadyConsumed: normalized.result === 21, - result: normalized.result, - errorMessage: normalized.errorMessage, - requestId: normalized.requestId, - serverTimestamp: normalized.serverTimestamp, - detail: context.detailResult?.detail || null, - raw: payload && typeof payload === 'object' ? payload : {}, - } -} - -export function resolveKuaishouEticketConsumePayload( - payload: JsonObject = {}, - detailResult: KuaishouEticketDetailResult | null = null, -) { - const detail = detailResult?.detail || null - const eTicketId = pickFirstNonEmpty([payload.eTicketId, detail?.eTicketId]) - const oid = pickFirstNonEmpty([payload.oid, detail?.oid]) - const formToken = pickFirstNonEmpty([payload.formToken, detail?.formToken]) - const storeId = pickFirstNonEmpty([payload.storeId, '0']) - const num = normalizePositiveInteger(payload.num, normalizePositiveInteger(detail?.leftCount, 1)) - - if (!eTicketId) { - throw createHttpError('请先填写 eTicketId', { - statusCode: 400, - errorCode: 'kuaishou_eticket_missing_eticket_id', - }) - } - - if (!oid) { - throw createHttpError('缺少核销所需 oid,请先查询核销信息', { - statusCode: 400, - errorCode: 'kuaishou_eticket_missing_oid', - }) - } - - if (!formToken) { - throw createHttpError('缺少核销所需 formToken,请先查询核销信息', { - statusCode: 400, - errorCode: 'kuaishou_eticket_missing_form_token', - }) - } - - return { - eTicketId, - num, - storeId, - oid, - formToken, - } -} - -export function normalizeKuaishouEticketApiResult(payload: JsonObject = {}) { - const result = Number(payload?.result || 0) - - return { - ok: result === 1, - result, - errorMessage: String(payload?.error_msg || '').trim(), - requestId: String(payload?.requestId || '').trim(), - serverTimestamp: String(payload?.serverTimestamp || '').trim(), - } -} - -export function mapKuaishouEticketDetailPayload(value: unknown): KuaishouEticketDetailPayload | null { - if (!isPlainObject(value)) { - return null - } - - return { - uid: String(value.uid || '').trim(), - fulfillDetailId: String(value.fulfillDetailId || '').trim(), - sellerId: String(value.sellerId || '').trim(), - formToken: String(value.formToken || '').trim(), - validEndTime: String(value.validEndTime || '').trim(), - validStartTime: String(value.validStartTime || '').trim(), - leftReverseCount: normalizeInteger(value.leftReverseCount, 0), - eTicketId: String(value.eTicketId || '').trim(), - oid: String(value.oid || '').trim(), - totalCount: normalizeInteger(value.totalCount, 0), - leftCount: normalizeInteger(value.leftCount, 0), - status: String(value.status || '').trim(), - } -} - -export function mapKuaishouEticketGoodsPayload(value: unknown) { - if (!isPlainObject(value)) { - return null - } - - return { - itemId: String(value.itemId || '').trim(), - itemPicUrl: String(value.itemPicUrl || '').trim(), - itemTitle: String(value.itemTitle || '').trim(), - price: String(value.price || '').trim(), - skuDesc: String(value.skuDesc || '').trim(), - skuId: String(value.skuId || '').trim(), - } -} - -function resolveKuaishouEticketActionContext(payload: JsonObject = {}) { - const config = resolveKuaishouEticketConfig(payload) - const eTicketId = String(payload.eTicketId || '').trim() - - if (!config.cookie) { - throw createHttpError('请先配置快手小店 Cookie', { - statusCode: 400, - errorCode: 'kuaishou_eticket_missing_cookie', - }) - } - - if (!eTicketId) { - throw createHttpError('请先填写 eTicketId', { - statusCode: 400, - errorCode: 'kuaishou_eticket_missing_eticket_id', - }) - } - - return { - ...config, - eTicketId, - } -} - -function shouldResolveConsumeDetail(payload: JsonObject = {}): boolean { - return !String(payload.oid || '').trim() || !String(payload.formToken || '').trim() -} - -function pickFirstNonEmpty(values: unknown[]): string { - for (const value of values) { - const normalized = String(value || '').trim() - if (normalized) { - return normalized - } - } - - return '' -} - -function normalizePositiveInteger(value: unknown, fallback: number): number { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback -} - -function normalizeInteger(value: unknown, fallback: number): number { - const parsed = Number(value) - return Number.isInteger(parsed) ? parsed : fallback -} - -function isPlainObject(value: unknown): value is JsonObject { - return Object.prototype.toString.call(value) === '[object Object]' -} diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/helpers.ts b/apps/backend/src/services/platforms/kuaishou-eticket/helpers.ts deleted file mode 100644 index e966bcb3..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/helpers.ts +++ /dev/null @@ -1,106 +0,0 @@ -const DEFAULT_BASE_URL = 'https://s.kwaixiaodian.com' -const DEFAULT_REFERER_PATH = '/zone/industry/voucher/verify-list' -const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36' - -export type KuaishouEticketConfigInput = { - enabled?: boolean - baseUrl?: string - cookie?: string - timeoutMs?: number | string - infoPath?: string - detailPath?: string - consumePath?: string - refererPath?: string - userAgent?: string -} - -export type KuaishouEticketConfig = { - enabled: boolean - baseUrl: string - cookie: string - timeoutMs: number - infoPath: string - detailPath: string - consumePath: string - refererPath: string - userAgent: string -} - -type HeaderInput = { - baseUrl?: string - refererPath?: string - userAgent?: string - cookie?: string - contentType?: string - extra?: Record -} - -export function resolveKuaishouEticketConfig(overrides: KuaishouEticketConfigInput = {}): KuaishouEticketConfig { - return { - enabled: overrides.enabled !== false, - baseUrl: normalizeBaseUrl(overrides.baseUrl || DEFAULT_BASE_URL) || DEFAULT_BASE_URL, - cookie: String(overrides.cookie || '').trim(), - timeoutMs: normalizePositiveInteger(overrides.timeoutMs, 8000), - infoPath: normalizePath(overrides.infoPath, '/gateway/pc/workbench/service/stable/info'), - detailPath: normalizePath(overrides.detailPath, '/gateway/industry/eticket/consume/detail'), - consumePath: normalizePath(overrides.consumePath, '/gateway/industry/eticket/consume'), - refererPath: normalizePath(overrides.refererPath, DEFAULT_REFERER_PATH), - userAgent: String(overrides.userAgent || DEFAULT_USER_AGENT).trim() || DEFAULT_USER_AGENT, - } -} - -export function buildKuaishouEticketUrl(baseUrl: unknown, pathname: unknown): URL { - return new URL(normalizePath(pathname, '/'), normalizeBaseUrl(baseUrl) || DEFAULT_BASE_URL) -} - -export function buildKuaishouEticketHeaders({ - baseUrl = DEFAULT_BASE_URL, - refererPath = DEFAULT_REFERER_PATH, - userAgent = DEFAULT_USER_AGENT, - cookie = '', - contentType = 'application/json', - extra = {}, -}: HeaderInput = {}): Record { - return { - accept: 'application/json', - 'accept-language': 'zh-CN,zh;q=0.9', - ...(contentType ? { 'content-type': contentType } : {}), - kpf: 'PC_WEB', - kpn: 'KWAIXIAODIAN', - origin: normalizeBaseUrl(baseUrl) || DEFAULT_BASE_URL, - referer: buildKuaishouEticketUrl(baseUrl, refererPath).toString(), - 'user-agent': String(userAgent || DEFAULT_USER_AGENT).trim() || DEFAULT_USER_AGENT, - ...(String(cookie || '').trim() ? { cookie: String(cookie || '').trim() } : {}), - ...normalizeHeaderMap(extra), - } -} - -function normalizeBaseUrl(value: unknown): string { - return String(value || '').trim().replace(/\/+$/, '') -} - -function normalizePath(value: unknown, fallback: string): string { - const normalized = String(value || '').trim() - if (!normalized) { - return fallback - } - - return normalized.startsWith('/') ? normalized : `/${normalized}` -} - -function normalizePositiveInteger(value: unknown, fallback: number): number { - const parsed = Number(value) - return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback -} - -function normalizeHeaderMap(extra: unknown): Record { - if (!extra || typeof extra !== 'object') { - return {} - } - - return Object.fromEntries( - Object.entries(extra) - .map(([key, value]) => [String(key || '').trim().toLowerCase(), String(value || '').trim()]) - .filter(([key, value]) => key && value), - ) -} diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts b/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts deleted file mode 100644 index 8fe8fd2f..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/http-client.ts +++ /dev/null @@ -1,200 +0,0 @@ -import http from 'node:http' -import https from 'node:https' -import type { IncomingHttpHeaders } from 'node:http' - -import { parseJsonObject } from '../../../utils/json.js' -import { createHttpError } from '../../../utils/http.js' -import { logInfo } from '../../../utils/logger.js' -import { buildKuaishouEticketHeaders, buildKuaishouEticketUrl, resolveKuaishouEticketConfig } from './helpers.js' - -type JsonObject = Record - -type KuaishouEticketRequestOptions = JsonObject & { - method?: string - body?: unknown - headers?: Record - timeoutMs?: number | string - cookie?: string - contentType?: string | null -} - -type HeaderAdapter = { - get(name: string): string | null -} - -type NodeHttpResponse = { - ok: boolean - status: number - headers: HeaderAdapter - bodyText: string -} - -export async function kuaishouEticketRequest(pathname: string, options: KuaishouEticketRequestOptions = {}) { - const config = resolveKuaishouEticketConfig(options) - const url = buildKuaishouEticketUrl(config.baseUrl, pathname) - const timeoutMs = Number(options.timeoutMs || config.timeoutMs || 8000) - const controller = new AbortController() - const timer = setTimeout(() => controller.abort(), timeoutMs) - const method = String(options.method || 'POST').trim().toUpperCase() - const headers = buildKuaishouEticketHeaders({ - baseUrl: config.baseUrl, - refererPath: config.refererPath, - userAgent: config.userAgent, - cookie: options.cookie ?? config.cookie, - contentType: options.contentType === null ? '' : options.contentType || inferContentType(options.body), - ...(options.headers !== undefined ? { extra: options.headers } : {}), - }) - - try { - const body = normalizeRequestBody(options.body, headers['content-type']) - const response = await requestViaNodeHttp(url, { - method, - headers, - signal: controller.signal, - ...(body !== undefined ? { body } : {}), - }) - - const rawText = response.bodyText - const payload = parseJsonObject(rawText, { preserveLargeIntegers: true }) - - if (!response.ok) { - throw createHttpError(`快手小店核销请求失败,HTTP ${response.status}`, { - statusCode: 502, - errorCode: 'kuaishou_eticket_http_error', - }) - } - - logInfo('[kuaishou-eticket/http]', '请求完成', { - method, - pathname, - status: response.status, - result: Number(payload?.result || 0), - }) - - return { - url: url.toString(), - status: response.status, - headers: response.headers, - payload, - rawText, - } - } catch (error) { - if (error instanceof Error && error.name === 'AbortError') { - throw createHttpError(`快手小店核销请求超时(${timeoutMs}ms)`, { - statusCode: 504, - errorCode: 'kuaishou_eticket_request_timeout', - }) - } - throw error - } finally { - clearTimeout(timer) - } -} - -function inferContentType(body: unknown): string { - if (body == null) { - return '' - } - - if (typeof body === 'string') { - return 'application/json' - } - - return 'application/json' -} - -function normalizeRequestBody(body: unknown, contentType: unknown): string | undefined { - if (body == null) { - return undefined - } - - if (typeof body === 'string') { - return body - } - - if (String(contentType || '').includes('application/json')) { - return JSON.stringify(body) - } - - return String(body) -} - -async function requestViaNodeHttp( - url: URL, - { method, headers, body, signal }: { - method: string - headers: Record - body?: string - signal: AbortSignal - }, -): Promise { - const isHttps = url.protocol === 'https:' - const transport = isHttps ? https : http - - return new Promise((resolve, reject) => { - const request = transport.request(url, { - method, - headers, - rejectUnauthorized: false, - }, (response) => { - const chunks: Buffer[] = [] - - response.on('data', (chunk) => { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) - }) - - response.on('end', () => { - const bodyText = Buffer.concat(chunks).toString('utf8') - resolve({ - ok: Number(response.statusCode || 0) >= 200 && Number(response.statusCode || 0) < 300, - status: Number(response.statusCode || 0), - headers: createHeaderAdapter(response.headers), - bodyText, - }) - }) - }) - - request.on('error', reject) - - if (signal) { - if (signal.aborted) { - const error = new Error('Request aborted') - error.name = 'AbortError' - request.destroy(error) - } else { - signal.addEventListener('abort', () => { - const error = new Error('Request aborted') - error.name = 'AbortError' - request.destroy(error) - }, { once: true }) - } - } - - if (typeof body !== 'undefined') { - request.write(body) - } - - request.end() - }) -} - -function createHeaderAdapter(headers: IncomingHttpHeaders): HeaderAdapter { - const normalized = new Map() - - for (const [key, value] of Object.entries(headers || {})) { - if (Array.isArray(value)) { - normalized.set(String(key || '').toLowerCase(), value.join(', ')) - continue - } - - if (typeof value === 'string') { - normalized.set(String(key || '').toLowerCase(), value) - } - } - - return { - get(name: string) { - return normalized.get(String(name || '').toLowerCase()) || null - }, - } -} diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/info-service.test.ts b/apps/backend/src/services/platforms/kuaishou-eticket/info-service.test.ts deleted file mode 100644 index de13e0c2..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/info-service.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import test from 'node:test' -import assert from 'node:assert/strict' - -import { mapKuaishouEticketStableInfoResult } from './info-service.js' - -test('mapKuaishouEticketStableInfoResult extracts shop info from payload', () => { - const result = mapKuaishouEticketStableInfoResult({ - result: 1, - error_msg: '成功', - requestId: '777795601162094230', - data: { - userInfo: { - userId: 4269276762, - userName: '稚嫩游戏交易店', - userAvatar: 'https://example.com/avatar.jpg', - }, - bizStatus: { - settleStatus: 200, - }, - }, - }, { - baseUrl: 'https://s.kwaixiaodian.com', - cookie: 'sid=abc', - }) - - assert.equal(result.ok, true) - assert.equal(result.shop.shopId, '4269276762') - assert.equal(result.shop.kshopName, '稚嫩游戏交易店') - assert.equal(result.shop.userAvatar, 'https://example.com/avatar.jpg') - assert.equal(result.shop.settleStatus, 200) - assert.equal(result.shop.hasCookie, true) -}) diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/info-service.ts b/apps/backend/src/services/platforms/kuaishou-eticket/info-service.ts deleted file mode 100644 index dc58faf6..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/info-service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createHttpError } from '../../../utils/http.js' -import { kuaishouEticketRequest } from './http-client.js' -import { resolveKuaishouEticketConfig } from './helpers.js' - -type JsonObject = Record - -export async function queryKuaishouEticketStableInfo(payload: JsonObject = {}) { - const config = resolveKuaishouEticketConfig(payload) - const cookie = String(payload.cookie || config.cookie || '').trim() - - if (!cookie) { - throw createHttpError('请先提供店铺 Cookie', { - statusCode: 400, - errorCode: 'kuaishou_eticket_missing_cookie', - }) - } - - const response = await kuaishouEticketRequest(config.infoPath, { - method: 'GET', - baseUrl: config.baseUrl, - cookie, - timeoutMs: config.timeoutMs, - contentType: null, - }) - - return mapKuaishouEticketStableInfoResult(response.payload, { - baseUrl: config.baseUrl, - cookie, - }) -} - -export function mapKuaishouEticketStableInfoResult(payload: JsonObject = {}, context: JsonObject = {}) { - const result = Number(payload?.result || 0) - const userInfo = isPlainObject(payload?.data?.userInfo) ? payload.data.userInfo : {} - const bizStatus = isPlainObject(payload?.data?.bizStatus) ? payload.data.bizStatus : {} - - return { - baseUrl: String(context.baseUrl || '').trim(), - ok: result === 1, - result, - errorMessage: String(payload?.error_msg || '').trim(), - requestId: String(payload?.requestId || '').trim(), - shop: { - shopId: String(userInfo.userId || '').trim(), - kshopName: String(userInfo.userName || '').trim(), - userAvatar: String(userInfo.userAvatar || '').trim(), - settleStatus: normalizeInteger(bizStatus.settleStatus, 0), - hasCookie: Boolean(String(context.cookie || '').trim()), - }, - raw: payload && typeof payload === 'object' ? payload : {}, - } -} - -function normalizeInteger(value: unknown, fallback: number): number { - const parsed = Number(value) - return Number.isInteger(parsed) ? parsed : fallback -} - -function isPlainObject(value: unknown): value is JsonObject { - return Object.prototype.toString.call(value) === '[object Object]' -} diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/mock-ticket-service.ts b/apps/backend/src/services/platforms/kuaishou-eticket/mock-ticket-service.ts deleted file mode 100644 index 4db1dc6c..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/mock-ticket-service.ts +++ /dev/null @@ -1,123 +0,0 @@ -import process from 'node:process' - -import { isProductionLike } from '../../../config/runtime-validation.js' - -type RuntimeEnvironment = { - NODE_ENV?: string -} - -type MockTicketDetailOptions = { - eTicketId: string - baseUrl?: string - oid?: string - formToken?: string - leftCount?: number - goodsTitle?: string -} - -type MockTicketConsumeOptions = { - eTicketId: string - baseUrl?: string - oid?: string - formToken?: string - num?: number - storeId?: string -} - -export function isKuaishouEticketMockTicketCode( - ticketCode: unknown, - env: RuntimeEnvironment = process.env, -): boolean { - const normalized = String(ticketCode || '').trim().toUpperCase() - - if (!normalized || isProductionLike(env)) { - return false - } - - return normalized === 'MOCK' || normalized.startsWith('MOCK-') || normalized.startsWith('MOCK_') -} - -export function buildMockKuaishouEticketDetailResult(options: MockTicketDetailOptions) { - const eTicketId = String(options.eTicketId || '').trim() - const oid = String(options.oid || `MOCK-OID-${eTicketId}`).trim() - const formToken = String(options.formToken || `MOCK-FORM-${eTicketId}`).trim() - const leftCount = normalizePositiveInteger(options.leftCount, 1) - const goodsTitle = String(options.goodsTitle || 'MOCK 快手商品').trim() - - return { - baseUrl: String(options.baseUrl || 'mock://kuaishou-eticket').trim(), - eTicketId, - ok: true, - alreadyConsumed: false, - result: 1, - errorMessage: '', - requestId: `mock-${Date.now()}`, - serverTimestamp: new Date().toISOString(), - detail: { - uid: 'mock', - fulfillDetailId: `MOCK-FULFILL-${eTicketId}`, - sellerId: 'mock', - formToken, - validEndTime: '', - validStartTime: '', - leftReverseCount: 0, - eTicketId, - oid, - totalCount: leftCount, - leftCount, - status: 'MOCK', - }, - goods: { - itemId: 'mock', - itemPicUrl: '', - itemTitle: goodsTitle, - price: '', - skuDesc: goodsTitle, - skuId: 'mock', - }, - raw: { - mock: true, - }, - } -} - -export function buildMockKuaishouEticketConsumeResult(options: MockTicketConsumeOptions) { - const eTicketId = String(options.eTicketId || '').trim() - const oid = String(options.oid || `MOCK-OID-${eTicketId}`).trim() - const formToken = String(options.formToken || `MOCK-FORM-${eTicketId}`).trim() - const num = normalizePositiveInteger(options.num, 1) - - return { - baseUrl: String(options.baseUrl || 'mock://kuaishou-eticket').trim(), - eTicketId, - oid, - formToken, - num, - storeId: String(options.storeId || '0').trim() || '0', - ok: true, - consumed: true, - alreadyConsumed: false, - result: 1, - errorMessage: '', - requestId: `mock-${Date.now()}`, - serverTimestamp: new Date().toISOString(), - detail: { - eTicketId, - oid, - formToken, - leftCount: num, - }, - raw: { - mock: true, - }, - } -} - -function normalizePositiveInteger(value: unknown, fallback: number) { - const normalized = Number(value) - if (!Number.isFinite(normalized) || normalized <= 0) { - return fallback - } - - return Math.floor(normalized) -} diff --git a/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts b/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts deleted file mode 100644 index 716ce122..00000000 --- a/apps/backend/src/services/platforms/kuaishou-eticket/source-config-service.ts +++ /dev/null @@ -1,158 +0,0 @@ -import path from 'node:path' - -import { PROJECT_ROOT } from '../../../config/runtime.js' -import { readJsonFile, writeJsonFile } from '../../../utils/json-file-store.js' - -const KUAISHOU_ETICKET_SOURCE_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'kuaishou-eticket-source.json') -const DEFAULT_BASE_URL = 'https://s.kwaixiaodian.com' - -type JsonObject = Record - -export type KuaishouEticketShopConfig = { - shopId: string - kshopName: string - cookie: string - userAvatar: string - enabled: boolean -} - -export type KuaishouEticketSourceConfig = { - enabled: boolean - baseUrl: string - shops: KuaishouEticketShopConfig[] -} - -export function getKuaishouEticketSourceFilePath(): string { - return KUAISHOU_ETICKET_SOURCE_FILE_PATH -} - -export function getKuaishouEticketSourceConfig(): KuaishouEticketSourceConfig { - return loadKuaishouEticketSourceConfigFromFile() -} - -export function saveKuaishouEticketSourceConfig(rawValue: unknown): KuaishouEticketSourceConfig { - return writeJsonFile( - KUAISHOU_ETICKET_SOURCE_FILE_PATH, - rawValue, - normalizeKuaishouEticketSourceConfig, - ) -} - -export function listKuaishouEticketShopConfigs(source: KuaishouEticketSourceConfig = getKuaishouEticketSourceConfig()): KuaishouEticketShopConfig[] { - return Array.isArray(source?.shops) ? source.shops : [] -} - -export function findKuaishouEticketShopConfig(shopId: unknown, source: KuaishouEticketSourceConfig = getKuaishouEticketSourceConfig()): KuaishouEticketShopConfig | null { - const normalizedShopId = String(shopId || '').trim() - if (!normalizedShopId) { - return null - } - - return listKuaishouEticketShopConfigs(source).find((item) => String(item.shopId || '').trim() === normalizedShopId) || null -} - -export function findKuaishouEticketShopConfigByName(kshopName: unknown, source: KuaishouEticketSourceConfig = getKuaishouEticketSourceConfig()): KuaishouEticketShopConfig | null { - const normalizedName = String(kshopName || '').trim() - if (!normalizedName) { - return null - } - - return listKuaishouEticketShopConfigs(source).find((item) => String(item.kshopName || '').trim() === normalizedName) || null -} - -export function resolveKuaishouEticketShopConfig( - { shopId = '', kshopName = '', shopName = '' }: { shopId?: unknown, kshopName?: unknown, shopName?: unknown } = {}, - source: KuaishouEticketSourceConfig = getKuaishouEticketSourceConfig(), -): KuaishouEticketShopConfig | null { - const byId = findKuaishouEticketShopConfig(shopId, source) - if (byId) { - return byId - } - - const normalizedName = String(kshopName || shopName || '').trim() - if (!normalizedName) { - return null - } - - return findKuaishouEticketShopConfigByName(normalizedName, source) -} - -export function getFirstAvailableKuaishouEticketShop(source: KuaishouEticketSourceConfig = getKuaishouEticketSourceConfig()): KuaishouEticketShopConfig | null { - return listKuaishouEticketShopConfigs(source).find((item) => item.enabled !== false && String(item.cookie || '').trim()) || null -} - -function loadKuaishouEticketSourceConfigFromFile(): KuaishouEticketSourceConfig { - return readJsonFile( - KUAISHOU_ETICKET_SOURCE_FILE_PATH, - createDefaultKuaishouEticketSourceConfig, - normalizeKuaishouEticketSourceConfig, - ) -} - -function normalizeKuaishouEticketSourceConfig(rawValue: unknown): KuaishouEticketSourceConfig { - const source = isPlainObject(rawValue) ? rawValue : {} - const shops = Array.isArray(source.shops) - ? source.shops - : buildLegacySingleShopList(source) - - return { - enabled: typeof source.enabled === 'boolean' ? source.enabled : true, - baseUrl: String(source.baseUrl || DEFAULT_BASE_URL).trim() || DEFAULT_BASE_URL, - shops: shops - .map((item) => normalizeKuaishouEticketShopConfig(item)) - .filter((item): item is KuaishouEticketShopConfig => Boolean(item)), - } -} - -function normalizeKuaishouEticketShopConfig(rawValue: unknown): KuaishouEticketShopConfig | null { - if (!isPlainObject(rawValue)) { - return null - } - - const shopId = String(rawValue.shopId || rawValue.userId || '').trim() - const kshopName = String(rawValue.kshopName || rawValue.shopName || rawValue.userName || '').trim() - const cookie = String(rawValue.cookie || '').trim() - const userAvatar = String(rawValue.userAvatar || '').trim() - const enabled = typeof rawValue.enabled === 'boolean' ? rawValue.enabled : true - - if (!shopId && !kshopName && !cookie) { - return null - } - - return { - shopId, - kshopName, - cookie, - userAvatar, - enabled, - } -} - -function buildLegacySingleShopList(source: JsonObject): JsonObject[] { - const cookie = String(source.cookie || '').trim() - if (!cookie) { - return [] - } - - return [ - { - shopId: '', - kshopName: '', - cookie, - userAvatar: '', - enabled: source.enabled !== false, - }, - ] -} - -function createDefaultKuaishouEticketSourceConfig(): KuaishouEticketSourceConfig { - return { - enabled: true, - baseUrl: DEFAULT_BASE_URL, - shops: [], - } -} - -function isPlainObject(value: unknown): value is JsonObject { - return Object.prototype.toString.call(value) === '[object Object]' -} diff --git a/apps/backend/src/types/admin/route-inputs.ts b/apps/backend/src/types/admin/route-inputs.ts index 461c0ff9..4d05b860 100644 --- a/apps/backend/src/types/admin/route-inputs.ts +++ b/apps/backend/src/types/admin/route-inputs.ts @@ -24,15 +24,10 @@ import type { AdminCloudtentaclesTestLoginInput, AdminCloudtentaclesValidateSessionInput, AdminCloudtentaclesVirtualNumberInput, - AdminKuaishouEticketConsumeInput, - AdminKuaishouEticketDetailQueryInput, - AdminKuaishouEticketShopInfoInput, - AdminKuaishouEticketSourceConfigInput, AdminNotificationConfigInput, AdminNotificationTestInput, AdminScheduledJobsConfigInput, AdminTaskKuaishouIndustryConsumeInput, - AdminTaskKuaishouCloudDispatchInput, AdminTaskManualDispatchInput, } from './write-inputs.js' @@ -40,7 +35,6 @@ export type AdminOrderRouteQuery = AdminOrderListQueryInput export type AdminTaskRouteQuery = AdminTaskListQueryInput export type AdminRouteAdminSession = AdminViewerSessionInput -export type AdminKuaishouEticketSourceConfigRouteBody = AdminKuaishouEticketSourceConfigInput export type AdminKuaishouIndustrySourceConfigRouteBody = AdminKuaishouIndustrySourceConfigInput export type AdminKuaishouIndustryAuthorizationCodeRouteBody = AdminKuaishouIndustryAuthorizationCodeInput export type AdminKuaishouIndustryRefundListRouteBody = AdminKuaishouIndustryRefundListInput @@ -53,9 +47,6 @@ export type AdminKuaishouIndustryVoucherResendRouteBody = AdminKuaishouIndustryV export type AdminNotificationConfigRouteBody = AdminNotificationConfigInput export type AdminNotificationTestRouteBody = AdminNotificationTestInput export type AdminScheduledJobsConfigRouteBody = AdminScheduledJobsConfigInput -export type AdminKuaishouEticketDetailQueryRouteBody = AdminKuaishouEticketDetailQueryInput -export type AdminKuaishouEticketConsumeRouteBody = AdminKuaishouEticketConsumeInput -export type AdminKuaishouEticketShopInfoRouteBody = AdminKuaishouEticketShopInfoInput export type AdminCloudtentaclesSourceConfigRouteBody = AdminCloudtentaclesSourceConfigInput export type AdminCloudtentaclesOverrideRuleConfigRouteBody = AdminCloudtentaclesOverrideRuleConfigInput export type AdminCloudtentaclesSendSmsCodeRouteBody = AdminCloudtentaclesSendSmsCodeInput @@ -68,7 +59,6 @@ export type AdminCloudtentaclesSkuUseRouteBody = AdminCloudtentaclesSkuUseInput export type AdminCloudtentaclesVirtualNumberRouteBody = AdminCloudtentaclesVirtualNumberInput export type AdminCloudtentaclesFullFlowRouteBody = AdminCloudtentaclesFullFlowInput export type AdminTaskManualDispatchRouteBody = AdminTaskManualDispatchInput -export type AdminTaskKuaishouCloudDispatchRouteBody = AdminTaskKuaishouCloudDispatchInput export type AdminTaskKuaishouIndustryConsumeRouteBody = AdminTaskKuaishouIndustryConsumeInput export type AdminOrderRouteParams = { orderId?: string } diff --git a/apps/backend/src/types/admin/write-inputs.ts b/apps/backend/src/types/admin/write-inputs.ts index bdffcf9a..866ffa32 100644 --- a/apps/backend/src/types/admin/write-inputs.ts +++ b/apps/backend/src/types/admin/write-inputs.ts @@ -1,17 +1,3 @@ -export type AdminKuaishouEticketShopConfigWriteItemInput = { - shopId?: string - kshopName?: string - cookie?: string - userAvatar?: string - enabled?: boolean -} - -export type AdminKuaishouEticketSourceConfigInput = { - enabled?: boolean - baseUrl?: string - shops?: AdminKuaishouEticketShopConfigWriteItemInput[] -} - export type AdminKuaishouIndustryShopConfigInput = { enabled?: boolean sellerId?: string @@ -122,30 +108,6 @@ export type AdminScheduledJobsConfigInput = { jobs?: AdminScheduledJobInput[] } -export type AdminKuaishouEticketShopInfoInput = { - baseUrl?: string - shopId?: string - cookie?: string -} - -export type AdminKuaishouEticketDetailQueryInput = { - baseUrl?: string - shopId?: string - cookie?: string - eTicketId?: string -} - -export type AdminKuaishouEticketConsumeInput = { - baseUrl?: string - shopId?: string - cookie?: string - eTicketId?: string - oid?: string - formToken?: string - num?: number | string - storeId?: string -} - export type AdminCloudtentaclesSourceConfigInput = { sourceKey?: string enabled?: boolean @@ -274,10 +236,6 @@ export type AdminTaskManualDispatchInput = { deliveredCredential?: string } -export type AdminTaskKuaishouCloudDispatchInput = { - ticketCode?: string -} - export type AdminTaskKuaishouIndustryConsumeInput = { consumeType?: string storeName?: string diff --git a/apps/frontend/src/pages/admin/AdminTaskDetailPage.tsx b/apps/frontend/src/pages/admin/AdminTaskDetailPage.tsx index 97a48968..5e3e049f 100644 --- a/apps/frontend/src/pages/admin/AdminTaskDetailPage.tsx +++ b/apps/frontend/src/pages/admin/AdminTaskDetailPage.tsx @@ -1088,11 +1088,11 @@ function KuaishouCloudPanel({ const checklist = [ { key: 'ticket', - label: '核销码校验', + label: '凭证确认', status: flow.ticket.status || 'pending', detail: flow.ticket.verifiedAt - ? `已于 ${formatAdminDateTime(flow.ticket.verifiedAt)} 校验` - : '等待客户提交并校验核销码', + ? `已于 ${formatAdminDateTime(flow.ticket.verifiedAt)} 确认` + : '等待电子凭证同步确认', }, { key: 'bind', @@ -1129,11 +1129,11 @@ function KuaishouCloudPanel({ }, { key: 'consume', - label: '快手核销', + label: '电子凭证收口', status: flow.consume.status || 'pending', detail: flow.consume.consumedAt - ? `已于 ${formatAdminDateTime(flow.consume.consumedAt)} 完成核销` - : flow.consume.errorMessage || '等待退号后核销收口', + ? `已于 ${formatAdminDateTime(flow.consume.consumedAt)} 完成收口` + : flow.consume.errorMessage || '等待退号后收口', }, ] diff --git a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx index 5d43fa57..6ea1d5f6 100644 --- a/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx +++ b/apps/frontend/src/pages/admin/platform/AdminPlatformShopsPage.tsx @@ -11,7 +11,6 @@ import { } from '@ant-design/icons' import { Alert, - Avatar, Button, Card, Empty, @@ -39,20 +38,16 @@ import { fetchAdminCloudtentaclesAsset, fetchAdminCloudtentaclesSkuList, fetchAdminCloudtentaclesSourceConfig, - fetchAdminKuaishouEticketSourceConfig, fetchAdminKuaishouFeifeiConfig, fetchAdminKuaishouIndustrySourceConfig, fetchAdminNinetyoneOrders, fetchAdminNotificationConfig, fetchAdminScheduledJobsConfig, matchAdminKuaishouFeifeiProduct, - queryAdminKuaishouEticketDetail, - queryAdminKuaishouEticketShopInfo, refreshAdminKuaishouIndustryAccessToken, retryAdminNinetyoneOrder, runAdminScheduledJob, saveAdminCloudtentaclesSourceConfig, - saveAdminKuaishouEticketSourceConfig, saveAdminKuaishouFeifeiConfig, saveAdminKuaishouIndustrySourceConfig, saveAdminNotificationConfig, @@ -66,10 +61,6 @@ import { import type { AdminCloudtentaclesSourceConfigResponse, AdminCloudtentaclesSourceItem, - AdminKuaishouEticketDetailResult, - AdminKuaishouEticketShopConfigItem, - AdminKuaishouEticketShopInfoResult, - AdminKuaishouEticketSourceConfig, AdminKuaishouIndustryConfigResponse, AdminKuaishouIndustryShopConfig, AdminKuaishouIndustrySourceConfig, @@ -91,7 +82,6 @@ import { formatAdminDateTime } from '@/utils/admin-time' type PlatformTab = | 'ninetyone' | 'notifications' - | 'kuaishouEticket' | 'kuaishouIndustry' | 'kuaishouFeifei' | 'cloudtentacles' @@ -119,7 +109,6 @@ type ScheduledJobsState = { const platformTabs: PlatformTab[] = [ 'ninetyone', 'notifications', - 'kuaishouEticket', 'kuaishouIndustry', 'kuaishouFeifei', 'cloudtentacles', @@ -132,10 +121,6 @@ export default function AdminPlatformShopsPage() { const [errorMessage, setErrorMessage] = useState('') const [notificationConfig, setNotificationConfig] = useState(null) const [scheduledJobs, setScheduledJobs] = useState(null) - const [eticketConfig, setEticketConfig] = useState<{ - filePath: string - source: AdminKuaishouEticketSourceConfig - } | null>(null) const [industryConfig, setIndustryConfig] = useState(null) const [feifeiConfig, setFeifeiConfig] = useState(null) const [cloudtentaclesConfig, setCloudtentaclesConfig] = @@ -159,7 +144,6 @@ export default function AdminPlatformShopsPage() { const [ notificationResponse, scheduledJobsResponse, - eticketResponse, industryResponse, feifeiResponse, cloudtentaclesResponse, @@ -167,7 +151,6 @@ export default function AdminPlatformShopsPage() { ] = await Promise.all([ fetchAdminNotificationConfig(), fetchAdminScheduledJobsConfig(), - fetchAdminKuaishouEticketSourceConfig(), fetchAdminKuaishouIndustrySourceConfig(), fetchAdminKuaishouFeifeiConfig(), fetchAdminCloudtentaclesSourceConfig(), @@ -176,7 +159,6 @@ export default function AdminPlatformShopsPage() { setNotificationConfig(notificationResponse.data) setScheduledJobs(scheduledJobsResponse.data) - setEticketConfig(eticketResponse.data) setIndustryConfig(industryResponse.data) setFeifeiConfig(feifeiResponse.data) setCloudtentaclesConfig(cloudtentaclesResponse.data) @@ -246,18 +228,6 @@ export default function AdminPlatformShopsPage() { ), }, - { - key: 'kuaishouEticket', - label: '快手小店核销', - children: eticketConfig ? ( - - ) : ( - - ), - }, { key: 'kuaishouIndustry', label: '行业电子凭证', @@ -838,232 +808,6 @@ function ScheduledJobCard({ ) } -function KuaishouEticketPanel({ - config, - onChange, -}: { - config: { filePath: string; source: AdminKuaishouEticketSourceConfig } - onChange: (config: { filePath: string; source: AdminKuaishouEticketSourceConfig }) => void -}) { - const [saving, setSaving] = useState(false) - const [querying, setQuerying] = useState(false) - const [selectedShopId, setSelectedShopId] = useState(config.source.shops[0]?.shopId || '') - const [ticketCode, setTicketCode] = useState('') - const [shopInfoResult, setShopInfoResult] = useState(null) - const [detailResult, setDetailResult] = useState(null) - const source = config.source - const selectedShop = source.shops.find((shop) => shop.shopId === selectedShopId) || source.shops[0] || null - - async function saveConfig() { - setSaving(true) - try { - const response = await saveAdminKuaishouEticketSourceConfig(source) - onChange(response.data) - showSuccess('快手小店核销配置已保存') - } catch (error) { - showError(error instanceof Error ? error.message : '保存快手小店核销配置失败') - } finally { - setSaving(false) - } - } - - async function queryShopInfo(shop = selectedShop) { - if (!shop?.cookie) { - showError('请先选择并填写店铺 Cookie') - return - } - - setQuerying(true) - try { - const response = await queryAdminKuaishouEticketShopInfo({ - baseUrl: source.baseUrl, - shopId: shop.shopId, - cookie: shop.cookie, - }) - setShopInfoResult(response.data) - updateShop(shop.shopId, { - shopId: response.data.shop.shopId || shop.shopId, - kshopName: response.data.shop.kshopName || shop.kshopName, - userAvatar: response.data.shop.userAvatar || shop.userAvatar, - }) - showSuccess(`已识别店铺:${response.data.shop.kshopName || response.data.shop.shopId}`) - } catch (error) { - showError(error instanceof Error ? error.message : '读取快手小店店铺信息失败') - } finally { - setQuerying(false) - } - } - - async function queryDetail() { - if (!selectedShop?.cookie) { - showError('请先选择并填写店铺 Cookie') - return - } - if (!ticketCode.trim()) { - showError('请输入查询券码') - return - } - - setQuerying(true) - try { - const response = await queryAdminKuaishouEticketDetail({ - baseUrl: source.baseUrl, - shopId: selectedShop.shopId, - cookie: selectedShop.cookie, - eTicketId: ticketCode.trim(), - }) - setDetailResult(response.data) - showSuccess('核销券详情查询完成') - } catch (error) { - showError(error instanceof Error ? error.message : '查询核销券详情失败') - } finally { - setQuerying(false) - } - } - - function updateSource(nextSource: AdminKuaishouEticketSourceConfig) { - onChange({ ...config, source: nextSource }) - } - - function updateShop(shopId: string, patch: Partial) { - updateSource({ - ...source, - shops: source.shops.map((shop) => (shop.shopId === shopId ? { ...shop, ...patch } : shop)), - }) - } - - function addShop() { - const shopId = `shop_${Date.now()}` - updateSource({ - ...source, - shops: [ - ...source.shops, - { - shopId, - kshopName: '', - cookie: '', - cookieMasked: '', - hasCookie: false, - userAvatar: '', - enabled: true, - }, - ], - }) - setSelectedShopId(shopId) - } - - return ( -
- - {config.filePath || '默认配置'} - - - } - > -
- updateSource({ ...source, enabled })} - /> -
- 接口地址 - updateSource({ ...source, baseUrl: event.target.value })} - /> -
-
- - } onClick={addShop}>新增店铺}> - {source.shops.length === 0 ? ( - - ) : ( - - {source.shops.map((shop) => ( -
- updateShop(shop.shopId, { enabled })} /> - {shop.kshopName?.slice(0, 1) || '店'} - updateShop(shop.shopId, { shopId: event.target.value })} - onFocus={() => setSelectedShopId(shop.shopId)} - /> - updateShop(shop.shopId, { kshopName: event.target.value })} - /> - - updateShop(shop.shopId, { - cookie: event.target.value, - hasCookie: Boolean(event.target.value.trim()), - }) - } - /> - -
- ))} -
- )} -
-
- - - - setTicketCode(event.target.value)} - onPressEnter={queryDetail} - /> - - - - {shopInfoResult || detailResult ? ( -
-            {JSON.stringify(detailResult || shopInfoResult, null, 2)}
-          
- ) : null} -
-
- ) -} - function KuaishouIndustryPanel({ config, onChange, diff --git a/apps/frontend/src/pages/claim/ClaimPage.tsx b/apps/frontend/src/pages/claim/ClaimPage.tsx index 613f6582..5d72cab1 100644 --- a/apps/frontend/src/pages/claim/ClaimPage.tsx +++ b/apps/frontend/src/pages/claim/ClaimPage.tsx @@ -1,21 +1,16 @@ import { CheckCircleOutlined, - ClockCircleOutlined, ExclamationCircleOutlined, LinkOutlined, LoadingOutlined, ReloadOutlined, - SendOutlined, SwapOutlined, } from '@ant-design/icons' import { Alert, Button, Card, - Collapse, Empty, - Image, - Input, Space, Spin, Tooltip, @@ -39,7 +34,6 @@ import { fetchClaimDetail, rebindKuaishouCloudClaimRole, redeemKuaishouCloudClaim, - verifyKuaishouCloudClaimTicket, } from '@/services/claim' import type { ClaimDetailData, @@ -66,14 +60,12 @@ export default function ClaimPage() { const token = String(routeToken || '').trim() const [loading, setLoading] = useState(true) - const [submitting, setSubmitting] = useState(false) const [refreshingRole, setRefreshingRole] = useState(false) const [confirmingRole, setConfirmingRole] = useState(false) const [rebindingRole, setRebindingRole] = useState(false) const [redeeming, setRedeeming] = useState(false) const [errorMessage, setErrorMessage] = useState('') const [detail, setDetail] = useState(null) - const [ticketCode, setTicketCode] = useState('') const [qrCodeDataUrl, setQrCodeDataUrl] = useState('') const pollTimerRef = useRef(0) @@ -117,7 +109,6 @@ export default function ClaimPage() { const applyDetail = useCallback( async (nextDetail: ClaimDetailData) => { setDetail(nextDetail) - setTicketCode((current) => current || nextDetail.kuaishouCloudFulfillment?.ticket.code || '') await generateQRCode( String(nextDetail.kuaishouCloudFulfillment?.binding.bindUrl || '').trim(), ) @@ -208,7 +199,6 @@ export default function ClaimPage() { rolePollBaselineAtRef.current = 0 rolePollBaselineKeyRef.current = '' setDetail(null) - setTicketCode('') setQrCodeDataUrl('') void loadDetail() @@ -234,27 +224,6 @@ export default function ClaimPage() { } }, [loadDetail, resolveNextPollDelay, snapshot, stopPolling]) - async function submitTicket() { - const normalizedTicketCode = ticketCode.trim() - if (!normalizedTicketCode) { - showError('请输入核销码') - return - } - - setSubmitting(true) - try { - const response = await verifyKuaishouCloudClaimTicket(token, { - ticketCode: normalizedTicketCode, - }) - await applyDetail(response.data) - showSuccess('核销已完成,系统已开始准备绑定资源') - } catch (error) { - showError(error instanceof Error ? error.message : '核销码验证失败') - } finally { - setSubmitting(false) - } - } - async function refreshRole() { setRefreshingRole(true) try { @@ -318,7 +287,7 @@ export default function ClaimPage() { async function rebindRole() { try { await showConfirm( - '换绑后当前绑定链接会失效,系统会退还当前虚拟号并生成新的绑定二维码。核销码和领取商品不会改变,但需要重新扫码绑定角色。', + '换绑后当前绑定链接会失效,系统会退还当前虚拟号并生成新的绑定二维码。领取商品不会改变,但需要重新扫码绑定角色。', '确认换绑角色', { okText: '确认换绑', @@ -407,15 +376,11 @@ export default function ClaimPage() { ) : detail && snapshot.flow ? ( void - onSubmitTicket: () => void onOpenBindUrl: (useCurrentPage?: boolean) => void onRefreshRole: () => void onRebindRole: () => void @@ -727,17 +681,7 @@ function KuaishouCloudClaimSteps({ } if (snapshot.currentStep === 1) { - return ( - - ) + return } if (snapshot.currentStep === 2) { @@ -773,84 +717,21 @@ function KuaishouCloudClaimSteps({ return } -function ClaimTicketStep({ - ticketCode, - canSubmitTicket, - submitting, - isBindingPreparing, - flow, - onTicketCodeChange, - onSubmitTicket, -}: { - ticketCode: string - canSubmitTicket: boolean - submitting: boolean - isBindingPreparing: boolean - flow: ClaimKuaishouCloudFlowInfo - onTicketCodeChange: (value: string) => void - onSubmitTicket: () => void -}) { +function ClaimDeprecatedTicketStep() { return ( - 第 1 步:提交核销码 + 旧领取流程已停用

- 请您先从快手小店复制核销码。提交后会立即核销,核销成功后系统会自动准备绑定资源。 + 当前订单已改用行业电子凭证方案处理,不再支持快手小店核销码提交。请联系商家确认新的领取方式。

- onTicketCodeChange(event.target.value)} - onPressEnter={onSubmitTicket} - /> - - - } - message={ - isBindingPreparing - ? '核销完成后会自动准备绑定资源。' - : '核销完成后会自动进入扫码绑定步骤。' - } + icon={} + message="快手小店核销入口已下线" + description="电子凭证发码、查询和核销请以新的电子凭证流程为准。" /> - - {flow.guideImages.length > 0 ? ( - -
- {flow.guideImages.map((imageUrl, index) => ( -
- {`步骤 -
步骤 {index + 1}
-
- ))} -
- - ), - }, - ]} - /> - ) : null}
) } diff --git a/apps/frontend/src/services/admin/platform-config/index.ts b/apps/frontend/src/services/admin/platform-config/index.ts index 66720b9d..19444dc8 100644 --- a/apps/frontend/src/services/admin/platform-config/index.ts +++ b/apps/frontend/src/services/admin/platform-config/index.ts @@ -1,7 +1,6 @@ export * from './notifications' export * from './scheduled-jobs' export * from './ninetyone' -export * from './kuaishou-eticket' export * from './kuaishou-industry' export * from './kuaishou-feifei' export * from './cloudtentacles' diff --git a/apps/frontend/src/services/admin/platform-config/kuaishou-eticket.ts b/apps/frontend/src/services/admin/platform-config/kuaishou-eticket.ts deleted file mode 100644 index cd324b2e..00000000 --- a/apps/frontend/src/services/admin/platform-config/kuaishou-eticket.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { apiGet, apiPost } from '@/lib/http' -import type { - AdminKuaishouEticketConsumeResult, - AdminKuaishouEticketDetailResult, - AdminKuaishouEticketShopInfoResult, - AdminKuaishouEticketSourceConfig, -} from '@/types/admin' - -export function fetchAdminKuaishouEticketSourceConfig() { - return apiGet<{ - filePath: string - source: AdminKuaishouEticketSourceConfig - }>('/api/v1/admin/platform-config/kuaishou-eticket-source') -} - -export function saveAdminKuaishouEticketSourceConfig(payload: AdminKuaishouEticketSourceConfig) { - return apiPost<{ - filePath: string - source: AdminKuaishouEticketSourceConfig - }>('/api/v1/admin/platform-config/kuaishou-eticket-source', payload) -} - -export function queryAdminKuaishouEticketDetail(payload: { - baseUrl?: string - shopId?: string - cookie?: string - eTicketId: string -}) { - return apiPost( - '/api/v1/admin/platform-config/kuaishou-eticket/query-detail', - payload, - ) -} - -export function queryAdminKuaishouEticketShopInfo(payload: { - baseUrl?: string - shopId?: string - cookie?: string -}) { - return apiPost( - '/api/v1/admin/platform-config/kuaishou-eticket/query-shop-info', - payload, - ) -} - -export function consumeAdminKuaishouEticket(payload: { - baseUrl?: string - shopId?: string - cookie?: string - eTicketId: string - oid?: string - formToken?: string - num?: number - storeId?: string -}) { - return apiPost( - '/api/v1/admin/platform-config/kuaishou-eticket/consume', - payload, - ) -} diff --git a/apps/frontend/src/services/admin/tasks.ts b/apps/frontend/src/services/admin/tasks.ts index eb643e0d..c0eff2c9 100644 --- a/apps/frontend/src/services/admin/tasks.ts +++ b/apps/frontend/src/services/admin/tasks.ts @@ -73,15 +73,10 @@ export function rebindAdminTaskKuaishouCloudRole(taskId: number | string) { ) } -export function dispatchAdminTaskKuaishouCloudFulfillment( - taskId: number | string, - payload: { - ticketCode?: string - } = {}, -) { +export function dispatchAdminTaskKuaishouCloudFulfillment(taskId: number | string) { return apiPost( `/api/v1/admin/tasks/${taskId}/kuaishou-cloud/dispatch`, - payload, + {}, ) } diff --git a/apps/frontend/src/services/claim.ts b/apps/frontend/src/services/claim.ts index e5b508c2..fcaae3fe 100644 --- a/apps/frontend/src/services/claim.ts +++ b/apps/frontend/src/services/claim.ts @@ -5,15 +5,6 @@ export function fetchClaimDetail(token: string) { return apiGet(`/api/v1/claim/${token}`) } -export function verifyKuaishouCloudClaimTicket( - token: string, - payload: { - ticketCode: string - }, -) { - return apiPost(`/api/v1/claim/${token}/kuaishou-cloud/verify-ticket`, payload) -} - export function confirmKuaishouCloudClaimRole(token: string) { return apiPost(`/api/v1/claim/${token}/kuaishou-cloud/confirm-role`, {}) } diff --git a/apps/frontend/src/styles/main.css b/apps/frontend/src/styles/main.css index 92bb460c..0b1f1411 100644 --- a/apps/frontend/src/styles/main.css +++ b/apps/frontend/src/styles/main.css @@ -580,32 +580,6 @@ select { color: #dc2626; } -.claim-guide-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 14px; -} - -.claim-guide-figure { - margin: 0; - background: #f8fafc; - border: 1px solid #e5e7eb; - border-radius: 6px; - overflow: hidden; -} - -.claim-guide-figure .ant-image, -.claim-guide-figure img { - width: 100%; - display: block; -} - -.claim-guide-figure figcaption { - padding: 10px 12px 14px; - color: #475569; - font-size: 13px; -} - .claim-qr-block { display: flex; flex-direction: column; @@ -1209,7 +1183,6 @@ select { font-size: 12px; } - .claim-guide-grid, .claim-info-grid { grid-template-columns: 1fr; } diff --git a/apps/frontend/src/types/admin/index.ts b/apps/frontend/src/types/admin/index.ts index 6a82627a..53383859 100644 --- a/apps/frontend/src/types/admin/index.ts +++ b/apps/frontend/src/types/admin/index.ts @@ -57,11 +57,6 @@ export type { AdminNinetyoneOrderItem, AdminNinetyoneOrderListResult, AdminNinetyoneOrderActionResult, - AdminKuaishouEticketSourceConfig, - AdminKuaishouEticketShopConfigItem, - AdminKuaishouEticketShopInfoResult, - AdminKuaishouEticketDetailResult, - AdminKuaishouEticketConsumeResult, AdminKuaishouIndustryAccessTokenStatus, AdminKuaishouIndustryShopConfig, AdminKuaishouIndustrySourceConfig, diff --git a/apps/frontend/src/types/admin/platform-config/index.ts b/apps/frontend/src/types/admin/platform-config/index.ts index 23b3ab06..09123ebf 100644 --- a/apps/frontend/src/types/admin/platform-config/index.ts +++ b/apps/frontend/src/types/admin/platform-config/index.ts @@ -22,14 +22,6 @@ export type { AdminNinetyoneOrderActionResult, } from './ninetyone' -export type { - AdminKuaishouEticketSourceConfig, - AdminKuaishouEticketShopConfigItem, - AdminKuaishouEticketShopInfoResult, - AdminKuaishouEticketDetailResult, - AdminKuaishouEticketConsumeResult, -} from './kuaishou-eticket' - export type { AdminKuaishouIndustryAccessTokenStatus, AdminKuaishouIndustryShopConfig, diff --git a/apps/frontend/src/types/admin/platform-config/kuaishou-eticket.ts b/apps/frontend/src/types/admin/platform-config/kuaishou-eticket.ts deleted file mode 100644 index 046bca9c..00000000 --- a/apps/frontend/src/types/admin/platform-config/kuaishou-eticket.ts +++ /dev/null @@ -1,83 +0,0 @@ -export interface AdminKuaishouEticketShopConfigItem { - shopId: string - kshopName: string - cookie: string - cookieMasked: string - hasCookie: boolean - userAvatar: string - enabled: boolean -} - -export interface AdminKuaishouEticketSourceConfig { - enabled: boolean - baseUrl: string - shops: AdminKuaishouEticketShopConfigItem[] -} - -export interface AdminKuaishouEticketShopInfoResult { - baseUrl: string - ok: boolean - result: number - errorMessage: string - requestId: string - shop: { - shopId: string - kshopName: string - userAvatar: string - settleStatus: number - hasCookie: boolean - } - raw: Record -} - -export interface AdminKuaishouEticketDetailResult { - baseUrl: string - eTicketId: string - ok: boolean - alreadyConsumed: boolean - result: number - errorMessage: string - requestId: string - serverTimestamp: string - detail: { - uid: string - fulfillDetailId: string - sellerId: string - formToken: string - validEndTime: string - validStartTime: string - leftReverseCount: number - eTicketId: string - oid: string - totalCount: number - leftCount: number - status: string - } | null - goods: { - itemId: string - itemPicUrl: string - itemTitle: string - price: string - skuDesc: string - skuId: string - } | null - raw: Record -} - -export interface AdminKuaishouEticketConsumeResult { - baseUrl: string - eTicketId: string - oid: string - formToken: string - num: number - storeId: string - ok: boolean - consumed: boolean - alreadyConsumed: boolean - result: number - errorMessage: string - requestId: string - serverTimestamp: string - detail: AdminKuaishouEticketDetailResult['detail'] - raw: Record -}