Skip to main content

Direct payments

A direct payment creates a one-time payment in a single, merchant-initiated call. Unlike a hosted checkout session, you supply the amount, currency, country, and payment_method yourself rather than handing the whole flow to Orchestr.

The payment is completed on a provider-hosted page, so payment details never touch your servers. (Paying by card without a redirect - collecting the card on your own page - is its own flow: see Card payments.) A successful POST /v1/payments returns one of:

  • status: requires_action - the customer has a step to finish; next_action says which.
  • status: completed - the payment settled immediately.
  • status: failed - the payment was declined or errored.

When to use a direct payment​

Use a direct payment when…Use a checkout session when…
You already know the amount, currency, country, and payment method and want a single server-to-server call.You want Orchestr to host the entire payment page and collect customer/payment details for you.
You manage your own return-URL redirect.You want line items, address/phone collection, and locale handling out of the box.
You want the provider's own page for the method.You chose the method on your page but still want Orchestr's hosted form for it - pass it as the single payment_methods entry (see Pre-selecting the payment method). For a redirect method you can also keep the session and skip the form with straight_through: true (see Going straight to the provider).

Both flows share the same providers and settlement - the resulting order surfaces with subtype: direct_payment on the Orders and Transactions endpoints.

1. Create a payment​

This POST /v1/payments call reuses the buildSignatureHeader helper from the Signing guide (the signed string for a request is timestamp + path + body). It assumes you've exported UPPROVE_SECRET_KEY and UPPROVE_SIGNING_SECRET as shown in Getting started.

REQ_PATH="/v1/payments"
BODY='{
"amount": "42.00",
"currency": "GBP",
"country": "GB",
"payment_method": {
"code": "paypal"
},
"merchant_reference": "order-1001",
"success_url": "https://shop.example.com/ok",
"cancel_url": "https://shop.example.com/cancel",
"customer": {
"email": "jane@example.com"
}
}'
TS=$(date +%s)
SIG=$(printf '%s' "${TS}${REQ_PATH}${BODY}" \
| openssl dgst -sha256 -hmac "$UPPROVE_SIGNING_SECRET" | sed 's/^.*= //')

curl -sS -X POST "https://api.sandbox.upprove.com${REQ_PATH}" \
-H "Authorization: Bearer $UPPROVE_SECRET_KEY" \
-H "X-Signature: t=${TS}, v1=${SIG}" \
-H "Content-Type: application/json" \
--data-raw "$BODY"

The required fields are amount, currency, country, payment_method, merchant_reference, success_url, and cancel_url. merchant_reference must be unique per account and doubles as your idempotency key - retrying with the same reference will not create a duplicate. See Payment methods for every accepted payment_method.code. Which of them are live for a given country and currency, and which customer / payment_method.details fields each one needs, you can ask the API before you build the request - GET /v1/payment-methods (see Discovering methods and required fields).

If your account runs several brands, sites or serviced clients as processing profiles, pass the profile's code in processing_profile to address the payment to it. The code is echoed back on the payment, on its transactions and on every webhook, and you can filter searches and exports by it. Omit the field to operate at account scope; an unknown or disabled profile is rejected with a validation error.

:::note LATAM local methods require the customer's identity document and IP

For PIX, BOLETO, SPEI, OXXO and PSE, pass the paying customer's national identity document in payment_method.details.document_id - the CPF in Brazil (11 digits, punctuation optional), CURP or RFC in Mexico, cedula in Colombia (6–10 digits) - and the shopper's IP address in customer.ip. The payment is rejected with a validation error when either is missing, or when the document is malformed for the method's country (see Payment methods).

"payment_method": {
"code": "pix",
"details": {
"document_id": "123.456.789-01"
}
},
"customer": {
"email": "jane@example.com",
"name": "Jane Doe",
"ip": "203.0.113.42"
}

customer.ip is the customer's address as your site sees it, never your server's - on a server-to-server call only you can supply it. It is forwarded to the provider as a risk signal and is not stored on the customer record.

:::

2. Read the response​

A 200 OK returns the payment. The X-Signature response header signs the body - verify it before trusting the response (see Signing).

Most payments come back requires_action - redirect the customer to next_action.redirect_url:

{
"order_id": "ord_5b4a3c2d",
"transaction_id": "txn_9f8e7d6c",
"status": "requires_action",
"amount": 42.00,
"currency": "GBP",
"next_action": {
"type": "redirect",
"redirect_url": "https://pay.provider.example/session/abc123"
}
}

Local methods return the artifact instead - branch on next_action.type: qr (e.g. Pix, MACH), voucher (e.g. OXXO, Boleto) or bank_transfer (e.g. SPEI), with expires_at closing the payment window. A qr action carries whichever of these the provider offers:

{
"status": "requires_action",
"next_action": {
"type": "qr",
"redirect_url": "https://pay.provider.example/qr/abc123",
"expires_at": "2026-10-15T12:30:00Z",
"qr": {
"data": "00020126580014br.gov.bcb.pix...",
"redirect_url": "https://pay.provider.example/qr/abc123",
"deep_links": [
{ "label": "Example Bank", "url": "https://app.examplebank.com/pay?ref=abc123", "target": "app" }
]
}
}
}

Render the QR from data (or show image_url when there is no payload), offer the deep_links as app (target: app, phones) or online-bank (target: web, desktop) buttons, and keep redirect_url as the provider-hosted alternative. When redirect_url is the only qr field set, redirecting is the only way to pay. Field details are on the NextAction schema in the API Reference.

Whenever the provider hosts a page for a qr or voucher action, its URL is also copied to the top-level next_action.redirect_url (from qr.redirect_url or voucher.document_url). If you do not render QRs or vouchers yourself, you can send the customer to next_action.redirect_url for every action type that carries it.

For a payment that settles immediately, status is completed, next_action is null, and provider_transaction_id carries the settlement reference:

{
"order_id": "ord_5b4a3c2d",
"transaction_id": "txn_9f8e7d6c",
"status": "completed",
"amount": 42.00,
"currency": "GBP",
"provider_transaction_id": "pi_3PqRsT2eZvKYlo2C1aBcD3eF",
"next_action": null
}
FieldDescription
order_idThe order created for this payment.
transaction_idThe payment transaction attempt.
statusrequires_action, completed, or failed. A card charge can also return authorized (a manual-capture hold), settling, or canceled.
amount / currencyThe payment amount and currency.
provider_transaction_idSettlement/charge reference; null before settlement.
additional_identifiersExtra provider-side identifiers, each {"identifier": …, "type": "checkout" | "order"}.
next_actionPresent when status is requires_action; null on terminal outcomes.

3. Handle the outcome​

The create response tells you the initial state; the final outcome arrives asynchronously via the payment.* webhooks (payment.succeeded, payment.failed, payment.requires_action, …). Fulfil the order on payment.succeeded.

Direct-payment declines are terminal

Unlike a checkout session, a declined direct payment has no retry surface - the payment does not reopen for another attempt. To try again, create a new payment with a new merchant_reference. Inspect the decline code on the failed transaction to decide whether retrying is worthwhile.

Next steps​