848 lines
33 KiB
JavaScript
848 lines
33 KiB
JavaScript
// @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
|
|
}
|