paymob-widget-alpha 1.0.15 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +229 -55
  2. package/main.js +6182 -72515
  3. package/package.json +1 -1
  4. package/0-interest.39e7455001b508e7.png +0 -0
  5. package/Delete-icon.3839812aa3996b863c286b42e3fffa3b.svg +0 -21
  6. package/Inter-Regular.otf +0 -0
  7. package/Inter-Regular.ttf +0 -0
  8. package/Inter-Regular.woff +0 -0
  9. package/Inter-Regular.woff2 +0 -0
  10. package/Inter.otf +0 -0
  11. package/Inter.ttf +0 -0
  12. package/Inter.woff +0 -0
  13. package/Inter.woff2 +0 -0
  14. package/arrow.40b47d938ade37f5820c66b81ae8b353.svg +0 -16
  15. package/assets/.gitkeep +0 -0
  16. package/assets/fonts/CurrencyFont/Inter.otf +0 -0
  17. package/assets/fonts/CurrencyFont/Inter.ttf +0 -0
  18. package/assets/fonts/CurrencyFont/Inter.woff +0 -0
  19. package/assets/fonts/CurrencyFont/Inter.woff2 +0 -0
  20. package/assets/icons/0-interest.png +0 -0
  21. package/assets/icons/Partner logo.png +0 -0
  22. package/assets/icons/arrow.png +0 -0
  23. package/assets/icons/bag.png +0 -0
  24. package/assets/icons/blue-logo.png +0 -0
  25. package/assets/icons/calender.png +0 -0
  26. package/assets/icons/card.png +0 -0
  27. package/assets/icons/close-arrow.png +0 -0
  28. package/assets/icons/close.png +0 -0
  29. package/assets/icons/open-arrow.png +0 -0
  30. package/assets/icons/paymob-logo.png +0 -0
  31. package/assets/icons/shop.png +0 -0
  32. package/authorize-icon.27f6de6230a7258b010095f2ecb62fbb.svg +0 -16
  33. package/bank-installment.e4813d8ad6f5e1f041f321f3c6dcbb2f.svg +0 -16
  34. package/card.23ed515bba2fc5ea60108513a05e1ba2.svg +0 -21
  35. package/check.021d9dd3dee18e90e138da39f88ef694.svg +0 -19
  36. package/checkGreenIcon.2fdf1421207f99ebe329055b1350a193.svg +0 -16
  37. package/checkIcon.da837e89923bb68f2c34c3b502b7b3f9.svg +0 -16
  38. package/chevron-down.413212cab44b2a7ef0f299360be6f2b1.svg +0 -16
  39. package/circle.22e1a0a2a35306fe278668ddabc49974.svg +0 -16
  40. package/contact.1ec295a5e0bf9727.png +0 -0
  41. package/copy.72a7096d2e875cbd4d02df03563ec7c7.svg +0 -16
  42. package/crypto.3caa56ce2aad387e232e31d62800f356.svg +0 -30
  43. package/email.928bbae47d30af2076391a2591696665.svg +0 -16
  44. package/emptyImage.7f00e4ccc5fe1c80425a8d28af3c0e2d.svg +0 -21
  45. package/error.87f0ee7801122d75f04ce506f360c984.svg +0 -16
  46. package/expiredIcon.32804bbd5021977df40094b1da6703e7.svg +0 -16
  47. package/kiosk.1bb782704007efe21b5b71e957b6baee.svg +0 -16
  48. package/loading-loader.6463e608601cd9d5550f67d4c08d513b.svg +0 -43
  49. package/madfu.4e378b2568497219.png +0 -0
  50. package/main.css +0 -4755
  51. package/mid-takseet.6c95c45e88ec6a5c.png +0 -0
  52. package/paylater.e1d5f1a0f0d9027330f541ded8752a30.svg +0 -16
  53. package/payment-succesful-1.dd6ee56033bac452.png +0 -0
  54. package/payment-succesful-2.a503559a0c9d7a70.png +0 -0
  55. package/paymentDeclined.b4b9f7ee7680300d.png +0 -0
  56. package/paymentDeclinedIcon.76cc8bf5bd5f0f9739af86a1b8de62ab.svg +0 -16
  57. package/paymentpending.ae4dace728429396.png +0 -0
  58. package/pending-icon.21ad7f408b9b33164ddeab11baa012e5.svg +0 -16
  59. package/phone.c867b950ae5b9bfd86c96f7a0937d482.svg +0 -16
  60. package/search-icon.2522b2e216184647659a86a07a383ccd.svg +0 -16
  61. package/styles.css +0 -253
  62. package/styles.js +0 -6
  63. package/wallet.c3700990d3a1ebb570cc2c9f37e07ffc.svg +0 -16
