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.

EnvironmentKeyAPI hostMerchant Portal
UATpk_test_...https://api-uat.qasimha.comhttps://portal-uat.qasimha.com
Productionpk_live_...https://api.qasimha.comhttps://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 as X-Api-Key is refused by the merchant API.

Use only pk_ keys in front-end code

Never 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&currency=SAR&lang=ar" \
  -H "X-Public-Key: pk_test_..."

In production, call https://api.qasimha.com with a pk_live_ key.

Request

Headers

HeaderRequiredNotes
X-Public-KeyYesYour publishable key, pk_test_... or pk_live_...
AcceptNoapplication/json

Send no cookies and no X-Api-Key. The endpoint does not need them.

Query parameters

ParameterTypeRequiredDefaultNotes
amountnumberYes—Product or basket price. Greater than 0, at most 2 decimals, at most 9999999999.99. Send it as 1200.00, with no thousands separator.
currencystringNoSAROnly SAR. Case does not matter.
langstringNo—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

FieldTypeMeaning
merchantNamestring or nullYour business name. null when the account is not approved.
currencystringSAR
minAmountnumberLowest amount any of your active policies finances. 0 when there is no active policy.
maxAmountnumberHighest amount any of your active policies finances. 0 when there is no active policy.
plansarrayOne plan per instalment count, sorted by instalment count, shortest first. Empty when nothing is offered.
productsarray or nullThe 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

FieldTypeMeaning
instalmentsintegerNumber of payments
frequencystringmonthly
instalmentAmountnumberThe regular instalment
firstInstalmentAmountnumberThe first instalment, paid at checkout. It can include fees charged at signing.
lastInstalmentAmountnumberThe last instalment. Rounding differences go here.
feesIncludingVatnumberEverything the customer pays on top of amount: totalPayable − amount
totalPayablenumberTotal the customer pays
shariaCompliantbooleantrue 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.
aprnumberAnnual percentage rate, 2 decimals
processingFeeIncludingVatnumberThe one-off processing fee, including VAT
profitIncludingVatnumberThe 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.
  • plans is empty when amount is outside minAmount to maxAmount, 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.
  • shariaCompliant is true for a plan with no finance charge. The widget shows its "interest-free" line only then.
  • If the pricing service cannot be reached, plans is 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.

SDKCall
Node.jsawait qasimha.checkout.getPlans(1200.00, { lang: 'ar' })
PHP$qasimha->checkout()->getPlans(1200.00, ['lang' => 'ar'])
.NETawait client.Checkout.GetPlansAsync(1200.00m)
Javaclient.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

TopicBehaviour
CORSAny 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 cacheThe platform keeps a merchant's quote for an amount for one minute. A policy change shows within a minute.
HTTP cachingResponses 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 limit600 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.
AuthenticationThe 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": []
}
StatusCodeCauseFix
400INVALID_AMOUNTamount is missing, zero or negative, has more than 2 decimals, or is too largeSend a positive decimal such as 1200.00
400UNSUPPORTED_CURRENCYcurrency is not SARSend SAR or leave it out
401PUBLIC_KEY_REQUIREDNo X-Public-Key headerSend your pk_ key in X-Public-Key
401INVALID_PUBLIC_KEYThe key is unknown, revoked, expired, not a publishable key, or from the other environmentCheck the key and the host match
429TOO_MANY_REQUESTSOver 600 requests per minute for this keyCache quotes per amount; wait for Retry-After seconds

Testing in UAT

  1. Create a pk_test_... key in https://portal-uat.qasimha.com.
  2. Call the endpoint on https://api-uat.qasimha.com with an amount inside your policy's range.
  3. Check that plans is not empty. Try an amount below minAmount and check that plans is empty.
  4. Before go-live, create a pk_live_... key in https://portal.qasimha.com and switch the host to https://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


Did this page help you?