// @ts-check import { query } from '../../db/client.js' import { getAgisoMessagingDefaults, getAgisoShopConfig, getAgisoShopConfigMap, getAgisoShopsFilePath, saveAgisoMessagingConfig, } from '../platforms/agiso/shop-config-service.js' import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js' import { mapKhhaoOrderPreviewList } from '../platforms/khhao/order-mapper-service.js' import { queryKhhaoOrderList } from '../platforms/khhao/order-query-service.js' import { syncKhhaoOrders } from '../platforms/khhao/order-sync-service.js' import { getKhhaoSourceConfig, getKhhaoSourcesFilePath, saveKhhaoSourceConfig } from '../platforms/khhao/source-config-service.js' import { ensureKhhaoSyncBaseline, getKhhaoSyncState, getKhhaoSyncStateFilePath } from '../platforms/khhao/sync-state-service.js' import { loginKhhaoSession } from '../platforms/khhao/session-service.js' import { consumeKuaishouEticket, queryKuaishouEticketConsumeDetail, } from '../platforms/kuaishou-eticket/consume-service.js' import { findKuaishouEticketShopConfig, getFirstAvailableKuaishouEticketShop, getKuaishouEticketSourceConfig, getKuaishouEticketSourceFilePath, listKuaishouEticketShopConfigs, resolveKuaishouEticketShopConfig, saveKuaishouEticketSourceConfig, } from '../platforms/kuaishou-eticket/source-config-service.js' import { queryKuaishouEticketStableInfo } from '../platforms/kuaishou-eticket/info-service.js' import { getCloudtentaclesSourceConfig, getCloudtentaclesSourcesFilePath, saveCloudtentaclesSourceConfig, } from '../platforms/cloudtentacles/source-config-service.js' import { clearCloudtentaclesSessionState, getCloudtentaclesSessionFilePath, getCloudtentaclesSessionState, saveCloudtentaclesSessionState, } from '../platforms/cloudtentacles/session-state-service.js' import { loginCloudtentaclesSession, sendCloudtentaclesSmsCode, validateCloudtentaclesSession, } from '../platforms/cloudtentacles/session-service.js' import { buyCloudtentaclesSku, getCloudtentaclesAsset, getCloudtentaclesCategories, listCloudtentaclesSku, useCloudtentaclesSku, } from '../platforms/cloudtentacles/catalog-service.js' import { getCloudtentaclesKnapsack } from '../platforms/cloudtentacles/knapsack-service.js' import { appointCloudtentaclesVirtualNumber, backCloudtentaclesVirtualNumber, fetchCloudtentaclesVirtualNumberCode, generateCloudtentaclesLoginCode, getCloudtentaclesBindUrl, listCloudtentaclesVirtualNumbers, verifyCloudtentaclesLoginCode, } from '../platforms/cloudtentacles/virtual-number-service.js' import { runCloudtentaclesFullDebugFlow } from '../platforms/cloudtentacles/debug-flow-service.js' import { getOrderFulfillmentBindingConfigs, getOrderFulfillmentBindingsFilePath, saveOrderFulfillmentBindingConfigs, } from '../order/fulfillment-binding-config-service.js' import { getKuaishouCloudFulfillmentConfig, getKuaishouCloudFulfillmentFilePath, saveKuaishouCloudFulfillmentConfig, } from '../order/kuaishou-cloud-fulfillment-config-service.js' import { getFulfillmentProfileByKey } from '../../repositories/fulfillment-profile-repo.js' import { createHttpError } from '../../utils/http.js' import { syncConfiguredFulfillmentBindings } from '../bootstrap/fulfillment-bootstrap-service.js' import { resolveDisplayShopName } from './admin-read-shared-helpers.js' import { hasCloudtentaclesCredentialContextChanged, isPlainObject, pickFirstNonEmpty, resolveCloudtentaclesAdminContext, resolveKuaishouEticketAdminContext, } from './admin-platform-config-context.js' import { buildFulfillmentLookupResult, normalizeFulfillmentLookupPayload, resolveFulfillmentLookupDetail, } from './admin-platform-config-fulfillment.js' import { listAdminObservedProducts } from './admin-platform-config-observed-products.js' import { assertAdminFulfillmentBindingsInput, buildAdminFulfillmentBindingUniqueKey, normalizeAdminFulfillmentBindingItem, } from './admin-platform-config-validation.js' import { applyOptionalStringField, mapAdminAgisoMessagingDefaults, mapAdminAgisoShopConfigItem, mapAdminFulfillmentBindingConfigItem, } from './admin-platform-config-domain.js' import { mapAdminCloudtentaclesSession, mapAdminCloudtentaclesSourceConfig, mapAdminKhhaoSourceConfig, mapAdminKhhaoSyncState, mapAdminKuaishouCloudFulfillmentSource, mapAdminKuaishouEticketSourceConfig, maskPhone, maskSecret, } from './admin-platform-config-mappers.js' /** @typedef {import('../../types/admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigSaveInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKuaishouEticketSourceConfigInput} AdminKuaishouEticketSourceConfigInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKuaishouEticketShopInfoInput} AdminKuaishouEticketShopInfoInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKuaishouEticketDetailQueryInput} AdminKuaishouEticketDetailQueryInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKuaishouEticketConsumeInput} AdminKuaishouEticketConsumeInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesSourceConfigInput} AdminCloudtentaclesSourceConfigInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminKuaishouCloudFulfillmentConfigInput} AdminKuaishouCloudFulfillmentConfigInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesSendSmsCodeInput} AdminCloudtentaclesSendSmsCodeInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesTestLoginInput} AdminCloudtentaclesTestLoginInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesValidateSessionInput} AdminCloudtentaclesValidateSessionInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesCatalogQueryInput} AdminCloudtentaclesCatalogQueryInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesSkuBuyInput} AdminCloudtentaclesSkuBuyInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesSkuUseInput} AdminCloudtentaclesSkuUseInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesVirtualNumberInput} AdminCloudtentaclesVirtualNumberInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminCloudtentaclesFullFlowInput} AdminCloudtentaclesFullFlowInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingLookupInput} AdminFulfillmentBindingLookupInput */ export async function getAdminAgisoShopConfigs() { const configMap = getAgisoShopConfigMap() const defaults = getAgisoMessagingDefaults() const rowsResult = await query( ` SELECT shop_id, MAX(CASE WHEN trim(shop_name) != '' THEN shop_name ELSE '' END) AS detected_shop_name, MAX(created_at) AS latest_seen_at, COUNT(*)::int AS webhook_event_count FROM webhook_events WHERE provider = 'agiso' AND trim(shop_id) != '' GROUP BY shop_id ORDER BY latest_seen_at DESC, shop_id DESC `, ) const rows = rowsResult.rows return { filePath: getAgisoShopsFilePath(), defaults: mapAdminAgisoMessagingDefaults(defaults), shops: Object.entries(configMap) .sort(([left], [right]) => left.localeCompare(right)) .map(([shopId, config]) => mapAdminAgisoShopConfigItem(shopId, config)), observedShops: rows.map((row) => ({ shopId: String(row.shop_id || '').trim(), detectedShopName: String(row.detected_shop_name || '').trim(), displayShopName: resolveDisplayShopName('agiso', row.shop_id, row.detected_shop_name), latestSeenAt: row.latest_seen_at || null, webhookEventCount: Number(row.webhook_event_count || 0), configured: Boolean(configMap[String(row.shop_id || '').trim()]), })), } } /** @param {AdminAgisoShopConfigSaveInput} [payload] */ export function updateAdminAgisoShopConfigs(payload = /** @type {AdminAgisoShopConfigSaveInput} */ ({})) { const rawItems = Array.isArray(payload.shops) ? payload.shops : [] const currentDefaults = getAgisoMessagingDefaults() const currentMap = getAgisoShopConfigMap() const nextDefaults = { ...currentDefaults } const nextMap = {} applyOptionalStringField(nextDefaults, 'messageTemplate', payload.defaults) applyOptionalStringField(nextDefaults, 'autoDeliveryMessageTemplate', payload.defaults) for (const item of rawItems) { const shopId = String(item?.shopId || '').trim() if (!shopId) { continue } const current = currentMap[shopId] || {} const next = { ...current } const shopName = String(item?.shopName || '').trim() const accessToken = String(item?.accessToken || '').trim() const messageTemplate = String(item?.messageTemplate || '').trim() const autoDeliveryMessageTemplate = String(item?.autoDeliveryMessageTemplate || '').trim() const appSecret = String(item?.appSecret || '').trim() const apiVersion = String(item?.apiVersion || '').trim() const sendMessageEndpoint = String(item?.sendMessageEndpoint || '').trim() if (shopName) { next.shopName = shopName } else { delete next.shopName } if (accessToken) { next.accessToken = accessToken } if (messageTemplate) { next.messageTemplate = messageTemplate } else if (typeof item?.messageTemplate === 'string') { delete next.messageTemplate } if (autoDeliveryMessageTemplate) { next.autoDeliveryMessageTemplate = autoDeliveryMessageTemplate } else if (typeof item?.autoDeliveryMessageTemplate === 'string') { delete next.autoDeliveryMessageTemplate } if (appSecret) { next.appSecret = appSecret } if (apiVersion) { next.apiVersion = apiVersion } if (sendMessageEndpoint) { next.sendMessageEndpoint = sendMessageEndpoint } if (typeof item?.enabled === 'boolean') { next.enabled = item.enabled } if (!next.accessToken) { continue } nextMap[shopId] = next } const saved = saveAgisoMessagingConfig({ defaults: nextDefaults, shops: nextMap, }) return { filePath: getAgisoShopsFilePath(), defaults: mapAdminAgisoMessagingDefaults(saved.defaults), shops: Object.entries(saved.shops) .sort(([left], [right]) => left.localeCompare(right)) .map(([shopId, config]) => mapAdminAgisoShopConfigItem(shopId, config)), } } export function getAdminKhhaoSourceConfig() { const config = getKhhaoSourceConfig() const syncState = getKhhaoSyncState() return { filePath: getKhhaoSourcesFilePath(), stateFilePath: getKhhaoSyncStateFilePath(), source: mapAdminKhhaoSourceConfig(config), syncState: mapAdminKhhaoSyncState(syncState), } } export function getAdminKuaishouEticketSourceConfig() { const config = getKuaishouEticketSourceConfig() return { filePath: getKuaishouEticketSourceFilePath(), source: mapAdminKuaishouEticketSourceConfig(config, listKuaishouEticketShopConfigs(config)), } } /** @param {AdminKuaishouEticketSourceConfigInput} [payload] */ export function updateAdminKuaishouEticketSourceConfig(payload = /** @type {AdminKuaishouEticketSourceConfigInput} */ ({})) { 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)), } } /** @param {AdminKhhaoSourceConfigInput} [payload] */ export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSourceConfigInput} */ ({})) { const saved = saveKhhaoSourceConfig({ enabled: payload.enabled !== false, baseUrl: String(payload.baseUrl || '').trim() || 'https://admin.khhao.com', username: String(payload.username || '').trim(), password: String(payload.password || '').trim(), maxCaptchaAttempts: Number(payload.maxCaptchaAttempts || 3) || 3, autoSync: { enabled: payload.autoSync?.enabled === true, intervalMinutes: Number(payload.autoSync?.intervalMinutes || 3) || 3, pages: Number(payload.autoSync?.pages || 2) || 2, pageSize: Number(payload.autoSync?.pageSize || 20) || 20, }, }) const syncState = saved.autoSync?.enabled ? ensureKhhaoSyncBaseline() : getKhhaoSyncState() return { filePath: getKhhaoSourcesFilePath(), stateFilePath: getKhhaoSyncStateFilePath(), source: mapAdminKhhaoSourceConfig(saved), syncState: mapAdminKhhaoSyncState(syncState), } } /** @param {AdminKhhaoTestLoginInput} [payload] */ export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLoginInput} */ ({})) { const savedSource = getKhhaoSourceConfig() const session = await loginKhhaoSession({ baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), username: String(payload.username || savedSource.username || '').trim(), password: String(payload.password || savedSource.password || '').trim(), maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, includeImageBase64: payload.includeImageBase64, requestId: `admin-khhao-test-login:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`, }) return { baseUrl: session.baseUrl, username: session.username, loggedInAt: session.loggedInAt, attempt: session.attempt, responseMessage: session.responseMessage, captcha: { recognizedText: session.captchaText, imageBase64: session.captchaImageBase64, }, session: { cookieKeys: Object.keys(session.cookieMap), cookieCount: Object.keys(session.cookieMap).length, cookieHeaderMasked: maskSecret(session.cookieHeader), }, } } /** @param {AdminKhhaoOrderQueryInput} [payload] */ export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { const savedSource = getKhhaoSourceConfig() const session = await loginKhhaoSession({ baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), username: String(payload.username || savedSource.username || '').trim(), password: String(payload.password || savedSource.password || '').trim(), maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, requestId: `admin-khhao-query-orders:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`, }) const result = await queryKhhaoOrderList({ baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), page: payload.page, limit: payload.limit, session, }) const previews = mapKhhaoOrderPreviewList(result.items) return { baseUrl: session.baseUrl, page: result.page, limit: result.limit, total: result.total, itemCount: result.items.length, items: previews, rawItems: result.items, } } /** @param {AdminKhhaoOrderQueryInput} [payload] */ export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { const savedSource = getKhhaoSourceConfig() return syncKhhaoOrders({ ...payload, baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), username: String(payload.username || savedSource.username || '').trim(), password: String(payload.password || savedSource.password || '').trim(), maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, }) } /** @param {AdminKuaishouEticketDetailQueryInput} [payload] */ export async function queryAdminKuaishouEticketDetail(payload = /** @type {AdminKuaishouEticketDetailQueryInput} */ ({})) { const context = resolveKuaishouEticketAdminContext(payload, { savedSource: getKuaishouEticketSourceConfig(), findShopConfig: findKuaishouEticketShopConfig, getFirstAvailableShop: getFirstAvailableKuaishouEticketShop, }) return queryKuaishouEticketConsumeDetail({ baseUrl: context.baseUrl, cookie: context.cookie, eTicketId: String(payload.eTicketId || '').trim(), }) } /** @param {AdminKuaishouEticketShopInfoInput} [payload] */ export async function queryAdminKuaishouEticketShopInfo(payload = /** @type {AdminKuaishouEticketShopInfoInput} */ ({})) { const context = resolveKuaishouEticketAdminContext(payload, { savedSource: getKuaishouEticketSourceConfig(), findShopConfig: findKuaishouEticketShopConfig, getFirstAvailableShop: getFirstAvailableKuaishouEticketShop, }) return queryKuaishouEticketStableInfo({ baseUrl: context.baseUrl, cookie: context.cookie, }) } /** @param {AdminKuaishouEticketConsumeInput} [payload] */ export async function consumeAdminKuaishouEticket(payload = /** @type {AdminKuaishouEticketConsumeInput} */ ({})) { const context = resolveKuaishouEticketAdminContext(payload, { savedSource: getKuaishouEticketSourceConfig(), findShopConfig: findKuaishouEticketShopConfig, getFirstAvailableShop: getFirstAvailableKuaishouEticketShop, }) 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(), }) } export function getAdminCloudtentaclesSourceConfig() { const config = getCloudtentaclesSourceConfig() const session = getCloudtentaclesSessionState() return { filePath: getCloudtentaclesSourcesFilePath(), sessionFilePath: getCloudtentaclesSessionFilePath(), source: mapAdminCloudtentaclesSourceConfig(config), session: mapAdminCloudtentaclesSession(session), } } /** @param {AdminCloudtentaclesSourceConfigInput} [payload] */ export function updateAdminCloudtentaclesSourceConfig(payload = /** @type {AdminCloudtentaclesSourceConfigInput} */ ({})) { const current = getCloudtentaclesSourceConfig() const saved = saveCloudtentaclesSourceConfig({ enabled: payload.enabled !== false, baseUrl: String(payload.baseUrl || '').trim() || 'https://123.207.217.176', username: String(payload.username || '').trim(), password: String(payload.password || '').trim(), phone: String(payload.phone || '').trim(), deviceId: String(payload.deviceId || '-').trim() || '-', deviceType: Number(payload.deviceType || 0), }) const shouldClearSession = hasCloudtentaclesCredentialContextChanged(current, saved) const session = shouldClearSession ? clearCloudtentaclesSessionState() : getCloudtentaclesSessionState() return { filePath: getCloudtentaclesSourcesFilePath(), sessionFilePath: getCloudtentaclesSessionFilePath(), source: mapAdminCloudtentaclesSourceConfig(saved), session: mapAdminCloudtentaclesSession(session), } } /** @param {AdminCloudtentaclesSendSmsCodeInput} [payload] */ export async function sendAdminCloudtentaclesSmsCode(payload = /** @type {AdminCloudtentaclesSendSmsCodeInput} */ ({})) { const savedSource = getCloudtentaclesSourceConfig() const result = await sendCloudtentaclesSmsCode({ baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://123.207.217.176']), username: pickFirstNonEmpty([payload.username, savedSource.username]), phone: pickFirstNonEmpty([payload.phone, savedSource.phone]), deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, '-']), deviceType: payload.deviceType ?? savedSource.deviceType, }) return { ...result, phoneMasked: maskPhone(result.phone), } } /** @param {AdminCloudtentaclesTestLoginInput} [payload] */ export async function testAdminCloudtentaclesLogin(payload = /** @type {AdminCloudtentaclesTestLoginInput} */ ({})) { const savedSource = getCloudtentaclesSourceConfig() const session = await loginCloudtentaclesSession({ baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://123.207.217.176']), username: pickFirstNonEmpty([payload.username, savedSource.username]), password: pickFirstNonEmpty([payload.password, savedSource.password]), phone: pickFirstNonEmpty([payload.phone, savedSource.phone]), code: String(payload.code || '').trim(), deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, '-']), deviceType: payload.deviceType ?? savedSource.deviceType, }) const savedSession = saveCloudtentaclesSessionState({ token: session.token, baseUrl: session.baseUrl, username: session.username, phone: session.phone, loggedInAt: session.loggedInAt, deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, '-']), deviceType: payload.deviceType ?? savedSource.deviceType, }) return { baseUrl: session.baseUrl, username: session.username, phoneMasked: maskPhone(session.phone), loggedInAt: session.loggedInAt, responseMessage: session.responseMessage, token: session.token, session: { tokenMasked: maskSecret(session.token), permissionCount: session.permissions.length, permissions: session.permissions, persisted: Boolean(savedSession.token), }, userInfo: session.userInfo, asset: session.asset, } } /** @param {AdminCloudtentaclesValidateSessionInput} [payload] */ export async function validateAdminCloudtentaclesSession(payload = /** @type {AdminCloudtentaclesValidateSessionInput} */ ({})) { const savedSource = getCloudtentaclesSourceConfig() const persistedSession = getCloudtentaclesSessionState() const session = await validateCloudtentaclesSession({ baseUrl: pickFirstNonEmpty([payload.baseUrl, savedSource.baseUrl, 'https://123.207.217.176']), token: pickFirstNonEmpty([payload.token, persistedSession.token]), deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, '-']), deviceType: payload.deviceType ?? savedSource.deviceType, }) const savedSession = saveCloudtentaclesSessionState({ token: session.token, baseUrl: session.baseUrl, username: pickFirstNonEmpty([persistedSession.username, savedSource.username]), phone: pickFirstNonEmpty([persistedSession.phone, savedSource.phone]), loggedInAt: session.loggedInAt, deviceId: pickFirstNonEmpty([payload.deviceId, savedSource.deviceId, persistedSession.deviceId, '-']), deviceType: payload.deviceType ?? savedSource.deviceType ?? persistedSession.deviceType, }) return { baseUrl: session.baseUrl, loggedInAt: session.loggedInAt, session: { tokenMasked: maskSecret(session.token), permissionCount: session.permissions.length, permissions: session.permissions, persisted: Boolean(savedSession.token), }, userInfo: session.userInfo, asset: session.asset, } } /** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ export async function getAdminCloudtentaclesAsset(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return getCloudtentaclesAsset(context) } /** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ export async function getAdminCloudtentaclesCategories(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return getCloudtentaclesCategories(context) } /** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ export async function getAdminCloudtentaclesSkuList(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return listCloudtentaclesSku(context) } /** @param {AdminCloudtentaclesSkuBuyInput} [payload] */ export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuBuyInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return buyCloudtentaclesSku({ ...context, id: payload.id, count: payload.count, }) } /** @param {AdminCloudtentaclesSkuUseInput} [payload] */ export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuUseInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return useCloudtentaclesSku({ ...context, id: payload.id, virtualNumberId: payload.virtualNumberId, phone: payload.phone, }) } /** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ export async function getAdminCloudtentaclesKnapsack(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return getCloudtentaclesKnapsack(context) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return listCloudtentaclesVirtualNumbers({ ...context, key: payload.key, }) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return appointCloudtentaclesVirtualNumber({ ...context, key: payload.key, }) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return generateCloudtentaclesLoginCode({ ...context, key: payload.key, id: payload.id, }) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return fetchCloudtentaclesVirtualNumberCode({ ...context, key: payload.key, phone: payload.phone, }) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return verifyCloudtentaclesLoginCode({ ...context, key: payload.key, id: payload.id, code: payload.code, }) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return getCloudtentaclesBindUrl({ ...context, key: payload.key, id: payload.id, }) } /** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return backCloudtentaclesVirtualNumber({ ...context, key: payload.key, id: payload.id, }) } /** @param {AdminCloudtentaclesFullFlowInput} [payload] */ export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) { const context = resolveCloudtentaclesAdminContext(payload, { savedSource: getCloudtentaclesSourceConfig(), persistedSession: getCloudtentaclesSessionState(), }) return runCloudtentaclesFullDebugFlow({ ...context, skuId: payload.skuId, skuCount: payload.skuCount, vnKey: payload.vnKey, }) } export async function getAdminFulfillmentBindingConfigs() { const bindings = getOrderFulfillmentBindingConfigs() return { filePath: getOrderFulfillmentBindingsFilePath(), bindings: bindings.map(mapAdminFulfillmentBindingConfigItem), observedProducts: await listAdminObservedProducts(bindings), } } export function getAdminKuaishouCloudFulfillmentConfig() { const config = getKuaishouCloudFulfillmentConfig() return { filePath: getKuaishouCloudFulfillmentFilePath(), source: mapAdminKuaishouCloudFulfillmentSource(config), } } /** @param {AdminKuaishouCloudFulfillmentConfigInput} [payload] */ export async function updateAdminKuaishouCloudFulfillmentConfig( payload = /** @type {AdminKuaishouCloudFulfillmentConfigInput} */ ({}), ) { const saved = saveKuaishouCloudFulfillmentConfig({ enabled: payload.enabled !== false, items: Array.isArray(payload.items) ? payload.items : [], }) await syncConfiguredFulfillmentBindings() return { filePath: getKuaishouCloudFulfillmentFilePath(), source: mapAdminKuaishouCloudFulfillmentSource(saved), } } /** @param {AdminFulfillmentBindingLookupInput} [payload] */ export async function lookupAdminFulfillmentBindingOrder(payload = /** @type {AdminFulfillmentBindingLookupInput} */ ({})) { const { provider, platform, shopId, platformOrderId } = normalizeFulfillmentLookupPayload(payload) const detailResult = await enrichAgisoXianyuTradeOrder({ provider, platform, shopId, shopName: '', platformOrderId, orderStatus: 'created', payStatus: 'unpaid', buyerId: '', buyerName: '', receiverContact: '', totalAmount: 0, currency: 'CNY', paidAt: null, rawPayload: {}, items: [], }, { requestId: `admin-fulfillment-lookup:${shopId}:${platformOrderId}`, }) const { detail } = resolveFulfillmentLookupDetail(detailResult, platformOrderId) const bindings = getOrderFulfillmentBindingConfigs() return buildFulfillmentLookupResult({ provider, platform, shopId, platformOrderId, detail, detailResult, bindings, fallbackShopName: getAgisoShopConfig(shopId)?.shopName, }) } /** @param {AdminFulfillmentBindingConfigSaveInput} [payload] */ export async function updateAdminFulfillmentBindingConfigs( payload = /** @type {AdminFulfillmentBindingConfigSaveInput} */ ({}), ) { const bindingsInput = Array.isArray(payload.bindings) ? payload.bindings : [] const normalizedBindings = await validateAdminFulfillmentBindingConfigs(bindingsInput) const saved = saveOrderFulfillmentBindingConfigs(normalizedBindings) await syncConfiguredFulfillmentBindings() return { filePath: getOrderFulfillmentBindingsFilePath(), bindings: saved.map(mapAdminFulfillmentBindingConfigItem), } } async function validateAdminFulfillmentBindingConfigs(bindings = []) { assertAdminFulfillmentBindingsInput(bindings) const seenKeys = new Set() const kuaishouEticketSource = getKuaishouEticketSourceConfig() const normalizedBindings = [] for (const [index, rawBinding] of bindings.entries()) { const normalized = normalizeAdminFulfillmentBindingItem(rawBinding, { index, kuaishouEticketSource, resolveKuaishouEticketShopConfig, }) const profile = await getFulfillmentProfileByKey(normalized.profileKey) if (!profile) { throw createHttpError(`第 ${index + 1} 条规则使用了不存在的履约方式: ${normalized.profileKey}`, { statusCode: 400, errorCode: 'admin_fulfillment_bindings_invalid_profile_key', }) } const uniqueKey = buildAdminFulfillmentBindingUniqueKey(normalized) if (seenKeys.has(uniqueKey)) { throw createHttpError(`第 ${index + 1} 条规则与其它规则重复,请调整匹配条件或内部履约 SKU`, { statusCode: 409, errorCode: 'admin_fulfillment_bindings_duplicate_rule', }) } seenKeys.add(uniqueKey) normalizedBindings.push(normalized) } return normalizedBindings }