Skip to content

API Sự kiện

Sale là emitter-only với Kafka — component Kafka đăng ký Producer nhưng không có Consumer. Inbound async đến qua webhook HTTP từ MQ-Pay.

1. Inbound — HTTP Webhook

EndpointAuthNguồnHandler
POST /v1/api/sale/webhooks/paymentkhông (tin cậy nội bộ)dịch vụ thanh toán @nx/mq-payPaymentWebhookService.handleEvent

Loại sự kiện

Định nghĩa trong src/common/webhook-types.ts:9 (PaymentWebhookEventTypes). Loại sự kiện đọc từ header X-Webhook-Event-Type hoặc body JSON eventType.

Sự kiệnConstantHiệu ứng
mq-pay:attempt.successATTEMPT_SUCCESSĐánh dấu thanh toán một phần/đầy đủ dựa trên transaction.paid so với total
mq-pay:attempt.failedATTEMPT_FAILEDĐánh dấu order/check CANCELLED nếu không có attempt thành công khác
mq-pay:attempt.expiredATTEMPT_EXPIREDGiống như failed
mq-pay:attempt.cancelledATTEMPT_CANCELLEDGiống như failed
mq-pay:transaction.settledTRANSACTION_SETTLEDXác nhận cuối cấp transaction
mq-pay:transaction.cancelledTRANSACTION_CANCELLEDCascade hủy

Định tuyến

payload.transaction.sourceType (hoặc attempt.metadata.source.type)Định tuyến đến
SaleOrderSaleOrderPaymentWebhookService.handleSaleOrderEvent
SaleCheckSaleCheckPaymentWebhookService.handleCheckEvent
khácwarn-log; bỏ qua

Schema payload (zod)

ts
// PaymentWebhookRequestSchema
{
  eventType: string,
  timestamp: number,
  payload: {
    timestamp: string,
    source?: string,
    transaction?: {
      id: string,
      uid: string,
      status: string,
      total: number | string,
      paid: number | string,
      sourceType?: string,
      sourceId?: string,
      metadata?: Record<string, unknown>,
    },
    attempt?: {
      id: string,
      uid: string,
      status: string,
      amount: number | string,
      paymentProvider: string,
      reason?: string,
      metadata?: { source: { id: string, uid: string, type: string } },
    },
  },
}

2. Outbound — Kafka

Cấu hình Producer: src/components/kafka/component.ts. Idempotent producer, lz4 compression, không có consumer.

TopicConstantTriggerProducer Site
payment.successKafkaTopics.PAYMENT_SUCCESSSau khi _handleOrderPaymentSuccess commit trạng thái PARTIAL/COMPLETEDsale-order-payment-webhook.service.ts:_enqueuePaymentSuccess
kitchen-ticket-item.status-changedKafkaTopics.KITCHEN_TICKET_ITEM_STATUS_CHANGEDMỗi lần KitchenTicketItem đổi trạng thái (PENDING/COOKING/READY/SERVED/VOIDED)kitchen-ticket-item.service.ts:_emitKitchenTicketItemStatusChanged

Consumers (downstream)

TopicConsumer
payment.success@nx/inventory (giảm stock + reserve materials), @nx/finance (ghi INCOME)
kitchen-ticket-item.status-changed@nx/inventory (tiêu thụ materials khi READY, hoàn lại khi VOIDED)

Schema payload

ts
// PAYMENT_SUCCESS — TSalePaymentSuccess
export interface TSalePaymentSuccess {
  saleOrderId: string;
  saleOrderNumber: string;
  saleOrderStatus: string;
  merchantId: string;
  saleChannelId: string;
  createdBy?: string;
  modifiedBy?: string;
  payment: { total: number; paid: number; status: string };
  items: Array<{
    itemId: string;
    itemType: 'PRODUCT_VARIANT' | 'MATERIAL';
    quantity: number;
    unitPrice: number;
    tax: number;
    discount: number;
    subtotal: number;
    total: number;
  }>;
}

// KITCHEN_TICKET_ITEM_STATUS_CHANGED — TKitchenTicketItemStatusChangedMessage
export interface TKitchenTicketItemStatusChangedMessage {
  kitchenTicketItemId: string;
  kitchenTicketId: string;
  saleOrderId: string;
  merchantId: string;
  status: 'PENDING' | 'COOKING' | 'READY' | 'SERVED' | 'VOIDED';
  item: {
    saleOrderItemId: string;
    itemType: 'PRODUCT_VARIANT' | 'MATERIAL';
    itemId: string;
    quantity: number;
  };
}

