197 lines
5.5 KiB
JavaScript
197 lines
5.5 KiB
JavaScript
// @ts-check
|
|
|
|
import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
|
import { formatFenToAmount, parseAmountToFen } from '../../utils/money.js'
|
|
import { extractAgisoTradePayload, resolveAgisoTradePlatformOrderId } from '../order/agiso-trade-parsing.js'
|
|
import { safeParseJson } from './admin-query-utils.js'
|
|
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
|
|
|
/** @typedef {import('../../types/admin-read-models.js').AdminWebhookEventListItem} AdminWebhookEventListItem */
|
|
/** @typedef {import('../../types/repository-rows.js').WebhookEventRow} WebhookEventRow */
|
|
|
|
/**
|
|
* @param {WebhookEventRow} item
|
|
* @returns {Promise<AdminWebhookEventListItem>}
|
|
*/
|
|
export async function mapAdminWebhookEvent(item, { includeRaw = false } = {}) {
|
|
const headers = normalizeRecord(safeParseJson(item.headers_json))
|
|
const query = normalizeRecord(safeParseJson(item.query_json))
|
|
const body = normalizeRecord(safeParseJson(item.body_json))
|
|
const payload = extractWebhookPayload(body)
|
|
const itemSources = extractWebhookItemSources(payload)
|
|
const rawAmount = pickFirstNonEmpty([
|
|
payload.total_fee,
|
|
payload.totalFee,
|
|
payload.TotalFee,
|
|
payload.pay_fee,
|
|
payload.payFee,
|
|
payload.Payment,
|
|
payload.payment,
|
|
payload.buy_amount,
|
|
payload.buyAmount,
|
|
])
|
|
const totalAmountFen = parseAmountToFen(rawAmount)
|
|
const taskCount = item.related_order_id ? (await listTasksByOrderId(item.related_order_id)).length : 0
|
|
const shopId = item.shop_id || pickFirstNonEmpty([
|
|
payload.PlatformUserId,
|
|
payload.platformUserId,
|
|
payload.platform_user_id,
|
|
payload.seller_id,
|
|
payload.sellerId,
|
|
payload.SellerId,
|
|
payload.seller_nick,
|
|
payload.sellerNick,
|
|
payload.SellerNick,
|
|
])
|
|
|
|
const mapped = {
|
|
eventId: item.id,
|
|
provider: item.provider || 'agiso',
|
|
platform: item.platform,
|
|
platformRaw: pickFirstNonEmpty([
|
|
query.fromPlatform,
|
|
query.from_platform,
|
|
body.fromPlatform,
|
|
body.from_platform,
|
|
payload.fromPlatform,
|
|
payload.FromPlatform,
|
|
payload.platform,
|
|
payload.Platform,
|
|
]),
|
|
shopId,
|
|
shopName: resolveDisplayShopName(
|
|
item.provider,
|
|
shopId,
|
|
item.shop_name || pickFirstNonEmpty([
|
|
payload.shopName,
|
|
payload.shop_name,
|
|
payload.ShopName,
|
|
payload.seller_nick,
|
|
payload.sellerNick,
|
|
payload.SellerNick,
|
|
]),
|
|
),
|
|
eventType: item.event_type,
|
|
eventKey: item.event_key,
|
|
signatureValid: Boolean(item.signature_valid),
|
|
processed: Boolean(item.processed),
|
|
processError: item.process_error,
|
|
visibilityLevel: resolveWebhookVisibilityLevel(item.process_error),
|
|
relatedOrderId: item.related_order_id,
|
|
createdAt: item.created_at,
|
|
platformOrderId: resolveAgisoTradePlatformOrderId(payload),
|
|
buyerId: pickFirstNonEmpty([
|
|
payload.buyer_id,
|
|
payload.buyerId,
|
|
payload.BuyerId,
|
|
payload.openid,
|
|
payload.BuyerOpenUid,
|
|
payload.buyer_open_uid,
|
|
payload.buyerOpenUid,
|
|
]),
|
|
buyerName: pickFirstNonEmpty([
|
|
payload.buyer_name,
|
|
payload.buyerName,
|
|
payload.BuyerName,
|
|
payload.nick,
|
|
payload.BuyerNick,
|
|
payload.buyer_nick,
|
|
payload.buyerNick,
|
|
]),
|
|
totalAmount: rawAmount ? formatFenToAmount(totalAmountFen) : '',
|
|
totalAmountFen,
|
|
currency: pickFirstNonEmpty([payload.currency, payload.Currency, 'CNY']) || 'CNY',
|
|
itemCount: itemSources.length,
|
|
taskCount,
|
|
messageText: pickFirstNonEmpty([
|
|
payload.MsgTypeDes,
|
|
payload.msgTypeDes,
|
|
payload.msg_type_desc,
|
|
payload.message,
|
|
payload.msg,
|
|
]),
|
|
requestTimestamp: pickFirstNonEmpty([query.timestamp]),
|
|
requestSign: pickFirstNonEmpty([query.sign]),
|
|
aopic: pickFirstNonEmpty([query.aopic]),
|
|
sourceHost: pickFirstNonEmpty([headers['x-forwarded-host'], headers.host]),
|
|
sourceIp: pickFirstNonEmpty([headers['x-forwarded-for'], headers['x-real-ip']]),
|
|
userAgent: pickFirstNonEmpty([headers['user-agent'], headers['User-Agent']]),
|
|
}
|
|
|
|
if (!includeRaw) {
|
|
return mapped
|
|
}
|
|
|
|
return {
|
|
...mapped,
|
|
headers,
|
|
query,
|
|
body,
|
|
payload,
|
|
}
|
|
}
|
|
|
|
function extractWebhookPayload(body) {
|
|
return extractAgisoTradePayload(body)
|
|
}
|
|
|
|
function extractWebhookItemSources(payload) {
|
|
const normalizedPayload = normalizeRecord(payload)
|
|
const candidates = [
|
|
normalizedPayload.orders,
|
|
normalizedPayload.Orders,
|
|
normalizedPayload.items,
|
|
normalizedPayload.Items,
|
|
normalizedPayload.order_items,
|
|
]
|
|
|
|
for (const candidate of candidates) {
|
|
if (Array.isArray(candidate) && candidate.length > 0) {
|
|
return candidate.map((item) => normalizeRecord(item)).filter((item) => Object.keys(item).length > 0)
|
|
}
|
|
}
|
|
|
|
if (
|
|
pickFirstNonEmpty([
|
|
normalizedPayload.item_id,
|
|
normalizedPayload.itemId,
|
|
normalizedPayload.ItemId,
|
|
normalizedPayload.num_iid,
|
|
normalizedPayload.numIid,
|
|
normalizedPayload.NumIid,
|
|
])
|
|
) {
|
|
return [normalizedPayload]
|
|
}
|
|
|
|
return []
|
|
}
|
|
|
|
export function resolveWebhookVisibilityLevel(processError) {
|
|
const normalized = String(processError || '').trim()
|
|
|
|
if (normalized.startsWith('ignored_')) {
|
|
return 'ignored'
|
|
}
|
|
|
|
return 'important'
|
|
}
|
|
|
|
function normalizeRecord(value) {
|
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
}
|
|
|
|
function pickFirstNonEmpty(values) {
|
|
for (const value of values) {
|
|
if (typeof value === 'string' && value.trim()) {
|
|
return value.trim()
|
|
}
|
|
|
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
return String(value)
|
|
}
|
|
}
|
|
|
|
return ''
|
|
}
|