paymob-widget-alpha 1.0.16 → 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 +216 -60
  2. package/main.js +6423 -76492
  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,108 +1,120 @@
1
- # `paymob-widget-alpha`
2
- Paymob Installments Widget that can be embedded in any web page to display installment plans and (optionally) let the user select a plan.
1
+ # Paymob Installments Widget
3
2
 
4
- ## Installing
3
+ `paymob-widget`
5
4
 
6
- ### CDN
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.
7
6
 
8
- Using jsDelivr:
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)).
10
+
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:
9
33
 
10
34
  ```html
11
- <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>
12
36
  ```
13
37
 
14
- ## Usage
38
+ ### 2. Add a container
15
39
 
16
- Create a container element in your page:
40
+ Place an empty element wherever you want the widget to appear:
17
41
 
18
42
  ```html
19
43
  <div id="paymob-widget"></div>
20
44
  ```
21
45
 
22
- Then instantiate the widget:
46
+ ### 3. Create the widget
23
47
 
24
48
  ```js
25
49
  new PaymobWidget({
26
50
  publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
27
51
  elementId: 'paymob-widget',
28
- amount: 100000, // required — order total in cents (1000 EGP)
52
+ amount: 100000, // order total in cents this is 1,000 EGP
29
53
  currency: 'EGP',
30
- integrationId: 123 || [123456, 2233], // optional — single number or array
31
54
  });
32
55
  ```
33
56
 
34
- If `amount` is missing, invalid, or not a positive finite number, the widget **will not initialize** and an error is logged to the console.
35
-
36
- ### When the widget is shown or hidden
57
+ That's it. If installment plans are available for that amount, the widget renders them.
37
58
 
38
- The widget is **only rendered** when all of the following are true:
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.
39
62
 
40
- - `amount` is valid (positive finite number in **cents**)
41
- - The installment-plans request returns a **2xx** status
42
- - At least one installment plan is available after processing the response
63
+ ---
43
64
 
44
- The widget is **not shown** when:
65
+ ## Options
45
66
 
46
- - `amount` is missing, `≤ 0`, or not a finite number → constructor stops and logs `console.error`
47
- - The API request **fails** (any non-2xx status, e.g. `400`) → widget hidden; `console.error` includes the status code
48
- - The API **succeeds** but returns **no plans** (e.g. invalid integration, amount below minimum, no bank installment configured) widget hidden; `console.error` is logged
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). |
49
77
 
50
- **End users do not see error messages** on the page or in the modal. Errors are for developers in the **browser console** only (`[PaymobWidget] ...`).
78
+ ---
51
79
 
52
- ### Selectable mode example
80
+ ## Letting customers select a plan
53
81
 
54
- 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.
55
83
 
56
84
  ```js
57
85
  new PaymobWidget({
58
86
  publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
59
87
  elementId: 'paymob-widget',
60
- amount: 100000, // required — order total in cents (1000 EGP)
88
+ amount: 100000, // 1,000 EGP
61
89
  currency: 'EGP',
62
- integrationId: 123 || [123456, 2233], // optional — single number or array
63
90
  theme: 'primary',
64
91
  customerCanSelect: true,
65
92
  onSubmit: (plan) => {
66
- // Called when the user clicks **Buy now**.
67
- // With customerCanSelect: true, plan is provided after the user selects a plan.
68
- // plan = { id: 123, tenure: 12, amount: 25000 } // amount = monthly installment in cents
69
- // With 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
70
96
  },
71
97
  });
72
98
  ```
73
99
 
74
- ## Properties
75
-
76
- | Property | Type | Required | Definition |
77
- | --- | --- | --- | --- |
78
- | publicKey | String | Yes | Your public key (used to resolve API base URL and authenticate requests). |
79
- | elementId | String | Yes | ID of the HTML element where the widget will be rendered. |
80
- | theme | `"primary" \| "light" \| "dark"` | No | Widget theme. Default: `"primary"`. |
81
- | amount | Number | Yes | Order total in **cents** (positive finite number). **Required** — no default. The widget will not initialize without it. |
82
- | currency | String | No | Currency code. Default: `"EGP"`. |
83
- | integrationId | Number \| Number[] | No | Paymob integration ID(s). If provided, sent when fetching installment plans. |
84
- | customerCanSelect | Boolean | No | If `true`, the user can select a plan and **Buy now** is enabled. Default: `false`. |
85
- | onSubmit | Function | No | Called when the user clicks **Buy now**. With `customerCanSelect: true`, receives `{ id, tenure, amount }`. With `false`, called with no payload. |
86
-
87
- ### `customerCanSelect` and Learn more
88
-
89
- | Value | Behavior |
90
- | --- | --- |
91
- | `true` | User can **select** a plan. Learn more includes the **Select Installment Plan** step. |
92
- | `false` | Plans are **read-only**. Learn more omits the selection step and uses different copy on the final payment step. |
93
-
94
- ### `onSubmit` payload
100
+ ### Handling the selection
95
101
 
96
- When `customerCanSelect` is `true`:
102
+ When `customerCanSelect` is `true`, `onSubmit` receives the selected plan:
97
103
 
98
104
  ```js
99
105
  {
100
- id: string | number, // installment plan id
106
+ id: string | number, // the installment plan ID
101
107
  tenure: number, // number of months
102
- amount: number // monthly installment amount in cents
108
+ amount: number // monthly installment amount, in cents
103
109
  }
104
110
  ```
105
111
 
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
+
106
118
  ## Full HTML example
107
119
 
108
120
  ```html
@@ -127,14 +139,14 @@ When `customerCanSelect` is `true`:
127
139
  <body>
128
140
  <div id="paymob-widget"></div>
129
141
 
130
- <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>
131
143
 
132
144
  <script>
133
145
  window.onload = () => {
134
146
  new PaymobWidget({
135
147
  publicKey: 'egy_pk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
136
148
  elementId: 'paymob-widget',
137
- amount: 100000, // required — order total in cents (1000 EGP)
149
+ amount: 100000, // 1,000 EGP
138
150
  currency: 'EGP',
139
151
  integrationId: 123,
140
152
  theme: 'primary',
@@ -149,7 +161,151 @@ When `customerCanSelect` is `true`:
149
161
  </html>
150
162
  ```
151
163
 
152
- ## 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. |
302
+
303
+ > **Note:** These errors are for developers only. Your customers never see error messages on the page or in the dialog.
304
+
305
+ ---
306
+
307
+ ## Good to know
153
308
 
154
- - **Rendering**: The widget renders into your `elementId` container only when eligible plans are available (see [When the widget is shown or hidden](#when-the-widget-is-shown-or-hidden)).
155
- - **Isolation**: UI is rendered inside a Shadow DOM wrapper to reduce CSS conflicts with the host page.
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_`).