Cấu hình Producer

src/components/kafka/component.ts KHÔNG đặt rõ ràng idempotent, acks, hoặc compression. Producer dùng mặc định của KafkaProducerHelper. Khác với @nx/inventory, sale không bật chế độ idempotent hoặc nén lz4 trong code.

Cài đặtGiá trị
requestTimeout60_000ms
connectTimeout30_000ms
acks / idempotent / compressionkhông đặt — mặc định KafkaProducerHelper
Client IDSVC-00030-SALE_PRODUCER (override qua APP_ENV_KAFKA_CLIENT_ID)
SASLcó điều kiện theo APP_ENV_KAFKA_SASL_ENABLE=true

3. Phát WebSocket

src/common/websocket.ts. 7 topic × nhiều helper room cho fanout.

Topics

TopicConstantNguồn
observation/sale/sale-orderSaleWebSocketTopics.ORDERSự kiện vòng đời order
observation/sale/sale-order-itemSaleWebSocketTopics.ORDER_ITEMThêm/cập nhật/xóa item
observation/sale/sale-checkSaleWebSocketTopics.CHECKCheck create/payment-update
observation/sale/kitchen-ticketSaleWebSocketTopics.KITCHEN_TICKETVòng đời ticket
observation/sale/kitchen-ticket-itemSaleWebSocketTopics.KITCHEN_TICKET_ITEMTrạng thái item / void
observation/allocation/allocation-usageSaleWebSocketTopics.ALLOCATION_USAGEUsage create/complete/cancel
observation/sale/reservationSaleWebSocketTopics.RESERVATIONReservation create/confirm/cancel/check-in

Room fanout (mỗi lần phát)

HelperRooms phát đến
getSaleOrderRooms({ orderId, merchantId })merchants/<m> + merchants/<m>/sale-orders + sale-orders/<orderId>
getSaleOrderItemRooms({ itemId, orderId, merchantId })+ sale-orders/<orderId>/sale-order-items + sale-orders/<orderId>/sale-order-items/<itemId>
getKitchenTicketRooms({ ticketId, orderId, merchantId, stationId? })merchants/<m> + merchants/<m>/kitchen + sale-orders/<orderId>/kitchen + kitchen-tickets/<ticketId> (+ kitchen-stations/<stationId> nếu được gán)
getKitchenTicketItemRooms({...})Tất cả rooms của kitchen ticket + kitchen-tickets/<id>/items + kitchen-ticket-items/<itemId>
getAllocationUsageRooms({ merchantId, unitId, zoneId?, parentId?, grandparentId? })merchants/<m> + merchants/<m>/allocation-usages + allocation-units/<unitId> (+ cây phân cấp zone)
getReservationRooms({...})merchants/<m> + merchants/<m>/reservations + reservations/<id> (+ allocation rooms)

Event actions (trường event của payload)

ts
SaleOrderEventActions = {
  ORDER_CREATED, ORDER_UPDATED, ORDER_DELETED, ORDER_CLEARED,
  ORDER_CHECKOUT, ORDER_REVERT_CHECKOUT, ORDER_PAYMENT_UPDATED,
  ITEM_ADDED, ITEM_CREATED, ITEM_UPDATED, ITEM_DELETED,
}
KitchenTicketEventActions = {
  TICKET_CREATED, TICKET_VOIDED, TICKET_RUSHED, TICKET_STATUS_CHANGED,
  ITEM_VOIDED, ITEM_STATUS_CHANGED,
}
AllocationUsageEventActions = {
  USAGE_CREATED, USAGE_COMPLETED, USAGE_CANCELLED,
}
ReservationEventActions = {
  RESERVATION_CREATED, RESERVATION_CONFIRMED, RESERVATION_CANCELLED, RESERVATION_CHECKED_IN,
}

4. Idempotency & Thứ tự

Bề mặtPhân phốiThứ tựPhục hồi
HTTP webhookat-most-once (HTTP)request-idMQ-Pay retry khi 5xx
Kafka payment.successat-least-onceper-saleOrderId qua keydedup phía consumer downstream (inventory tracking, finance idempotency)
Kafka kitchen-ticket-item.status-changedat-least-onceper-saleOrderIddedup phía consumer downstream
WebSocketbest-effortkhông (broadcast)client refetch từ REST khi reconnect

5. Trang liên quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.