优化了自动发货和发货后的消息
This commit is contained in:
@@ -53,7 +53,7 @@ module.exports = {
|
||||
},
|
||||
autoDelivery: {
|
||||
enabled: true,
|
||||
endpoint: 'https://gw-api.agiso.com/aldsIdle/Alds/AldsSend',
|
||||
endpoint: 'https://gw-api.agiso.com/aldsIdle/Order/DummySend',
|
||||
apiVersion: '1',
|
||||
aldsType: 1,
|
||||
ignoreAldsLog: false,
|
||||
@@ -72,6 +72,7 @@ module.exports = {
|
||||
accessToken: '',
|
||||
appSecret: '',
|
||||
messageTemplate: '您的订单 {platformOrderId} 已创建领取链接,请在 {expiredAt} 前完成领取:{claimUrl}',
|
||||
autoDeliveryMessageTemplate: '您的订单 {platformOrderId} 已完成自动发货,请注意查收。',
|
||||
shops: {},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -270,6 +270,11 @@ function applyEnvOverrides(baseConfig) {
|
||||
nextConfig.platforms.agiso.messaging.messageTemplate = agisoMessageTemplate
|
||||
}
|
||||
|
||||
const agisoAutoDeliveryMessageTemplate = String(process.env.AGISO_AUTO_DELIVERY_MESSAGE_TEMPLATE || '').trim()
|
||||
if (agisoAutoDeliveryMessageTemplate) {
|
||||
nextConfig.platforms.agiso.messaging.autoDeliveryMessageTemplate = agisoAutoDeliveryMessageTemplate
|
||||
}
|
||||
|
||||
const agisoShops = parseJsonObject(process.env.AGISO_SHOPS_JSON)
|
||||
if (agisoShops) {
|
||||
nextConfig.platforms.agiso.messaging.shops = normalizeAgisoMessagingShops(agisoShops)
|
||||
@@ -402,7 +407,7 @@ function normalizeAgisoMessagingShops(rawValue) {
|
||||
next.enabled = enabled
|
||||
}
|
||||
|
||||
for (const key of ['shopName', 'accessToken', 'messageTemplate', 'appSecret', 'apiVersion', 'sendMessageEndpoint']) {
|
||||
for (const key of ['shopName', 'accessToken', 'messageTemplate', 'autoDeliveryMessageTemplate', 'appSecret', 'apiVersion', 'sendMessageEndpoint']) {
|
||||
const value = String(config[key] || '').trim()
|
||||
if (value) {
|
||||
next[key] = value
|
||||
|
||||
@@ -52,6 +52,7 @@ export async function getAdminAgisoShopConfigs() {
|
||||
accessTokenMasked: maskSecret(config.accessToken),
|
||||
enabled: typeof config.enabled === 'boolean' ? config.enabled : null,
|
||||
messageTemplate: String(config.messageTemplate || '').trim(),
|
||||
autoDeliveryMessageTemplate: String(config.autoDeliveryMessageTemplate || '').trim(),
|
||||
appSecretConfigured: Boolean(String(config.appSecret || '').trim()),
|
||||
apiVersion: String(config.apiVersion || '').trim(),
|
||||
sendMessageEndpoint: String(config.sendMessageEndpoint || '').trim(),
|
||||
@@ -70,6 +71,7 @@ export async function getAdminAgisoShopConfigs() {
|
||||
/** @param {AdminAgisoShopConfigSaveInput} [payload] */
|
||||
export function updateAdminAgisoShopConfigs(payload = /** @type {AdminAgisoShopConfigSaveInput} */ ({})) {
|
||||
const rawItems = Array.isArray(payload.shops) ? payload.shops : []
|
||||
const currentMap = getAgisoShopConfigMap()
|
||||
const nextMap = {}
|
||||
|
||||
for (const item of rawItems) {
|
||||
@@ -78,22 +80,33 @@ export function updateAdminAgisoShopConfigs(payload = /** @type {AdminAgisoShopC
|
||||
continue
|
||||
}
|
||||
|
||||
const next = {}
|
||||
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
|
||||
@@ -128,6 +141,7 @@ export function updateAdminAgisoShopConfigs(payload = /** @type {AdminAgisoShopC
|
||||
accessTokenMasked: maskSecret(config.accessToken),
|
||||
enabled: typeof config.enabled === 'boolean' ? config.enabled : null,
|
||||
messageTemplate: String(config.messageTemplate || '').trim(),
|
||||
autoDeliveryMessageTemplate: String(config.autoDeliveryMessageTemplate || '').trim(),
|
||||
appSecretConfigured: Boolean(String(config.appSecret || '').trim()),
|
||||
apiVersion: String(config.apiVersion || '').trim(),
|
||||
sendMessageEndpoint: String(config.sendMessageEndpoint || '').trim(),
|
||||
|
||||
@@ -64,7 +64,7 @@ function normalizeAgisoShopConfigMap(rawValue) {
|
||||
next.enabled = enabled
|
||||
}
|
||||
|
||||
for (const key of ['shopName', 'accessToken', 'messageTemplate', 'appSecret', 'apiVersion', 'sendMessageEndpoint', 'tradeDetailEndpoint']) {
|
||||
for (const key of ['shopName', 'accessToken', 'messageTemplate', 'autoDeliveryMessageTemplate', 'appSecret', 'apiVersion', 'sendMessageEndpoint', 'tradeDetailEndpoint']) {
|
||||
const value = String(config[key] || '').trim()
|
||||
if (value) {
|
||||
next[key] = value
|
||||
|
||||
@@ -4,6 +4,8 @@ import { runtimeConfig } from '../../../../config/runtime.js'
|
||||
import { listTasksByOrderId, updateTask } from '../../../../repositories/task-repo.js'
|
||||
import { createTaskEvent } from '../../../../repositories/task-event-repo.js'
|
||||
import { getAgisoShopConfig } from '../shop-config-service.js'
|
||||
import { queryAgisoXianyuOrderDetail } from './order-detail-service.js'
|
||||
import { ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask } from './message-service.js'
|
||||
import { parseJsonObject } from '../../../../utils/json.js'
|
||||
import { logWebhook } from '../../../../utils/logger.js'
|
||||
import { nowIso } from '../../../../utils/time.js'
|
||||
@@ -113,7 +115,46 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
|
||||
if (success) {
|
||||
const requestId = String(parsed?.RequestId || '').trim()
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', 'Agiso 咸鱼自动发货成功', {
|
||||
|
||||
// DummySend 接口返回成功后,再查一次 Order/Detail 确认订单真的进入已发货状态 meow~
|
||||
const confirmResult = await confirmAgisoXianyuAutoDeliveryShipped({
|
||||
shopId: order.shop_id,
|
||||
platformOrderId: order.platform_order_id,
|
||||
requestId,
|
||||
})
|
||||
|
||||
if (!confirmResult.shipped) {
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', 'Agiso 咸鱼自动发货接口返回成功,但订单状态未确认进入已发货', {
|
||||
trigger,
|
||||
orderId: order.id,
|
||||
taskId: task.id,
|
||||
shopId: order.shop_id,
|
||||
platformOrderId: order.platform_order_id,
|
||||
responseStatus: response.status,
|
||||
requestId,
|
||||
confirmOrderStatus: confirmResult.orderStatus,
|
||||
confirmShipTime: confirmResult.shipTime,
|
||||
confirmReason: confirmResult.reason,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return persistAgisoAutoDeliveryResult(task, {
|
||||
status: 'failed',
|
||||
trigger,
|
||||
reason: 'delivery_not_confirmed',
|
||||
order,
|
||||
responseStatus: response.status,
|
||||
response: parsed,
|
||||
errorMessage: `接口返回成功,但订单状态未确认进入已发货 (orderStatus=${confirmResult.orderStatus}, shipTime=${confirmResult.shipTime})`,
|
||||
detail: {
|
||||
requestId,
|
||||
aldsType: config.aldsType,
|
||||
confirmOrderStatus: confirmResult.orderStatus,
|
||||
confirmShipTime: confirmResult.shipTime,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', 'Agiso 咸鱼自动发货成功,订单已确认进入已发货状态', {
|
||||
trigger,
|
||||
orderId: order.id,
|
||||
taskId: task.id,
|
||||
@@ -121,9 +162,11 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
platformOrderId: order.platform_order_id,
|
||||
responseStatus: response.status,
|
||||
requestId,
|
||||
confirmOrderStatus: confirmResult.orderStatus,
|
||||
confirmShipTime: confirmResult.shipTime,
|
||||
})
|
||||
|
||||
return persistAgisoAutoDeliveryResult(task, {
|
||||
const result = await persistAgisoAutoDeliveryResult(task, {
|
||||
status: 'success',
|
||||
trigger,
|
||||
reason: '',
|
||||
@@ -133,8 +176,15 @@ export async function ensureAgisoXianyuAutoDeliveryForDeliveredTask({
|
||||
detail: {
|
||||
requestId,
|
||||
aldsType: config.aldsType,
|
||||
confirmOrderStatus: confirmResult.orderStatus,
|
||||
confirmShipTime: confirmResult.shipTime,
|
||||
},
|
||||
})
|
||||
|
||||
// 发货确认成功后,发送自定义消息通知 meow~
|
||||
await sendAutoDeliveryMessage({ order, task })
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const errorMessage = resolveAgisoAutoDeliveryErrorMessage(parsed, rawText, response.status)
|
||||
@@ -411,3 +461,72 @@ function normalizeBooleanLike(value, fallbackValue) {
|
||||
function isPlainObject(value) {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
|
||||
/**
|
||||
* DummySend 返回成功后,再查一次 Order/Detail 确认订单是否真的进入已发货状态 meow~
|
||||
* 只有 ship_time > 0 或 orderStatus >= 3 才算发货确认通过
|
||||
*/
|
||||
async function confirmAgisoXianyuAutoDeliveryShipped({ shopId = '', platformOrderId = '', requestId = '' } = {}) {
|
||||
try {
|
||||
const detailResult = await queryAgisoXianyuOrderDetail({ shopId, platformOrderId, requestId })
|
||||
|
||||
if (detailResult.success && detailResult.shipped) {
|
||||
return {
|
||||
shipped: true,
|
||||
orderStatus: detailResult.orderStatus,
|
||||
shipTime: detailResult.shipTime,
|
||||
reason: '',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
shipped: false,
|
||||
orderStatus: detailResult.orderStatus || 0,
|
||||
shipTime: detailResult.shipTime || 0,
|
||||
reason: detailResult.success ? 'order_not_shipped' : detailResult.reason,
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || '订单发货状态确认失败')
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', '自动发货确认查询异常', {
|
||||
requestId,
|
||||
shopId,
|
||||
platformOrderId,
|
||||
errorMessage: message,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return {
|
||||
shipped: false,
|
||||
orderStatus: 0,
|
||||
shipTime: 0,
|
||||
reason: 'confirm_query_failed',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动发货成功后发送消息通知 meow~
|
||||
*/
|
||||
async function sendAutoDeliveryMessage({ order, task } = {}) {
|
||||
try {
|
||||
const result = await ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask({ order, task })
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', '自动发货消息通知结果', {
|
||||
orderId: order?.id,
|
||||
taskId: task?.id,
|
||||
platformOrderId: order?.platform_order_id,
|
||||
sent: result.sent,
|
||||
skipped: result.skipped,
|
||||
reason: result.reason,
|
||||
})
|
||||
return result
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || '发送自动发货消息通知失败')
|
||||
logWebhook('[agiso/xianyu/auto-delivery]', '自动发货消息通知异常', {
|
||||
orderId: order?.id,
|
||||
taskId: task?.id,
|
||||
platformOrderId: order?.platform_order_id,
|
||||
errorMessage: message,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return { sent: false, skipped: true, reason: 'message_send_error' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,53 @@ import {
|
||||
import { nowIso } from '../../../../utils/time.js'
|
||||
|
||||
const AGISO_XIANYU_MESSAGE_CHANNEL = 'agiso_im'
|
||||
const AGISO_XIANYU_AUTO_DELIVERY_MESSAGE_CHANNEL = 'agiso_im_auto_delivery'
|
||||
|
||||
export async function ensureAgisoXianyuClaimMessageDeliveredForTask({ order, task, claimUrl, expiredAt }) {
|
||||
if (!order || !task || !claimUrl) {
|
||||
const config = resolveAgisoXianyuMessagingConfig(order)
|
||||
const messageContent = renderAgisoClaimMessage({
|
||||
order,
|
||||
task,
|
||||
claimUrl,
|
||||
expiredAt,
|
||||
template: config.messageTemplate,
|
||||
shopName: config.shopName,
|
||||
})
|
||||
|
||||
return deliverAgisoXianyuMessageForTask({
|
||||
order,
|
||||
task,
|
||||
channel: AGISO_XIANYU_MESSAGE_CHANNEL,
|
||||
messageContent,
|
||||
claimUrl,
|
||||
})
|
||||
}
|
||||
|
||||
export async function ensureAgisoXianyuAutoDeliveryMessageDeliveredForTask({ order, task } = {}) {
|
||||
const config = resolveAgisoXianyuMessagingConfig(order)
|
||||
const messageContent = renderAgisoAutoDeliveryMessage({
|
||||
order,
|
||||
task,
|
||||
template: config.autoDeliveryMessageTemplate,
|
||||
shopName: config.shopName,
|
||||
})
|
||||
|
||||
return deliverAgisoXianyuMessageForTask({
|
||||
order,
|
||||
task,
|
||||
channel: AGISO_XIANYU_AUTO_DELIVERY_MESSAGE_CHANNEL,
|
||||
messageContent,
|
||||
})
|
||||
}
|
||||
|
||||
async function deliverAgisoXianyuMessageForTask({
|
||||
order,
|
||||
task,
|
||||
channel,
|
||||
messageContent,
|
||||
claimUrl = '',
|
||||
} = {}) {
|
||||
if (!order || !task || !messageContent) {
|
||||
return { sent: false, skipped: true, reason: 'missing_message_context' }
|
||||
}
|
||||
|
||||
@@ -28,19 +72,11 @@ export async function ensureAgisoXianyuClaimMessageDeliveredForTask({ order, tas
|
||||
return { sent: false, skipped: true, reason: 'missing_endpoint_or_access_token_or_app_secret' }
|
||||
}
|
||||
|
||||
const successful = await findLatestSuccessfulMessageDeliveryByTask(task.id, AGISO_XIANYU_MESSAGE_CHANNEL)
|
||||
const successful = await findLatestSuccessfulMessageDeliveryByTask(task.id, channel)
|
||||
if (successful) {
|
||||
return { sent: false, skipped: true, reason: 'already_sent', deliveryId: successful.id }
|
||||
}
|
||||
|
||||
const messageContent = renderAgisoClaimMessage({
|
||||
order,
|
||||
task,
|
||||
claimUrl,
|
||||
expiredAt,
|
||||
template: config.messageTemplate,
|
||||
shopName: config.shopName,
|
||||
})
|
||||
const url = buildRequestUrl(endpoint)
|
||||
const requestBody = buildRequestBody({
|
||||
tid: String(order.platform_order_id || ''),
|
||||
@@ -57,7 +93,7 @@ export async function ensureAgisoXianyuClaimMessageDeliveredForTask({ order, tas
|
||||
platform: String(order.platform || '').trim() || 'unknown',
|
||||
shopId: String(order.shop_id || '').trim(),
|
||||
shopName: String(order.shop_name || '').trim(),
|
||||
channel: AGISO_XIANYU_MESSAGE_CHANNEL,
|
||||
channel,
|
||||
orderId: order.id,
|
||||
taskId: task.id,
|
||||
platformOrderId: order.platform_order_id,
|
||||
@@ -85,11 +121,12 @@ export async function ensureAgisoXianyuClaimMessageDeliveredForTask({ order, tas
|
||||
const rawText = await response.text()
|
||||
const parsed = safeParseJson(rawText)
|
||||
const success = isAgisoSendSuccess(response.status, parsed)
|
||||
const errorMessage = success ? '' : resolveAgisoErrorMessage(parsed, rawText, response.status)
|
||||
const updated = await updateMessageDelivery(delivery.id, {
|
||||
status: success ? 'success' : 'failed',
|
||||
response_status: response.status,
|
||||
response_json: JSON.stringify(parsed ?? { rawText }),
|
||||
error_message: success ? '' : resolveAgisoErrorMessage(parsed, rawText, response.status),
|
||||
error_message: errorMessage,
|
||||
sent_at: success ? nowIso() : null,
|
||||
updated_at: nowIso(),
|
||||
})
|
||||
@@ -100,7 +137,7 @@ export async function ensureAgisoXianyuClaimMessageDeliveredForTask({ order, tas
|
||||
deliveryId: updated?.id || delivery.id,
|
||||
responseStatus: response.status,
|
||||
response: parsed ?? { rawText },
|
||||
errorMessage: success ? '' : resolveAgisoErrorMessage(parsed, rawText, response.status),
|
||||
errorMessage,
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || '发送消息失败')
|
||||
@@ -174,15 +211,48 @@ function generateSign(params, appSecret) {
|
||||
|
||||
function renderAgisoClaimMessage({ order, task, claimUrl, expiredAt, template, shopName = '' }) {
|
||||
const source = String(template || '').trim() || '您的订单 {platformOrderId} 已创建领取链接,请在 {expiredAt} 前完成领取:{claimUrl}'
|
||||
const resolvedShopName = String(shopName || order.shop_name || order.shop_id || '').trim()
|
||||
|
||||
return source
|
||||
.replaceAll('{platformOrderId}', String(order.platform_order_id || ''))
|
||||
.replaceAll('{taskNo}', String(task.task_no || ''))
|
||||
return renderAgisoMessageTemplate({
|
||||
order,
|
||||
task,
|
||||
template: source,
|
||||
shopName,
|
||||
claimUrl,
|
||||
expiredAt: String(expiredAt || '尽快'),
|
||||
})
|
||||
}
|
||||
|
||||
export function renderAgisoAutoDeliveryMessage({ order, task, template, shopName = '' }) {
|
||||
const source = String(template || '').trim() || '您的订单 {platformOrderId} 已完成自动发货,请注意查收。'
|
||||
|
||||
return renderAgisoMessageTemplate({
|
||||
order,
|
||||
task,
|
||||
template: source,
|
||||
shopName,
|
||||
resultMessage: '自动发货成功',
|
||||
})
|
||||
}
|
||||
|
||||
function renderAgisoMessageTemplate({
|
||||
order,
|
||||
task,
|
||||
template,
|
||||
shopName = '',
|
||||
claimUrl = '',
|
||||
expiredAt = '',
|
||||
resultMessage = '',
|
||||
} = {}) {
|
||||
const resolvedShopName = String(shopName || order?.shop_name || order?.shop_id || '').trim()
|
||||
|
||||
return String(template || '')
|
||||
.replaceAll('{platformOrderId}', String(order?.platform_order_id || ''))
|
||||
.replaceAll('{taskNo}', String(task?.task_no || ''))
|
||||
.replaceAll('{shopName}', resolvedShopName)
|
||||
.replaceAll('{shopId}', String(order.shop_id || ''))
|
||||
.replaceAll('{claimUrl}', claimUrl)
|
||||
.replaceAll('{expiredAt}', String(expiredAt || '尽快'))
|
||||
.replaceAll('{shopId}', String(order?.shop_id || ''))
|
||||
.replaceAll('{claimUrl}', String(claimUrl || ''))
|
||||
.replaceAll('{expiredAt}', String(expiredAt || ''))
|
||||
.replaceAll('{resultMessage}', String(resultMessage || ''))
|
||||
}
|
||||
|
||||
function isAgisoSendSuccess(statusCode, payload) {
|
||||
|
||||
@@ -14,23 +14,68 @@ export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {
|
||||
return { parsed, enriched: false, reason: 'not_needed' }
|
||||
}
|
||||
|
||||
const config = resolveAgisoXianyuTradeDetailConfig(parsed.shopId)
|
||||
const detailResult = await queryAgisoXianyuOrderDetail({
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
requestId,
|
||||
})
|
||||
|
||||
if (!detailResult.success) {
|
||||
return {
|
||||
parsed,
|
||||
enriched: false,
|
||||
reason: detailResult.reason,
|
||||
errorMessage: detailResult.errorMessage,
|
||||
}
|
||||
}
|
||||
|
||||
const detailPayload = detailResult.detailPayload
|
||||
if (detailResult.totalAmountFen <= 0) {
|
||||
return {
|
||||
parsed: mergeEnrichedTrade(parsed, detailPayload, { keepOriginalAmount: true }),
|
||||
enriched: false,
|
||||
reason: 'amount_still_missing',
|
||||
}
|
||||
}
|
||||
|
||||
const merged = mergeEnrichedTrade(parsed, detailPayload)
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情补查成功', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
totalAmountFen: merged.totalAmount,
|
||||
paidAt: merged.paidAt,
|
||||
})
|
||||
|
||||
return {
|
||||
parsed: merged,
|
||||
enriched: true,
|
||||
totalAmountFen: merged.totalAmount,
|
||||
}
|
||||
}
|
||||
|
||||
export async function queryAgisoXianyuOrderDetail({ shopId = '', platformOrderId = '', requestId = '' } = {}) {
|
||||
const normalizedShopId = String(shopId || '').trim()
|
||||
const normalizedPlatformOrderId = String(platformOrderId || '').trim()
|
||||
const config = resolveAgisoXianyuTradeDetailConfig(normalizedShopId)
|
||||
|
||||
if (!config.endpoint || !config.accessToken || !config.appSecret) {
|
||||
logWebhook('[agiso/xianyu/order-detail]', '跳过 Agiso 咸鱼订单详情补查:缺少必要配置', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
hasEndpoint: Boolean(config.endpoint),
|
||||
hasAccessToken: Boolean(config.accessToken),
|
||||
hasAppSecret: Boolean(config.appSecret),
|
||||
}, { level: 'warn' })
|
||||
|
||||
return { parsed, enriched: false, reason: 'missing_config' }
|
||||
return createFailedDetailResult('missing_config')
|
||||
}
|
||||
|
||||
const requestBody = buildRequestBody({
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
appSecret: config.appSecret,
|
||||
})
|
||||
const requestHeaders = buildRequestHeaders({
|
||||
@@ -40,9 +85,9 @@ export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {
|
||||
|
||||
logWebhook('[agiso/xianyu/order-detail]', '开始补查 Agiso 咸鱼订单详情', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
endpoint: config.endpoint,
|
||||
})
|
||||
|
||||
@@ -55,92 +100,103 @@ export async function enrichAgisoXianyuTradeOrder(parsed, { requestId = '' } = {
|
||||
const rawText = await response.text()
|
||||
const payload = parseJsonObject(rawText, { preserveLargeIntegers: true })
|
||||
const detailPayload = extractAgisoDetailPayload(payload)
|
||||
const detailAmountFen = resolveTotalAmountFen(detailPayload)
|
||||
const delivery = resolveAgisoXianyuOrderDeliveryState(detailPayload)
|
||||
const totalAmountFen = resolveTotalAmountFen(detailPayload)
|
||||
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
const message = resolveAgisoDetailErrorMessage(payload, rawText, response.status)
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情补查失败', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
responseStatus: response.status,
|
||||
errorMessage: message,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return { parsed, enriched: false, reason: 'http_error', errorMessage: message }
|
||||
return createFailedDetailResult('http_error', {
|
||||
responseStatus: response.status,
|
||||
payload,
|
||||
detailPayload,
|
||||
errorMessage: message,
|
||||
totalAmountFen,
|
||||
delivery,
|
||||
})
|
||||
}
|
||||
|
||||
if (!isAgisoDetailSuccess(payload)) {
|
||||
const message = resolveAgisoDetailErrorMessage(payload, rawText, response.status)
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情补查返回业务失败', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
responseStatus: response.status,
|
||||
errorMessage: message,
|
||||
response: payload,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return { parsed, enriched: false, reason: 'business_error', errorMessage: message }
|
||||
return createFailedDetailResult('business_error', {
|
||||
responseStatus: response.status,
|
||||
payload,
|
||||
detailPayload,
|
||||
errorMessage: message,
|
||||
totalAmountFen,
|
||||
delivery,
|
||||
})
|
||||
}
|
||||
|
||||
if (!detailPayload || typeof detailPayload !== 'object' || Object.keys(detailPayload).length === 0) {
|
||||
if (!isPlainObject(detailPayload) || Object.keys(detailPayload).length === 0) {
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情补查未返回可用订单体', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
responseStatus: response.status,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return { parsed, enriched: false, reason: 'empty_detail_payload' }
|
||||
return createFailedDetailResult('empty_detail_payload', {
|
||||
responseStatus: response.status,
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
||||
if (detailAmountFen <= 0) {
|
||||
if (totalAmountFen <= 0) {
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情已返回,但仍未解析出金额', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
responseStatus: response.status,
|
||||
detailKeys: Object.keys(detailPayload),
|
||||
}, { level: 'warn' })
|
||||
|
||||
return {
|
||||
parsed: mergeEnrichedTrade(parsed, detailPayload, { keepOriginalAmount: true }),
|
||||
enriched: false,
|
||||
reason: 'amount_still_missing',
|
||||
}
|
||||
}
|
||||
|
||||
const merged = mergeEnrichedTrade(parsed, detailPayload)
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情补查成功', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
totalAmountFen: merged.totalAmount,
|
||||
paidAt: merged.paidAt,
|
||||
})
|
||||
|
||||
return {
|
||||
parsed: merged,
|
||||
enriched: true,
|
||||
totalAmountFen: merged.totalAmount,
|
||||
success: true,
|
||||
reason: '',
|
||||
errorMessage: '',
|
||||
responseStatus: response.status,
|
||||
payload: isPlainObject(payload) ? payload : {},
|
||||
detailPayload,
|
||||
totalAmountFen,
|
||||
shipped: delivery.shipped,
|
||||
shipTime: delivery.shipTime,
|
||||
orderStatus: delivery.orderStatus,
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || 'Agiso 咸鱼订单详情补查失败')
|
||||
logWebhook('[agiso/xianyu/order-detail]', 'Agiso 咸鱼订单详情补查异常', {
|
||||
requestId,
|
||||
platform: parsed.platform,
|
||||
shopId: parsed.shopId,
|
||||
platformOrderId: parsed.platformOrderId,
|
||||
platform: 'xianyu',
|
||||
shopId: normalizedShopId,
|
||||
platformOrderId: normalizedPlatformOrderId,
|
||||
errorMessage: message,
|
||||
}, { level: 'warn' })
|
||||
|
||||
return { parsed, enriched: false, reason: 'request_failed', errorMessage: message }
|
||||
return createFailedDetailResult('request_failed', {
|
||||
errorMessage: message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +266,29 @@ function extractAgisoDetailPayload(payload) {
|
||||
return payload
|
||||
}
|
||||
|
||||
export function resolveAgisoXianyuOrderDeliveryState(payload) {
|
||||
const shipTime = normalizeTimestampValue(pickFirstNonEmpty([
|
||||
payload?.ship_time,
|
||||
payload?.shipTime,
|
||||
payload?.ShipTime,
|
||||
payload?.delivery_time,
|
||||
payload?.deliveryTime,
|
||||
]))
|
||||
const orderStatus = normalizeInteger(
|
||||
pickFirstNonEmpty([
|
||||
payload?.order_status,
|
||||
payload?.orderStatus,
|
||||
payload?.status,
|
||||
]),
|
||||
)
|
||||
|
||||
return {
|
||||
shipped: shipTime > 0 || orderStatus >= 3,
|
||||
shipTime,
|
||||
orderStatus,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTotalAmountFen(payload) {
|
||||
const fenAmount = normalizeFenInteger(pickFirstNonEmpty([
|
||||
payload.payment,
|
||||
@@ -559,6 +638,33 @@ function normalizeFenInteger(value) {
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : 0
|
||||
}
|
||||
|
||||
function normalizeTimestampValue(value) {
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : 0
|
||||
}
|
||||
|
||||
function createFailedDetailResult(reason, {
|
||||
responseStatus = 0,
|
||||
payload = {},
|
||||
detailPayload = {},
|
||||
errorMessage = '',
|
||||
totalAmountFen = 0,
|
||||
delivery = { shipped: false, shipTime: 0, orderStatus: 0 },
|
||||
} = {}) {
|
||||
return {
|
||||
success: false,
|
||||
reason: String(reason || '').trim(),
|
||||
errorMessage: String(errorMessage || '').trim(),
|
||||
responseStatus: Number(responseStatus || 0),
|
||||
payload: isPlainObject(payload) ? payload : {},
|
||||
detailPayload: isPlainObject(detailPayload) ? detailPayload : {},
|
||||
totalAmountFen: Number(totalAmountFen || 0),
|
||||
shipped: Boolean(delivery?.shipped),
|
||||
shipTime: Number(delivery?.shipTime || 0),
|
||||
orderStatus: Number(delivery?.orderStatus || 0),
|
||||
}
|
||||
}
|
||||
|
||||
function pickFirstNonEmpty(values) {
|
||||
for (const value of values) {
|
||||
const normalized = String(value || '').trim()
|
||||
|
||||
@@ -37,6 +37,7 @@ export {}
|
||||
* accessToken?: string
|
||||
* enabled?: boolean | null
|
||||
* messageTemplate?: string
|
||||
* autoDeliveryMessageTemplate?: string
|
||||
* appSecret?: string
|
||||
* apiVersion?: string
|
||||
* sendMessageEndpoint?: string
|
||||
|
||||
@@ -28,6 +28,7 @@ export {}
|
||||
* accessTokenMasked: string
|
||||
* enabled: boolean | null
|
||||
* messageTemplate: string
|
||||
* autoDeliveryMessageTemplate: string
|
||||
* appSecretConfigured: boolean
|
||||
* apiVersion: string
|
||||
* sendMessageEndpoint: string
|
||||
|
||||
@@ -17,6 +17,7 @@ export {}
|
||||
* shopName?: string
|
||||
* accessToken?: string
|
||||
* messageTemplate?: string
|
||||
* autoDeliveryMessageTemplate?: string
|
||||
* appSecret?: string
|
||||
* apiVersion?: string
|
||||
* sendMessageEndpoint?: string
|
||||
@@ -91,6 +92,7 @@ export {}
|
||||
* apiVersion: string
|
||||
* sendMessageEndpoint: string
|
||||
* messageTemplate: string
|
||||
* autoDeliveryMessageTemplate: string
|
||||
* shops: AgisoMessagingShopsConfig
|
||||
* }
|
||||
* }
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface AdminAgisoShopConfigItem {
|
||||
accessTokenMasked: string
|
||||
enabled: boolean | null
|
||||
messageTemplate: string
|
||||
autoDeliveryMessageTemplate: string
|
||||
appSecretConfigured: boolean
|
||||
apiVersion: string
|
||||
sendMessageEndpoint: string
|
||||
|
||||
@@ -14,6 +14,7 @@ type EditableShop = {
|
||||
accessToken: string
|
||||
enabled: boolean
|
||||
messageTemplate: string
|
||||
autoDeliveryMessageTemplate: string
|
||||
}
|
||||
|
||||
const loading = ref(true)
|
||||
@@ -31,6 +32,7 @@ function createEmptyShop(): EditableShop {
|
||||
accessToken: '',
|
||||
enabled: true,
|
||||
messageTemplate: '',
|
||||
autoDeliveryMessageTemplate: '',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +44,7 @@ function mapEditableShop(item: AdminAgisoShopConfigItem): EditableShop {
|
||||
accessToken: item.accessToken,
|
||||
enabled: item.enabled !== false,
|
||||
messageTemplate: item.messageTemplate,
|
||||
autoDeliveryMessageTemplate: item.autoDeliveryMessageTemplate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +85,7 @@ function importObservedShop(item: AdminAgisoObservedShopItem) {
|
||||
accessToken: '',
|
||||
enabled: true,
|
||||
messageTemplate: '',
|
||||
autoDeliveryMessageTemplate: '',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -93,6 +97,7 @@ async function saveConfigs() {
|
||||
accessToken: item.accessToken.trim(),
|
||||
enabled: item.enabled,
|
||||
messageTemplate: item.messageTemplate.trim(),
|
||||
autoDeliveryMessageTemplate: item.autoDeliveryMessageTemplate.trim(),
|
||||
}))
|
||||
.filter((item) => item.shopId && item.accessToken)
|
||||
|
||||
@@ -174,13 +179,22 @@ onMounted(loadConfigs)
|
||||
</label>
|
||||
|
||||
<label class="field-block field-wide">
|
||||
<span>消息模板</span>
|
||||
<span>领取链接消息模板</span>
|
||||
<textarea
|
||||
v-model="shop.messageTemplate"
|
||||
class="text-area"
|
||||
placeholder="留空则沿用全局 AGISO_MESSAGE_TEMPLATE"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field-block field-wide">
|
||||
<span>自动发货消息模板</span>
|
||||
<textarea
|
||||
v-model="shop.autoDeliveryMessageTemplate"
|
||||
class="text-area"
|
||||
placeholder="留空则沿用全局 AGISO_AUTO_DELIVERY_MESSAGE_TEMPLATE"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="shop-actions">
|
||||
|
||||
@@ -224,6 +224,8 @@ function formatAutoDeliveryReason(value: string) {
|
||||
return 'Agiso 接口返回失败'
|
||||
case 'request_error':
|
||||
return '请求 Agiso 接口时发生异常'
|
||||
case 'delivery_not_confirmed':
|
||||
return '接口返回成功,但订单状态未确认进入已发货'
|
||||
case 'task_not_delivered':
|
||||
return '当前任务尚未进入已交付状态'
|
||||
case 'not_supported':
|
||||
|
||||
Reference in New Issue
Block a user