Intent Lifecycle
An intent tracks its own lifecycle — whether it has been used — not the outcome of the payment it created.
| Status | Meaning |
|---|---|
created | Awaiting fulfillment. Usable until expires_at. |
fulfilled | Used to create a payment. Whether that payment succeeded is tracked on the payment, not here. |
canceled | Canceled before use. The client_secret no longer works. |
fulfilled does not mean paid
An intent becomes fulfilled the moment it is used to originate a payment — including
when that payment is declined. The intent's job is to authorize the attempt, and it is
done once the attempt is made.
Never treat "status": "fulfilled" as confirmation of a successful charge. To know whether
you were paid, read the payment: Fetch Payment,
or a payment_paid webhook.
Expiry
Every intent expires one hour after it is created. expires_at is always set, and the
window is fixed — it cannot be configured per intent.
Expiry is not a status. An intent past expires_at stays created, but it can no longer
be fulfilled or canceled — both return a 400. Decide whether an intent is still usable
from expires_at, not from status alone.
Because the clock starts at creation and not at the pay button, create the intent close to
the moment the customer is ready to pay. See the on_initiating approach in
Fulfill an Intent.
Read an intent
Endpoint: GET /v1/payment_intents/{id}
Authentication: Secret key
curl https://api.moyasar.com/v1/payment_intents/{intent_id} \
-u sk_test_YOUR_SECRET_KEY:
The secret key is required because the response contains the client_secret. A publishable
key gets 403.
Cancel an intent
Endpoint: POST /v1/payment_intents/{id}/cancel
Authentication: Secret key
curl -X POST https://api.moyasar.com/v1/payment_intents/{intent_id}/cancel \
-u sk_test_YOUR_SECRET_KEY:
No request body is needed. The intent's status becomes canceled and its client_secret
stops working.
Cancel when you know the checkout is over and you would rather the outstanding secret not be usable — the customer emptied their cart, the order was cancelled, or your backend priced the order again and needs a new intent for a different amount.
An intent that is already fulfilled, already canceled, or expired returns 400 with
"type": "intent_not_cancelable". Expired intents need no cancellation; they are already
unusable.
Quick reference
| Operation | Endpoint | Auth | Allowed when |
|---|---|---|---|
| Create | POST /v1/payment_intents | Secret key | — |
| Fulfill | POST /v1/payment_intents/{id}/fulfill | Publishable key | created and not expired |
| Fetch | GET /v1/payment_intents/{id} | Secret key | Always |
| Cancel | POST /v1/payment_intents/{id}/cancel | Secret key | created and not expired |