/** * 开发环境专用 Mock 数据工厂。 * 生产环境禁止调用;由路由层 isDevMockEnabled 拦截。 */ import crypto from 'node:crypto' import { createOrder, findOrderByPlatformOrderId } from '../../repositories/order-repo.js' import { replaceOrderItems } from '../../repositories/order-item-repo.js' import { createTask, updateTask } from '../../repositories/task-repo.js' import { upsertKuaishouIndustryVoucher } from '../../repositories/kuaishou-industry-voucher-repo.js' import { getFulfillmentProfileByKey, upsertFulfillmentProfile, } from '../../repositories/fulfillment-profile-repo.js' import { buildClaimUrl, createTaskClaimToken } from '../claim/claim-service.js' import { assertValidClaimUid, normalizeClaimUid } from '../claim/claim-identity.js' import { OPEN_91_PLATFORM, OPEN_91_PROVIDER, assertOpen91Config } from '../open-91/config.js' import { parseOpen91ProductNo } from '../platforms/ninetyone/order-service.js' import { getKuaishouIndustrySourceConfig, listKuaishouIndustryShopConfigs, } from '../platforms/kuaishou-industry/source-config-service.js' import { isProductionLike } from '../../config/runtime-validation.js' import { createHttpError } from '../../utils/http.js' import { addHours, nowIso } from '../../utils/time.js' import { randomId } from '../../utils/random.js' import { TASK_STATUS } from '../../domain/task-status.js' export type LewanMockStep = 'uid' | 'binding' | 'confirm' | 'result' export type FeifeiMockStep = 'uid' | 'ready' | 'completed' | 'failed' export type DevMockCreateResult = { platform: 'lewan' | 'feifei' | 'affiliate_dash' orderId: number orderNo: string taskId: number taskNo: string claimToken: string claimUrl: string frontendClaimUrl: string expectedUid: string step: string productName: string tips: string[] open91QueryHint: string } export type DevMockKuaishouIndustryVoucherResult = { createdCount: number sellerId: string vouchers: Array<{ voucherCode: string oid: string status: string }> } type DeliveryItem = { cloudSkuId: number cloudSkuName: string quantity: number } export function isDevMockEnabled(env: NodeJS.ProcessEnv = process.env): boolean { if (String(env.ENABLE_DEV_MOCK || '').trim() === '1') { return true } if ( String(env.ENABLE_DEV_MOCK || '') .trim() .toLowerCase() === 'true' ) { return true } return !isProductionLike(env) } export function assertDevMockEnabled() { if (!isDevMockEnabled()) { throw createHttpError('当前环境未开启开发 Mock(仅非 production 或 ENABLE_DEV_MOCK=1)', { statusCode: 403, errorCode: 'dev_mock_disabled', }) } } export function getDevMockStatus() { const enabled = isDevMockEnabled() return { enabled, nodeEnv: String(process.env.NODE_ENV || 'development'), platforms: enabled ? [ { key: 'lewan', name: 'kuaishou-lewan', description: '本站 claim + 强制 UID 匹配 + mock 兑换(不调 CloudTentacles)', steps: ['uid', 'binding', 'confirm', 'result'], }, { key: 'feifei', name: 'kuaishou-feifei', description: '本站 claim + 拼 uid 的 H5 链接(不调真实 feifei 下单)', steps: ['uid', 'ready', 'completed', 'failed'], }, { key: 'affiliate_dash', name: 'affiliate-dash', description: '本站 claim + mock 绑定二维码/提交发货(不调真实 affiliate-dash 平台)', steps: ['uid', 'bind', 'submitted', 'completed', 'failed'], }, ] : [], defaultUid: '10001', } } export async function createLewanMockClaim( input: { step?: unknown orderNo?: unknown productNo?: unknown uid?: unknown items?: unknown frontendBaseUrl?: unknown } = {}, ): Promise { assertDevMockEnabled() const step = normalizeLewanStep(input.step) const orderNo = String(input.orderNo || `MOCK91${Date.now()}`).trim() const productNo = String(input.productNo || '套餐_1----3676797936').trim() const productInfo = parseOpen91ProductNo(productNo) const productName = productInfo.productName || productInfo.rawProductNo || '套餐_1' const consumeShopId = productInfo.shopId || '3676797936' const expectedUid = resolveMockUid(input.uid, step === 'uid' ? '' : '10001') const deliveryItems = parseDeliveryItems(input.items) const now = nowIso() await assertOrderNoAvailable(orderNo) const profile = await ensureProfile('kuaishou_ct_assisted', 'kuaishou-lewan 履约', false) const order = await createBaseOrder({ orderNo, productNo, productName, now, platformTag: 'lewan', }) const orderItem = await createBaseOrderItem({ orderId: order.id, orderNo, productNo, productName, consumeShopId, deliveryItems, now, executor: 'lewan', }) const task = await createTask({ orderId: order.id, orderItemId: orderItem.id, unitIndex: 1, provider: OPEN_91_PROVIDER, platform: OPEN_91_PLATFORM, shopId: OPEN_91_PROVIDER, shopName: '91卡券', platformOrderId: orderNo, taskNo: randomId('DT'), profileId: profile.id, executorKey: 'kuaishou_ct_assisted', taskStatus: resolveLewanTaskStatus(step), deliveryStatus: step === 'result' ? 'success' : 'pending', resultCode: step === 'result' ? 'mock_success' : '', resultMessage: step === 'result' ? '开发 mock 已模拟兑换成功' : '', claimToken: '', claimExpiresAt: null, automationMode: 'manual', requiresClaim: true, userActionStatus: step === 'result' ? 'not_required' : 'pending_claim', attemptCount: 0, lastError: '', contextJson: JSON.stringify( buildLewanMockContext({ step, orderNo, productName, productNo, consumeShopId, deliveryItems, expectedUid, timestamp: now, }), ), createdAt: now, updatedAt: now, }) if (!task) { throw createHttpError('履约任务创建失败', { statusCode: 500, errorCode: 'dev_mock_task_failed', }) } const claimToken = await createTaskClaimToken(task.id) await updateTask(task.id, { claim_token: claimToken.token, claim_expires_at: claimToken.expired_at, claimed_at: step === 'uid' ? null : now, role_confirmed_at: step === 'confirm' || step === 'result' ? now : null, redeemed_at: step === 'result' ? now : null, role_id: ['binding', 'confirm', 'result'].includes(step) ? expectedUid || '10001' : '', role_name: ['binding', 'confirm', 'result'].includes(step) ? '测试角色' : '', updated_at: now, }) return buildCreateResult({ platform: 'lewan', orderId: order.id, orderNo, taskId: task.id, taskNo: task.task_no, claimToken: claimToken.token, claimUrl: buildClaimUrl(claimToken.token), frontendBaseUrl: input.frontendBaseUrl, expectedUid: expectedUid || '10001', step, productName, tips: buildLewanTips(step, expectedUid || '10001'), }) } export async function createFeifeiMockClaim( input: { step?: unknown orderNo?: unknown productName?: unknown productCode?: unknown uid?: unknown h5Url?: unknown frontendBaseUrl?: unknown } = {}, ): Promise { assertDevMockEnabled() const step = normalizeFeifeiStep(input.step) const orderNo = String(input.orderNo || `MOCKFF${Date.now()}`).trim() const productName = String(input.productName || '套装-浪漫天命').trim() const productCode = String(input.productCode || `MOCK-FF-${Date.now()}`).trim() const expectedUid = resolveMockUid(input.uid, step === 'uid' ? '' : '166909256') const rawH5 = String(input.h5Url || '').trim() || `http://skin-exchange.yiquyou.icu/h5/bind?code=mock_${encodeURIComponent(orderNo)}&product_name=${encodeURIComponent(productName)}` const now = nowIso() await assertOrderNoAvailable(orderNo) const profile = await ensureProfile('kuaishou_feifei', 'kuaishou-feifei 履约', true) const order = await createBaseOrder({ orderNo, productNo: productName, productName, now, platformTag: 'feifei', }) const orderItem = await createBaseOrderItem({ orderId: order.id, orderNo, productNo: productName, productName, consumeShopId: '', deliveryItems: [], now, executor: 'feifei', productCode, }) const rechargeStatus = step === 'completed' ? 30 : step === 'failed' ? 40 : step === 'ready' ? 15 : 15 const rechargeStatusLabel = step === 'completed' ? '充值成功' : step === 'failed' ? '充值失败' : step === 'ready' ? '待绑定' : '待领取' const task = await createTask({ orderId: order.id, orderItemId: orderItem.id, unitIndex: 1, provider: OPEN_91_PROVIDER, platform: OPEN_91_PLATFORM, shopId: OPEN_91_PROVIDER, shopName: '91卡券', platformOrderId: orderNo, taskNo: randomId('DT'), profileId: profile.id, executorKey: 'kuaishou_feifei', taskStatus: step === 'completed' ? TASK_STATUS.COMPLETED : step === 'failed' ? TASK_STATUS.MANUAL_REVIEW : TASK_STATUS.LINK_GENERATED, deliveryStatus: step === 'completed' ? 'delivered' : 'pending', resultCode: step === 'completed' ? 'kuaishou_feifei_completed' : step === 'failed' ? 'kuaishou_feifei_status_40' : '', resultMessage: rechargeStatusLabel, claimToken: '', claimExpiresAt: null, automationMode: 'manual', requiresClaim: true, userActionStatus: step === 'completed' ? 'not_required' : 'pending_claim', attemptCount: 0, lastError: step === 'failed' ? '开发 mock 模拟 feifei 失败' : '', contextJson: JSON.stringify({ profileKey: 'kuaishou_feifei', profileName: 'kuaishou-feifei 履约', skuCode: productName, skuName: productName, claimIdentity: expectedUid ? { expectedUid, submittedAt: now, source: 'dev_mock', } : { expectedUid: '', submittedAt: null, source: '', }, kuaishouFeifei: { flowType: 'kuaishou_feifei', productCode, productName, platformOrderNo: orderNo, orderNo: `FF-${orderNo}`, rechargeStatus, rechargeStatusLabel, rechargeResultMessage: step === 'failed' ? '开发 mock 失败' : '', claimUrl: '', consumeStatus: step === 'completed' ? 'not_required' : 'pending', h5: { entryUrl: rawH5, rechargeUrl: rawH5, }, lastSyncedAt: now, mock: { enabled: true, orderNo, createdAt: now, }, }, }), createdAt: now, updatedAt: now, }) if (!task) { throw createHttpError('履约任务创建失败', { statusCode: 500, errorCode: 'dev_mock_task_failed', }) } const claimToken = await createTaskClaimToken(task.id) await updateTask(task.id, { claim_token: claimToken.token, claim_expires_at: claimToken.expired_at, claimed_at: expectedUid ? now : null, redeemed_at: step === 'completed' ? now : null, updated_at: now, }) return buildCreateResult({ platform: 'feifei', orderId: order.id, orderNo, taskId: task.id, taskNo: task.task_no, claimToken: claimToken.token, claimUrl: buildClaimUrl(claimToken.token), frontendBaseUrl: input.frontendBaseUrl, expectedUid: expectedUid || '166909256', step, productName, tips: buildFeifeiTips(step, expectedUid || '166909256'), }) } type AffiliateDashMockStep = 'uid' | 'bind' | 'submitted' | 'completed' | 'failed' /** * 生成 affiliate-dash 领取 mock:不调用真实 affiliate-dash 平台。 * 上下文带 mock 标记,sync/refresh/submit 全部短路,领取页可走完四步。 */ export async function createAffiliateDashMockClaim( input: { step?: unknown orderNo?: unknown productName?: unknown productSku?: unknown uid?: unknown frontendBaseUrl?: unknown } = {}, ): Promise { assertDevMockEnabled() const step = normalizeAffiliateDashStep(input.step) const orderNo = String(input.orderNo || `MOCKAD${Date.now()}`).trim() const productName = String(input.productName || '幸运币90个').trim() const productSku = String(input.productSku || 'lucky_coin_x90').trim() const expectedUid = resolveMockUid(input.uid, step === 'uid' ? '' : '166909256') const fallbackUid = expectedUid || '166909256' const bindUrl = step === 'uid' ? '' : `https://skin.khhao.com/mock/bind?order=${encodeURIComponent(orderNo)}&uid=${encodeURIComponent(fallbackUid)}` const bound = step === 'submitted' || step === 'completed' || step === 'failed' const orderStatus = step === 'completed' ? 'delivered' : step === 'failed' ? 'ship_failed' : step === 'submitted' ? 'delivering' : 'paid' const now = nowIso() await assertOrderNoAvailable(orderNo) const profile = await ensureProfile('affiliate_dash', 'affiliate-dash 履约', true) const order = await createBaseOrder({ orderNo, productNo: productSku, productName, now, platformTag: 'affiliate-dash', }) const orderItem = await createBaseOrderItem({ orderId: order.id, orderNo, productNo: productSku, productName, consumeShopId: '', deliveryItems: [], now, executor: 'affiliate_dash', productCode: productSku, }) const task = await createTask({ orderId: order.id, orderItemId: orderItem.id, unitIndex: 1, provider: OPEN_91_PROVIDER, platform: OPEN_91_PLATFORM, shopId: OPEN_91_PROVIDER, shopName: '91卡券', platformOrderId: orderNo, taskNo: randomId('DT'), profileId: profile.id, executorKey: 'affiliate_dash', taskStatus: step === 'completed' ? TASK_STATUS.REDEEMED : step === 'failed' ? TASK_STATUS.RETRY_PENDING : step === 'submitted' ? TASK_STATUS.REDEEMING : TASK_STATUS.LINK_GENERATED, deliveryStatus: step === 'completed' ? 'delivered' : step === 'submitted' ? 'delivering' : 'pending', resultCode: step === 'completed' ? 'affiliate_dash_delivered' : step === 'failed' ? 'affiliate_dash_ship_failed' : '', resultMessage: step === 'completed' ? 'affiliate-dash 履约成功' : step === 'failed' ? '开发 mock 模拟发货失败' : '等待用户领取', claimToken: '', claimExpiresAt: null, automationMode: 'manual', requiresClaim: true, userActionStatus: step === 'completed' ? 'not_required' : 'pending_claim', attemptCount: 0, lastError: step === 'failed' ? '开发 mock 模拟发货失败' : '', contextJson: JSON.stringify({ profileKey: 'affiliate_dash', profileName: 'affiliate-dash 履约', skuCode: productName, skuName: productName, claimIdentity: expectedUid ? { expectedUid, submittedAt: now, source: 'dev_mock', } : { expectedUid: '', submittedAt: null, source: '', }, affiliateDash: { flowType: 'affiliate_dash', sku: productSku, productName, orderNo: `AD-${orderNo}`, clientOrderNo: '', orderStatus, canShip: true, cannotShipReason: '', providerOrderNo: '', failureReason: step === 'failed' ? '开发 mock 模拟发货失败' : '', amount: 20, currency: 'POINT', bindUuid: bound ? `mock-bind-${orderNo}` : '', bindUrl, qrUrl: '', gameAccount: bound ? fallbackUid : '', expectedGameAccount: expectedUid || '', bindMismatch: false, bound, boundAccount: bound ? fallbackUid : '', gameChannel: 'Android', submitStatus: step === 'completed' ? 'delivered' : step === 'failed' ? 'ship_failed' : step === 'submitted' ? 'submitting' : '', consumeStatus: step === 'completed' ? 'not_required' : 'pending', lastSyncedAt: now, mock: { enabled: true, orderNo, createdAt: now, }, }, }), createdAt: now, updatedAt: now, }) if (!task) { throw createHttpError('履约任务创建失败', { statusCode: 500, errorCode: 'dev_mock_task_failed', }) } const claimToken = await createTaskClaimToken(task.id) await updateTask(task.id, { claim_token: claimToken.token, claim_expires_at: claimToken.expired_at, claimed_at: expectedUid ? now : null, redeemed_at: step === 'completed' ? now : null, updated_at: now, }) return buildCreateResult({ platform: 'affiliate_dash', orderId: order.id, orderNo, taskId: task.id, taskNo: task.task_no, claimToken: claimToken.token, claimUrl: buildClaimUrl(claimToken.token), frontendBaseUrl: input.frontendBaseUrl, expectedUid: fallbackUid, step, productName, tips: buildAffiliateDashTips(step, fallbackUid), }) } /** * 生成电子凭证列表测试数据,不调用快手接口。 * 覆盖未使用、已核销、已销毁和发码失败等运营页面常见状态。 */ export async function createKuaishouIndustryVoucherMockData( input: { sellerId?: unknown } = {}, ): Promise { assertDevMockEnabled() const now = nowIso() const timestamp = Date.now() const sellerId = resolveMockIndustrySellerId(input.sellerId) const specimens = [ { status: 'UNUSED', callbackStatus: 'success', productName: '周卡 VIP 尊享礼包' }, { status: 'UNUSED', callbackStatus: 'failed', callbackError: '开发 Mock 模拟发码回调失败', productName: '幸运盲盒 限定款', }, { status: 'CONSUMED', callbackStatus: 'success', consumeSerialNum: `CONSUME-MOCK-${timestamp}-3`, consumedAt: now, productName: '星轨通行证月卡', }, { status: 'UNUSED', callbackStatus: 'success', productName: '精英荣耀专属礼盒' }, { status: 'DESTROYED', callbackStatus: 'success', destroyedAt: now, productName: '退款销毁测试券', }, { status: 'CONSUMED', callbackStatus: 'success', consumeSerialNum: `CONSUME-MOCK-${timestamp}-6`, consumedAt: now, productName: '浪漫天命皮肤礼包', }, ] const vouchers: DevMockKuaishouIndustryVoucherResult['vouchers'] = [] for (const [index, specimen] of specimens.entries()) { const oid = `MOCK-KS-${timestamp}-${index + 1}` const voucher = await upsertKuaishouIndustryVoucher({ oid, unitIndex: 1, sellerId, token: `mock-token-${timestamp}-${index + 1}`, eticketType: 'GAME_OPEN_TICKET_CONSUME', status: specimen.status, validStartTime: timestamp - 60 * 60 * 1000, validEndTime: timestamp + 7 * 24 * 60 * 60 * 1000, consumeSerialNum: specimen.consumeSerialNum || '', consumedAt: specimen.consumedAt || null, destroyedAt: specimen.destroyedAt || null, sendCallbackStatus: specimen.callbackStatus, sendCallbackAttemptCount: specimen.callbackStatus === 'failed' ? 1 : 0, sendCallbackLastError: specimen.callbackError || '', sendCallbackResponseJson: { mock: true, success: specimen.callbackStatus === 'success', }, sendCallbackSentAt: now, rawPayloadJson: { source: 'dev_mock', mock: true, productName: specimen.productName, }, createdAt: now, updatedAt: now, }) if (!voucher) { throw createHttpError('电子凭证 Mock 创建失败', { statusCode: 500, errorCode: 'dev_mock_kuaishou_industry_voucher_failed', }) } vouchers.push({ voucherCode: voucher.voucher_code, oid: voucher.oid, status: voucher.status, }) } return { createdCount: vouchers.length, sellerId, vouchers, } } /** * 生成 91 查询请求体(带签名),可选对本机发起查询。 */ export async function buildOpen91QueryMock( input: { orderNo?: unknown execute?: unknown baseUrl?: unknown } = {}, ) { assertDevMockEnabled() const orderNo = String(input.orderNo || '').trim() if (!orderNo) { throw createHttpError('请填写 orderNo', { statusCode: 400, errorCode: 'dev_mock_order_no_required', }) } const config = assertOpen91Config() const timestamp = Math.floor(Date.now() / 1000) const payload = { orderNo, timestamp, version: config.version || '1.0', } const sign = signOpen91Payload(payload, config.secret) const body = { ...payload, sign } const port = String(process.env.PORT || process.env.BACKEND_PORT || '3000').trim() || '3000' const defaultBase = `http://127.0.0.1:${port}` const baseUrl = String(input.baseUrl || defaultBase) .replace(/\/#\/claim\/?$/, '') .replace(/\/$/, '') const endpoint = `${baseUrl || defaultBase}/api/v1/open/91/orders/query` let response: unknown = null if (input.execute === true || input.execute === 'true' || input.execute === 1) { try { const res = await fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), }) const text = await res.text() try { response = { status: res.status, body: JSON.parse(text) } } catch { response = { status: res.status, body: text } } } catch (error) { response = { status: 0, error: error instanceof Error ? error.message : String(error), tip: '本机请求失败时,可复制 requestBody 用 curl 手动打到 backend', } } } return { endpoint, requestBody: body, curl: `curl -sS -X POST '${endpoint}' -H 'content-type: application/json' -d '${JSON.stringify(body)}'`, response, } } // --- helpers --- async function assertOrderNoAvailable(orderNo: string) { const existing = await findOrderByPlatformOrderId({ provider: OPEN_91_PROVIDER, platform: OPEN_91_PLATFORM, shopId: OPEN_91_PROVIDER, platformOrderId: orderNo, }) if (existing) { throw createHttpError(`订单号已存在:${orderNo},请换一个或留空自动生成`, { statusCode: 409, errorCode: 'dev_mock_order_exists', }) } } async function ensureProfile(profileKey: string, name: string, requiresClaim: boolean) { const existing = await getFulfillmentProfileByKey(profileKey) if (existing) { return existing } const now = nowIso() const profile = await upsertFulfillmentProfile({ profileKey, name, executorKey: profileKey, requiresClaim, autoDispatch: false, inventoryStrategy: 'external_platform', configJson: '{}', createdAt: now, updatedAt: now, }) if (!profile) { throw createHttpError('履约配置创建失败', { statusCode: 500, errorCode: 'dev_mock_profile_failed', }) } return profile } async function createBaseOrder(input: { orderNo: string productNo: string productName: string now: string platformTag: string }) { const order = await createOrder({ provider: OPEN_91_PROVIDER, platform: OPEN_91_PLATFORM, shopId: OPEN_91_PROVIDER, shopName: '91卡券', platformOrderId: input.orderNo, orderStatus: 'paid', payStatus: 'paid', buyerId: 'mock-buyer', buyerName: 'mock 领取客户', receiverContact: '', totalAmount: 100, currency: 'CNY', rawPayloadJson: JSON.stringify({ source: OPEN_91_PROVIDER, mock: true, platformTag: input.platformTag, receivedAt: input.now, body: { orderNo: input.orderNo, productNo: input.productNo, productName: input.productName, buyNum: 1, maxAmount: '1.00', }, }), paidAt: input.now, createdAt: input.now, updatedAt: input.now, }) if (!order) { throw createHttpError('订单创建失败', { statusCode: 500, errorCode: 'dev_mock_order_failed' }) } return order } async function createBaseOrderItem(input: { orderId: number orderNo: string productNo: string productName: string consumeShopId: string deliveryItems: DeliveryItem[] now: string executor: 'lewan' | 'feifei' | 'affiliate_dash' productCode?: string }) { const snapshot = input.executor === 'feifei' ? { source: OPEN_91_PROVIDER, orderNo: input.orderNo, productNo: input.productNo, productName: input.productName, kuaishouFeifei: { productCode: input.productCode || '', productName: input.productName, matchMode: 'mock', }, } : input.executor === 'affiliate_dash' ? { source: OPEN_91_PROVIDER, orderNo: input.orderNo, productNo: input.productNo, productName: input.productName, affiliateDash: { sku: input.productCode || input.productNo, productName: input.productName, matchMode: 'mock', }, } : { source: OPEN_91_PROVIDER, orderNo: input.orderNo, productNo: input.productNo, productName: input.productName, shopId: input.consumeShopId, cloudtentacles: { matchMode: 'mock', normalizedProductName: input.productName, cloudSourceKeys: ['mock-cloudtentacles'], resolvedSourceKey: 'mock-cloudtentacles', deliveryItems: input.deliveryItems, }, } const [orderItem] = await replaceOrderItems(input.orderId, [ { skuCode: input.productName, skuName: input.productName, quantity: 1, specJson: JSON.stringify({ source: OPEN_91_PROVIDER, orderNo: input.orderNo, productNo: input.productNo, productName: input.productName, }), itemSnapshotJson: JSON.stringify(snapshot), createdAt: input.now, updatedAt: input.now, }, ]) if (!orderItem) { throw createHttpError('订单商品创建失败', { statusCode: 500, errorCode: 'dev_mock_item_failed', }) } return orderItem } function buildLewanMockContext(input: { step: LewanMockStep orderNo: string productName: string productNo: string consumeShopId: string deliveryItems: DeliveryItem[] expectedUid: string timestamp: string }) { const roleReady = ['binding', 'confirm', 'result'].includes(input.step) const roleConfirmed = ['confirm', 'result'].includes(input.step) const completed = input.step === 'result' const boundUid = roleReady ? input.expectedUid || '10001' : '' const primaryItem = input.deliveryItems[0] || { cloudSkuId: 910001, cloudSkuName: 'Mock 商品', quantity: 1, } return { profileKey: 'kuaishou_ct_assisted', profileName: 'kuaishou-lewan 履约', skuCode: input.productName, skuName: input.productName, claimIdentity: input.expectedUid ? { expectedUid: input.expectedUid, submittedAt: input.timestamp, source: 'dev_mock', } : { expectedUid: '', submittedAt: null, source: '', }, kuaishouCloudFulfillment: { flowType: 'kuaishou_cloud_fulfillment', configId: `mock:${input.productName}`, internalSkuCode: input.productName, internalSkuName: input.productName, deliveryItems: input.deliveryItems.length ? input.deliveryItems : [primaryItem], mock: { enabled: true, orderNo: input.orderNo, productNo: input.productNo, createdAt: input.timestamp, }, ticket: { code: roleReady ? `MOCK-${input.orderNo}` : '', status: roleReady ? 'verified' : 'pending', capturedAt: roleReady ? input.timestamp : null, capturedBy: roleReady ? { source: 'dev_mock' } : null, verifiedAt: roleReady ? input.timestamp : null, oid: roleReady ? `MOCK-OID-${input.orderNo}` : '', formToken: roleReady ? `MOCK-FORM-${input.orderNo}` : '', leftCount: roleReady ? 1 : 0, goodsTitle: roleReady ? input.productName : '', }, binding: { prepareStatus: roleReady ? 'ready' : 'pending', cloudSourceKeys: ['mock-cloudtentacles'], resolvedSourceKey: 'mock-cloudtentacles', skuId: primaryItem.cloudSkuId, skuName: primaryItem.cloudSkuName, vnKey: '1', vnId: roleReady ? 900001 : 0, vnPhone: roleReady ? '13800000000' : '', bindUrl: roleReady ? `https://example.com/mock-kuaishou-cloud-bind/${input.orderNo}` : '', bindPreparedAt: roleReady ? input.timestamp : null, bindExpiresAt: roleReady ? addHours(input.timestamp, 24) : null, bindProbeAt: roleReady ? input.timestamp : null, bindProbeStatus: roleReady ? 'success' : '', bindProbeMessage: '', roleName: roleReady ? '测试角色' : '', roleId: boundUid, }, role: { status: roleReady ? 'ready' : 'pending', name: roleReady ? '测试角色' : '', rid: boundUid, refreshedAt: roleReady ? input.timestamp : null, errorMessage: '', rawInfo: roleReady ? { mock: true } : null, defaultName: roleReady ? '默认机位角色' : '', defaultRid: roleReady ? 'DEFAULT-000' : '', defaultCapturedAt: roleReady ? input.timestamp : null, defaultCaptureStatus: roleReady ? 'captured' : '', defaultErrorMessage: '', isDefaultRole: false, }, purchase: { autoBuyEnabled: true, minAssetReserve: 0, usedKnapsack: false, purchaseTriggered: false, assetBefore: 0, assetAfter: 0, purchaseAt: null, items: [], }, dispatch: { status: completed ? 'success' : 'pending', dispatchAt: completed ? input.timestamp : null, dispatchBy: completed ? { source: 'dev_mock' } : null, sendType: 0, note: completed ? '开发 mock 已模拟发货成功' : '', items: completed ? input.deliveryItems : [], }, returnNumber: { status: completed ? 'success' : 'pending', returnedAt: completed ? input.timestamp : null, returnedBy: completed ? { source: 'dev_mock' } : null, autoReturnEnabled: true, }, consume: { status: completed ? 'success' : 'pending', shopId: input.consumeShopId, shopName: '', autoConsumeEnabled: true, consumedAt: completed ? input.timestamp : null, errorMessage: '', }, }, } } function resolveLewanTaskStatus(step: LewanMockStep) { if (step === 'result') return TASK_STATUS.COMPLETED if (step === 'confirm') return TASK_STATUS.ROLE_CONFIRMED if (step === 'binding') return TASK_STATUS.WAITING_BINDING return TASK_STATUS.LINK_GENERATED } function normalizeLewanStep(value: unknown): LewanMockStep { const step = String(value || 'uid').trim() if (['uid', 'binding', 'confirm', 'result'].includes(step)) { return step as LewanMockStep } // 兼容旧 ticket 命名 if (step === 'ticket') return 'uid' return 'uid' } function normalizeFeifeiStep(value: unknown): FeifeiMockStep { const step = String(value || 'uid').trim() if (['uid', 'ready', 'completed', 'failed'].includes(step)) { return step as FeifeiMockStep } return 'uid' } function normalizeAffiliateDashStep(value: unknown): AffiliateDashMockStep { const step = String(value || 'uid').trim() if (['uid', 'bind', 'submitted', 'completed', 'failed'].includes(step)) { return step as AffiliateDashMockStep } return 'uid' } function resolveMockUid(value: unknown, fallback: string) { const raw = String(value ?? '').trim() if (!raw) { return normalizeClaimUid(fallback) } return assertValidClaimUid(raw) } function resolveMockIndustrySellerId(value: unknown): string { const sellerId = String(value || '').trim() if (sellerId) { return sellerId } const shop = listKuaishouIndustryShopConfigs(getKuaishouIndustrySourceConfig()).find( (item) => item.enabled !== false && (item.sellerId || item.shopId), ) return String(shop?.sellerId || shop?.shopId || '141242642').trim() } function parseDeliveryItems(value: unknown): DeliveryItem[] { if (!value) { return [ { cloudSkuId: 910001, cloudSkuName: 'Mock 商品', quantity: 1, }, ] } if (typeof value === 'string') { try { return parseDeliveryItems(JSON.parse(value)) } catch { return parseDeliveryItems(null) } } if (!Array.isArray(value)) { return parseDeliveryItems(null) } const items = value .map((item) => { const source = item && typeof item === 'object' ? (item as Record) : {} const cloudSkuId = Number(source.cloudSkuId || source.skuId || 0) || 0 const quantity = Number(source.quantity || 1) || 1 if (!cloudSkuId) return null return { cloudSkuId, cloudSkuName: String(source.cloudSkuName || source.skuName || 'Mock 商品').trim(), quantity: quantity > 0 ? quantity : 1, } }) .filter((item): item is DeliveryItem => Boolean(item)) return items.length > 0 ? items : parseDeliveryItems(null) } function buildCreateResult(input: { platform: 'lewan' | 'feifei' | 'affiliate_dash' orderId: number orderNo: string taskId: number taskNo: string claimToken: string claimUrl: string frontendBaseUrl?: unknown expectedUid: string step: string productName: string tips: string[] }): DevMockCreateResult { return { platform: input.platform, orderId: input.orderId, orderNo: input.orderNo, taskId: input.taskId, taskNo: input.taskNo, claimToken: input.claimToken, claimUrl: input.claimUrl, frontendClaimUrl: buildFrontendClaimUrl(input.claimToken, input.frontendBaseUrl), expectedUid: input.expectedUid, step: input.step, productName: input.productName, tips: input.tips, open91QueryHint: `可在 Mock 页「91 查单」使用 orderNo=${input.orderNo},或 CLI:npm run mock:open91 -- --mode=query --orderNo=${input.orderNo}`, } } function buildFrontendClaimUrl(token: string, frontendBaseUrl?: unknown) { const base = String(frontendBaseUrl || '').trim() if (base) { return `${base.replace(/\/+$/, '')}/#/claim/${token}` } return `http://127.0.0.1/#/claim/${token}` } function buildLewanTips(step: LewanMockStep, uid: string) { if (step === 'uid') { return [ `打开领取链接后,Step1 填写 UID:${uid}`, '提交后进入绑定步;mock 绑定链接仅作展示,点击刷新/确认前请保证 UID 与角色 ID 一致', ] } if (step === 'binding') { return [ `已预填 UID=${uid} 且角色 ID 已匹配`, '可直接点「UID 已匹配,下一步」→ 确认兑换(mock 不会真实发货)', ] } if (step === 'confirm') { return ['已进入确认兑换步,点击确认兑换即可模拟成功'] } return ['已模拟兑换完成,可直接看结果页'] } function buildFeifeiTips(step: FeifeiMockStep, uid: string) { if (step === 'uid') { return [`打开领取链接,Step1 填 UID:${uid}`, '提交后点「打开领取链接」,URL 应带 uid 参数'] } if (step === 'ready') { return [`已预填 UID=${uid},可直接打开带 uid 的 H5(mock 链接,无需真实平台)`] } if (step === 'failed') { return ['已模拟 feifei 失败态,用于看领取页结果展示'] } return ['已模拟 feifei 成功完成态'] } function buildAffiliateDashTips(step: AffiliateDashMockStep, uid: string) { if (step === 'uid') { return [`打开领取链接,Step1 填 UID:${uid}(或自定义)`, '提交后进入绑定步,mock 会给出二维码'] } if (step === 'bind') { return [ `已预填 UID=${uid},绑定二维码为 mock 生成(扫描无效)`, '正常流程:扫码完成真实绑定后自动进入下一步', ] } if (step === 'submitted') { return ['已模拟绑定成功,可点「提交发货」(mock 直接模拟发货成功)'] } if (step === 'failed') { return ['已模拟发货失败态,用于看领取页结果展示'] } return ['已模拟 affiliate-dash 发货完成态'] } function signOpen91Payload(payload: Record, secret: string) { const queryString = Object.entries(payload) .filter(([key]) => key !== 'sign') .sort(([left], [right]) => left.localeCompare(right)) .map(([key, value]) => `${key}=${value == null ? '' : String(value)}`) .join('&') return crypto .createHash('md5') .update(`${secret}${queryString}${secret}`, 'utf8') .digest('hex') .toUpperCase() }