Unirail

Payment intents, legs and custody

A payment along an approved quote, the legs it is made of, and who holds the money at each step.

A payment intent (pi_…) is a payment along a quote your user approved. You create it, help your user through any action it needs, and then listen: Unirail drives the legs with provider webhooks, polling and status deadlines, and tells you how it ends.

const intent = await unirail.paymentIntents.create(
  {
    quote: quote.id,
    digest: quote.digest,
    returnUri: "https://example.com/pay/done",
    reference: "Dinner",
    psu: { ip: request.ip, userAgent: request.headers.get("user-agent") ?? undefined },
    metadata: { paymentId: payment.id },
  },
  { context: { idempotencyKey: `payment:${payment.id}` } },
);

psu forwards context from your user's device; some banks require it. metadata comes back on every event about the intent, so you can find your own record.

Status

statusMeaning
requires_actionYour user has to do something: see nextAction (user actions).
processingSubmitted; Unirail is following the legs.
succeededThe payment completed.
failedIt didn't work; failure explains why.
cancelledIt was cancelled, for example with paymentIntents.cancel.

After a redirect or SDK step, call paymentIntents.advance({ id, returnParams }) or paymentIntents.advance({ id, sdkResult }). Each status change also arrives as a payment_intent.* event.

Legs

A payment is made of one or more legs, in sequence. Each leg is one hop on one rail:

FieldMeaning
from, toThe accounts at each end.
railThe provider that moves this hop, such as plaid.
amountWhat this hop moves.
custodyAfterWho holds the money once this hop completes.
stateWhere the hop is (below).
providerRefThe provider's own reference, for support conversations with that provider.

Leg states run from created through the waiting states (awaiting-user, awaiting-step-up, awaiting-submit, awaiting-funds, blocked-on-information) to submitted, accepted, confirming and credited. They can end in failed, cancelled, refunded or returned. Rails with no completion signal end in unconfirmed or attested rather than credited. leg.updated fires on every change.

Custody

Custody is data, not an assumption. Every leg says who holds the money after it:

custodyAfterWho holds the money
noneNobody. The payer's bank pays the payee's bank directly.
payer-own-accountThe payer, in an account they already hold with the provider.
provider-for-userThe provider, in an account or wallet in the user's name.
provider-transitThe provider, between collecting from the payer and paying the payee.
platform-entityYour company, in an account it holds.
partner-entityA partner, in an account it holds on your behalf.

Your routing policy lists the custody kinds each environment admits. A route whose legs need anything else is returned as infeasible, so a platform that must never touch funds can say so in policy and have Unirail enforce it.

On this page