Shopping
Payments
Paying for an order with the Moyasar SDK โ the payment attempt, verification, retries, and why the server always has the last word.
Orders are paid by card through Moyasar. The app charges the card with the Moyasar SDK; the server decides the amount beforehand and confirms the result with Moyasar afterwards. A payment is never trusted on the app's word alone.
The flow#
sequenceDiagram
participant App as Customer app
participant API as Tote API
participant SDK as Moyasar SDK
participant M as Moyasar
App->>API: POST orders/checkout
API-->>App: order (unpaid) + payment {payment_id, amount}
App->>SDK: charge(given_id = payment_id, amount, metadata)
SDK->>M: create payment
M-->>SDK: result
SDK-->>App: done
App->>API: POST payments/verify {order_id, payment_id}
API->>M: fetch the payment
M-->>API: paid, amount
API-->>App: 200, order paid
Note over API,M: Backup paths: Moyasar's webhook,<br/>and a re-check every 10 minutes
- Checkout places the order and returns a
paymentobject: the first payment attempt. Ifpaymentisnull, open one withPOST payments/intent. - Start the Moyasar SDK with the values from that object.
- When the SDK finishes, call
POST payments/verify. The server asks Moyasar for the real result and marks the order paid.
The payment attempt#
{
"payment_id": "5f0c7b9e-2a41-4c1f-9d6e-8a3b2c1d0e9f",
"amount": 13950,
"currency": "SAR",
"description": "Order #1042",
"metadata": { "order_id": "1042", "order_number": "#1042" }
}
Pass these to the SDK unchanged:
payment_idgoes in as Moyasar'sgiven_id. Moyasar uses it as the id of the payment it creates, so if the app retries a charge that timed out, Moyasar returns the first payment instead of charging twice.amountis in halalas (1 SAR = 100 halalas), as Moyasar expects. Never recompute it from prices on the device.descriptionandmetadatatie the payment to the order. Verification rejects a payment whose metadata names a different order.
Verifying#
POST /api/v1/customers/auth/payments/verify
{"order_id": 1042, "payment_id": "5f0c7b9e-2a41-4c1f-9d6e-8a3b2c1d0e9f"}
| Response | Meaning |
|---|---|
200 |
The order is paid (or already was). |
400 |
Moyasar says the payment did not succeed, or its amount does not match the order. |
500 |
Moyasar could not be reached. The payment may still have succeeded. |
On a 500, retry verify after a moment, or simply show the order as "processing". Payments do not depend on the app getting through: Moyasar also notifies the server directly, and the server re-checks unfinished attempts every 10 minutes. Either path marks the order paid and sends an order_paid push.
Retrying a failed payment#
Calling payments/intent again within 30 minutes returns the same attempt, so a double tap cannot open two.
After a declined card, open a fresh attempt with "retry": true. A Moyasar given_id can only ever belong to one payment, failed or not, so a new card try needs a new payment_id.
payments/intent answers 409 when the order can no longer be paid: it is already paid, or it was cancelled.
After payment#
- The order shows
is_paid: true, and the store is pushedorder_paid. - A store sees an order in its queue as soon as it is placed, before it is paid. Merchant apps should show
is_paidclearly. - Refunds โ for a cancelled paid order, or an approved Return โ always go back to the original card. They are issued by Tote; the apps never start one.