package/README.md CHANGED
@@ -1,85 +1,121 @@
1
- # `paymob-widget-alpha`
2
- Paymob Installments Widget that can be embedded in any web page to display **0% interest plans** and (optionally) let the user select a plan.
1
+ # Paymob Installments Widget
3
2
 
4
- ## Installing
3
+ `paymob-widget`
5
4
 
5
+ 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.
6
6
 
7
- ### CDN
7
+ No framework required — it works on any HTML page with a single `<script>` tag,
8
+ and it also drops into React, Angular or Vue apps (see
9
+ [Use in a framework](#use-in-a-framework-react-angular-vue)).
8
10
 
9
- Using jsDelivr:
11
+ ---
12
+
13
+ ## How it works
14
+
15
+ 1. You add the script and a container element to your page.
16
+ 2. You create a `PaymobWidget` with your public key and the order amount.
17
+ 3. The widget calls Paymob, fetches the installment plans for that amount, and renders them inside your container.
18
+
19
+ There are two modes:
20
+
21
+ | Mode | What the customer sees | When to use it |
22
+ | --- | --- | --- |
23
+ | **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. |
24
+ | **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. |
25
+
26
+ ---
27
+
28
+ ## Quick start
29
+
30
+ ### 1. Add the script
31
+
32
+ Using the jsDelivr CDN:
10
33
 
11
34
  ```html
12
- <script src="https://cdn.jsdelivr.net/npm/paymob-widget-alpha@latest/main.js" type="module"></script>
35
+ <script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>
13
36
  ```
14
37
 
15
- ## Usage
38
+ ### 2. Add a container
16
39
 
17
- Create a container element in your page:
40
+ Place an empty element wherever you want the widget to appear:
18
41
 
19
42
  ```html
20
43
  <div id="paymob-widget"></div>
21
44
  ```
22
45
 
23
- Then instantiate the widget:
46
+ ### 3. Create the widget
24
47
 
25
48
  ```js
26
49
  new PaymobWidget({
27
50
  publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
28
51
  elementId: 'paymob-widget',
29
- amount: 1000, // amount in cents
52
+ amount: 100000, // order total in cents — this is 1,000 EGP
30
53
  currency: 'EGP',
31
- integrationId: 123, // optional
32
54
  });
33
55
  ```
34
56
 
35
- ### Selectable mode example
57
+ That's it. If installment plans are available for that amount, the widget renders them.
58
+
59
+ > **⚠️ `amount` is in cents, not whole currency units.**
60
+ > To show plans for **1,000 EGP**, pass `amount: 100000` (1,000 × 100).
61
+ > If `amount` is missing, zero, negative, or not a number, the widget will **not** render and an error is logged to the browser console.
62
+
63
+ ---
64
+
65
+ ## Options
66
+
67
+ | Option | Type | Required | Description |
68
+ | --- | --- | --- | --- |
69
+ | `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_`). |
70
+ | `elementId` | `string` | **Yes** | The `id` of the HTML element the widget renders into. |
71
+ | `amount` | `number` | **Yes** | The order total **in cents** (a positive number). No default — the widget won't render without it. |
72
+ | `currency` | `string` | No | Currency code. Default: `"EGP"`. |
73
+ | `integrationId` | `number \| number[]` | No | One or more Paymob integration IDs. When provided, plans are fetched for those integrations only. |
74
+ | `theme` | `"primary" \| "light" \| "dark"` | No | Visual theme. Default: `"primary"`. |
75
+ | `customerCanSelect` | `boolean` | No | Set to `true` to let the customer select a plan and enable **Buy now**. Default: `false` (read-only). |
76
+ | `onSubmit` | `function` | No | Called when the customer clicks **Buy now**. See [Handling the selection](#handling-the-selection). |
77
+
78
+ ---
79
+
80
+ ## Letting customers select a plan
36
81
 
37
- To let the user select an installment plan and handle the selection in your app, enable `customerCanSelect` and pass an `onSubmit` callback.
82
+ 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.
38
83
 
39
84
  ```js
40
85
  new PaymobWidget({
41
86
  publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
42
87
  elementId: 'paymob-widget',
43
- amount: 1000, // amount in cents
88
+ amount: 100000, // 1,000 EGP
44
89
  currency: 'EGP',
45
- integrationId: 123, // optional
46
90
  theme: 'primary',
47
- customerCanSelect: true, // Enable the customer to select a plan.
91
+ customerCanSelect: true,
48
92
  onSubmit: (plan) => {
49
- // `onSubmit` is called when the user clicks **Buy now**.
50
- // In selectable mode (`customerCanSelect: true`), `plan` will be provided after the user selects a plan.
51
- // plan = { id: 123, tenure: 12, amount: 250 }
52
- // In non-selectable mode (`customerCanSelect: false`), `onSubmit` is called with no payload.
93
+ // plan = { id: 123, tenure: 12, amount: 25000 }
94
+ console.log('Customer selected plan', plan);
95
+ // continue your purchase flow here
53
96
  },
54
97
  });
55
98
  ```
56
99
 
57
- ## Properties
100
+ ### Handling the selection
58
101
 
59
- The full list of properties is as follows:
60
-
61
- | Property | Type | Required | Definition |
62
- | --- | --- | --- | --- |
63
- | publicKey | String | Yes | Your public key (used to resolve API base URL and authenticate requests). |
64
- | elementId | String | Yes | ID of the HTML element where the widget will be rendered. |
65
- | theme | `"primary" \| "light" \| "dark"` | No | Widget theme. Default: `"primary"`. |
66
- | amount | Number | Yes | Order total in **cents** (positive finite number). Required — the widget will not initialize without it. |
67
- | currency | String | No | Currency code. Default: `"EGP"`. |
68
- | integrationId | Number | No | Paymob integration ID. If provided, it will be sent when fetching installment plans. |
69
- | customerCanSelect | Boolean | No | If `true`, the widget allows selecting a plan and will show **Buy now** actions. Default: `false`. |
70
- | onSubmit | Function | No | Called when the user clicks **Buy now**. In selectable mode (`customerCanSelect: true`), it receives `{ id, tenure, amount }`. In non-selectable mode (`customerCanSelect: false`), it is called with no payload. |
71
-
72
- ### `onSubmit` payload
102
+ When `customerCanSelect` is `true`, `onSubmit` receives the selected plan:
73
103
 
74
104
  ```js
75
105
  {
76
- id: string | number, // installment plan id
106
+ id: string | number, // the installment plan ID
77
107
  tenure: number, // number of months
78
- amount: number // monthly amount
108
+ amount: number // monthly installment amount, in cents
79
109
  }
80
110
  ```
81
111
 
82
- ### Full HTML example
112
+ 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.
113
+
114
+ The built-in **Learn more** dialog also adapts: with `customerCanSelect: true` it includes a "Select installment plan" step; with `false` it omits that step.
115
+
116
+ ---
117
+
118
+ ## Full HTML example
83
119
 
84
120
  ```html
85
121
  <!DOCTYPE html>
@@ -91,35 +127,32 @@ The full list of properties is as follows:
91
127
  <meta name="viewport" content="width=device-width, initial-scale=1" />
92
128
  <style>
93
129
  #paymob-widget {
94
- width: 100%;
95
- margin: 10% auto 0;
96
- }
130
+ width: 100%;
131
+ margin: 10% auto 0;
132
+ }
97
133
  body {
98
- margin: 0;
99
- }
134
+ margin: 0;
135
+ }
100
136
  </style>
101
137
  </head>
102
138
 
103
139
  <body>
104
140
  <div id="paymob-widget"></div>
105
141
 
106
- <script src="https://cdn.jsdelivr.net/npm/paymob-widget-alpha@latest/main.js" type="module"></script>
142
+ <script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>
107
143
 
108
144
  <script>
109
145
  window.onload = () => {
110
146
  new PaymobWidget({
111
147
  publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
112
148
  elementId: 'paymob-widget',
113
- amount: 1000,
149
+ amount: 100000, // 1,000 EGP
114
150
  currency: 'EGP',
115
- integrationId: 123, // optional
116
- theme: 'primary', // "primary" | "light" | "dark"
117
- customerCanSelect: true, // Enable the customer to select a plan.
151
+ integrationId: 123,
152
+ theme: 'primary',
153
+ customerCanSelect: true,
118
154
  onSubmit: (plan) => {
119
- // `onSubmit` is called when the user clicks **Buy now**.
120
- // In selectable mode (`customerCanSelect: true`), `plan` will be provided after the user selects a plan.
121
- // plan = { id: 123, tenure: 12, amount: 250 }
122
- // In non-selectable mode (`customerCanSelect: false`), `onSubmit` is called with no payload.
155
+ console.log('Selected plan', plan);
123
156
  },
124
157
  });
125
158
  };
@@ -128,10 +161,151 @@ The full list of properties is as follows:
128
161
  </html>
129
162
  ```
130
163
 
131
- ## Notes
164
+ ---
165
+
166
+ ## Use in a framework (React, Angular, Vue)
167
+
168
+ The SDK ships as a single self-contained `main.js` — styles, fonts and icons are
169
+ bundled in, so there are **no extra CSS files to import**. Loading the bundle
170
+ registers the widget on `window.PaymobWidget`; you then create it against a
171
+ container element from your component's lifecycle hook.
172
+
173
+ You can load the bundle in either of two ways:
174
+
175
+ - **npm** — `npm install paymob-widget`, then `import 'paymob-widget';` (a
176
+ side-effect import that registers `window.PaymobWidget`).
177
+ - **CDN** — add `<script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>` once (e.g. in `index.html`); no import needed.
178
+
179
+ > The published bundle exposes the class on `window` (it does **not** provide a
180
+ > named ESM export), so always construct it as `new window.PaymobWidget({...})`.
181
+ > Use a **unique `elementId` per instance** if you render more than one widget.
182
+
183
+ **TypeScript:** add this once (e.g. `paymob-widget.d.ts`) so `window.PaymobWidget` is typed:
184
+
185
+ ```ts
186
+ interface PaymobWidgetPlan {
187
+ id: string | number;
188
+ tenure: number;
189
+ amount: number;
190
+ }
191
+
192
+ interface PaymobWidgetOptions {
193
+ publicKey: string;
194
+ elementId: string;
195
+ amount: number;
196
+ currency?: string;
197
+ integrationId?: number | number[];
198
+ theme?: 'primary' | 'light' | 'dark';
199
+ customerCanSelect?: boolean;
200
+ onSubmit?: (plan?: PaymobWidgetPlan) => void;
201
+ }
202
+
203
+ declare global {
204
+ interface Window {
205
+ PaymobWidget: new (options: PaymobWidgetOptions) => unknown;
206
+ }
207
+ }
208
+
209
+ export {};
210
+ ```
211
+
212
+ ### React
213
+
214
+ ```tsx
215
+ import { useEffect } from 'react';
216
+ import 'paymob-widget'; // registers window.PaymobWidget
217
+
218
+ export function InstallmentsWidget() {
219
+ useEffect(() => {
220
+ new window.PaymobWidget({
221
+ publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
222
+ elementId: 'paymob-widget',
223
+ amount: 100000, // 1,000 EGP
224
+ currency: 'EGP',
225
+ customerCanSelect: true,
226
+ onSubmit: (plan) => console.log('Selected plan', plan),
227
+ });
228
+ }, []);
229
+
230
+ return <div id="paymob-widget" />;
231
+ }
232
+ ```
233
+
234
+ ### Angular
235
+
236
+ Add `import 'paymob-widget';` (or the CDN `<script>` in `index.html`), then
237
+ initialise the widget after the view is ready:
238
+
239
+ ```ts
240
+ import { Component, AfterViewInit } from '@angular/core';
241
+ import 'paymob-widget'; // registers window.PaymobWidget
242
+
243
+ @Component({
244
+ selector: 'app-installments',
245
+ template: '<div id="paymob-widget"></div>',
246
+ })
247
+ export class InstallmentsComponent implements AfterViewInit {
248
+ ngAfterViewInit(): void {
249
+ new (window as any).PaymobWidget({
250
+ publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
251
+ elementId: 'paymob-widget',
252
+ amount: 100000, // 1,000 EGP
253
+ currency: 'EGP',
254
+ customerCanSelect: true,
255
+ onSubmit: (plan: unknown) => console.log('Selected plan', plan),
256
+ });
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### Vue 3
262
+
263
+ ```vue
264
+ <script setup lang="ts">
265
+ import { onMounted } from 'vue';
266
+ import 'paymob-widget'; // registers window.PaymobWidget
267
+
268
+ onMounted(() => {
269
+ new window.PaymobWidget({
270
+ publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
271
+ elementId: 'paymob-widget',
272
+ amount: 100000, // 1,000 EGP
273
+ currency: 'EGP',
274
+ customerCanSelect: true,
275
+ onSubmit: (plan) => console.log('Selected plan', plan),
276
+ });
277
+ });
278
+ </script>
279
+
280
+ <template>
281
+ <div id="paymob-widget"></div>
282
+ </template>
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Troubleshooting — "the widget isn't showing"
288
+
289
+ The widget renders **only** when all of these are true:
290
+
291
+ - `amount` is a valid positive number (in cents).
292
+ - The plans request to Paymob succeeds (returns a `2xx` status).
293
+ - At least one installment plan is available for that amount.
294
+
295
+ If any of these fail, the widget stays hidden and the reason is logged to the **browser console** (prefixed with `[PaymobWidget]`):
296
+
297
+ | Symptom | Likely cause |
298
+ | --- | --- |
299
+ | Nothing renders, console shows an `amount` error | `amount` is missing, `0`, negative, or not a number. |
300
+ | Nothing renders, console shows a status code (e.g. `400`) | The request to Paymob failed — check your `publicKey`, `integrationId`, and `currency`. |
301
+ | 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. |
132
302
 
133
- - **Rendering**: The widget renders a Installment Widget into your `elementId` container.
134
- - **Isolation**: UI is rendered inside a Shadow DOM wrapper to reduce CSS conflicts with the host page.
303
+ > **Note:** These errors are for developers only. Your customers never see error messages on the page or in the dialog.
135
304
 
305
+ ---
136
306
 
307
+ ## Good to know
137
308
 
309
+ - **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.)
310
+ - **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).
311
+ - **Region:** the API region is chosen automatically from your public key's country prefix (`egy_`, `are_`, `sau_`, `pak_`, `omn_`).