Skip to content

Middleware — Payments

Folder: src/routes/payments/
Mount: api/v1/bookdirect/payments
Status: to implement (Paystack first; Squad / Tsara same shape).
Do not store the booking only in Mongo. Persist on Core.

Middleware · winner

POST https://middleware-dev.bookdirect.live/api/v1/bookdirect/payments/initialize

POST https://middleware-dev.bookdirect.live/api/v1/bookdirect/payments/verify/{reference}

POST https://middleware-dev.bookdirect.live/api/v1/bookdirect/payments/webhook/paystack

Frontend: Checkout and pay.

Initialize (frontend → Middleware)

json
{
  "booking_uuid": "{booking_uuid}",
  "provider": "paystack",
  "amount_kobo": 6000000,
  "currency": "NGN",
  "callback_url": "https://your-app/booking/confirmation"
}

Middleware must

  1. Validate JWT. Load booking from Core:

    GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/booking/{booking_uuid}

  2. Reject if owned_by ≠ JWT user, or status ≠ pending, or amount ≠ extra_data.total_amount_ngn * 100.

  3. Create Core payment:

    POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/payment

    json
    {
      "request_id": "pay-{booking_uuid}-{n}",
      "meta_key": "pay_{reference}",
      "title_name": "Payment for booking",
      "parent_entity_type": "booking",
      "parent_entity_uuid": "{booking_uuid}",
      "owned_by": "{guest_uuid}",
      "status": "initialized",
      "extra_data": {
        "provider": "paystack",
        "reference": "{reference}",
        "amount_kobo": 6000000,
        "currency": "NGN",
        "booking_uuid": "{booking_uuid}"
      }
    }
  4. Call Paystack transaction/initialize. Return authorization_url, reference, payment_uuid.

Webhook (PSP → Middleware)

Verify signature. Then:

PUT https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/payment/{payment_uuid}status: paid

PUT https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/booking/{booking_uuid}status: confirmed

Then send mail: Emails. Optionally push ARI: Google rates.

Webhook has no user JWT. Authenticate the PSP only. Never accept a client body that says “mark paid” without a verified webhook or verify call.

Verify

Used by the confirmation page if the webhook is slow. Same Core writes as the webhook after Paystack verify succeeds.

IDOR

  • Initialize: booking must belong to the JWT user.
  • Webhook: do not take owned_by from the PSP payload as a new owner.
  • Middleware → Core uses a service x-api-key plus the guest Bearer (or a tightly scoped service identity). Do not write bookings as a different guest.

API-first. Middleware wins over Core when both exist.