Skip to main content

How it works: the payment lifecycle

Every payment on TensorRail moves through the same four stages, whatever the market or method: create then route then authorize/collect then record, with webhooks throughout. The merchant-visible object is always a payment with an amount, a currency, a status, and a client_secret, identical across hosted checkout, payment links, and the direct API.

1. Create

You call POST /payments with an amount (in the smallest currency unit) and a currency (which must be a corridor enabled for your account):

curl -X POST https://api.tensorrail.com/payments \
-H "api-key: rail_full_test_xxx" \
-H "Idempotency-Key: 5f0c9e2a-1b7d-4d3e-9a1c-6b2f8e4a1c33" \
-H "Content-Type: application/json" \
-d '{ "amount": 50000, "currency": "INR", "return_url": "https://yourshop.com/done" }'
{
"payment_id": "pay_N5cPeGw6uS2QIMjnsjVF",
"status": "requires_payment_method",
"amount": 50000,
"currency": "INR",
"client_secret": "pay_N5cPeGw6uS2QIMjnsjVF_secret_…"
}

Two identifiers come back and each has a distinct job: payment_id is the server-side handle you store against your order; client_secret is the browser-safe credential that lets the hosted page (or a client-side confirm with your publishable key) act on exactly this one payment and nothing else.

Creation is idempotent, and idempotency is enforced: retrying this call with the same Idempotency-Key returns the original payment instead of creating a duplicate collection. See Orchestration and routing.

2. Route and present

You either hand off to the hosted page, which renders every enabled method, or render the enabled methods yourself and confirm the customer's choice. Behind the single surface, TensorRail selects and drives the appropriate rail. You never pick a processor and never see which one was used; the platform chooses an approval-optimized route for the payment's market, method, currency, and account configuration.

3. Authorize and collect

The customer completes on their local rail. Many rails require a customer action: an approval in a bank or wallet app, a redirect, or a reference to pay to. This is surfaced as a generic next_action on the payment object that you simply forward the customer to, while the payment sits at status: "requires_customer_action". Once the rail has the payment, status moves to processing, and the terminal outcome (succeeded or failed) arrives by webhook. The same handling works as new methods are added, because your code never branches on the method.

The return to return_url is not proof of payment

Local rails complete asynchronously after the customer leaves checkout. The authoritative signal is the success webhook (or a retrieve returning status: "succeeded"), never the customer landing back on your return_url. A customer can close the tab after paying, or return without paying; your fulfilment must key off the webhook.

4. Record

Cleared value is recorded to your account balance for the currency it was collected in. Under that balance sits a double-entry ledger with automated reconciliation, so the number is provable rather than merely displayed: every fee is its own posting, and every posting names the payment it belongs to. See Money integrity for how the balance is backed and reconciled, and Fees and currency for where fees and conversion appear.

Status vocabulary

Branch your logic on the payment status, never on message text. These are the values you meet on the common paths:

StatusMeaningYour move
requires_payment_methodCreated; no method submitted yetPresent methods; confirm
requires_confirmationMethod attached; needs confirmationCall confirm
requires_customer_actionWaiting on the customer (approval, redirect, reference)Forward the customer to next_action
processingSubmitted to the rail; outcome pendingWait for the webhook
requires_captureAuthorized; awaiting capture (manual capture flow)Capture on fulfilment
succeededCollectedFulfil (on the webhook)
failedDid not completeRead error_code / error_message; offer retry
cancelledVoided before completionNothing further
Handle an unrecognised status defensively

The table above is the set you meet on the common paths, not the complete enum. A collection the customer never completes in time ends at expired — on live traffic that is the most frequent terminal outcome of all, so handle it first. Partial-capture flows add partially_captured, partially_captured_and_capturable, partially_authorized_and_requires_capture and partially_captured_and_processing; a payment awaiting an action on our side reports requires_merchant_action; a void after capture reports cancelled_post_capture; and a payment whose amount or currency does not match what the processor reports is conflicted — do not fulfil that one.

Which of these a given integration meets depends on the payment method and on what is enabled for your account, so give your switch a default that treats an unknown status as "not yet terminal, wait for the webhook" rather than as a failure. That way a method you enable later cannot turn into a wrongly-failed order.

Two of these are terminal (succeeded, cancelled); failed is terminal for that payment, but the customer can try again with a new payment. A payment that is never completed can also expire (you receive a payment_expired event); treat it like a failure for fulfilment purposes.

The same trace, end to end

A typical asynchronous local-rail collection looks like this on the wire:

  1. POST /payments returns status: "requires_payment_method".
  2. POST /payments/{payment_id}/confirm with the customer's method returns status: "requires_customer_action" plus a next_action.
  3. The customer approves on their rail; a payment_processing webhook may arrive.
  4. The payment_succeeded webhook arrives with the full payment object in content; GET /payments/{payment_id} now returns status: "succeeded".
  5. The cleared value posts to your balance and appears in your exports.

Every method family (mobile money, bank transfer, virtual account, local instant rails, crypto) is a variation of this one trace; only what the customer does at step 3 differs.

Webhooks throughout

Outcome events (payment_processing, payment_succeeded, payment_failed, and the rest of the payment lifecycle) are delivered to your endpoint at each state change, signed, with retries on failure. Webhooks are the reliable way to learn outcomes because rails complete asynchronously. See Handle webhooks.


TensorRail, Limassol, Cyprus.