diff --git a/apps/backend/src/services/admin/platform-config/agiso-service.js b/apps/backend/src/services/admin/platform-config/agiso-service.js new file mode 100644 index 00000000..d839d9fc --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/agiso-service.js @@ -0,0 +1,68 @@ +// @ts-check + +import { query } from '../../../db/client.js' +import { + getAgisoMessagingDefaults, + getAgisoShopConfigMap, + getAgisoShopsFilePath, + saveAgisoMessagingConfig, +} from '../../platforms/agiso/shop-config-service.js' +import { resolveDisplayShopName } from '../admin-read-shared-helpers.js' +import { + mapAdminAgisoMessagingDefaults, + mapAdminAgisoShopConfigItem, +} from './domain.js' +import { buildAdminAgisoMessagingSavePayload } from './writes.js' + +/** @typedef {import('../../../types/admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigSaveInput */ + +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 saved = saveAgisoMessagingConfig(buildAdminAgisoMessagingSavePayload(payload, { + currentDefaults: getAgisoMessagingDefaults(), + currentMap: getAgisoShopConfigMap(), + })) + + return { + filePath: getAgisoShopsFilePath(), + defaults: mapAdminAgisoMessagingDefaults(saved.defaults), + shops: Object.entries(saved.shops) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([shopId, config]) => mapAdminAgisoShopConfigItem(shopId, config)), + } +} diff --git a/apps/backend/src/services/admin/platform-config/cloudtentacles-service.js b/apps/backend/src/services/admin/platform-config/cloudtentacles-service.js new file mode 100644 index 00000000..5c631818 --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/cloudtentacles-service.js @@ -0,0 +1,250 @@ +// @ts-check + +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 { hasCloudtentaclesCredentialContextChanged, pickFirstNonEmpty } from './context.js' +import { + buildAdminCloudtentaclesLoginResult, + buildAdminCloudtentaclesPersistedSessionPayload, + buildAdminCloudtentaclesValidateResult, + resolveAdminCloudtentaclesCredentialPayload, + resolveAdminCloudtentaclesSessionPayload, +} from './cloudtentacles.js' +import { normalizeAdminCloudtentaclesSourceConfigPayload } from './writes.js' +import { + mapAdminCloudtentaclesSession, + mapAdminCloudtentaclesSourceConfig, + maskPhone, +} from './mappers.js' + +/** @typedef {import('../../../types/admin-write-inputs.js').AdminCloudtentaclesSourceConfigInput} AdminCloudtentaclesSourceConfigInput */ +/** @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 */ + +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(normalizeAdminCloudtentaclesSourceConfigPayload(payload)) + 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 result = await sendCloudtentaclesSmsCode(resolveAdminCloudtentaclesCredentialPayload(payload)) + + return { + ...result, + phoneMasked: maskPhone(result.phone), + } +} + +/** @param {AdminCloudtentaclesTestLoginInput} [payload] */ +export async function testAdminCloudtentaclesLogin(payload = /** @type {AdminCloudtentaclesTestLoginInput} */ ({})) { + const savedSource = getCloudtentaclesSourceConfig() + const credentialContext = resolveAdminCloudtentaclesCredentialPayload(payload, { savedSource }) + const session = await loginCloudtentaclesSession({ + ...credentialContext, + code: String(payload.code || '').trim(), + }) + const savedSession = saveCloudtentaclesSessionState(buildAdminCloudtentaclesPersistedSessionPayload(session, { + deviceId: credentialContext.deviceId, + deviceType: credentialContext.deviceType, + })) + + return buildAdminCloudtentaclesLoginResult(session, savedSession) +} + +/** @param {AdminCloudtentaclesValidateSessionInput} [payload] */ +export async function validateAdminCloudtentaclesSession(payload = /** @type {AdminCloudtentaclesValidateSessionInput} */ ({})) { + const savedSource = getCloudtentaclesSourceConfig() + const persistedSession = getCloudtentaclesSessionState() + const sessionContext = resolveAdminCloudtentaclesSessionPayload(payload, { savedSource, persistedSession }) + const session = await validateCloudtentaclesSession(sessionContext) + const savedSession = saveCloudtentaclesSessionState(buildAdminCloudtentaclesPersistedSessionPayload(session, { + username: pickFirstNonEmpty([persistedSession.username, savedSource.username]), + phone: pickFirstNonEmpty([persistedSession.phone, savedSource.phone]), + deviceId: sessionContext.deviceId, + deviceType: sessionContext.deviceType, + })) + + return buildAdminCloudtentaclesValidateResult(session, savedSession) +} + +/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ +export async function getAdminCloudtentaclesAsset(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { + return getCloudtentaclesAsset(resolveAdminCloudtentaclesSessionPayload(payload)) +} + +/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ +export async function getAdminCloudtentaclesCategories(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { + return getCloudtentaclesCategories(resolveAdminCloudtentaclesSessionPayload(payload)) +} + +/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ +export async function getAdminCloudtentaclesSkuList(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { + return listCloudtentaclesSku(resolveAdminCloudtentaclesSessionPayload(payload)) +} + +/** @param {AdminCloudtentaclesSkuBuyInput} [payload] */ +export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuBuyInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return buyCloudtentaclesSku({ + ...context, + id: payload.id, + count: payload.count, + }) +} + +/** @param {AdminCloudtentaclesSkuUseInput} [payload] */ +export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuUseInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return useCloudtentaclesSku({ + ...context, + id: payload.id, + virtualNumberId: payload.virtualNumberId, + phone: payload.phone, + }) +} + +/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ +export async function getAdminCloudtentaclesKnapsack(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { + return getCloudtentaclesKnapsack(resolveAdminCloudtentaclesSessionPayload(payload)) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return listCloudtentaclesVirtualNumbers({ + ...context, + key: payload.key, + }) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return appointCloudtentaclesVirtualNumber({ + ...context, + key: payload.key, + }) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return generateCloudtentaclesLoginCode({ + ...context, + key: payload.key, + id: payload.id, + }) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return fetchCloudtentaclesVirtualNumberCode({ + ...context, + key: payload.key, + phone: payload.phone, + }) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return verifyCloudtentaclesLoginCode({ + ...context, + key: payload.key, + id: payload.id, + code: payload.code, + }) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return getCloudtentaclesBindUrl({ + ...context, + key: payload.key, + id: payload.id, + }) +} + +/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ +export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return backCloudtentaclesVirtualNumber({ + ...context, + key: payload.key, + id: payload.id, + }) +} + +/** @param {AdminCloudtentaclesFullFlowInput} [payload] */ +export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) { + const context = resolveAdminCloudtentaclesSessionPayload(payload) + return runCloudtentaclesFullDebugFlow({ + ...context, + skuId: payload.skuId, + skuCount: payload.skuCount, + vnKey: payload.vnKey, + }) +} diff --git a/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js b/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js new file mode 100644 index 00000000..0b983d7b --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/fulfillment-bindings-service.js @@ -0,0 +1,90 @@ +// @ts-check + +import { getAgisoShopConfig } from '../../platforms/agiso/shop-config-service.js' +import { + getOrderFulfillmentBindingConfigs, + getOrderFulfillmentBindingsFilePath, + saveOrderFulfillmentBindingConfigs, +} from '../../order/fulfillment-binding-config-service.js' +import { enrichAgisoXianyuTradeOrder } from '../../platforms/agiso/xianyu/order-detail-service.js' +import { getKuaishouEticketSourceConfig, resolveKuaishouEticketShopConfig } from '../../platforms/kuaishou-eticket/source-config-service.js' +import { syncConfiguredFulfillmentBindings } from '../../bootstrap/fulfillment-bootstrap-service.js' +import { getFulfillmentProfileByKey } from '../../../repositories/fulfillment-profile-repo.js' +import { + buildFulfillmentLookupResult, + normalizeFulfillmentLookupPayload, + resolveFulfillmentLookupDetail, +} from './fulfillment.js' +import { listAdminObservedProducts } from './observed-products.js' +import { validateAdminFulfillmentBindingConfigs } from './validation.js' +import { mapAdminFulfillmentBindingConfigItem } from './domain.js' + +/** @typedef {import('../../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */ +/** @typedef {import('../../../types/admin-write-inputs.js').AdminFulfillmentBindingLookupInput} AdminFulfillmentBindingLookupInput */ + +export async function getAdminFulfillmentBindingConfigs() { + const bindings = getOrderFulfillmentBindingConfigs() + + return { + filePath: getOrderFulfillmentBindingsFilePath(), + bindings: bindings.map(mapAdminFulfillmentBindingConfigItem), + observedProducts: await listAdminObservedProducts(bindings), + } +} + +/** @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, { + kuaishouEticketSource: getKuaishouEticketSourceConfig(), + resolveKuaishouEticketShopConfig, + getFulfillmentProfileByKey, + }) + const saved = saveOrderFulfillmentBindingConfigs(normalizedBindings) + await syncConfiguredFulfillmentBindings() + + return { + filePath: getOrderFulfillmentBindingsFilePath(), + bindings: saved.map(mapAdminFulfillmentBindingConfigItem), + } +} diff --git a/apps/backend/src/services/admin/platform-config/khhao-service.js b/apps/backend/src/services/admin/platform-config/khhao-service.js new file mode 100644 index 00000000..de708750 --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/khhao-service.js @@ -0,0 +1,76 @@ +// @ts-check + +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 { + buildAdminKhhaoLoginPayload, + buildAdminKhhaoLoginResult, + buildAdminKhhaoOrderQueryResult, + buildAdminKhhaoSyncPayload, + resolveAdminKhhaoCredentialPayload, +} from './khhao.js' +import { normalizeAdminKhhaoSourceConfigPayload } from './writes.js' +import { + mapAdminKhhaoSourceConfig, + mapAdminKhhaoSyncState, +} from './mappers.js' + +/** @typedef {import('../../../types/admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigInput */ +/** @typedef {import('../../../types/admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryInput */ +/** @typedef {import('../../../types/admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginInput */ + +export function getAdminKhhaoSourceConfig() { + const config = getKhhaoSourceConfig() + const syncState = getKhhaoSyncState() + + return { + filePath: getKhhaoSourcesFilePath(), + stateFilePath: getKhhaoSyncStateFilePath(), + source: mapAdminKhhaoSourceConfig(config), + syncState: mapAdminKhhaoSyncState(syncState), + } +} + +/** @param {AdminKhhaoSourceConfigInput} [payload] */ +export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSourceConfigInput} */ ({})) { + const saved = saveKhhaoSourceConfig(normalizeAdminKhhaoSourceConfigPayload(payload)) + 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 session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload)) + return buildAdminKhhaoLoginResult(session) +} + +/** @param {AdminKhhaoOrderQueryInput} [payload] */ +export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { + const credential = resolveAdminKhhaoCredentialPayload(payload) + const session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload, { + requestIdScope: 'query-orders', + })) + + const result = await queryKhhaoOrderList({ + baseUrl: credential.baseUrl, + page: payload.page, + limit: payload.limit, + session, + }) + + return buildAdminKhhaoOrderQueryResult(session, result) +} + +/** @param {AdminKhhaoOrderQueryInput} [payload] */ +export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { + return syncKhhaoOrders(buildAdminKhhaoSyncPayload(payload)) +} diff --git a/apps/backend/src/services/admin/platform-config/kuaishou-cloud-fulfillment-service.js b/apps/backend/src/services/admin/platform-config/kuaishou-cloud-fulfillment-service.js new file mode 100644 index 00000000..bd22ff48 --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/kuaishou-cloud-fulfillment-service.js @@ -0,0 +1,37 @@ +// @ts-check + +import { + getKuaishouCloudFulfillmentConfig, + getKuaishouCloudFulfillmentFilePath, + saveKuaishouCloudFulfillmentConfig, +} from '../../order/kuaishou-cloud-fulfillment-config-service.js' +import { syncConfiguredFulfillmentBindings } from '../../bootstrap/fulfillment-bootstrap-service.js' +import { mapAdminKuaishouCloudFulfillmentSource } from './mappers.js' + +/** @typedef {import('../../../types/admin-write-inputs.js').AdminKuaishouCloudFulfillmentConfigInput} AdminKuaishouCloudFulfillmentConfigInput */ + +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), + } +} diff --git a/apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.js b/apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.js new file mode 100644 index 00000000..3e17f2b7 --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/kuaishou-eticket-service.js @@ -0,0 +1,89 @@ +// @ts-check + +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 './context.js' +import { mapAdminKuaishouEticketSourceConfig } from './mappers.js' + +/** @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 */ + +function resolveAdminKuaishouEticketContext(payload = {}) { + return resolveKuaishouEticketAdminContext(payload, { + savedSource: getKuaishouEticketSourceConfig(), + findShopConfig: findKuaishouEticketShopConfig, + getFirstAvailableShop: getFirstAvailableKuaishouEticketShop, + }) +} + +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 {AdminKuaishouEticketDetailQueryInput} [payload] */ +export async function queryAdminKuaishouEticketDetail(payload = /** @type {AdminKuaishouEticketDetailQueryInput} */ ({})) { + const context = resolveAdminKuaishouEticketContext(payload) + + 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 = resolveAdminKuaishouEticketContext(payload) + + return queryKuaishouEticketStableInfo({ + baseUrl: context.baseUrl, + cookie: context.cookie, + }) +} + +/** @param {AdminKuaishouEticketConsumeInput} [payload] */ +export async function consumeAdminKuaishouEticket(payload = /** @type {AdminKuaishouEticketConsumeInput} */ ({})) { + 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/platform-config/service.js b/apps/backend/src/services/admin/platform-config/service.js index 068699aa..658f73b8 100644 --- a/apps/backend/src/services/admin/platform-config/service.js +++ b/apps/backend/src/services/admin/platform-config/service.js @@ -1,603 +1,53 @@ -// @ts-check +export { + getAdminAgisoShopConfigs, + updateAdminAgisoShopConfigs, +} from './agiso-service.js' -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 { 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 { syncConfiguredFulfillmentBindings } from '../../bootstrap/fulfillment-bootstrap-service.js' -import { resolveDisplayShopName } from '../admin-read-shared-helpers.js' -import { - hasCloudtentaclesCredentialContextChanged, - isPlainObject, - pickFirstNonEmpty, - resolveKuaishouEticketAdminContext, -} from './context.js' -import { - buildFulfillmentLookupResult, - normalizeFulfillmentLookupPayload, - resolveFulfillmentLookupDetail, -} from './fulfillment.js' -import { - buildAdminKhhaoLoginPayload, - buildAdminKhhaoLoginResult, - buildAdminKhhaoOrderQueryResult, - buildAdminKhhaoSyncPayload, - resolveAdminKhhaoCredentialPayload, -} from './khhao.js' -import { - buildAdminCloudtentaclesLoginResult, - buildAdminCloudtentaclesPersistedSessionPayload, - buildAdminCloudtentaclesValidateResult, - resolveAdminCloudtentaclesCredentialPayload, - resolveAdminCloudtentaclesSessionPayload, -} from './cloudtentacles.js' -import { listAdminObservedProducts } from './observed-products.js' -import { - validateAdminFulfillmentBindingConfigs, -} from './validation.js' -import { - buildAdminAgisoMessagingSavePayload, - normalizeAdminCloudtentaclesSourceConfigPayload, - normalizeAdminKhhaoSourceConfigPayload, -} from './writes.js' -import { - mapAdminAgisoMessagingDefaults, - mapAdminAgisoShopConfigItem, - mapAdminFulfillmentBindingConfigItem, -} from './domain.js' -import { - mapAdminCloudtentaclesSession, - mapAdminCloudtentaclesSourceConfig, - mapAdminKhhaoSourceConfig, - mapAdminKhhaoSyncState, - mapAdminKuaishouCloudFulfillmentSource, - mapAdminKuaishouEticketSourceConfig, - maskPhone, -} from './mappers.js' +export { + getAdminKhhaoSourceConfig, + updateAdminKhhaoSourceConfig, + testAdminKhhaoLogin, + queryAdminKhhaoOrders, + syncAdminKhhaoOrders, +} from './khhao-service.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 { + getAdminKuaishouEticketSourceConfig, + updateAdminKuaishouEticketSourceConfig, + queryAdminKuaishouEticketDetail, + queryAdminKuaishouEticketShopInfo, + consumeAdminKuaishouEticket, +} from './kuaishou-eticket-service.js' -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 +export { + getAdminCloudtentaclesSourceConfig, + updateAdminCloudtentaclesSourceConfig, + sendAdminCloudtentaclesSmsCode, + testAdminCloudtentaclesLogin, + validateAdminCloudtentaclesSession, + getAdminCloudtentaclesAsset, + getAdminCloudtentaclesCategories, + getAdminCloudtentaclesSkuList, + buyAdminCloudtentaclesSku, + useAdminCloudtentaclesSku, + getAdminCloudtentaclesKnapsack, + listAdminCloudtentaclesVirtualNumbers, + appointAdminCloudtentaclesVirtualNumber, + generateAdminCloudtentaclesLoginCode, + fetchAdminCloudtentaclesVirtualNumberCode, + verifyAdminCloudtentaclesLoginCode, + getAdminCloudtentaclesBindUrl, + backAdminCloudtentaclesVirtualNumber, + runAdminCloudtentaclesFullFlow, +} from './cloudtentacles-service.js' - 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()]), - })), - } -} +export { + getAdminFulfillmentBindingConfigs, + lookupAdminFulfillmentBindingOrder, + updateAdminFulfillmentBindingConfigs, +} from './fulfillment-bindings-service.js' -/** @param {AdminAgisoShopConfigSaveInput} [payload] */ -export function updateAdminAgisoShopConfigs(payload = /** @type {AdminAgisoShopConfigSaveInput} */ ({})) { - const saved = saveAgisoMessagingConfig(buildAdminAgisoMessagingSavePayload(payload, { - currentDefaults: getAgisoMessagingDefaults(), - currentMap: getAgisoShopConfigMap(), - })) - - 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(normalizeAdminKhhaoSourceConfigPayload(payload)) - 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 session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload)) - return buildAdminKhhaoLoginResult(session) -} - -/** @param {AdminKhhaoOrderQueryInput} [payload] */ -export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { - const credential = resolveAdminKhhaoCredentialPayload(payload) - const session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload, { - requestIdScope: 'query-orders', - })) - - const result = await queryKhhaoOrderList({ - baseUrl: credential.baseUrl, - page: payload.page, - limit: payload.limit, - session, - }) - - return buildAdminKhhaoOrderQueryResult(session, result) -} - -/** @param {AdminKhhaoOrderQueryInput} [payload] */ -export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { - return syncKhhaoOrders(buildAdminKhhaoSyncPayload(payload)) -} - -/** @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(normalizeAdminCloudtentaclesSourceConfigPayload(payload)) - 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 result = await sendCloudtentaclesSmsCode(resolveAdminCloudtentaclesCredentialPayload(payload)) - - return { - ...result, - phoneMasked: maskPhone(result.phone), - } -} - -/** @param {AdminCloudtentaclesTestLoginInput} [payload] */ -export async function testAdminCloudtentaclesLogin(payload = /** @type {AdminCloudtentaclesTestLoginInput} */ ({})) { - const savedSource = getCloudtentaclesSourceConfig() - const credentialContext = resolveAdminCloudtentaclesCredentialPayload(payload, { savedSource }) - const session = await loginCloudtentaclesSession({ - ...credentialContext, - code: String(payload.code || '').trim(), - }) - const savedSession = saveCloudtentaclesSessionState(buildAdminCloudtentaclesPersistedSessionPayload(session, { - deviceId: credentialContext.deviceId, - deviceType: credentialContext.deviceType, - })) - - return buildAdminCloudtentaclesLoginResult(session, savedSession) -} - -/** @param {AdminCloudtentaclesValidateSessionInput} [payload] */ -export async function validateAdminCloudtentaclesSession(payload = /** @type {AdminCloudtentaclesValidateSessionInput} */ ({})) { - const savedSource = getCloudtentaclesSourceConfig() - const persistedSession = getCloudtentaclesSessionState() - const sessionContext = resolveAdminCloudtentaclesSessionPayload(payload, { savedSource, persistedSession }) - const session = await validateCloudtentaclesSession(sessionContext) - const savedSession = saveCloudtentaclesSessionState(buildAdminCloudtentaclesPersistedSessionPayload(session, { - username: pickFirstNonEmpty([persistedSession.username, savedSource.username]), - phone: pickFirstNonEmpty([persistedSession.phone, savedSource.phone]), - deviceId: sessionContext.deviceId, - deviceType: sessionContext.deviceType, - })) - - return buildAdminCloudtentaclesValidateResult(session, savedSession) -} - -/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ -export async function getAdminCloudtentaclesAsset(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { - return getCloudtentaclesAsset(resolveAdminCloudtentaclesSessionPayload(payload)) -} - -/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ -export async function getAdminCloudtentaclesCategories(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { - return getCloudtentaclesCategories(resolveAdminCloudtentaclesSessionPayload(payload)) -} - -/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ -export async function getAdminCloudtentaclesSkuList(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { - return listCloudtentaclesSku(resolveAdminCloudtentaclesSessionPayload(payload)) -} - -/** @param {AdminCloudtentaclesSkuBuyInput} [payload] */ -export async function buyAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuBuyInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return buyCloudtentaclesSku({ - ...context, - id: payload.id, - count: payload.count, - }) -} - -/** @param {AdminCloudtentaclesSkuUseInput} [payload] */ -export async function useAdminCloudtentaclesSku(payload = /** @type {AdminCloudtentaclesSkuUseInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return useCloudtentaclesSku({ - ...context, - id: payload.id, - virtualNumberId: payload.virtualNumberId, - phone: payload.phone, - }) -} - -/** @param {AdminCloudtentaclesCatalogQueryInput} [payload] */ -export async function getAdminCloudtentaclesKnapsack(payload = /** @type {AdminCloudtentaclesCatalogQueryInput} */ ({})) { - return getCloudtentaclesKnapsack(resolveAdminCloudtentaclesSessionPayload(payload)) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function listAdminCloudtentaclesVirtualNumbers(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return listCloudtentaclesVirtualNumbers({ - ...context, - key: payload.key, - }) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function appointAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return appointCloudtentaclesVirtualNumber({ - ...context, - key: payload.key, - }) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function generateAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return generateCloudtentaclesLoginCode({ - ...context, - key: payload.key, - id: payload.id, - }) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function fetchAdminCloudtentaclesVirtualNumberCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return fetchCloudtentaclesVirtualNumberCode({ - ...context, - key: payload.key, - phone: payload.phone, - }) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function verifyAdminCloudtentaclesLoginCode(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return verifyCloudtentaclesLoginCode({ - ...context, - key: payload.key, - id: payload.id, - code: payload.code, - }) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function getAdminCloudtentaclesBindUrl(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return getCloudtentaclesBindUrl({ - ...context, - key: payload.key, - id: payload.id, - }) -} - -/** @param {AdminCloudtentaclesVirtualNumberInput} [payload] */ -export async function backAdminCloudtentaclesVirtualNumber(payload = /** @type {AdminCloudtentaclesVirtualNumberInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - return backCloudtentaclesVirtualNumber({ - ...context, - key: payload.key, - id: payload.id, - }) -} - -/** @param {AdminCloudtentaclesFullFlowInput} [payload] */ -export async function runAdminCloudtentaclesFullFlow(payload = /** @type {AdminCloudtentaclesFullFlowInput} */ ({})) { - const context = resolveAdminCloudtentaclesSessionPayload(payload) - 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, { - kuaishouEticketSource: getKuaishouEticketSourceConfig(), - resolveKuaishouEticketShopConfig, - getFulfillmentProfileByKey, - }) - const saved = saveOrderFulfillmentBindingConfigs(normalizedBindings) - await syncConfiguredFulfillmentBindings() - - return { - filePath: getOrderFulfillmentBindingsFilePath(), - bindings: saved.map(mapAdminFulfillmentBindingConfigItem), - } -} +export { + getAdminKuaishouCloudFulfillmentConfig, + updateAdminKuaishouCloudFulfillmentConfig, +} from './kuaishou-cloud-fulfillment-service.js'