/** * 开发环境专用 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 { query, withTransaction } from '../../db/client.js' import { getFulfillmentProfileByKey, upsertFulfillmentProfile, } from '../../repositories/fulfillment-profile-repo.js' import { buildOrderClaimUrl, createTaskClaimToken } from '../claim/claim-service.js' import { assertValidClaimUid, normalizeClaimUid } from '../claim/claim-identity.js' import { saveManualFulfillmentConfig } from '../cost-center/manual-fulfillment-config-service.js' import { assignFulfillmentChannelsWithExecutor, attachFulfillmentConfigurationsWithExecutor, } from '../fulfillment/fulfillment-state-service.js' import { containsCjk } from '../fulfillment/internal-sku-resolver.js' import { OPEN_91_PLATFORM, OPEN_91_PROVIDER, assertOpen91Config } from '../open-91/config.js' import { getKuaishouIndustrySourceConfig, listKuaishouIndustryShopConfigs, } from '../platforms/kuaishou-industry/source-config-service.js' import { createHttpError } from '../../utils/http.js' import { nowIso } from '../../utils/time.js' import { randomId } from '../../utils/random.js' import { TASK_STATUS } from '../../domain/task-status.js' import { ensureSemiAutoTasksForOrder } from '../semi-auto/semi-auto-task-service.js' import { defaultSemiAutoConnectorMode, isExternalSemiAutoConnectorConfigured, type SemiAutoConnectorMode, } from '../semi-auto/huya-connector-client.js' import { assertDevMockEnabled, isDevMockEnabled } from './dev-mock-enabled.js' export { assertDevMockEnabled, isDevMockEnabled } export type DevMockCreateResult = { platform: '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 }> } export type DevMockSemiAutoResult = { platform: 'semi_auto' semiAutoPlatform: 'huya' | 'weibo' orderId: number orderNo: string orderItemId: number fulfillmentId: number configId: number taskId: number skuKey: string connectorMode: SemiAutoConnectorMode connectorConfigured: boolean customerPath: string adminPath: string tips: string[] } export function getDevMockStatus() { const enabled = isDevMockEnabled() return { enabled, nodeEnv: String(process.env.NODE_ENV || 'development'), platforms: enabled ? [ { key: 'affiliate_dash', name: 'affiliate-dash', description: '本站 claim + mock 绑定二维码/提交发货(不调真实 affiliate-dash 平台)', steps: ['uid', 'bind', 'submitted', 'completed', 'failed'], }, { key: 'semi_auto', name: '半自动履约', description: '创建人工渠道半自动测试单;可选择仅 Mock 订单,登录、绑定和票据注入走真实 Connector', steps: ['create_order', 'login', 'binding', 'browser_ticket', 'complete'], }, ] : [], defaultUid: '10001', semiAuto: { defaultConnectorMode: defaultSemiAutoConnectorMode(), externalConnectorConfigured: isExternalSemiAutoConnectorConfigured(), mockConnectorAvailable: enabled, platform: 'huya', }, } } 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, now, 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, orderNo) 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: buildOrderClaimUrl(orderNo), frontendBaseUrl: input.frontendBaseUrl, expectedUid: fallbackUid, step, productName, tips: buildAffiliateDashTips(step, fallbackUid), }) } /** * 生成半自动履约测试单:人工 SKU 配置、订单、履约和半自动任务都走真实领域服务。 * 平台登录、绑定和浏览器票据按 connectorMode 选择真实 Connector 或内置 Mock。 */ export async function createSemiAutoMock( input: { platform?: unknown orderNo?: unknown skuName?: unknown quantity?: unknown pageTitle?: unknown pageDescription?: unknown browserTargetUrl?: unknown connectorMode?: unknown } = {}, ): Promise { assertDevMockEnabled() const platform = normalizeMockSemiAutoPlatform(input.platform) const orderNo = String( input.orderNo || `${platform === 'weibo' ? 'MOCKWB' : 'MOCKSA'}${Date.now()}`, ).trim() const skuKey = normalizeMockSemiAutoSku(input.skuName, platform) const quantity = normalizeMockSemiAutoQuantity(input.quantity) // 微博目前只支持内置 Connector,忽略其他模式,避免生成必然失败的测试单。 const connectorMode = platform === 'weibo' ? 'embedded' : normalizeMockSemiAutoConnectorMode(input.connectorMode) const pageTitle = String( input.pageTitle || (platform === 'weibo' ? '微博发帖授权测试' : '虎牙账号授权测试'), ) .trim() .slice(0, 60) const pageDescription = String( input.pageDescription || (platform === 'weibo' ? '开发 Mock:完成微博登录即视为授权,客服在后台完成发帖履约。' : '开发 Mock:完成登录和角色绑定后,客服在后台完成履约。'), ) .trim() .slice(0, 500) const browserTargetUrl = String( input.browserTargetUrl || (platform === 'weibo' ? 'https://weibo.com/' : 'https://www.huya.com/'), ) .trim() .slice(0, 1000) await assertOrderNoAvailable(orderNo) const config = await saveManualFulfillmentConfig({ skuKey, enabled: true, note: '开发 Mock 半自动履约', channels: [{ platformKey: 'fund_cash', creditQuantity: 100, enabled: true }], semiAutoEnabled: true, semiAutoPlatform: platform, semiAutoConfig: { pageTitle, pageDescription, loginInstructions: platform === 'weibo' ? '真实登录:使用微博绑定手机号接收验证码登录,或使用微博 App 扫码登录。登录仅用于本次订单履约,不会在页面展示账号 Cookie。' : connectorMode === 'mock' ? 'Mock 登录:点击获取二维码后,页面轮询两次会自动模拟登录成功。' : '真实登录:点击获取二维码后,请使用虎牙 App 扫码并在手机上确认登录。', ...(platform === 'weibo' ? { loginMethods: ['sms', 'qr'] } : {}), browserTargets: [ { key: platform, label: platform === 'weibo' ? '微博' : '虎牙', url: browserTargetUrl, }, ], }, }) const configId = Number(config.id || 0) const canonicalProductId = config.canonicalProductId ? Number(config.canonicalProductId) : null if (!configId) { throw createHttpError('半自动履约 Mock 配置创建失败', { statusCode: 500, errorCode: 'dev_mock_semi_auto_config_failed', }) } const now = nowIso() const order = await createBaseOrder({ orderNo, productNo: skuKey, productName: skuKey, now, platformTag: 'semi-auto', totalAmount: 100 * quantity, }) const [orderItem] = await replaceOrderItems(order.id, [ { skuCode: skuKey, skuName: skuKey, quantity, specJson: JSON.stringify({ source: 'dev_mock', skuNick: skuKey, semiAuto: { platform, mock: true }, }), itemSnapshotJson: JSON.stringify({ source: 'dev_mock', skuKey, platform, mock: true, }), createdAt: now, updatedAt: now, }, ]) const orderItemId = Number(orderItem?.id || 0) if (!orderItemId) { throw createHttpError('半自动履约 Mock 订单项创建失败', { statusCode: 500, errorCode: 'dev_mock_semi_auto_item_failed', }) } const fulfillmentResult = await query<{ id: number }>( 'SELECT id FROM fulfillments WHERE order_item_id = $1 LIMIT 1', [orderItemId], ) const fulfillmentId = Number(fulfillmentResult.rows[0]?.id || 0) if (!fulfillmentId) { throw createHttpError('半自动履约 Mock 履约单创建失败', { statusCode: 500, errorCode: 'dev_mock_semi_auto_fulfillment_failed', }) } await withTransaction(async (client) => { await assignFulfillmentChannelsWithExecutor(client, { assignments: [{ orderItemId, channel: 'manual', source: 'manual_confirm' }], eventType: 'dev_mock_semi_auto_channel_assigned', reason: 'dev_mock', actor: { type: 'system' }, }) await attachFulfillmentConfigurationsWithExecutor(client, { mappings: [ { orderItemId, configId, ...(canonicalProductId ? { canonicalProductId } : {}), }, ], expected: { status: 'assigned', channel: 'manual' }, onlyWhenConfigNull: true, }) }) const ensured = await ensureSemiAutoTasksForOrder(orderNo) let taskId = Number(ensured.createdIds[0] || 0) if (!taskId) { const existing = await query<{ id: number }>( 'SELECT id FROM semi_auto_tasks WHERE order_item_id = $1 LIMIT 1', [orderItemId], ) taskId = Number(existing.rows[0]?.id || 0) } if (!taskId) { throw createHttpError('半自动履约 Mock 任务创建失败', { statusCode: 500, errorCode: 'dev_mock_semi_auto_task_failed', }) } await query('UPDATE semi_auto_tasks SET connector_mode = $2, updated_at = NOW() WHERE id = $1', [ taskId, connectorMode, ]) const connectorConfigured = connectorMode === 'external' ? isExternalSemiAutoConnectorConfigured() : true return { platform: 'semi_auto', semiAutoPlatform: platform, orderId: Number(order.id), orderNo, orderItemId, fulfillmentId, configId, taskId, skuKey, connectorMode, connectorConfigured, customerPath: `/c/${encodeURIComponent(orderNo)}`, adminPath: `/admin/semi-auto-tasks?keyword=${encodeURIComponent(orderNo)}`, tips: platform === 'weibo' ? [ '完成履约仍走真实确认流程,会写入成本快照和财务确认。', '微博使用内置 Connector:短信验证码会真实发送到客户手机号;扫码登录需在微博 App 中确认。', '后台领取任务后生成 weibo-ticket-* 浏览器票据,扩展消费票据后写入微博 Cookie。', '微博登录成功即视为授权完成,任务会直接进入等待客服处理。', ] : [ '完成履约仍走真实确认流程,会写入成本快照和财务确认。', connectorMode === 'mock' ? '当前使用内置 Mock Connector:打开客户页点击登录,轮询两次后自动登录;绑定同样自动识别角色。' : connectorMode === 'external' ? connectorConfigured ? '当前使用远程真实 Connector:订单是 Mock 的,登录、绑定和浏览器票据都走外部服务。' : '当前选择远程真实 Connector,但 SEMI_AUTO_CONNECTOR_BASE_URL / API_KEY 未配置,登录会失败。' : '当前使用 order_site 内置真实 Connector:无需启动 live-hub-py,扫码登录、绑定和票据都在本进程完成。', connectorMode === 'mock' ? '后台领取任务后生成浏览器票据;扩展消费票据后写入 Mock Cookie。' : connectorMode === 'external' ? '请确保远程 Connector 已启动,并保证两边 SEMI_AUTO_CONNECTOR_API_KEY 一致。' : '登录凭据只保存在当前进程内存中,重启后端会要求客户重新扫码。', ], } } function normalizeMockSemiAutoPlatform(value: unknown): 'huya' | 'weibo' { return String(value || '') .trim() .toLowerCase() === 'weibo' ? 'weibo' : 'huya' } function normalizeMockSemiAutoConnectorMode(value: unknown): SemiAutoConnectorMode { const mode = String(value || '') .trim() .toLowerCase() if (mode === 'external' || mode === 'mock') return mode return 'embedded' } function normalizeMockSemiAutoSku(value: unknown, platform: 'huya' | 'weibo' = 'huya') { const prefix = platform === 'weibo' ? '微博半自动' : '虎牙半自动' const raw = String(value || '').trim() const withCjk = raw ? containsCjk(raw) ? raw : `${prefix}-${raw}` : `${prefix}测试-${Date.now()}` return withCjk.slice(0, 48) } function normalizeMockSemiAutoQuantity(value: unknown) { const parsed = Math.trunc(Number(value || 1)) if (!Number.isFinite(parsed) || parsed < 1) return 1 return Math.min(10, parsed) } 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 totalAmount?: number }) { const totalAmount = input.totalAmount ?? 100 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, 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: (totalAmount / 100).toFixed(2), }, }), 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 now: string productCode?: string }) { const snapshot = { source: OPEN_91_PROVIDER, orderNo: input.orderNo, productNo: input.productNo, productName: input.productName, affiliateDash: { sku: input.productCode || input.productNo, productName: input.productName, matchMode: 'mock', }, } 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 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 buildCreateResult(input: { platform: '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: buildOrderClaimUrl(input.orderNo, input.frontendBaseUrl || undefined), 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 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() }