Skip to main content

credit_limits

The Credit Limit payment method. Admins grant a per-Organization limit; Customers consume it at checkout. Reservations are atomic and idempotently released on invoice paid or order cancellation.

Public surface​

All admin routes are gated by the credit_limits:manage permission.

Verb + PathAudiencePurpose
GET /api/v1/me/credit-limitcustomerView granted limit + currently reserved (404 CREDIT_LIMIT_NOT_GRANTED if none)
GET /api/v1/admin/credit-limitsadminRoster of every granted limit with active reservations
GET /api/v1/admin/organizations/:id/credit-limitadminOne organization's limit + reservations
POST /api/v1/admin/organizations/:id/credit-limitadminGrant initial limit
PATCH /api/v1/admin/organizations/:id/credit-limitadminAdjust amount (rejects below active reservations unless allowOverAllocation)

Storefront UX: the Account profile and Checkout page render a CreditLimitWidget (granted / available / reservation breakdown) when the limit exists; credit_limit-kind payment methods are filtered out of Checkout when no limit is granted or the cart total exceeds the available credit.

Concurrency model​

CreditLimitService.reserve() opens a SELECT … FOR UPDATE on the credit_limits row, then inserts the reservation. Two simultaneous orders that would together exceed the limit are serialized; one succeeds and the other receives 409 LIMIT_INSUFFICIENT. See backend/test/contract/credit_limits/concurrent-race.test.ts.

Entities​

CreditLimit, CreditLimitReservation (status: active | released). Index on (credit_limit_id, status) keeps the available-balance query fast.

Events emitted​

credit_limit.granted.v1, credit_limit.adjusted.v1, credit_limit.reservation_released.v1.

Extension points​

  • Release reasons — releaseByOrder(reason) accepts 'invoice_paid' and 'order_cancelled' today. New reasons (e.g. 'manual_override') are added by extending the enum and wiring the caller.