import { Request, Response } from 'express'; import { logger } from '../../utils/logger'; import { ConsumerOrderService, ConsumerOrder } from '../../domains/Trade/ConsumerOrderService'; import { OrderService } from '../../services/OrderService'; /** * [BIZ_OPS_01] 多平台订单 Webhook 接收器 (Order Webhook Receiver) * @description 接收来自不同平台 (Amazon, AliExpress, Shopify, TikTok) 的订单 Webhook 推送, * 并将其映射为系统统一的 cf_order 模型进行入库处理。 */ export class OrderController { /** * 平台 Webhook 入口 * POST /api/v1/orders/webhook/:platform */ static async handlePlatformWebhook(req: Request, res: Response) { const { platform } = req.params; const payload = req.body; const tenantId = req.headers['x-tenant-id'] as string || 'default-tenant'; const shopId = req.headers['x-shop-id'] as string || 'default-shop'; logger.info(`[OrderWebhook] Received webhook from platform: ${platform} for Tenant: ${tenantId}`); try { // 1. 根据不同平台解析 Payload (Mapping) const normalizedOrder = this.mapPlatformPayloadToOrder(platform as string, payload, tenantId, shopId); if (!normalizedOrder) { return res.status(400).json({ success: false, error: `Unsupported or invalid payload for platform: ${platform}` }); } // 2. 调用 ConsumerOrderService 进行同步 const orderId = await ConsumerOrderService.syncPlatformOrder(normalizedOrder); logger.info(`[OrderWebhook] Successfully synced order ${normalizedOrder.platform_order_id} to internal ID: ${orderId}`); // 3. 返回平台要求的 200 OK return res.json({ success: true, orderId }); } catch (err: any) { logger.error(`[OrderWebhook] Webhook handling failed for ${platform}: ${err.message}`); return res.status(500).json({ success: false, error: 'Internal server error' }); } } /** * 将各平台原始 Payload 映射为系统统一模型 */ private static mapPlatformPayloadToOrder(platform: string, payload: any, tenantId: string, shopId: string): Partial | null { const upperPlatform = platform.toUpperCase(); switch (upperPlatform) { case 'SHOPIFY': return { tenant_id: tenantId, shop_id: shopId, platform: 'SHOPIFY', platform_order_id: String(payload.id || payload.name), customer_name: payload.customer?.first_name ? `${payload.customer.first_name} ${payload.customer.last_name}` : 'Unknown', customer_email: payload.customer?.email, shipping_address: payload.shipping_address, items: payload.line_items?.map((item: any) => ({ skuId: item.sku, title: item.title, price: Number(item.price), quantity: item.quantity, costPrice: item.cost_price || 0 })), total_amount: Number(payload.total_price), currency: payload.currency || 'USD', status: this.mapShopifyStatus(payload.financial_status, payload.fulfillment_status), payment_status: payload.financial_status === 'paid' ? 'COMPLETED' : 'PENDING', fulfillment_status: payload.fulfillment_status === 'fulfilled' ? 'SHIPPED' : 'PENDING', trace_id: `webhook-${Date.now()}` }; case 'AMAZON': // 模拟 Amazon SP-API 结构 return { tenant_id: tenantId, shop_id: shopId, platform: 'AMAZON', platform_order_id: payload.AmazonOrderId, customer_name: payload.BuyerInfo?.BuyerName || 'Amazon Customer', customer_email: payload.BuyerInfo?.BuyerEmail, shipping_address: payload.ShippingAddress, items: payload.OrderItems?.map((item: any) => ({ skuId: item.SellerSKU, title: item.Title, price: Number(item.ItemPrice?.Amount || 0), quantity: item.QuantityOrdered, costPrice: 0 })), total_amount: Number(payload.OrderTotal?.Amount || 0), currency: payload.OrderTotal?.CurrencyCode || 'USD', status: payload.OrderStatus === 'Shipped' ? 'SHIPPED' : 'PAID', payment_status: 'COMPLETED', fulfillment_status: payload.OrderStatus === 'Shipped' ? 'SHIPPED' : 'PENDING', trace_id: `webhook-${Date.now()}` }; case 'TIKTOK': // 模拟 TikTok Shop API 结构 return { tenant_id: tenantId, shop_id: shopId, platform: 'TIKTOK', platform_order_id: payload.order_id, customer_name: payload.recipient_address?.name || 'TikTok Customer', shipping_address: payload.recipient_address, items: payload.item_list?.map((item: any) => ({ skuId: item.sku_id, title: item.product_name, price: Number(item.sku_sale_price), quantity: 1, costPrice: 0 })), total_amount: Number(payload.total_amount), currency: payload.currency || 'USD', status: payload.order_status === 100 ? 'PAID' : 'UNPAID', payment_status: payload.order_status >= 100 ? 'COMPLETED' : 'PENDING', fulfillment_status: 'PENDING', trace_id: `webhook-${Date.now()}` }; default: return null; } } private static mapShopifyStatus(financial: string, fulfillment: string): 'UNPAID' | 'PAID' | 'SHIPPED' | 'DELIVERED' { if (fulfillment === 'fulfilled') return 'SHIPPED'; if (financial === 'paid') return 'PAID'; return 'UNPAID'; } /** * 手动触发同步 (API 入口) * POST /api/v1/orders/sync */ static async triggerManualSync(req: Request, res: Response) { try { const { platform, shopId } = req.body; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; logger.info(`[OrderSync] Manual sync triggered for Platform: ${platform}, Shop: ${shopId}`); // 此处应调用各平台 Connector 的 supportsOrderPull 能力 // 示例:Mock 拉取成功 res.json({ success: true, message: `Manual sync triggered for ${platform}` }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 获取订单统计 * GET /api/v1/orders/stats */ static async getStats(req: Request, res: Response) { try { const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const stats = await ConsumerOrderService.getOrderStats(tenantId); res.json({ success: true, data: stats }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 创建订单 * POST /api/v1/orders */ static async createOrder(req: Request, res: Response) { try { const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const orderData = { ...req.body, tenant_id: tenantId }; const orderId = await OrderService.createOrder(orderData); res.json({ success: true, orderId }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 获取订单详情 * GET /api/v1/orders/:id */ static async getOrderById(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const order = await OrderService.getOrderById(id as string, tenantId); if (order) { res.json({ success: true, data: order }); } else { res.status(404).json({ success: false, error: 'Order not found' }); } } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 更新订单 * PUT /api/v1/orders/:id */ static async updateOrder(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; await OrderService.updateOrder(id as string, tenantId, req.body); res.json({ success: true, message: 'Order updated successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 删除订单 * DELETE /api/v1/orders/:id */ static async deleteOrder(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; await OrderService.deleteOrder(id as string, tenantId); res.json({ success: true, message: 'Order deleted successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 获取订单列表 * GET /api/v1/orders */ static async getOrders(req: Request, res: Response) { try { const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const params = { page: parseInt(req.query.page as string) || 1, pageSize: parseInt(req.query.pageSize as string) || 20, status: req.query.status as string, platform: req.query.platform as string, startDate: req.query.startDate as string, endDate: req.query.endDate as string, }; const result = await OrderService.getOrders(tenantId, params); res.json({ success: true, data: result }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 批量更新订单 * PUT /api/v1/orders/batch */ static async batchUpdateOrders(req: Request, res: Response) { try { const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { orderIds, updates } = req.body; const result = await OrderService.batchUpdateOrders(tenantId, orderIds, updates); res.json({ success: true, data: result }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 订单状态流转 * POST /api/v1/orders/:id/status */ static async transitionOrderStatus(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { status, reason } = req.body; await OrderService.transitionOrderStatus(tenantId, id as string, status, reason); res.json({ success: true, message: 'Order status updated successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 批量审核订单 * POST /api/v1/orders/batch/audit */ static async batchAuditOrders(req: Request, res: Response) { try { const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { orderIds } = req.body; const result = await OrderService.batchAuditOrders(tenantId, orderIds); res.json({ success: true, data: result }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 批量发货 * POST /api/v1/orders/batch/ship */ static async batchShipOrders(req: Request, res: Response) { try { const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { orderIds } = req.body; const result = await OrderService.batchShipOrders(tenantId, orderIds); res.json({ success: true, data: result }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 标记订单为异常 * POST /api/v1/orders/:id/exception */ static async markOrderAsException(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { reason } = req.body; await OrderService.markOrderAsException(tenantId, id as string, reason); res.json({ success: true, message: 'Order marked as exception' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 自动改派订单 * POST /api/v1/orders/:id/reroute */ static async autoRerouteOrder(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; await OrderService.autoRerouteOrder(tenantId, id as string); res.json({ success: true, message: 'Order rerouted successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 重试异常订单 * POST /api/v1/orders/:id/retry */ static async retryExceptionOrder(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; await OrderService.retryExceptionOrder(tenantId, id as string); res.json({ success: true, message: 'Order retried successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 取消订单 * POST /api/v1/orders/:id/cancel */ static async cancelOrder(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { reason } = req.body; await OrderService.cancelOrder(tenantId, id as string, reason); res.json({ success: true, message: 'Order cancelled successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 申请退款 * POST /api/v1/orders/:id/refund */ static async requestRefund(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { reason, amount } = req.body; const refundId = await OrderService.requestRefund(tenantId, id as string, reason, amount); res.json({ success: true, refundId }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 审批退款 * POST /api/v1/orders/refund/:id/approve */ static async approveRefund(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { approved, note } = req.body; await OrderService.approveRefund(tenantId, id as string, approved, note); res.json({ success: true, message: 'Refund processed successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 申请售后 * POST /api/v1/orders/:id/after-sales */ static async requestAfterSales(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { type, reason, items } = req.body; const serviceId = await OrderService.requestAfterSales(tenantId, id as string, type, reason, items); res.json({ success: true, serviceId }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 处理售后申请 * POST /api/v1/orders/after-sales/:id/process */ static async processAfterSales(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; const { action, note } = req.body; await OrderService.processAfterSales(tenantId, id as string, action, note); res.json({ success: true, message: 'After-sales service processed successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } /** * 完成订单 * POST /api/v1/orders/:id/complete */ static async completeOrder(req: Request, res: Response) { try { const { id } = req.params; const { tenantId } = (req as any).traceContext || { tenantId: 'default-tenant' }; await OrderService.completeOrder(tenantId, id as string); res.json({ success: true, message: 'Order completed successfully' }); } catch (err: any) { res.status(500).json({ success: false, error: err.message }); } } }