Orders and payment links
A payment link pays one order, once. The mistakes are in the seams: a payer who comes back to the checkout, a success page that is taken as proof, a payment for less than the order, and a link that is still open when it should not be.
One link per order
Create the link when the payer clicks Pay, and store its "url" with
the order, and in v2 its "id" too. If the payer comes back to your checkout, as
after pressing back in the browser, send them to the same link: it
keeps its state on our servers, and can be paid only once.
A second link for the same order is a second payment that can go
through. Two links for one order, both paid, are two transactions with
the same "orderid", and one of them has to be refunded. So create a
new link only when the old one can no longer be paid:
- It expired. Its
"lifetime"is up, which you know from when you created it. - In v2, you deleted it, because the order changed: delete first, then create the new link.
In v1, a link can't be deleted, so a link that is out there can be paid
until it expires. Give each link a "lifetime" no longer than the
payer needs.
The order ID
Give each order an "orderid" that no other order of yours has. It is
how you match the transaction to the order when seq returns it, and
the reference that the payer's bank and your acquirer see. v1 lets you
leave it out, and v2 does not: in both, always send it, as a string. If an order
can be paid in parts, or paid again after a link expired, the
transactions share its "orderid": match them by their "id", which
seq returns, and sum them.
The success page is no proof
The payment window sends the payer to your "successurl" when they
have paid, without adding anything to it. So:
- Anyone can open it, paid or not. Never mark an order as paid because the payer reached it: mark it when seq returns the transaction.
- The payer can reach it before seq returns the transaction, and the ping may come a moment later. Look the order up, and if it is not paid yet, synchronize once before you say anything. See the success page.
- The payer may never reach it, if they close the browser after paying. The order is paid all the same, and seq tells you.
Put what the page needs in its URL, such as
https://shop.example/thanks?order=1234zx, as we add nothing.
What was authorized
In v1, a partial authorization is accepted: a bank that approves less
than the link's total, as some prepaid cards do, makes a transaction
for what it approved. Compare the transaction's totals.authorized
with the order before you deliver it, and treat a shortfall as unpaid,
or ask the payer for the rest. See
The transaction object.
In v2, a partial authorization is declined, and the payer can try again, so a transaction is always for the link's whole total.
A link that must not be paid
Sometimes paying a link has become wrong:
- The payer paid another way, such as by bank transfer.
- What it was for is gone, such as the last seat on a bus.
- The order changed, so the amount is wrong.
In v2, delete the link. Deleting is final.
If the payer is paying the link at that moment, the delete waits for
their bank's answer, and fails with 409 link is paid if the bank
authorized the payment. Then the order is paid after all: deliver it,
or void the transaction when seq returns it.
In v1, there is no request to stop a link, so plan for a payment that
comes anyway. Mark the order so that a payment for it is not
delivered, and when seq returns its transaction, don't capture it: the
payer's bank releases the held amount when the authorization runs out.
If it was captured, as with "autocapture",
refund it. A short "lifetime" keeps the
window for this small.
Declines and abandoned links
A declined payment is not an error, and you hear nothing of it: the payer sees the decline in the payment window and can try again until the link expires. Seq returns a transaction only when a payment succeeds, so a link that expires unpaid leaves no trace in seq.
To follow up on abandoned checkouts, keep the time each link expires, and treat an order whose link expired without a transaction as abandoned.