API Events
1. Inbound — IPN (handled by @nx/mq-pay)
Payment provider IPN endpoints are owned by @nx/mq-pay (registered as controllers when running in FULL or API mode). Payment package itself does not declare IPN routes.
| Provider | IPN endpoint | Auth |
|---|---|---|
| VNPAY QR MMS | (provider-specific) | Provider signature verification (in MQ-Pay PaymentVerificationService) |
| VNPAY PhonePOS | (provider-specific) | Same |
| SYSTEM | n/a (in-platform) | n/a |
Exact IPN paths are defined inside
@nx/mq-payprovider registry; consult that package for routes.
2. Outbound — HTTP Webhooks
Subscribers register their endpoints via
WebhookConfigController(REST). On each MQ-Pay state change,WebhookEventHandlerHelperqueries active webhooks subscribed to the event type and dispatches viaWebhookDispatcherService.
Filtering
WebhookEventHandlerHelper.handle() (src/helpers/webhook-event-handler/helper.ts:54-96):
- Skip dispatch for non-actionable events (
TRANSACTION_CREATED,ATTEMPT_CREATED,ATTEMPT_SENT) — those are WS-only. - Query
WebhookConfigRepository.find({ status=ACTIVATED, eventTypes contains eventType }). - For each matching webhook →
WebhookDispatcherService.dispatch({ webhookConfig, eventType, payload }).
Event types
From
@nx/mq-pay/src/common/events/index.ts:14-28.
| Event | Payload type | Triggered by | Webhook dispatched |
|---|---|---|---|
mq-pay:transaction.created | ITransactionCreatedPayload | Transaction insert | ✗ (WS only) |
mq-pay:transaction.settled | ITransactionSettledPayload | Full settlement | ✓ |
mq-pay:transaction.cancelled | ITransactionCancelledPayload | Auto/admin cancel | ✓ |
mq-pay:attempt.created | IAttemptCreatedPayload | Attempt insert | ✗ (WS only) |
mq-pay:attempt.sent | IAttemptSentPayload | Provider call success (QR available) | ✗ (WS only) |
mq-pay:attempt.success | IAttemptSuccessPayload | IPN confirmed | ✓ |
mq-pay:attempt.failed | IAttemptFailedPayload | Provider call failed | ✓ |
mq-pay:attempt.expired | IAttemptExpiredPayload | QR expired without payment | ✓ |
mq-pay:attempt.cancelled | IAttemptCancelledPayload | Cancel cascade | ✓ |
Webhook payload shape
{
event: string, // e.g. 'mq-pay:attempt.success'
transaction?: TTransaction,
attempt?: TPaymentAttempt,
timestamp: ISO,
source?: { id: string, type: string }, // typically SaleOrder/SaleCheck
}Subscriber side (sale)
Sale's PaymentWebhookController receives these at POST /v1/api/sale/webhooks/payment. See sale/payments.md for the full handler logic.
3. Outbound — WebSocket
PaymentSocketEventService(src/services/payment-socket-event.service.ts).
| Topic | Constant | Trigger |
|---|---|---|
observation/payment/transaction | PaymentWebSocketTopics.TRANSACTION | Any transaction event (*.created, *.settled, *.cancelled) |
observation/payment/payment-attempt | PaymentWebSocketTopics.ATTEMPT | Any attempt event (*.created, *.sent, *.success, *.failed, *.expired, *.cancelled) |
Room fanout
// Transaction (PaymentWebSocketRooms.getTransactionRooms)
[
'merchants/<merchantId>',
'merchants/<merchantId>/transactions',
'transactions/<transactionId>',
]
// Attempt (PaymentWebSocketRooms.getPaymentAttemptRooms)
[
'merchants/<merchantId>',
'merchants/<merchantId>/payment-attempts',
'payment-attempts/<attemptId>',
'transactions/<transactionId>/payment-attempts', // when transactionId present
]merchantId extracted from transaction.metadata.merchant.source.id or attempt.metadata.merchant.source.id.
4. BullMQ (internal — within MQ-Pay)
| Queue | Type | Concurrency | Source |
|---|---|---|---|
scheduler | scheduler jobs (3 partitions: P01–P03) | 10 jobs / partition | @nx/mq-pay/src/services/queue/... |
confirmation | confirmation jobs (3 partitions) | 10 jobs / partition | same |
Queues are internal to
@nx/mq-pay. Workers consume from these. Mode-conditional:FULLandWORKERmodes spawn workers;APImode produces jobs but does not consume.
5. Idempotency & Ordering
| Surface | Delivery | Recovery |
|---|---|---|
| Webhook (HTTP) | at-most-once per dispatch; retried on transport failure | WebhookDispatcherService retry policy (default: timeoutMs=30000, maxRetries=3 from WebhookConfig.metadata) |
| WebSocket | best-effort | clients refetch via REST on reconnect |
| BullMQ | at-least-once | per-job idempotency key in @nx/mq-pay |
6. Related Pages
- Webhook Dispatch — full handler details
- Integration — sale subscribes to these events
- Architecture — IPN flow