pay_test_… / pay_live_… object your server creates with a
secret key. A card is captured in the
same answer; a bank debit is processing until it settles, when the processor sends it to the bank in
its next batch. The
charge runs through the same engine as the rest of Payra, so it shows up in the dashboard, reconciles
and refunds like any other payment.
1
Charge the payment method
Response
amount is in the smallest unit of the currency, so 12550 is $125.50, and it is charged exactly:
the API adds no fee or surcharge. amount_refunded is how much of it has since gone back
through refunds, wherever they were made. currency is USD or CAD; charge CAD
only on a workspace Payra has set up to take Canadian dollars. description and reference are
optional, the second being your own order or invoice number: it is stored as the payment’s order
number and shown in the dashboard, and it does not apply the payment to a Payra invoice. When the
method is saved on a customer and reference matches one of that customer’s prepayment orders
(orders synced from your ERP through Payra Connect), the charge counts against that order:
amount may not exceed what is still due on it, other pending charges included
(400 parameter_invalid on amount), and a succeeded charge reduces what is due. customer, if you send it, must be the customer the method is saved on.
Instead of payment_method, send payment_method_session, a session your page confirmed with
Elements, within 30 minutes of the confirm: the payment method is made from it in this same call
(one-time for a card) and comes back as payment_method. That is one call instead of two, the fastest checkout.
Send one of the two, not both (or terminal instead, see Terminal payments). A session pays once: a retry with the same Idempotency-Key answers the same
payment, and another key answers 400 payment_method_session_used.
To charge a returning buyer’s saved card, find it first: see Customers.
If the card’s bank wants to verify the customer, the charge says how: see 3-D Secure.
To record what the customer bought, send line_items: see Order lines.2
Read it back while it settles
402, and this is where
you see why.Idempotency-Key is required. The same key with the same body answers the same
payment, including after a crash on our side, because the payment itself remembers the key. The same
key with a different body is 400 idempotency_key_reused; after 24 hours only payment_method,
amount and currency are compared (for a terminal payment, terminal, customer, amount and currency).
What a payment costs you is not deducted from it. Processing fees are settled on your Payra statement,
not per payment, because the card networks adjust interchange after the fact (a business card that
qualified for a lower rate, say); amount is always what the customer paid, and a refund sends back
exactly amount. Accounts that reconcile per order get processing_fee on the object, Payra’s estimate
of that cost, or null: see Fees per payment.
Charging a saved payment method emails the customer Payra’s payment receipt once the charge succeeds
(for a bank debit, when the processor accepts the debit, before it settles), when the customer has an
email address whose email notifications are on, the workspace’s Email payment receipts to
customers setting is on, and its notifications are not paused. A one-time method has no customer, so
its payment gets no receipt.
A one-time payment method is spent by its first charge, approved or declined, and reads consumed
afterwards. Only a request refused before it reaches the processor (a 400 on amount, currency or
customer, payment_provider_not_configured, or a reused key) leaves it active. A saved one can be charged again.
Order lines
Send the order behindamount and Payra keeps it on the payment: line_items, one per product, and
amount_details for the shipping, tax and discount around them. Both are optional; a payment sent without them
works as before.
The lines must come to what you charge:
amount, the create answers
400 parameter_invalid on line_items with both sums in the message, and nothing is charged:
Idempotency-Key. If your
own totals can be a cent off their parts (prices that include tax, or a unit price with a fraction of a cent, which unit_amount cannot carry), leave the lines out of that payment
rather than adjusting them to fit.
The payment returns them, each line with its amount (quantity × unit_amount, rounded half up to the cent), and so do its
payment.* events. A payment made without them has "line_items": null and
"amount_details": null.
Response (abbreviated)
sku becomes the line’s product
code (capital letters and digits only, shortened to what the processor allows), shipping goes as a line of its own and tax on the charge. A line with a decimal quantity goes as quantity 1 at the line’s amount, so the processor’s line can differ from line_items; the payment keeps the quantity you sent. On a bank debit they are kept on the
payment only. The hosted checkout takes the same two fields and lists them on
its page.
Save the card with the payment
A buyer who ticks “save my card” at checkout needs no separate verification where the processor allows it: the payment proves the card. Charge the session, created with the buyer’scustomer, and send save_payment_method: true:
payment_method becomes
usage: "saved": store that id for the buyer’s next checkout. The same works with a one-time payment_method you
already created from the session. Sending the field is your attestation
that the cardholder agreed to keep the card on file, as account_holder_authorization is when a card is saved without a
payment.
- A declined charge keeps nothing: the method stays one-time, and spent.
- After 3-D Secure, the card is kept once the payment succeeds, within 30 minutes of the card being collected.
- If the charge succeeded but the card could not be kept, the payment stands and the method stays one-time. Read the
method back (
GET /payment-methods/{id}) to see which. - A method that is already saved ignores the field.
- If the customer already had this card saved, it is not saved twice: the payment’s
payment_methodis the method that holds it, and the one-time method made from the session stays spent. Store that id as usual. - To save a card without charging it, as on an “add a card” page, create the method with
account_holder_authorization: Payra verifies the card first.
Bank debits
A bank account is charged for what its holder authorized. A one-time account takes exactly theamount and currency its session named, else 400 parameter_invalid
on amount; a saved one takes the amount you send. currency is USD for a US account and CAD for a
Canadian one. The answer is a processing payment with payment_method_type: "us_bank_account" and a
us_bank_account block (last4, account_type, account_holder_type), card being null:
succeeded when it settles, once the processor’s next batch sends it to the bank, and
succeeded_at is set then; the bank can instead refuse the debit, which turns it returned with
failure.code: "ach_returned", or the processor can reject it outright, which turns it failed.
Listen for payment.succeeded, payment.returned and payment.failed rather than
polling, and do not ship on processing. Every debit carries the authorization evidence
Payra recorded when the holder accepted it, so a dispute can be answered from Payra’s records.
When a debit settles, and when it can come back
A bank debit moves on the ACH network’s own clock; there is no instant rail, and the create never answerssucceeded for one.
- Settlement. The processor sends its batch to the bank once a banking day. A debit accepted
before that day’s cut-off goes out that night and turns
succeededthen; one accepted after it waits for the next batch. Weekends and bank holidays are not banking days, so a debit taken on a Friday evening settles on Monday at the earliest. - Returns. After settlement the bank can still send the debit back, which turns the payment
returnedwithfailure.code: "ach_returned"and sendspayment.returned. Under the network’s rules, most returns (no such account, closed account, insufficient funds) arrive within two banking days of settlement; a holder disputing the authorization on a personal account has up to 60 calendar days. Treatsucceededon a debit as “very likely”, not “done”: release what you are shipping on the business risk you are comfortable with, and reverse it onpayment.returned. - Nothing to configure. The schedule is the processor’s; Payra reports each step as it happens. Accounts with a funding arrangement of their own still see the same statuses at the same moments.
Canadian bank debits (EFT)
A Canadian account (a session of typeacss_debit) is debited under the pre-authorized debit (PAD) agreement its
holder accepted, in CAD only, and follows the same lifecycle. The payment carries
payment_method_type: "acss_debit" and an acss_debit block, with the branch the account belongs to, in place of
us_bank_account:
succeeded once the processor settles it, failed when the processor rejects it, and returned with
failure.code: "acss_returned" when the bank sends it back, before or after it settled. Canadian debits are set up per
workspace: where they are not, the session is refused at creation with
400 payment_provider_not_configured, so no customer reaches the fields.
3-D Secure
The card’s bank can ask to verify the customer before a card charge goes through, on a workspace where Payra has turned 3-D Secure on. The charge then answers201 with nothing captured yet, status: "requires_action" and a
next_action that says what to do.
Where Payra runs 3-D Secure itself, on a card confirmed with Elements, it runs whether or not you send
return_url; return_url only decides how the customer goes through it:
- Without
return_url, the steps run on your page, in frames: Payra Elements does it in one call. - With
return_url, the customer goes to a page and comes back to that address: for pages that can only send the customer somewhere, as the WooCommerce plugin does.
return_url can be verified, by redirect. Sending return_url
whenever the customer is there works in both cases.
On your page
A card confirmed with Elements carries the customer’s browser, and a charge withoutreturn_url
answers with a Payra page for your page to show:
Response (abbreviated)
next_action.type is three_ds_fingerprint (the bank looks at the browser, nothing to see) or
three_ds_challenge (the bank asks the customer something); url is a page of Payra’s that your page runs in a
frame. Elements does all of it in one call, which also does nothing for any other status:
requires_action payment to your page: a server that
neither sends return_url nor calls handleNextAction leaves the payment waiting, and a step nobody answers fails
it after 15 minutes. Charge while the customer is still on your page, for the same reason: a card confirmed with
Elements is verified on any charge, one your server makes later included. Read the outcome on your server
(GET /payments/{id}, or the payment.succeeded / payment.failed webhooks): the page’s word is
never what confirms an order.
By redirect
Sendreturn_url, the address your customer comes back to afterwards:
201 with nothing captured yet and a page to
send the customer to: Payra’s own, which runs the bank’s steps and shows its challenge, or the bank’s.
Response (abbreviated)
succeeded or a decline.
1
Send the customer to the page
Hand the payment to your page and send the customer to
next_action.url. With
Elements that is one line, which does nothing for any other status:2
The customer comes back
After the bank, the customer returns through Payra, which settles the payment, and lands on your
return_url with ?payment=pay_… appended. A customer who gives up on the way lands there too, with
the payment failed or still requires_action.3
Confirm on your server
Read
GET /payments/{id}, or wait for payment.succeeded / payment.failed. Do not
rely on the landing alone: anyone can open your return_url.return_urlmust behttps(a sandbox key may also usehttp://localhostwhile you build), at most 2048 characters.- A verification the customer does not finish fails after 15 minutes, with
failure.code: "authentication_failed"and apayment.failedevent; so does one the customer closes or cancels.requires_actionitself has no event. - A one-time payment method stays spent: after a failed verification, collect the card again with a new session.
- Saved cards are verified the same way when the customer is there. Leave
return_urlout of a charge made with nobody at the keyboard, a scheduled charge say: there is no one to send to a page. return_urlhas no effect on a bank debit, nor on a workspace where Payra has not turned 3-D Secure on: the charge runs as if it was not sent, so one integration works on every workspace. Ask Payra to turn it on for yours.- A retry with the same
Idempotency-Keyreplays the first answer,next_actionincluded;GET /payments/{id}reads the payment as it is now. - The hosted checkout does all of this for you.
Terminal payments
Sendterminal instead of payment_method or payment_method_session to charge a card presented on one of
your card terminals. Send exactly one of the three.
customeris required,currencyisUSD, andreturn_urlandsave_payment_methodare refused. A terminal that is not yours, not assigned yet, or in the other environment answers400 parameter_invalidonterminal. Nothing is created, so the sameIdempotency-Keyworks once the terminal is assigned.- The answer is
201with aprocessingpayment:payment_methodisnull,payment_method_typeiscard_present,terminalnames the terminal andcardisnull. On every other paymentterminalisnull. - The outcome comes later:
succeededonce the card is approved,canceledwith acancellation_reason, orfailedwithfailure. A declined card may not end the payment: the terminal shows the decline and lets the customer try another card, and the payment can stayprocessing. Where the processor records the decline, the payment endsfailedinstead. If nobody pays, it endscanceledwithcancellation_reason: "expired"5 minutes after it was sent. - Keep the payment open until its outcome comes from Payra: the
payment.succeeded,payment.failedorpayment.canceledwebhook, or a read-back whosestatusis no longerprocessing. The customer needs time to reach the terminal and present the card, and a canceled payment cannot take a card. Do not cancel after a short wait or on a timer of your own: the 5-minute expiry already covers a payment nobody pays (with the exceptions below). Cancel only when the customer or your user asks to stop (see Cancel a terminal payment). - The terminal shows the card approved a few seconds before Payra sends the outcome. Until the outcome
comes from Payra, show the payment as waiting in your app, and treat it as paid only once it is
succeeded, never on what the terminal’s screen shows. There is no separate status for a card being read: the payment staysprocessingwhile the terminal waits for the card and while it reads it, andterminal_progressusually says which (see Where the customer is). If it is stillprocessinglong after the terminal showed approval, read it back before charging the customer again on another terminal. - A refusal by the processor at the moment the payment is sent, a terminal the processor could not reach,
or a payment Payra could not record and stopped before a card was read, answers
402 card_declinedwithdecline_code: "processing_error". No card was read: check the terminal and retry. - If Payra loses the processor’s answer, or the processor fails with a server error, the payment stays
processinguntil the outcome is known. It is never reported as a decline. Until then it does not expire, cannot be canceled, and its terminal answersterminal_busy. - A second payment to a terminal still on one answers
400 terminal_busyand creates nothing; the sameIdempotency-Keyworks once the terminal is free. - A
referencenaming a prepayment order is capped at what is still due on it, as on any payment. line_itemsare kept and shown back on the payment, but are not sent to the card networks for terminal payments.
Where the customer is
While a terminal payment isprocessing, GET /payments/{id} also asks the terminal which step it is on and
answers it as terminal_progress, so your screen can say more than “waiting”:
- Poll
GET /payments/{id}no faster than once a second. Polling faster rarely shows a newer step: each Payra server asks a terminal at most once a second per payment and answers the same step in between. Every poll counts toward your key’s read limit, so with many terminals in flight at once, poll each less often. - The read waits up to three seconds for the terminal, so allow for that in your client’s timeout. It never
fails because of the terminal: when the terminal does not answer, the payment comes back with
terminal_progress: null. - It is the terminal’s step at the moment of the read. Nothing is stored, so a step shorter than your polling interval is never seen.
- The outcome is still the payment’s
status.terminal_progressnever says approved or declined, and it isnullonce the payment has leftprocessing. - It is also
nullon the create, in lists and in webhook events, for the first three seconds after the payment is sent, while a cancel is running, when the terminal did not answer within three seconds or gave no answer (the server that asked then leaves it alone for five), on a step that does not place the customer (a receipt printing), in the moments after the terminal finished and before Payra has the outcome, and on a terminal payment Payra routes through a processor that does not report steps. Anullis never a step of its own: keep showing the last value you read. - A cancel sent while one of these reads is at the terminal waits for it first, three seconds at most.
- More
stateandpromptvalues may be added. Show one you do not know as still processing.
Cancel a terminal payment
Cancel only when a person decides not to pay: the customer says so, or your user stops the sale in your app. Never cancel because the outcome has not come after a few seconds: the customer may still be reaching for the card, and the 5-minute expiry already covers a payment nobody pays.canceled with cancellation_reason: "requested",
and the terminal is free for the next payment.
Canceling one already canceled answers it as it is. Once a card was presented (the terminal refuses the cancel,
or already has the card’s transaction), or on any payment that is not a terminal payment, the answer is
400 payment_not_cancelable and the payment finishes on its own. 409 conflict means the cancel could not be
confirmed and the payment is still waiting: it changed while the cancel ran, or the terminal’s answer was lost.
The payment was not marked canceled, and the same Idempotency-Key, if you sent one, can be retried: the retry
answers the payment as canceled, or payment_not_cancelable once the card was presented.
Status
status is Payra’s own lifecycle. It is never a string from the processor.
Why a payment failed
failure is set when status is failed or returned, and null otherwise. It carries a code
from a fixed vocabulary, a reason when the processor says exactly what it refused, and a message
written by Payra. The processor’s own wording is never relayed, so you can show or log message
safely and branch on code, then on reason where you want a finer sentence.
failure.reason is null unless the processor names the cause. More values may be added.
message follows the reason, except for card_blocked, restricted_card and suspected_fraud, which keep the
plain decline wording: tell the customer the card was declined, not why the bank refused it.
Declines
A refusal is an answer, not an outage: the create responds402 card_declined and leaves the
payment behind for you to read. Testing lists the sandbox cards that produce each one.
code is card_declined for every refusal, so decline_code is what you branch on. When the
processor names the cause, decline_reason carries it, the same value as failure.reason; it is
absent otherwise. decline_code uses the
same vocabulary as failure.code, which means it can be processing_error: that one is not the
issuer refusing the card, and it is worth retrying with a new Idempotency-Key (the same key
replays this 402). A saved method can simply be charged again; a one-time method was spent by the
attempt, so collect the card again with a new session. A
charge the processor refuses, or that Payra could not send at all (the workspace has no processor
account for that currency, say), answers this same 402, never a 500: the failed payment exists,
reads back and lists. When Payra cannot tell whether the processor received the charge (the request
timed out, or the connection failed), the create answers 201 with status: "processing" instead,
and a later webhook says how it ended.
Fraud screening
Fraud screening is not a separate service or a separate call. Payra is both the gateway and the processor of the payment, so every card payment is scored by the risk engine synchronously, inside the authorization itself, on every workspace, with nothing to turn on and nothing to send. The payment answers with its outcome already screened. A payment the screening blocks is a decline like any other:402 card_declined, with decline_reason: "suspected_fraud" when the
processor says so, and the payment reads back failed with the same failure. The bank’s own fraud
rules answer the same way, so your code handles one case, not two. For a check the customer takes
part in, add 3-D Secure: it shifts liability for fraud to the card’s bank on the
payments that complete it.
Listing payments
GET /payments answers the payments your keys have made, newest first, a page at a time. It takes
limit and starting_after, and works the same as every other list; see
pagination for walking the pages.
Response (abbreviated)