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 routes the payment to a rail enabled for your account, chosen by the payment's market, method and currency and the routing set up for your account. You do not need to pick a route.

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 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 rail 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.