Auth: the user-facing routes (
POST /users/{user_id}/contributions/self,
GET /users/{user_id}/orders/{id}) use bearer JWT with self-scope —
jwt.sub == user_id on the path. The admin cross-user list
(GET /admin/orders) requires an admin JWT.Overview
An order is a single funding event crediting a user’s PBA-backed (HSA) account. Theorders table is universal across funding flavours;
a source URN column on every row identifies the funding leg.
Today only the self contribution flow is exposed through the public API. Sponsor
contributions (PBA-to-PBA transfers from a sponsor’s account) share the same table shape
but settle through a separate, internal flow.
Funding source (source)
Every order carries a source field on the response, URN-encoded:
Validation runs at every entry point — JSON deserialization and DB row reads. A malformed
URN is a 400 with the parse error.
Order lifecycle (self contribution)
- Create —
POST /users/{user_id}/contributions/selftakes{ amount, currency }(amountin major units,currencyan ISO-4217 code controlling the major→minor conversion), opens a Juspay session, and inserts anordersrow withsource = "self_contribution", internalstatus = INITIATED, and a nullexternal_status. The Juspay/sessionresponse is returned verbatim aspayload(the frontend usespayload.sdk_payloadto render the payment page). - Poll —
GET /users/{user_id}/orders/{id}calls Juspay/orders/{id}on every invocation, merges the latestexternal_status(CHARGED/PENDING_VBV/FAILED/ verbatimOther(...)for unknown values) and payment-method fields into the row, and returns the refreshed snapshot. Frontend controls polling cadence — there is no server-side short-circuit. - Settle on CHARGED — the first poll observing
CHARGEDconfirms the PBA hold and credits the user’s HSA, advancing the internalstatustoSUCCESSandwallet_txn_data.statustoSUCCESS. Oncewallet_txn_data.statusis terminal, subsequent polls do nothing. PBA’sidempotency_key = order.idguards against double-deposits even if the gate is bypassed.
GET /users/{user_id}/orders/{id} — they settle
synchronously at create time through the sponsor flow and are read via their own surface.
GET /admin/orders is a cross-user, paginated admin list of funding orders (for the
dashboard Orders tab). It returns lean rows straight off the orders table (no Juspay
poll) and filters by user_id, source_kind (self_contribution / sponsor),
statuses (comma-separated OrderStatus), and a created_at time range.
Response shape
The
SelfContribution create response carries payload (the Juspay SDK blob) but
not status / wallet_txn_data. The GetOrderStatus poll and GET /admin/orders
rows carry status and wallet_txn_data but not payload. All three share the
remaining fields.