paymob-widget
v1.0.3
Published
`paymob-widget`
Maintainers
Readme
Paymob Installments Widget
paymob-widget
A drop-in widget you embed in any web page (product page, cart, or checkout) to show your customers the installment plans available for a given amount. You can use it purely to display plans, or let customers pick a plan and hand the selection back to your code.
No framework required — it works on any HTML page with a single <script> tag,
and it also drops into React, Angular or Vue apps (see
Use in a framework).
How it works
- You add the script and a container element to your page.
- You create a
PaymobWidgetwith your public key and the order amount. - The widget calls Paymob, fetches the installment plans for that amount, and renders them inside your container.
There are two modes:
| Mode | What the customer sees | When to use it |
| --- | --- | --- |
| Display only (default) | A read-only list of available installment plans. | You just want to promote installments (e.g. "Pay in 12 months") on a product page. |
| Selectable (customerCanSelect: true) | The same plans, but the customer can select one and click Buy now. | You want the customer to choose a plan and continue the purchase in your own flow. |
Quick start
1. Add the script
Using the jsDelivr CDN:
<script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>2. Add a container
Place an empty element wherever you want the widget to appear:
<div id="paymob-widget"></div>3. Create the widget
new PaymobWidget({
publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
elementId: 'paymob-widget',
amount: 100000, // order total in cents — this is 1,000 EGP
currency: 'EGP',
});That's it. If installment plans are available for that amount, the widget renders them.
⚠️
amountis in cents, not whole currency units. To show plans for 1,000 EGP, passamount: 100000(1,000 × 100). Ifamountis missing, zero, negative, or not a number, the widget will not render and an error is logged to the browser console.
Options
| Option | Type | Required | Description |
| --- | --- | --- | --- |
| publicKey | string | Yes | Your Paymob public key. Authenticates requests and selects the correct regional API (based on the key's country prefix, e.g. egy_, are_, sau_, pak_, omn_). |
| elementId | string | Yes | The id of the HTML element the widget renders into. |
| amount | number | Yes | The order total in cents (a positive number). No default — the widget won't render without it. |
| currency | string | No | Currency code. Default: "EGP". |
| integrationId | number \| number[] | No | One or more Paymob integration IDs. When provided, plans are fetched for those integrations only. |
| theme | "primary" \| "light" \| "dark" | No | Visual theme. Default: "primary". |
| customerCanSelect | boolean | No | Set to true to let the customer select a plan and enable Buy now. Default: false (read-only). |
| onSubmit | function | No | Called when the customer clicks Buy now. See Handling the selection. |
Letting customers select a plan
Set customerCanSelect: true and provide an onSubmit callback. Your callback runs when the customer picks a plan and clicks Buy now, so you can continue the purchase in your own flow.
new PaymobWidget({
publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
elementId: 'paymob-widget',
amount: 100000, // 1,000 EGP
currency: 'EGP',
theme: 'primary',
customerCanSelect: true,
onSubmit: (plan) => {
// plan = { id: 123, tenure: 12, amount: 25000 }
console.log('Customer selected plan', plan);
// → continue your purchase flow here
},
});Handling the selection
When customerCanSelect is true, onSubmit receives the selected plan:
{
id: string | number, // the installment plan ID
tenure: number, // number of months
amount: number // monthly installment amount, in cents
}When customerCanSelect is false, the plans are read-only. Buy now is not part of that flow, and if onSubmit is called it receives no payload.
The built-in Learn more dialog also adapts: with customerCanSelect: true it includes a "Select installment plan" step; with false it omits that step.
Full HTML example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Paymob Widget Example</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
#paymob-widget {
width: 100%;
margin: 10% auto 0;
}
body {
margin: 0;
}
</style>
</head>
<body>
<div id="paymob-widget"></div>
<script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>
<script>
window.onload = () => {
new PaymobWidget({
publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
elementId: 'paymob-widget',
amount: 100000, // 1,000 EGP
currency: 'EGP',
integrationId: 123,
theme: 'primary',
customerCanSelect: true,
onSubmit: (plan) => {
console.log('Selected plan', plan);
},
});
};
</script>
</body>
</html>Use in a framework (React, Angular, Vue)
The SDK ships as a single self-contained main.js — styles, fonts and icons are
bundled in, so there are no extra CSS files to import. Loading the bundle
registers the widget on window.PaymobWidget; you then create it against a
container element from your component's lifecycle hook.
You can load the bundle in either of two ways:
- npm —
npm install paymob-widget, thenimport 'paymob-widget';(a side-effect import that registerswindow.PaymobWidget). - CDN — add
<script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>once (e.g. inindex.html); no import needed.
The published bundle exposes the class on
window(it does not provide a named ESM export), so always construct it asnew window.PaymobWidget({...}). Use a uniqueelementIdper instance if you render more than one widget.
TypeScript: add this once (e.g. paymob-widget.d.ts) so window.PaymobWidget is typed:
interface PaymobWidgetPlan {
id: string | number;
tenure: number;
amount: number;
}
interface PaymobWidgetOptions {
publicKey: string;
elementId: string;
amount: number;
currency?: string;
integrationId?: number | number[];
theme?: 'primary' | 'light' | 'dark';
customerCanSelect?: boolean;
onSubmit?: (plan?: PaymobWidgetPlan) => void;
}
declare global {
interface Window {
PaymobWidget: new (options: PaymobWidgetOptions) => unknown;
}
}
export {};React
import { useEffect } from 'react';
import 'paymob-widget'; // registers window.PaymobWidget
export function InstallmentsWidget() {
useEffect(() => {
new window.PaymobWidget({
publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
elementId: 'paymob-widget',
amount: 100000, // 1,000 EGP
currency: 'EGP',
customerCanSelect: true,
onSubmit: (plan) => console.log('Selected plan', plan),
});
}, []);
return <div id="paymob-widget" />;
}Angular
Add import 'paymob-widget'; (or the CDN <script> in index.html), then
initialise the widget after the view is ready:
import { Component, AfterViewInit } from '@angular/core';
import 'paymob-widget'; // registers window.PaymobWidget
@Component({
selector: 'app-installments',
template: '<div id="paymob-widget"></div>',
})
export class InstallmentsComponent implements AfterViewInit {
ngAfterViewInit(): void {
new (window as any).PaymobWidget({
publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
elementId: 'paymob-widget',
amount: 100000, // 1,000 EGP
currency: 'EGP',
customerCanSelect: true,
onSubmit: (plan: unknown) => console.log('Selected plan', plan),
});
}
}Vue 3
<script setup lang="ts">
import { onMounted } from 'vue';
import 'paymob-widget'; // registers window.PaymobWidget
onMounted(() => {
new window.PaymobWidget({
publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
elementId: 'paymob-widget',
amount: 100000, // 1,000 EGP
currency: 'EGP',
customerCanSelect: true,
onSubmit: (plan) => console.log('Selected plan', plan),
});
});
</script>
<template>
<div id="paymob-widget"></div>
</template>Troubleshooting — "the widget isn't showing"
The widget renders only when all of these are true:
amountis a valid positive number (in cents).- The plans request to Paymob succeeds (returns a
2xxstatus). - At least one installment plan is available for that amount.
If any of these fail, the widget stays hidden and the reason is logged to the browser console (prefixed with [PaymobWidget]):
| Symptom | Likely cause |
| --- | --- |
| Nothing renders, console shows an amount error | amount is missing, 0, negative, or not a number. |
| Nothing renders, console shows a status code (e.g. 400) | The request to Paymob failed — check your publicKey, integrationId, and currency. |
| Request succeeds but nothing renders | No plans are available — e.g. the amount is below the minimum, the integration has no bank installments configured, or the integrationId is invalid. |
Note: These errors are for developers only. Your customers never see error messages on the page or in the dialog.
Good to know
- Self-contained: everything — CSS, the widget font, and icons — is inlined into
main.js. There are no extra files to host or import; the single file is all you ship. (At runtime the widget still calls the Paymob API, and the design system may pull a few supplementary web fonts from Google Fonts, with graceful fallback.) - Style isolation: the widget renders inside a Shadow DOM, so its styles won't clash with your page's CSS (and your CSS won't bleed into it).
- Region: the API region is chosen automatically from your public key's country prefix (
egy_,are_,sau_,pak_,omn_).
