Ulsav Docs
Orders

Payments

One provider order per Order, how confirmation really happens, and the double-entry record it leaves

A payment is a short, strict sequence: one Ulsav Order, one provider order, one confirmation. The buyer's browser is never part of the decision, which is why a receipt cannot be faked by reloading a page.

Razorpay is the only payment provider wired up today. Ulsav never holds card details; the provider's own modal does.

  • Provider

    Razorpay

    The only live adapter

  • Provider orders

    One per Ulsav Order

    Retries reuse it

  • Merchant account

    Chosen once

    No fallback to another account

  • Payment window

    15 minutes

    Matches the seat hold

  • Confirms from

    A signed callback

    Or an explicit status re-check

  • Records

    Ledger entries

    Written in the same transaction

One Order, one provider order

When checkout starts, the Order is created and a single provider order is opened for its exact total. Every retry by the buyer reuses that same provider order, and the provider itself refuses a second successful charge on it.

The MerchantAccount — the credential set that will receive the money — is chosen once, at that moment. A method you have pinned wins; otherwise the account with the lowest priority number that supports the currency and method is used.

There is no walking down a list of accounts after a failure. If that account fails, the Order fails, and the buyer starts a fresh attempt. That keeps the settlement with one merchant instead of splitting it across two.

The provider is opened outside the database

Creating the provider order is a network call, so it happens after the database transaction that created the Order and its holds has committed. Only the resulting reference is written back.

If the provider refuses, Ulsav cancels the Order, unlinks the entries, releases the holds, and frees the coupon — in that order, so a buyer is never left holding seats for a checkout that cannot complete.

What confirms a payment

A confirmation arrives in one of two ways: a signed provider callback, or an explicit status re-check when a callback is late or lost. Both go through the same code path.

firstThe signature is verified

The raw body is authenticated before any business data is read. An unauthenticated body changes nothing and is answered with an error.

thenThe amount is matched

The captured amount, and the currency when the provider reports one, must equal the Order's stored total. A mismatch confirms nothing and opens a reconciliation case.

thenOne transaction settles everything

The Order is locked, marked confirmed, its entries confirmed, the coupon redemption confirmed, the holds consumed, guest links rotated, ledger entries written, and notifications queued.

after commitSlow work runs separately

Invoices issue, emails send, webhooks deliver, and analytics project. A failing email never blocks a sale.

Because it is one transaction, an Order is never half-confirmed: no state exists where the money is recorded but the entries are not.

The ledger

Every confirmed Order writes a balanced journal, debiting the money that arrived and crediting each party's share. Nothing is edited afterwards; a correction is a compensating entry.

For Order ORD-000123 at ₹3,894.12:

EntryDebitCreditAmount
Organizer payableprovider clearingorganizer payable₹3,717.00
Organizer taxprovider clearingorganizer tax₹567.00
Ulsav revenueprovider clearingUlsav revenue₹150.10
Ulsav taxprovider clearingUlsav tax₹27.02

The four credits add to ₹3,894.12, which is what the debit side collects. Organizer payable is the remainder after the fee, the fee's tax, and the organizer's own tax, so the journal balances under either fee bearer.

When money arrives that should not

Two situations produce a captured payment with no sale: a payment that lands after the checkout expired or was cancelled, and a second successful payment against an Order that is already confirmed.

Neither is confirmed and neither is ignored. The capture is recorded, and an automatic refund of that exact payment is queued. The refund is unique per provider payment, so a repeated callback cannot refund twice.

A third case is quieter: the provider reports an amount that does not match the Order. Nothing is confirmed, and a reconciliation case opens for a person to resolve while the Order stays exactly as it was.

What the buyer experiences

The pay page shows the amount, the Event and Pass name, and a countdown. Reloading it reopens the same provider order rather than creating a new one.

If the 15 minutes run out, the Order expires and the buyer is asked to start again. A pending payment is never silently charged later: either the provider confirms inside the window, or nothing happens.

A payment that fails leaves the Order pending until expiry, which is deliberate. Provider failures are often retried by the buyer within seconds, and cancelling the Order on the first error would throw away seats unnecessarily.

Reconciliation and the worker

Three jobs keep payments honest:

  • a reaper expires unpaid Orders about once a minute
  • a reconciler polls refunds that are still processing
  • an outbox drainer turns reconciliation events into durable cases and runs automatic refunds

An organizer does not have to trigger any of this. The pay page can also ask for a re-check on the spot, which is the fastest path when a callback is slow.

Failure paths

What happenedResult
Provider refuses to open the orderOrder cancelled, seats released, buyer can retry
Signature does not verifyNothing changes; the callback is rejected
Amount differs from the OrderReconciliation case; no confirmation
Payment succeeds after expiryAutomatic refund of the captured amount
Duplicate success on a confirmed OrderAutomatic refund of the extra capture
The 15 minutes pass with no paymentOrder expires, entries unlink

On this page