Plans widget and publishable keys
Show the instalment price right under your product price. A single endpoint powers both promo surfaces: the <qasimha-widget> element on your pages and the promo views in the mobile SDK. To add the widget, see Install the widget.
GET /api/v1/widget/plans returns the plans your policy offers for a given amount. For each plan you'll get the instalment count, the instalment amount, the first and last instalments, the fees including VAT, and the total payable.
Because the endpoint uses no customer data, it's safe to call from anywhere, a shopper's browser or a mobile app with a publishable key, or your own server when deciding whether to offer Qasimha for a basket. Call it directly when you render custom price messaging, and let the widget handle itself otherwise.
Publishable keys
A publishable key (pktest... or pklive...) is designed for use in untrusted environments such as web browsers, mobile applications, and client-side widgets.
Publishable keys are restricted to a single endpoint:
GET /api/v1/widget/plans All other API endpoints reject requests authenticated with a publishable key. In particular, publishable keys cannot access or modify Orders, Settlements and payouts, Customer information etc.
Each publishable key is issued with a single scope widget:read
To create a publishable key, navigate to Merchant Portal → Settings → API Keys and choose Publishable as the key type. Once generated, you can safely use the key in your frontend applications to authenticate requests to the plans endpoint.
| Environment | Key | API host | Merchant Portal |
|---|---|---|---|
| UAT | pk_test_... | https://api-uat.qasimha.com | https://portal-uat.qasimha.com |
| Production | pk_live_... | https://api.qasimha.com | https://portal.qasimha.com |
- A publishable key works only in the environment where you created it.
- Revoke a publishable key in the portal like any other key. A revoked or expired key gets
401 INVALID_PUBLIC_KEY. - A secret key (
sk_...) sent as a publishable key is refused, and a publishable key sent asX-Api-Keyis refused by the merchant API.
Use onlypk_keys in front-end codeNever put a secret
sk_key in a web page or app. Anyone can read it there.
Get plan quotes
curl "https://api-uat.qasimha.com/api/v1/widget/plans?amount=1200.00¤cy=SAR&lang=ar" \
-H "X-Public-Key: pk_test_..."In production, call https://api.qasimha.com with a pk_live_ key.
Request
Headers
| Header | Required | Notes |
|---|---|---|
X-Public-Key | Yes | Your publishable key, pk_test_... or pk_live_... |
Accept | No | application/json |
Send no cookies and no X-Api-Key. The endpoint does not need them.
Query parameters
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
amount | number | Yes | — | Product or basket price. Greater than 0, at most 2 decimals, at most 9999999999.99. Send it as 1200.00, with no thousands separator. |
currency | string | No | SAR | Only SAR. Case does not matter. |
lang | string | No | — | ar or en. The response carries no text today, so this does not change it. The widget always sends it. |
Response
{
"success": true,
"uuid": "76eeb42b-b7b1-4bc7-9395-dae97215584e",
"code": "OK",
"message": "Plans quoted",
"data": {
"merchantName": "Demo Store",
"currency": "SAR",
"minAmount": 100,
"maxAmount": 4000,
"plans": [
{
"instalments": 2,
"frequency": "monthly",
"instalmentAmount": 614.38,
"firstInstalmentAmount": 614.38,
"lastInstalmentAmount": 614.37,
"feesIncludingVat": 28.75,
"totalPayable": 1228.75,
"shariaCompliant": true
},
{
"instalments": 4,
"frequency": "monthly",
"instalmentAmount": 307.19,
"firstInstalmentAmount": 307.19,
"lastInstalmentAmount": 307.18,
"feesIncludingVat": 28.75,
"totalPayable": 1228.75,
"shariaCompliant": true
}
]
},
"errors": null
}The numbers above are an example. Your own policy sets the plans, fees and limits. Each plan also carries apr, processingFeeIncludingVat and profitIncludingVat, and data also carries products. Both are described in the tables below.
data fields
| Field | Type | Meaning |
|---|---|---|
merchantName | string or null | Your business name. null when the account is not approved. |
currency | string | SAR |
minAmount | number | Lowest amount any of your active policies finances. 0 when there is no active policy. |
maxAmount | number | Highest amount any of your active policies finances. 0 when there is no active policy. |
plans | array | One plan per instalment count, sorted by instalment count, shortest first. Empty when nothing is offered. |
products | array or null | The same plans grouped by financing product (for example BNPL), each with its own minAmount and maxAmount. null when the account is not approved. |
Plan fields
| Field | Type | Meaning |
|---|---|---|
instalments | integer | Number of payments |
frequency | string | monthly |
instalmentAmount | number | The regular instalment |
firstInstalmentAmount | number | The first instalment, paid at checkout. It can include fees charged at signing. |
lastInstalmentAmount | number | The last instalment. Rounding differences go here. |
feesIncludingVat | number | Everything the customer pays on top of amount: totalPayable − amount |
totalPayable | number | Total the customer pays |
shariaCompliant | boolean | true when the plan has no profit (finance) charge. A processing fee may still apply. The widget shows its "interest-free · Sharia-compliant" line only then. |
apr | number | Annual percentage rate, 2 decimals |
processingFeeIncludingVat | number | The one-off processing fee, including VAT |
profitIncludingVat | number | The profit over the term, including VAT |
feesIncludingVat can be larger than processingFeeIncludingVat + profitIncludingVat. The difference is an administration fee, which is included in the total but not listed on its own.
How to read the response
- Each plan shows the instalment count, the instalment amount, the first and last instalment, fees including VAT, and the total payable.
plansis empty whenamountis outsideminAmounttomaxAmount, or when your account has no active policy. The widget shows nothing in that case.- When you have more than one active policy, each instalment count shows the cheapest plan across them.
- Quotes come from your price list and use no customer data. The customer's own terms are set at checkout and may differ. A plan here is the lowest price your policy lists.
shariaCompliantistruefor a plan with no finance charge. The widget shows its "interest-free" line only then.- If the pricing service cannot be reached,
plansis empty. The endpoint never returns an estimated number.
From your server
The server SDKs call the same endpoint with the publishable key you set on the client. They never send your secret key on this call.
| SDK | Call |
|---|---|
| Node.js | await qasimha.checkout.getPlans(1200.00, { lang: 'ar' }) |
| PHP | $qasimha->checkout()->getPlans(1200.00, ['lang' => 'ar']) |
| .NET | await client.Checkout.GetPlansAsync(1200.00m) |
| Java | client.checkout().getPlans(new BigDecimal("1200.00")) |
Set the client's publishableKey option (pk_test_... with an sk_test_... key, pk_live_... with an sk_live_... key), or pass one per call. Without one, getPlans throws a configuration error before any request. An empty plans list is returned as is; it is not an error.
Behaviour
| Topic | Behaviour |
|---|---|
| CORS | Any origin. Methods GET and OPTIONS. Allowed request headers X-Public-Key, Accept, Content-Type. No credentials. The preflight answers 204 with Access-Control-Allow-Origin: *. |
| Server cache | The platform keeps a merchant's quote for an amount for one minute. A policy change shows within a minute. |
| HTTP caching | Responses are marked uncacheable (Cache-Control: no-store, no-cache, private, max-age=0). Cache in your own page or app if you need to; the widget caches per page. |
| Rate limit | 600 requests per minute per publishable key (per IP address when no key is sent). Over the limit you get 429 TOO_MANY_REQUESTS with a Retry-After header in seconds. |
| Authentication | The key is checked on every call. A revoked key stops working at once. |
Errors
Errors use the standard envelope with success: false:
{
"success": false,
"uuid": "…",
"code": "INVALID_PUBLIC_KEY",
"message": "The publishable key is unknown, revoked or expired.",
"data": null,
"errors": []
}| Status | Code | Cause | Fix |
|---|---|---|---|
| 400 | INVALID_AMOUNT | amount is missing, zero or negative, has more than 2 decimals, or is too large | Send a positive decimal such as 1200.00 |
| 400 | UNSUPPORTED_CURRENCY | currency is not SAR | Send SAR or leave it out |
| 401 | PUBLIC_KEY_REQUIRED | No X-Public-Key header | Send your pk_ key in X-Public-Key |
| 401 | INVALID_PUBLIC_KEY | The key is unknown, revoked, expired, not a publishable key, or from the other environment | Check the key and the host match |
| 429 | TOO_MANY_REQUESTS | Over 600 requests per minute for this key | Cache quotes per amount; wait for Retry-After seconds |
Testing in UAT
- Create a
pk_test_...key inhttps://portal-uat.qasimha.com. - Call the endpoint on
https://api-uat.qasimha.comwith an amount inside your policy's range. - Check that
plansis not empty. Try an amount belowminAmountand check thatplansis empty. - Before go-live, create a
pk_live_...key inhttps://portal.qasimha.comand switch the host tohttps://api.qasimha.com.
plans is always empty
Check that amount is between minAmount and maxAmount, that your account is approved (merchantName is not null), and that a policy is assigned to your account. Contact [email protected] if no policy is assigned.
The browser shows a CORS error
The endpoint allows every origin, so a CORS error usually hides another failure: a wrong host, a 401 from a missing header, or a request sent with credentials. Send the key in X-Public-Key and don't set credentials: 'include'.
Related
Updated about 20 hours ago
