可以接收到咸鱼的订单创建连接
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
UPDATE orders
|
||||
SET total_amount = total_amount * 100
|
||||
WHERE total_amount != 0;
|
||||
@@ -0,0 +1,11 @@
|
||||
UPDATE orders
|
||||
SET platform = 'xianyu'
|
||||
WHERE lower(trim(platform)) = 'aldsidle';
|
||||
|
||||
UPDATE webhook_events
|
||||
SET platform = 'xianyu'
|
||||
WHERE lower(trim(platform)) = 'aldsidle';
|
||||
|
||||
UPDATE message_deliveries
|
||||
SET platform = 'xianyu'
|
||||
WHERE lower(trim(platform)) = 'aldsidle';
|
||||
@@ -7,6 +7,7 @@ import { getOrderById, listOrders } from '../repositories/order-repo.js'
|
||||
import { getTaskById, listTasks, listTasksByOrderId, updateTask } from '../repositories/task-repo.js'
|
||||
import { getWebhookEventById, listWebhookEvents, listWebhookEventsByOrderId } from '../repositories/webhook-event-repo.js'
|
||||
import { createHttpError } from '../utils/http.js'
|
||||
import { formatFenToAmount, normalizeFen, parseAmountToFen } from '../utils/money.js'
|
||||
import { nowIso } from '../utils/time.js'
|
||||
import { reserveCdkForTask } from './cdk-service.js'
|
||||
import { replayAgisoTradeWebhookEvent } from './webhook-service.js'
|
||||
@@ -80,7 +81,8 @@ export function getAdminOrderDetail(orderId) {
|
||||
buyerId: order.buyer_id,
|
||||
buyerName: order.buyer_name,
|
||||
receiverContact: order.receiver_contact,
|
||||
totalAmount: order.total_amount,
|
||||
totalAmount: formatFenToAmount(order.total_amount),
|
||||
totalAmountFen: normalizeFen(order.total_amount),
|
||||
currency: order.currency,
|
||||
paidAt: order.paid_at,
|
||||
createdAt: order.created_at,
|
||||
@@ -356,20 +358,7 @@ export function getAdminWebhookEvents(query = {}) {
|
||||
})
|
||||
|
||||
return {
|
||||
items: items.map((item) => ({
|
||||
eventId: item.id,
|
||||
provider: item.provider || 'agiso',
|
||||
platform: item.platform,
|
||||
shopId: item.shop_id || '',
|
||||
shopName: item.shop_name || '',
|
||||
eventType: item.event_type,
|
||||
eventKey: item.event_key,
|
||||
signatureValid: Boolean(item.signature_valid),
|
||||
processed: Boolean(item.processed),
|
||||
processError: item.process_error,
|
||||
relatedOrderId: item.related_order_id,
|
||||
createdAt: item.created_at,
|
||||
})),
|
||||
items: items.map((item) => mapAdminWebhookEvent(item)),
|
||||
pagination: { page, pageSize, total },
|
||||
}
|
||||
}
|
||||
@@ -384,23 +373,7 @@ export function getAdminWebhookEventDetail(eventId) {
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
eventId: event.id,
|
||||
provider: event.provider || 'agiso',
|
||||
platform: event.platform,
|
||||
shopId: event.shop_id || '',
|
||||
shopName: event.shop_name || '',
|
||||
eventType: event.event_type,
|
||||
eventKey: event.event_key,
|
||||
signatureValid: Boolean(event.signature_valid),
|
||||
processed: Boolean(event.processed),
|
||||
processError: event.process_error,
|
||||
relatedOrderId: event.related_order_id,
|
||||
createdAt: event.created_at,
|
||||
headers: safeParseJson(event.headers_json),
|
||||
query: safeParseJson(event.query_json),
|
||||
body: safeParseJson(event.body_json),
|
||||
}
|
||||
return mapAdminWebhookEvent(event, { includeRaw: true })
|
||||
}
|
||||
|
||||
export async function replayAdminWebhookEvent(eventId) {
|
||||
@@ -607,6 +580,130 @@ function mapAdminTaskSummary(task) {
|
||||
}
|
||||
}
|
||||
|
||||
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 firstItem = itemSources[0] || {}
|
||||
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 ? listTasksByOrderId(item.related_order_id).length : 0
|
||||
|
||||
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: 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,
|
||||
]),
|
||||
shopName: 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,
|
||||
relatedOrderId: item.related_order_id,
|
||||
createdAt: item.created_at,
|
||||
platformOrderId: pickFirstNonEmpty([
|
||||
payload.biz_order_id,
|
||||
payload.bizOrderId,
|
||||
payload.Tid,
|
||||
payload.tid,
|
||||
payload.Oid,
|
||||
payload.oid,
|
||||
payload.order_id,
|
||||
payload.orderId,
|
||||
firstItem.Oid,
|
||||
firstItem.oid,
|
||||
]),
|
||||
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 mapAdminCdkListItem(item) {
|
||||
const task = item.reserved_by_task_id ? getTaskById(item.reserved_by_task_id) : null
|
||||
const order = task?.order_id ? getOrderById(task.order_id) : null
|
||||
@@ -711,7 +808,8 @@ function mapAdminOrderListItem(item) {
|
||||
orderStatus: item.order_status,
|
||||
payStatus: item.pay_status,
|
||||
buyerName: item.buyer_name,
|
||||
totalAmount: item.total_amount,
|
||||
totalAmount: formatFenToAmount(item.total_amount),
|
||||
totalAmountFen: normalizeFen(item.total_amount),
|
||||
currency: item.currency,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
@@ -932,6 +1030,67 @@ function safeParseJson(rawText) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRecord(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
||||
}
|
||||
|
||||
function extractWebhookPayload(body) {
|
||||
const normalizedBody = normalizeRecord(body)
|
||||
const rawJson = String(normalizedBody.json || normalizedBody.JSON || '').trim()
|
||||
|
||||
if (rawJson) {
|
||||
return normalizeRecord(safeParseJson(rawJson))
|
||||
}
|
||||
|
||||
return normalizedBody
|
||||
}
|
||||
|
||||
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 []
|
||||
}
|
||||
|
||||
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 ''
|
||||
}
|
||||
|
||||
function maskCode(value) {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { findTaskByClaimTokenId, updateTask } from '../repositories/task-repo.js
|
||||
import { getCdkById, markCdkDelivered, releaseReservedCdk } from '../repositories/cdk-repo.js'
|
||||
import { buildClaimUrl } from './claim-service.js'
|
||||
import { createHttpError } from '../utils/http.js'
|
||||
import { formatFenToAmount, normalizeFen } from '../utils/money.js'
|
||||
import { nowIso } from '../utils/time.js'
|
||||
|
||||
const CLAIM_TERMINAL_STATUSES = new Set(['expired', 'closed'])
|
||||
@@ -385,7 +386,8 @@ function buildClaimDetailPayload({ claimToken, task, order, orderItem, session }
|
||||
platformOrderId: order.platform_order_id,
|
||||
payStatus: order.pay_status,
|
||||
orderStatus: order.order_status,
|
||||
totalAmount: order.total_amount,
|
||||
totalAmount: formatFenToAmount(order.total_amount),
|
||||
totalAmountFen: normalizeFen(order.total_amount),
|
||||
currency: order.currency,
|
||||
},
|
||||
orderItem: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createWebhookEvent, updateWebhookEvent } from '../repositories/webhook-
|
||||
import { upsertOrderFromWebhook } from './order-service.js'
|
||||
import { createHttpError } from '../utils/http.js'
|
||||
import { logWebhook } from '../utils/logger.js'
|
||||
import { parseAmountToFen } from '../utils/money.js'
|
||||
import { nowIso } from '../utils/time.js'
|
||||
|
||||
export async function processAgisoTradeWebhook(requestLike) {
|
||||
@@ -229,7 +230,7 @@ function parseAgisoTradeRequest(requestLike) {
|
||||
payload.receiver_mobile,
|
||||
payload.receiverMobile,
|
||||
]),
|
||||
totalAmount: normalizeInteger(
|
||||
totalAmount: parseAmountToFen(
|
||||
pickFirstNonEmpty([
|
||||
payload.total_fee,
|
||||
payload.totalFee,
|
||||
@@ -524,6 +525,7 @@ function resolveBusinessPlatform(rawPlatform) {
|
||||
xianyu: 'xianyu',
|
||||
idlefish: 'xianyu',
|
||||
idle: 'xianyu',
|
||||
aldsidle: 'xianyu',
|
||||
taobaoidle: 'xianyu',
|
||||
taobaoxianyu: 'xianyu',
|
||||
taobao: 'taobao',
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
export function parseAmountToFen(value) {
|
||||
const normalized = normalizeAmountInput(value)
|
||||
|
||||
if (!normalized) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const sign = normalized.startsWith('-') ? -1 : 1
|
||||
const unsigned = sign < 0 ? normalized.slice(1) : normalized
|
||||
const match = unsigned.match(/^(\d+)(?:\.(\d+))?$/)
|
||||
|
||||
if (!match) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const integerPart = Number(match[1] || '0')
|
||||
|
||||
if (!Number.isSafeInteger(integerPart)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const fractionRaw = String(match[2] || '')
|
||||
const fractionForRound = `${fractionRaw}000`
|
||||
let cents = integerPart * 100 + Number(fractionForRound.slice(0, 2))
|
||||
|
||||
if (Number(fractionForRound[2] || '0') >= 5) {
|
||||
cents += 1
|
||||
}
|
||||
|
||||
return cents * sign
|
||||
}
|
||||
|
||||
export function formatFenToAmount(value) {
|
||||
const normalizedFen = normalizeFen(value)
|
||||
const sign = normalizedFen < 0 ? '-' : ''
|
||||
const absoluteFen = Math.abs(normalizedFen)
|
||||
const integerPart = Math.floor(absoluteFen / 100)
|
||||
const fractionPart = String(absoluteFen % 100).padStart(2, '0')
|
||||
return `${sign}${integerPart}.${fractionPart}`
|
||||
}
|
||||
|
||||
export function normalizeFen(value) {
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? Math.round(parsed) : 0
|
||||
}
|
||||
|
||||
function normalizeAmountInput(value) {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
return ''
|
||||
}
|
||||
|
||||
return value.trim().replace(/,/g, '')
|
||||
}
|
||||
Reference in New Issue
Block a user