Angular SDK

Add Qasimha checkout, return handling and instalment plans to an Angular app with @qasimha/angular.

@qasimha/angular presents the Qasimha hosted checkout from an Angular app, reads the result when the shopper comes back, and shows instalment plans. Your server creates the checkout session with a server SDK; this package never sees your secret key.

Requirements

ItemRequirement
Package@qasimha/angular (npm)
Angular19 or later (@angular/core, @angular/common, @angular/router). Standalone components, signals, zoneless-ready.
Key in the browserPublishable key only: pk_test_... (UAT) or pk_live_... (production)
ServerA backend using a Qasimha server SDK (@qasimha/sdk, qasimha/php-sdk, Qasimha.Sdk or com.qasimha:qasimha-sdk)

Install

npm install @qasimha/angular

Configure

Register the SDK once with provideQasimha. Load the configuration from your own backend so keys and return URLs live in one place:

// app.config.ts
import { ApplicationConfig, inject } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { provideQasimha, QasimhaConfig } from '@qasimha/angular';
import { firstValueFrom } from 'rxjs';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withComponentInputBinding()),
    provideHttpClient(),
    provideQasimha(() => firstValueFrom(inject(HttpClient).get<QasimhaConfig>('/api/qasimha/config'))),
  ],
};

Or pass a static config:

provideQasimha({ publicKey: 'pk_test_...', language: 'ar', merchantUrl: { success, failure, cancel } });

The loader runs during bootstrap (provideAppInitializer), so the config is ready before the first component renders.

OptionDefaultNotes
publicKeynonepk_test_... or pk_live_.... Needed for <qasimha-promo>, <qasimha-payment-option> and QasimhaPlans. A secret key (sk_...) is refused.
language'ar''ar' or 'en'. Change at runtime with inject(QasimhaConfigStore).update({ language }).
environmentfrom the key'sandbox' or 'production'. Must agree with the key prefix.
apiBaseUrlfrom the environmenthttps only (http for localhost).
merchantUrlnoneThe same success, failure, cancel URLs your server sends when it creates the session. Lets the SDK recognise the return, also inside the popup.
presentation'redirect'Default for pay(): 'redirect' or 'popup'.
widgetnonescriptUrl and optional integrity. Loads the on-site widget and keeps it in step with the SDK.

QasimhaConfigStore.update() with a new key or environment moves plan quotes and the widget to the matching API host.

Start checkout

Your createSession function

createSession calls your backend, never Qasimha. Send only product IDs and quantities:

createSession = () =>
  firstValueFrom(this.http.post<{ paymentUrl: string; orderId: string }>('/api/checkout', { items: this.cart() }));

Your backend prices the basket from its own catalogue, calls checkout.createSession with the secret key and the same merchantUrl set, and returns paymentUrl and orderId. See Checkout flows. The hosted checkout expires after 2 hours.

With the components

<qasimha-payment-option [amount]="total()" [(selected)]="payWithQasimha" />

<qasimha-pay-button
  [createSession]="createSession"
  presentation="redirect"
  (result)="onResult($event)"
  (failed)="onError($event)" />

<qasimha-payment-option> is the Qasimha entry for your payment-method list.

With the service

const checkout = inject(QasimhaCheckout);

async onPay() {                     // call from a click handler
  const result = await checkout.pay({ createSession: this.createSession, presentation: 'popup' });
  // redirect: the page has navigated away (result is null)
  // popup:    result is { type: 'success' | 'failure' | 'cancelled', ... }
}
redirect (default)popup
What happensThe page goes to the hosted checkout and comes back to your merchantUrl.Checkout opens in a window above your page.
Where the result arrivesYour return route (qasimhaReturnResolver)pay() resolves and (result) emits in the original tab
Popup blockedNot applicableThe SDK opens the window inside the click, before createSession runs. If the browser still blocks it, the SDK falls back to redirect.

Call pay() directly from the click handler so the popup can open. While the popup is open, <qasimha-pay-button> shows an overlay with "Open it again" and "Cancel". In code, use checkout.reopen(), checkout.cancel() and the popupState signal.

There is no iframe mode. In popup mode, the popup lands on your return URL and your app (started by provideQasimha) passes the result to the original tab and closes the popup.

Handle the return

Qasimha appends orderId, orderReferenceId and status to your return URL. status is confirmed, declined, expired, failed or cancelled. See Return URLs.

