Payment flow
From creating a payment to confirming it, and why the redirect alone is never proof.
A payment moves between four parties: your server, the customer's browser, the bridge, and the payment provider (APS today, or the simulator in test mode). This page follows one payment from start to finish.
The steps
-
Your server creates the payment.
POST /api/v1/paymentswith the amount, yourorder_referenceand, optionally, areturn_url. The bridge opens the payment with the provider and answers201with apendingpayment and itscheckout_url. -
You redirect the customer to
checkout_url. The bridge's hosted checkout page shows your application's name and logo, the amount in IQD and the order reference, in Arabic or English (from the payment'slocale, or the customer's choice on the page). The customer presses Pay, or cancels. -
The customer pays on the provider's page. The bridge sends the customer to the provider's secure card page. Card details never touch the bridge or your application.
-
The customer comes back through the bridge. The provider returns the customer to the bridge, which asks the provider for the result straight away. If the provider has not decided yet, the customer sees a short "confirming your payment" page that checks again every 3 seconds for about 30 seconds.
-
The bridge redirects to your
return_urlwithpayment_id,statusandorder_referenceadded to the query string:texthttps://shop.example/return?payment_id=01a13c8c-8592-70f4-9ef9-d3767e74d54c&status=paid&order_reference=ORD-1001If your
return_urlalready has a query string, the parameters are appended to it. -
Your server confirms. Either your webhook endpoint receives
payment.paid(or another event), or your server callsGET /api/v1/payments/{id}. Only then do you deliver the order.
The bridge also checks every pending payment with the provider in the background, about once a minute, until it is decided or expires. So a customer who closes the browser before coming back still gets their payment recorded, and you still receive the webhook.
Never trust the redirect alone
The query string on your return_url is written by the customer's browser, and anyone can type
status=paid into an address bar. Use it only to choose what to show on the page ("Thank you",
"Payment failed"). Before you ship anything, confirm the status on your server with a verified
webhook or with GET /api/v1/payments/{id}.
A status=pending redirect means the provider had not answered within about 30 seconds. Show the
customer that the payment is being checked; the webhook follows when it is decided.
When the customer does not pay
- Cancel on the checkout page: the payment becomes
cancelledand the customer goes back to yourreturn_urlwithstatus=cancelled. - Card declined: the payment becomes
failed. - Nobody pays in time: an unpaid payment expires after 30 minutes by default (Alphabet can
change this per application). It becomes
expired, and an expired checkout link sends the customer back to yourreturn_url. - Your server cancels it:
POST /api/v1/payments/{id}/cancelwhile it is stillpending.
Each of these sends a webhook. To try again, create a new payment; the same order_reference can
be reused once the earlier payment is failed, cancelled or expired.
A cancelled payment can still turn paid
The provider's card page cannot be closed from outside. A customer who still has it open can pay
after the payment was cancelled through the API, or after Alphabet closed it by hand. So the
bridge keeps checking a closed payment with the provider until its original expires_at.
If the provider reports it paid in that window, the payment moves to paid and you receive
payment.paid, like any other payment. The customer's money has been taken, so your application
must then either deliver the order or refund the payment. Payments that the provider itself
declined or expired are final and do not change again.
Who tells you what
| Channel | Comes from | Use it for |
|---|---|---|
return_url query string |
The customer's browser | Choosing which page to show |
| Webhook | The bridge, signed with your secret | Fulfilling the order |
GET /api/v1/payments/{id} |
The bridge, with your API key | Fulfilling the order, checking any time |