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.
- package/README.md +229 -55
- package/main.js +6182 -72515
- package/package.json +1 -1
- package/0-interest.39e7455001b508e7.png +0 -0
- package/Delete-icon.3839812aa3996b863c286b42e3fffa3b.svg +0 -21
- package/Inter-Regular.otf +0 -0
- package/Inter-Regular.ttf +0 -0
- package/Inter-Regular.woff +0 -0
- package/Inter-Regular.woff2 +0 -0
- package/Inter.otf +0 -0
- package/Inter.ttf +0 -0
- package/Inter.woff +0 -0
- package/Inter.woff2 +0 -0
- package/arrow.40b47d938ade37f5820c66b81ae8b353.svg +0 -16
- package/assets/.gitkeep +0 -0
- package/assets/fonts/CurrencyFont/Inter.otf +0 -0
- package/assets/fonts/CurrencyFont/Inter.ttf +0 -0
- package/assets/fonts/CurrencyFont/Inter.woff +0 -0
- package/assets/fonts/CurrencyFont/Inter.woff2 +0 -0
- package/assets/icons/0-interest.png +0 -0
- package/assets/icons/Partner logo.png +0 -0
- package/assets/icons/arrow.png +0 -0
- package/assets/icons/bag.png +0 -0
- package/assets/icons/blue-logo.png +0 -0
- package/assets/icons/calender.png +0 -0
- package/assets/icons/card.png +0 -0
- package/assets/icons/close-arrow.png +0 -0
- package/assets/icons/close.png +0 -0
- package/assets/icons/open-arrow.png +0 -0
- package/assets/icons/paymob-logo.png +0 -0
- package/assets/icons/shop.png +0 -0
- package/authorize-icon.27f6de6230a7258b010095f2ecb62fbb.svg +0 -16
- package/bank-installment.e4813d8ad6f5e1f041f321f3c6dcbb2f.svg +0 -16
- package/card.23ed515bba2fc5ea60108513a05e1ba2.svg +0 -21
- package/check.021d9dd3dee18e90e138da39f88ef694.svg +0 -19
- package/checkGreenIcon.2fdf1421207f99ebe329055b1350a193.svg +0 -16
- package/checkIcon.da837e89923bb68f2c34c3b502b7b3f9.svg +0 -16
- package/chevron-down.413212cab44b2a7ef0f299360be6f2b1.svg +0 -16
- package/circle.22e1a0a2a35306fe278668ddabc49974.svg +0 -16
- package/contact.1ec295a5e0bf9727.png +0 -0
- package/copy.72a7096d2e875cbd4d02df03563ec7c7.svg +0 -16
- package/crypto.3caa56ce2aad387e232e31d62800f356.svg +0 -30
- package/email.928bbae47d30af2076391a2591696665.svg +0 -16
- package/emptyImage.7f00e4ccc5fe1c80425a8d28af3c0e2d.svg +0 -21
- package/error.87f0ee7801122d75f04ce506f360c984.svg +0 -16
- package/expiredIcon.32804bbd5021977df40094b1da6703e7.svg +0 -16
- package/kiosk.1bb782704007efe21b5b71e957b6baee.svg +0 -16
- package/loading-loader.6463e608601cd9d5550f67d4c08d513b.svg +0 -43
- package/madfu.4e378b2568497219.png +0 -0
- package/main.css +0 -4755
- package/mid-takseet.6c95c45e88ec6a5c.png +0 -0
- package/paylater.e1d5f1a0f0d9027330f541ded8752a30.svg +0 -16
- package/payment-succesful-1.dd6ee56033bac452.png +0 -0
- package/payment-succesful-2.a503559a0c9d7a70.png +0 -0
- package/paymentDeclined.b4b9f7ee7680300d.png +0 -0
- package/paymentDeclinedIcon.76cc8bf5bd5f0f9739af86a1b8de62ab.svg +0 -16
- package/paymentpending.ae4dace728429396.png +0 -0
- package/pending-icon.21ad7f408b9b33164ddeab11baa012e5.svg +0 -16
- package/phone.c867b950ae5b9bfd86c96f7a0937d482.svg +0 -16
- package/search-icon.2522b2e216184647659a86a07a383ccd.svg +0 -16
- package/styles.css +0 -253
- package/styles.js +0 -6
- package/wallet.c3700990d3a1ebb570cc2c9f37e07ffc.svg +0 -16
package/README.md
CHANGED
|
@@ -1,85 +1,121 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
35
|
+
<script src="https://cdn.jsdelivr.net/npm/paymob-widget@latest/main.js" type="module"></script>
|
|
13
36
|
```
|
|
14
37
|
|
|
15
|
-
|
|
38
|
+
### 2. Add a container
|
|
16
39
|
|
|
17
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
88
|
+
amount: 100000, // 1,000 EGP
|
|
44
89
|
currency: 'EGP',
|
|
45
|
-
integrationId: 123, // optional
|
|
46
90
|
theme: 'primary',
|
|
47
|
-
customerCanSelect: true,
|
|
91
|
+
customerCanSelect: true,
|
|
48
92
|
onSubmit: (plan) => {
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
//
|
|
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
|
-
|
|
100
|
+
### Handling the selection
|
|
58
101
|
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
width: 100%;
|
|
131
|
+
margin: 10% auto 0;
|
|
132
|
+
}
|
|
97
133
|
body {
|
|
98
|
-
|
|
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
|
|
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:
|
|
149
|
+
amount: 100000, // 1,000 EGP
|
|
114
150
|
currency: 'EGP',
|
|
115
|
-
integrationId: 123,
|
|
116
|
-
theme: 'primary',
|
|
117
|
-
customerCanSelect: true,
|
|
151
|
+
integrationId: 123,
|
|
152
|
+
theme: 'primary',
|
|
153
|
+
customerCanSelect: true,
|
|
118
154
|
onSubmit: (plan) => {
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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_`).
|