Payment Flow
Understand the complete payment lifecycle
Overview#
A payment goes through several states from creation to completion. The gateway supports two capture methods: direct capture (immediate) and reservation (capture later).
Payment States#
| State | Code | Description |
|---|---|---|
CREATED | - | Payment intent has been created, awaiting payment method |
PROCESSING | - | Payment is being processed with the provider |
REQUIRES_ACTION | 12 | OTP verification required — redirect user to payUrl |
PRE_AUTHORIZED | 13 | Funds reserved, awaiting capture or release |
SUCCEEDED | 2 | Payment completed successfully |
FAILED | 3 | Payment was rejected (terminal state) |
CANCELED | 10 | Pre-authorization was released (terminal state) |
REFUNDED | 4 | Payment was fully refunded (terminal state) |
PARTIALLY_REFUNDED | 16 | Payment was partially refunded |
Direct Capture Flow#
This is the default flow. The payment is captured immediately when processed.
- Register Customer — Ensure the customer exists by calling
POST /customerswith their phone number. This is idempotent — if the customer already exists, handle the409and continue. - Create Payment Intent — Your server creates a payment intent with the amount and currency. The
userIdmust be the registered customer's phone number. LeavecaptureMethodas default (DIRECT) or omit it. - Proceed with Wallet — Call proceed with
paymentMethodCode: "WALLET_V2". - Handle OTP — The response includes
require3DS: trueand apayUrl. Redirect the user to thepayUrlfor OTP verification. Append yourreturnUrlso the user is redirected back after verification. - Check Result — After the user returns from OTP, call the Check endpoint to confirm the payment status. A
statusCodeof2means success. - Webhook — Your server receives a webhook with
remoteStatusCode: 2(succeeded) or3(rejected).
OTP Verification#
When you call the Proceed endpoint with Yassir Cash, the response includes require3DS: true and a payUrl in the metadata. The user needs to verify the payment with a one-time password (OTP), as shown in steps 4-5 of the diagram above.
How it works#
You don't need to implement any OTP logic. Yassir handles the entire OTP flow — your integration only needs to:
- Redirect the user to the
payUrlfrom the Proceed response. Append areturnUrlquery parameter so the user is sent back to your app after verification. - Wait for the redirect back — After the user enters their OTP on the Yassir-hosted page, they are automatically redirected to your
returnUrl. - Check the result — Call the Check endpoint or rely on the webhook to confirm whether the payment succeeded.
// Proceed response with OTP required:
{
"data": {
"statusCode": 12,
"require3DS": true,
"metadata": {
"payUrl": "https://otp.payment.yassir.io/otp/verify?requestId=...&phone=...&amount=1500¤cy=DZD"
}
}
}
// Your code: redirect the user
const { payUrl } = response.data.metadata;
const returnUrl = `${window.location.origin}/payment-result?paymentId=${paymentId}`;
const url = new URL(payUrl);
url.searchParams.set('returnUrl', returnUrl);
window.location.href = url.toString();
// That's it! Yassir shows the OTP page, verifies the code,
// and redirects the user back to your returnUrl.
// Then check the payment status:
GET /payments/intents/:id/check
// → statusCode: 2 (succeeded) or 3 (rejected)// Proceed response with OTP required:
{
"data": {
"statusCode": 12,
"require3DS": true,
"metadata": {
"payUrl": "https://otp.payment.yassir.io/otp/verify?requestId=...&phone=...&amount=1500¤cy=DZD"
}
}
}
// Your code: redirect the user
const { payUrl } = response.data.metadata;
const returnUrl = `${window.location.origin}/payment-result?paymentId=${paymentId}`;
const url = new URL(payUrl);
url.searchParams.set('returnUrl', returnUrl);
window.location.href = url.toString();
// That's it! Yassir shows the OTP page, verifies the code,
// and redirects the user back to your returnUrl.
// Then check the payment status:
GET /payments/intents/:id/check
// → statusCode: 2 (succeeded) or 3 (rejected)OTP is fully managed
Redirect URL (Return URL)#
After OTP verification or 3DS authentication, Yassir redirects the user back to your application. To receive these redirects, you must configure a redirect URL for your service during onboarding (or request it to be updated via your Yassir contact).
Redirect URL format#
When Yassir redirects the user back, the following query parameters are appended to your configured redirect URL:
| Parameter | Type | Description |
|---|---|---|
paymentId | string | The payment intent ID |
statusCode | number | The payment status code (e.g., 2 = succeeded, 3 = rejected, 13 = pre-authorized) |
// Example redirect URL after successful payment:
https://yourapp.com/payment-result?paymentId=123e4567-e89b-12d3-a456-426614174000&statusCode=2
// Example redirect URL after failed payment:
https://yourapp.com/payment-result?paymentId=123e4567-e89b-12d3-a456-426614174000&statusCode=3
// Your handler:
const params = new URLSearchParams(window.location.search);
const paymentId = params.get('paymentId');
const statusCode = Number(params.get('statusCode'));
if (statusCode === 2) {
// Payment succeeded — show success page
} else if (statusCode === 13) {
// Pre-authorized — show capture/release UI
} else {
// Payment failed — show error and allow retry
}// Example redirect URL after successful payment:
https://yourapp.com/payment-result?paymentId=123e4567-e89b-12d3-a456-426614174000&statusCode=2
// Example redirect URL after failed payment:
https://yourapp.com/payment-result?paymentId=123e4567-e89b-12d3-a456-426614174000&statusCode=3
// Your handler:
const params = new URLSearchParams(window.location.search);
const paymentId = params.get('paymentId');
const statusCode = Number(params.get('statusCode'));
if (statusCode === 2) {
// Payment succeeded — show success page
} else if (statusCode === 13) {
// Pre-authorized — show capture/release UI
} else {
// Payment failed — show error and allow retry
}Always verify server-side
Reservation Flow (Manual Capture)#
For scenarios where you need to reserve funds first and capture later (e.g., hotels, car rentals, service bookings), use the reservation flow.
- Register Customer — Same as direct flow, ensure the customer exists.
- Create Payment Intent — Set
captureMethod: "RESERVATION". - Proceed with Wallet — Same as direct flow. The user will be redirected to the OTP page for verification.
- Pre-Authorized — After successful OTP verification, the payment enters the
PRE_AUTHORIZEDstate (statusCode13). Funds are reserved in the user's wallet but not yet captured. - Capture or Release — When ready:
- Capture — Call the Capture endpoint. You can capture the full amount or a partial amount (the remainder is automatically released).
- Release — Call the Release endpoint to release all reserved funds back to the user.
- Webhook — Your server receives a webhook after capture (
remoteStatusCode: 2) or release (remoteStatusCode: 10).
Refund Flow#
After a payment has been successfully captured (statusCode 2), you can refund it fully or partially.
- Full refund — Omit the
amountfield. The entire payment amount is refunded to the user's wallet. - Partial refund — Specify an
amountless than the original payment. The specified amount is refunded. - Multiple partial refunds — You can issue multiple partial refunds on the same payment. Each refund reduces the remaining refundable balance until it reaches zero.
Webhook Notifications
remoteStatusCode field to determine the event type. See the Webhooks guide for the full payload format.