# Northline Pay event catalogue
# Owner: platform team. Every topic is keyed by payment_id so all events of
# one payment land on the same partition, in order.
#
# Conventions
#   - event_id is unique per event, prefixed evt_.
#   - occurred_at is RFC 3339 in UTC with the Z suffix. Consumers validate it.
#   - Schemas are JSON Schema. A consumer that fails validation sends the
#     message to its dead-letter topic and does not retry.

topics:
  - name: payments.payment.authorized
    version: 1
    key: payment_id
    producer: orchestrator
    consumers: [webhook-dispatcher, risk]
    public: true          # delivered to merchants as payment.authorized
    schema:
      type: object
      required: [event_id, event_type, occurred_at, payment_id, merchant_id, amount, currency]
      properties:
        event_id: { type: string, pattern: "^evt_[A-Za-z0-9]{16}$" }
        event_type: { type: string, const: payment.authorized }
        occurred_at: { type: string, format: date-time }
        payment_id: { type: string, pattern: "^pay_[A-Za-z0-9]{16}$" }
        merchant_id: { type: string, pattern: "^mer_[A-Za-z0-9]{8}$" }
        amount: { type: integer, minimum: 1 }
        currency: { type: string, enum: [CAD, EUR] }

  - name: payments.payment.captured
    version: 1
    key: payment_id
    producer: orchestrator
    consumers: [ledger, webhook-dispatcher]
    public: true          # delivered as payment.captured
    schema:
      type: object
      required: [event_id, event_type, occurred_at, payment_id, merchant_id, captured_amount, currency, captured_at]
      properties:
        event_id: { type: string }
        event_type: { type: string, const: payment.captured }
        occurred_at: { type: string, format: date-time }
        payment_id: { type: string }
        merchant_id: { type: string }
        captured_amount: { type: integer, minimum: 1 }
        currency: { type: string, enum: [CAD, EUR] }
        captured_at: { type: string, format: date-time }

  - name: payments.payment.failed
    version: 1
    key: payment_id
    producer: orchestrator
    consumers: [webhook-dispatcher]
    public: true
    schema:
      type: object
      required: [event_id, event_type, occurred_at, payment_id, merchant_id, failure_code]
      properties:
        event_id: { type: string }
        event_type: { type: string, const: payment.failed }
        occurred_at: { type: string, format: date-time }
        payment_id: { type: string }
        merchant_id: { type: string }
        failure_code: { type: string }
        failure_message: { type: string }

  - name: refunds.refund.succeeded
    version: 1
    key: payment_id
    producer: orchestrator
    consumers: [ledger, webhook-dispatcher]
    public: true
    schema:
      type: object
      required: [event_id, event_type, occurred_at, payment_id, refund_id, merchant_id, amount, currency]
      properties:
        event_id: { type: string }
        event_type: { type: string, const: refund.succeeded }
        occurred_at: { type: string, format: date-time }
        payment_id: { type: string }
        refund_id: { type: string, pattern: "^re_[A-Za-z0-9]{16}$" }
        merchant_id: { type: string }
        amount: { type: integer, minimum: 1 }
        currency: { type: string, enum: [CAD, EUR] }

  - name: ledger.entry.posted
    version: 1
    key: payment_id
    producer: ledger
    consumers: [settlement]
    public: false
    schema:
      type: object
      required: [event_id, event_type, occurred_at, payment_id, entry_id, merchant_id, kind, amount, currency]
      properties:
        event_id: { type: string }
        event_type: { type: string, const: ledger.entry.posted }
        occurred_at: { type: string, format: date-time }
        payment_id: { type: string }
        entry_id: { type: string, pattern: "^led_[A-Za-z0-9]{16}$" }
        merchant_id: { type: string }
        kind: { type: string, enum: [capture, refund] }
        amount: { type: integer }
        currency: { type: string, enum: [CAD, EUR] }

  - name: settlement.payment.settled
    version: 1
    key: payment_id
    producer: settlement
    consumers: [webhook-dispatcher]
    public: true          # delivered as payment.settled
    schema:
      type: object
      required: [event_id, event_type, occurred_at, payment_id, settlement_id, merchant_id, amount, currency]
      properties:
        event_id: { type: string }
        event_type: { type: string, const: payment.settled }
        occurred_at: { type: string, format: date-time }
        payment_id: { type: string }
        settlement_id: { type: string, pattern: "^set_[A-Za-z0-9]{12}$" }
        merchant_id: { type: string }
        amount: { type: integer }
        currency: { type: string, enum: [CAD, EUR] }

dead_letter_topics:
  - name: ledger.dlq
    owner: ledger
    description: Messages the ledger consumer could not validate or process. Each message keeps the original payload and adds error, source_topic, source_offset, and failed_at headers.
  - name: settlement.dlq
    owner: settlement
  - name: webhook-dispatcher.dlq
    owner: webhook-dispatcher

consumer_groups:
  ledger:
    topics: [payments.payment.captured, refunds.refund.succeeded]
    dead_letter: ledger.dlq
    on_validation_error: dead_letter   # no retry, message is never reprocessed automatically
  settlement:
    topics: [ledger.entry.posted]
    dead_letter: settlement.dlq
  webhook-dispatcher:
    topics: [payments.payment.authorized, payments.payment.captured, payments.payment.failed, refunds.refund.succeeded, settlement.payment.settled]
    dead_letter: webhook-dispatcher.dlq
    delivery_retries: 8               # exponential backoff over 24 hours