Add the resolver to your return route:

// app.routes.ts
import { qasimhaReturnResolver } from '@qasimha/angular';

{ path: 'checkout/return', component: ReturnPage, resolve: { qasimha: qasimhaReturnResolver } }

Read it as an input (needs withComponentInputBinding()):

// return.page.ts
readonly qasimha = input.required<QasimhaReturn>();
// qasimha().result:
//   { type: 'success', orderId, orderReferenceId }
//   { type: 'failure', status, ... }
//   { type: 'cancelled' }
//   null
// qasimha().relayedToOpener: true inside the popup -> show "you can close this window"

inject(QasimhaCheckout).parseReturn() reads the same values on any page without side effects, for example on your cart page for the cancel URL.

Confirm on your server

The result above is only what the URL says. Before you show "paid" or fulfil, ask your backend to read the order with the secret key:

// your server, with @qasimha/sdk
const order = await qasimha.orders.get(orderId);
const paid = order.status === 'Confirmed';
// your return page
const order = await firstValueFrom(this.http.get<{ status: string; paid: boolean }>(`/api/orders/${orderId}`));

An order that completes the hosted checkout is already Confirmed. The demo backend also treats FullyFulfilled and PartiallyFulfilled as paid. Your server should also process the signed webhook, which is the durable record. See Order lifecycle.

Plans and promo components

Component or serviceUse
<qasimha-promo [amount]="price" />Bordered instalment badge
<qasimha-promo [amount]="price" variant="compact" />One line: 4 × [symbol] 300, Learn more
<qasimha-promo [amount]="total" variant="text" />Text-only line
QasimhaPlans.getPlans(amount, lang?)Same data for your own layout (Plans or null)
<qasimha-amount [amount]="1200" />Riyal symbol left of the number, in a left-to-right span

"Learn more" opens a modal with the full schedule for every plan. <qasimha-promo> renders nothing, never a computed number, when there is no publishable key, the amount is outside your account's range, or the quote fails. Quotes use fetch with credentials: 'omit' instead of HttpClient, so your interceptors (auth headers, cookies) never reach Qasimha. Results are cached for the page lifetime. The data comes from the plans endpoint.

Using the on-site widget instead

With widget in the config, provideQasimha loads @qasimha/widget once and keeps its key, language and API host in step. Use the element in any component that declares CUSTOM_ELEMENTS_SCHEMA:

@Component({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <qasimha-widget type="summary" [attr.amount]="price.toFixed(2)"></qasimha-widget>
    <qasimha-widget type="summary" inline-type="6" [attr.amount]="total.toFixed(2)"></qasimha-widget>
    <qasimha-widget type="faq"></qasimha-widget>
  `,
})

Pass amount as a plain decimal string. See Install the on-site widget for layouts.

Styling

Set CSS custom properties on any parent element: --qasimha-primary, --qasimha-on-primary, --qasimha-text, --qasimha-muted, --qasimha-border, --qasimha-surface, --qasimha-bg, --qasimha-radius, --qasimha-font-size. Components use your page font.

Errors

The SDK throws QasimhaCheckoutError with a code:

codeWhen
INVALID_CONFIGprovideQasimha received a bad key, environment, base URL or return URL. field names it.
INVALID_OPTIONSpay() got no createSession, or the returned paymentUrl is not https.
NOT_CONFIGUREDQasimhaConfigStore.update() before the config loaded.
CHECKOUT_IN_PROGRESSpay() in popup mode while a popup checkout is already open.

Errors thrown by your createSession (for example your backend's validation errors) pass through unchanged to (failed) on the button. For API error codes on your server, see Errors.

Security

  • Never put a secret key (sk_...) in the browser. provideQasimha refuses one.
  • Compute amounts on the server from your own prices.
  • Confirm every outcome with orders.get or the webhook before fulfilling.
  • paymentUrl and the return URLs must be https (http only on localhost).
  • The SDK stores nothing (no cookies, no storage) and sends nothing to Qasimha besides the plan quote.
  • Payment happens on the Qasimha hosted page, so no card data touches your app (PCI DSS SAQ-A).
  • If you use a CSP, allow the Qasimha API host in connect-src for plan quotes (https://api.qasimha.com in production, https://merchant-api-uat.qasimha.com in UAT).

Next steps


Did this page help you?