esaalat 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +245 -2
- package/dist/esaalat.global.js +222 -0
- package/dist/index.cjs +1409 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +401 -0
- package/dist/index.d.ts +401 -0
- package/dist/index.js +1377 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Esaalat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,246 @@
|
|
|
1
|
-
#
|
|
1
|
+
# esaalat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Framework-agnostic browser SDK for the Esaalat hosted checkout. Create a
|
|
4
|
+
checkout, let the buyer pick one of the merchant's wallets and enter their
|
|
5
|
+
reference in a browser window, and receive a verified result — without
|
|
6
|
+
implementing the payment flow yourself.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { EsaalatCheckout } from "esaalat";
|
|
10
|
+
|
|
11
|
+
const checkout = new EsaalatCheckout({
|
|
12
|
+
publishableKey: "pk_live_...", // Dashboard → API Keys
|
|
13
|
+
locale: "ar", // "ar" (default) or "en"
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const result = await checkout.open({
|
|
17
|
+
amount: "5000.00",
|
|
18
|
+
currency: "YER",
|
|
19
|
+
externalOrderId: "order-123",
|
|
20
|
+
onResult: (result) => {
|
|
21
|
+
if (result.status === "VERIFIED") {
|
|
22
|
+
// Update the buyer's UI. Confirm the order server-side before shipping.
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(result.status); // VERIFIED | WRONG_AMOUNT | EXPIRED | CANCELLED | FAILED
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install esaalat
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To work on the SDK from this repository, install the folder instead — from the
|
|
37
|
+
web app in this repository (`fe/esaalat`) that is:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install file:../../sdk/typescript
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Script tag: the package ships a minified IIFE global. Copy it into a directory
|
|
44
|
+
your storefront serves — no CDN account needed:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cp node_modules/esaalat/dist/esaalat.global.js public/ # your app's static dir
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<script src="/esaalat.global.js"></script>
|
|
52
|
+
<script>
|
|
53
|
+
const checkout = new EsaalatCheckout({ publishableKey: "pk_live_..." });
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The package ships ESM, CommonJS, TypeScript declarations, and a minified IIFE
|
|
58
|
+
global. It has no runtime dependencies.
|
|
59
|
+
|
|
60
|
+
## Publishable key
|
|
61
|
+
|
|
62
|
+
`publishable_key` is created with every API key in **Dashboard → API Keys** and
|
|
63
|
+
is safe to embed in a storefront. It can only call
|
|
64
|
+
`POST /api/v1/checkout/sdk/sessions/`; it cannot read wallets, intents, devices,
|
|
65
|
+
or account details. Keep `client_secret` on the server.
|
|
66
|
+
|
|
67
|
+
## Lock the amount server-side
|
|
68
|
+
|
|
69
|
+
The browser can choose the amount when it creates the session. For real orders,
|
|
70
|
+
create the session from your server with the existing client JWT:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
POST /api/v1/checkout/merchant/sessions/
|
|
74
|
+
Authorization: Bearer <access_token>
|
|
75
|
+
Content-Type: application/json
|
|
76
|
+
|
|
77
|
+
{"amount": "5000.00", "external_order_id": "order-123"}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then open the returned token in the browser:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
await checkout.open({
|
|
84
|
+
sessionToken: session.session_token,
|
|
85
|
+
portalUrl: session.portal_url, // optional
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`sessionToken` takes precedence over `amount` / `items`.
|
|
90
|
+
|
|
91
|
+
## Options
|
|
92
|
+
|
|
93
|
+
### `new EsaalatCheckout(options)`
|
|
94
|
+
|
|
95
|
+
| Option | Default | Notes |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `publishableKey` | — | Required unless every `open()` passes a `sessionToken`. Safe to expose. |
|
|
98
|
+
| `locale` | `"ar"` | Portal language (`"ar"` / `"en"`). |
|
|
99
|
+
| `apiBaseUrl` | `https://api.esaalat.com/api/v1` | Override for sandbox/local. |
|
|
100
|
+
| `portalBaseUrl` | `https://pay.esaalat.com` | Fallback only; the API's `portal_url` wins. |
|
|
101
|
+
| `presentation` | `"window"` | `"window"` opens a browser window; `"modal"` embeds it in an iframe. |
|
|
102
|
+
| `zIndex` | `2147483000` | Modal stacking order (modal only). |
|
|
103
|
+
| `onResult` | — | Default result handler; `open({ onResult })` overrides. |
|
|
104
|
+
| `onError` | — | Default session/network error handler. |
|
|
105
|
+
| `fetch` | global `fetch` | Custom transport (tests, proxies). |
|
|
106
|
+
|
|
107
|
+
### `open(options)`
|
|
108
|
+
|
|
109
|
+
| Option | Notes |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `amount` | Order total. Required unless `items` or `sessionToken` is used. |
|
|
112
|
+
| `items` | `[{ name, description?, price, quantity? }]`; determines the amount. |
|
|
113
|
+
| `currency` | Defaults to `YER` server-side. |
|
|
114
|
+
| `externalOrderId` | Your order reference, echoed back in the result. |
|
|
115
|
+
| `notes` | Internal merchant notes. |
|
|
116
|
+
| `wallet` | Optional preselected receiving wallet id. |
|
|
117
|
+
| `sessionToken` | Open a server-created session instead of creating one. |
|
|
118
|
+
| `portalUrl` | Portal URL for a server-created session (optional). |
|
|
119
|
+
| `idempotencyKey` | Stable retry key; generated per call when omitted. |
|
|
120
|
+
| `locale` | Overrides the client locale for this checkout. |
|
|
121
|
+
| `onResult` / `onError` | Per-checkout callbacks. |
|
|
122
|
+
| `onBeforeCreateOrder` | `(order) => order \| void`. Runs before the session is created; return the body to change it or add fields, throw to cancel. |
|
|
123
|
+
| `onOrderCreated` | `(session) => void`. Runs with the new session, before the portal loads. Not called with `sessionToken`. |
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
await checkout.open({
|
|
127
|
+
amount: "5000.00",
|
|
128
|
+
onBeforeCreateOrder: (order) => ({ ...order, external_order_id: cart.id }),
|
|
129
|
+
onOrderCreated: (session) => console.log(session.session_token),
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`open()` rejects with `EsaalatCheckoutError` when the session cannot be created
|
|
134
|
+
or reached. Check `.code` (`configuration`, `network`, `api`, `session`,
|
|
135
|
+
`unavailable`) and `.status` for HTTP failures.
|
|
136
|
+
|
|
137
|
+
## Result statuses
|
|
138
|
+
|
|
139
|
+
| Status | Meaning |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `VERIFIED` | A wallet alert matched the order and the amount was verified. |
|
|
142
|
+
| `WRONG_AMOUNT` | A payment matched but the amount differs; see `paidAmount`. |
|
|
143
|
+
| `EXPIRED` | The buyer did not finish in time. |
|
|
144
|
+
| `CANCELLED` | The buyer dismissed the checkout, or the intent was cancelled. |
|
|
145
|
+
| `FAILED` | The portal could not load a valid session. |
|
|
146
|
+
|
|
147
|
+
`reason` can add `dismissed`, `intent_cancelled`, `session_expired`,
|
|
148
|
+
`session_not_found`, or `destroyed`.
|
|
149
|
+
|
|
150
|
+
**The result is not a licence to fulfil.** Confirm the order with the signed
|
|
151
|
+
`payment.verified` webhook or `GET /api/v1/merchant/intents/{id}/` on your
|
|
152
|
+
server first.
|
|
153
|
+
|
|
154
|
+
## Lifecycle
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
checkout.isOpen; // boolean
|
|
158
|
+
checkout.close(); // dismiss; an in-flight open() resolves CANCELLED
|
|
159
|
+
checkout.destroy(); // close and remove listeners/timers
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
A second `open()` while a checkout is visible rejects with a configuration
|
|
163
|
+
error.
|
|
164
|
+
|
|
165
|
+
## Window or modal
|
|
166
|
+
|
|
167
|
+
The checkout opens in its own browser window by default. Keep `open()` in the
|
|
168
|
+
buyer's click handler: the window is requested synchronously, before the
|
|
169
|
+
session API call, or popup blockers discard it. If the browser blocks it, the
|
|
170
|
+
SDK falls back to the modal, so the buyer can still pay.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const checkout = new EsaalatCheckout({
|
|
174
|
+
publishableKey: "pk_live_...",
|
|
175
|
+
presentation: "modal", // opt into the in-page iframe instead
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Drop-in button
|
|
180
|
+
|
|
181
|
+
The package also defines `<esaalat-checkout-button>` — one control for plain
|
|
182
|
+
HTML, Vue and Angular. `esaalat-react` wraps it for React with the same props
|
|
183
|
+
and callbacks.
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<esaalat-checkout-button
|
|
187
|
+
publishable-key="pk_live_..."
|
|
188
|
+
amount="5000.00"
|
|
189
|
+
external-order-id="order-123"
|
|
190
|
+
></esaalat-checkout-button>
|
|
191
|
+
|
|
192
|
+
<script>
|
|
193
|
+
const button = document.querySelector("esaalat-checkout-button");
|
|
194
|
+
button.onVerified = (result) => showSuccess(result.intentCode);
|
|
195
|
+
button.onCancelled = () => showCancelled();
|
|
196
|
+
</script>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
One callback runs per outcome (`onVerified`, `onCancelled`, `onWrongAmount`,
|
|
200
|
+
`onExpired`, `onFailed`), and each also reaches the element as a `CustomEvent`
|
|
201
|
+
(`esaalat-verified`, `esaalat-cancelled`, …) whose `detail` is the result.
|
|
202
|
+
`onError` receives a rejected `open()`. `label`, `locale`, `presentation`,
|
|
203
|
+
`sessionToken` and the rest of `open()`'s options are attributes too.
|
|
204
|
+
|
|
205
|
+
Importing the module does not define the tag — the `<script>` build does. Call
|
|
206
|
+
it once yourself when bundling:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { registerEsaalatCheckoutButton } from "esaalat";
|
|
210
|
+
registerEsaalatCheckoutButton();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Theme the button from your stylesheet with `--esaalat-button-background`,
|
|
214
|
+
`--esaalat-button-color`, `--esaalat-button-radius`, `--esaalat-button-padding`,
|
|
215
|
+
`--esaalat-button-font-size` and `--esaalat-button-border`.
|
|
216
|
+
|
|
217
|
+
## CSP
|
|
218
|
+
|
|
219
|
+
If your site uses a Content-Security-Policy, allow the SDK's two destinations:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
script-src 'self';
|
|
223
|
+
connect-src https://api.esaalat.com;
|
|
224
|
+
frame-src https://pay.esaalat.com;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`frame-src` is only needed with `presentation: "modal"` (or as the fallback).
|
|
228
|
+
The modal itself lives in a shadow root; no merchant CSS is injected into the
|
|
229
|
+
checkout.
|
|
230
|
+
|
|
231
|
+
## Development
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npm install --legacy-peer-deps
|
|
235
|
+
npm run typecheck
|
|
236
|
+
npm test
|
|
237
|
+
npm run build
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`npm run build` emits `dist/index.js`, `dist/index.cjs`, `dist/index.d.ts`, and
|
|
241
|
+
`dist/esaalat.global.js`. `npm publish` builds first (`prepack`), so a
|
|
242
|
+
stale `dist/` can never be published.
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT © Esaalat
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";(()=>{var n=class extends Error{constructor(t,e,r={}){super(t),this.name="EsaalatCheckoutError",this.code=e,this.status=r.status,this.details=r.details,r.cause!==void 0&&(this.cause=r.cause)}};var M=`
|
|
2
|
+
:host { all: initial; }
|
|
3
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
4
|
+
.overlay {
|
|
5
|
+
position: fixed;
|
|
6
|
+
inset: 0;
|
|
7
|
+
z-index: 0;
|
|
8
|
+
display: grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
padding: 12px;
|
|
11
|
+
background: rgba(12, 11, 8, 0.58);
|
|
12
|
+
-webkit-backdrop-filter: blur(7px);
|
|
13
|
+
backdrop-filter: blur(7px);
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
15
|
+
color: #0b0b0b;
|
|
16
|
+
pointer-events: auto;
|
|
17
|
+
animation: fade 180ms ease-out;
|
|
18
|
+
}
|
|
19
|
+
.dialog {
|
|
20
|
+
position: relative;
|
|
21
|
+
width: min(460px, calc(100vw - 24px));
|
|
22
|
+
height: min(720px, calc(100dvh - 24px));
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
border-radius: 22px;
|
|
25
|
+
background: #fffefa;
|
|
26
|
+
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
27
|
+
box-shadow: 0 36px 100px rgba(0, 0, 0, 0.4), 0 6px 24px rgba(0, 0, 0, 0.2);
|
|
28
|
+
animation: rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
29
|
+
}
|
|
30
|
+
.dialog.is-frame { background: #f3f0e8; }
|
|
31
|
+
.chrome {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 10px;
|
|
35
|
+
padding: 14px 16px;
|
|
36
|
+
border-bottom: 1px solid #e7e2d7;
|
|
37
|
+
background: #fffefa;
|
|
38
|
+
}
|
|
39
|
+
.dialog.is-frame .chrome { display: none; }
|
|
40
|
+
.mark {
|
|
41
|
+
width: 28px;
|
|
42
|
+
height: 28px;
|
|
43
|
+
border-radius: 8px;
|
|
44
|
+
display: grid;
|
|
45
|
+
place-items: center;
|
|
46
|
+
background: #0b0b0b;
|
|
47
|
+
color: #fff;
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
font-weight: 800;
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
}
|
|
52
|
+
.chrome-title {
|
|
53
|
+
font-size: 13px;
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
line-height: 1.3;
|
|
56
|
+
flex: 1;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
text-overflow: ellipsis;
|
|
60
|
+
white-space: nowrap;
|
|
61
|
+
}
|
|
62
|
+
.close {
|
|
63
|
+
width: 32px;
|
|
64
|
+
height: 32px;
|
|
65
|
+
display: grid;
|
|
66
|
+
place-items: center;
|
|
67
|
+
border: 0;
|
|
68
|
+
border-radius: 9px;
|
|
69
|
+
background: transparent;
|
|
70
|
+
color: #716f67;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
flex-shrink: 0;
|
|
73
|
+
}
|
|
74
|
+
.close:hover { background: #f3f0e8; color: #0b0b0b; }
|
|
75
|
+
.close:focus-visible { outline: 2px solid #0b0b0b; outline-offset: 2px; }
|
|
76
|
+
.close svg { width: 18px; height: 18px; }
|
|
77
|
+
.content {
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 57px 0 0;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
}
|
|
82
|
+
.dialog.is-frame .content { inset: 0; }
|
|
83
|
+
.frame {
|
|
84
|
+
display: block;
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 100%;
|
|
87
|
+
border: 0;
|
|
88
|
+
background: #f3f0e8;
|
|
89
|
+
}
|
|
90
|
+
.panel {
|
|
91
|
+
height: 100%;
|
|
92
|
+
display: grid;
|
|
93
|
+
place-content: center;
|
|
94
|
+
justify-items: center;
|
|
95
|
+
gap: 12px;
|
|
96
|
+
padding: 32px;
|
|
97
|
+
text-align: center;
|
|
98
|
+
}
|
|
99
|
+
.spinner {
|
|
100
|
+
width: 34px;
|
|
101
|
+
height: 34px;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
border: 2px solid #e7e2d7;
|
|
104
|
+
border-top-color: #0b0b0b;
|
|
105
|
+
animation: spin 900ms linear infinite;
|
|
106
|
+
}
|
|
107
|
+
.loading { font-size: 13px; color: #716f67; }
|
|
108
|
+
.error-icon {
|
|
109
|
+
width: 54px;
|
|
110
|
+
height: 54px;
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
display: grid;
|
|
113
|
+
place-items: center;
|
|
114
|
+
background: #fef1ef;
|
|
115
|
+
border: 1px solid #f5cfca;
|
|
116
|
+
color: #b42318;
|
|
117
|
+
}
|
|
118
|
+
.error-icon svg { width: 25px; height: 25px; }
|
|
119
|
+
.error-title { margin: 0; font-size: 17px; line-height: 1.4; }
|
|
120
|
+
.error-message { margin: 0; font-size: 13px; line-height: 1.7; color: #716f67; max-width: 34ch; }
|
|
121
|
+
.error-close {
|
|
122
|
+
margin-top: 6px;
|
|
123
|
+
min-height: 42px;
|
|
124
|
+
padding: 9px 22px;
|
|
125
|
+
border: 1px solid #e7e2d7;
|
|
126
|
+
border-radius: 11px;
|
|
127
|
+
background: transparent;
|
|
128
|
+
color: #0b0b0b;
|
|
129
|
+
font: inherit;
|
|
130
|
+
font-size: 13px;
|
|
131
|
+
font-weight: 700;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
.error-close:hover { background: #f3f0e8; }
|
|
135
|
+
.error-close:focus-visible { outline: 2px solid #0b0b0b; outline-offset: 2px; }
|
|
136
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
137
|
+
@keyframes fade { from { opacity: 0; } }
|
|
138
|
+
@keyframes rise { from { opacity: 0; transform: translateY(10px) scale(0.985); } }
|
|
139
|
+
@media (max-width: 520px) {
|
|
140
|
+
.overlay { padding: 0; }
|
|
141
|
+
.dialog {
|
|
142
|
+
width: 100vw;
|
|
143
|
+
height: 100dvh;
|
|
144
|
+
max-height: none;
|
|
145
|
+
border-radius: 0;
|
|
146
|
+
border: 0;
|
|
147
|
+
}
|
|
148
|
+
.chrome { padding-top: calc(14px + env(safe-area-inset-top)); }
|
|
149
|
+
}
|
|
150
|
+
@media (prefers-color-scheme: dark) {
|
|
151
|
+
.overlay { background: rgba(0, 0, 0, 0.7); }
|
|
152
|
+
.dialog, .chrome { background: #1d1b17; border-color: #343028; color: #f6f2e8; }
|
|
153
|
+
.dialog.is-frame, .frame { background: #141310; }
|
|
154
|
+
.chrome-title { color: #f6f2e8; }
|
|
155
|
+
.close { color: #aaa59a; }
|
|
156
|
+
.close:hover { background: #2b2721; color: #f6f2e8; }
|
|
157
|
+
.spinner { border-color: #343028; border-top-color: #f6f2e8; }
|
|
158
|
+
.loading, .error-message { color: #aaa59a; }
|
|
159
|
+
.error-icon { background: #2b1b19; border-color: #4b2c28; color: #f18d83; }
|
|
160
|
+
.error-close { border-color: #343028; color: #f6f2e8; }
|
|
161
|
+
.error-close:hover { background: #2b2721; }
|
|
162
|
+
}
|
|
163
|
+
@media (prefers-reduced-motion: reduce) {
|
|
164
|
+
.overlay, .dialog { animation: none; }
|
|
165
|
+
.spinner { animation-duration: 0.01ms; }
|
|
166
|
+
}
|
|
167
|
+
`;function c(i,t){let e=document.createElement(i);return t&&(e.className=t),e}function _(){let i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("viewBox","0 0 24 24"),i.setAttribute("fill","none"),i.setAttribute("stroke","currentColor"),i.setAttribute("stroke-width","1.8"),i.setAttribute("stroke-linecap","round"),i.setAttribute("aria-hidden","true");let t=document.createElementNS("http://www.w3.org/2000/svg","path");return t.setAttribute("d","M18 6 6 18M6 6l12 12"),i.append(t),i}function D(){let i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.setAttribute("viewBox","0 0 24 24"),i.setAttribute("fill","none"),i.setAttribute("stroke","currentColor"),i.setAttribute("stroke-width","1.8"),i.setAttribute("stroke-linecap","round"),i.setAttribute("stroke-linejoin","round"),i.setAttribute("aria-hidden","true");let t=document.createElementNS("http://www.w3.org/2000/svg","path");return t.setAttribute("d","M12 9v4M12 17h.01M10.3 3.9 2.5 17.4A2 2 0 0 0 4.2 20h15.6a2 2 0 0 0 1.7-2.6L13.7 3.9a2 2 0 0 0-3.4 0Z"),i.append(t),i}var y=class{constructor(t){this.options=t;this.frame=null;this.previousOverflow="";this.previousFocus=null;this.destroyed=!1;this.onLoad=null;this.handleKeyDown=t=>{t.key==="Escape"&&(t.preventDefault(),this.onDismiss())};this.onDismiss=t.onDismiss,this.host=document.createElement("div"),this.host.setAttribute("data-esaalat-checkout",""),this.host.style.cssText=`all: initial; position: fixed; inset: 0; z-index: ${t.zIndex}; pointer-events: none;`,this.shadow=this.host.attachShadow({mode:"open"});let e=c("style");e.textContent=M,this.shadow.append(e),this.overlay=c("div","overlay"),this.overlay.addEventListener("mousedown",d=>{d.target===this.overlay&&this.onDismiss()}),this.dialog=c("div","dialog"),this.dialog.setAttribute("role","dialog"),this.dialog.setAttribute("aria-modal","true"),this.dialog.setAttribute("aria-label",t.title);let r=c("div","chrome"),o=c("span","mark");o.textContent="E",o.setAttribute("aria-hidden","true");let a=c("span","chrome-title");a.textContent=t.title,this.closeButton=c("button","close"),this.closeButton.type="button",this.closeButton.setAttribute("aria-label",t.labels.close),this.closeButton.append(_()),this.closeButton.addEventListener("click",()=>this.onDismiss()),r.append(o,a,this.closeButton),this.content=c("div","content"),this.showLoading(),this.dialog.append(r,this.content),this.overlay.append(this.dialog),this.shadow.append(this.overlay)}open(){if(!document.body)throw new Error("Esaalat checkout needs a document body.");this.previousOverflow=document.body.style.overflow,this.previousFocus=document.activeElement,document.body.style.overflow="hidden",document.body.append(this.host),document.addEventListener("keydown",this.handleKeyDown,!0),this.closeButton.focus({preventScroll:!0})}load(t){if(this.destroyed)return;this.dialog.classList.add("is-frame"),this.content.replaceChildren();let e=document.createElement("iframe");e.className="frame",e.src=t,e.title=this.options.title,e.setAttribute("allow","clipboard-write"),e.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),e.setAttribute("loading","eager"),e.addEventListener("load",()=>{this.onLoad?.();try{e.focus({preventScroll:!0})}catch{}}),this.frame=e,this.content.append(e)}get contentWindow(){return this.frame?.contentWindow??null}showError(t){if(this.destroyed)return;this.content.replaceChildren();let e=c("div","panel"),r=c("span","error-icon");r.append(D());let o=c("h2","error-title");o.textContent=this.options.labels.errorTitle;let a=c("p","error-message");a.textContent=t;let d=c("button","error-close");d.type="button",d.textContent=this.options.labels.close,d.addEventListener("click",()=>this.onDismiss()),e.append(r,o,a,d),this.content.append(e),this.dialog.classList.remove("is-frame"),d.focus({preventScroll:!0})}showLoading(){this.content.replaceChildren();let t=c("div","panel"),e=c("span","spinner");e.setAttribute("aria-hidden","true");let r=c("p","loading");r.textContent=this.options.labels.loading,t.append(e,r),this.content.append(t)}destroy(){if(!this.destroyed){if(this.destroyed=!0,document.removeEventListener("keydown",this.handleKeyDown,!0),document.body.style.overflow=this.previousOverflow,this.host.remove(),this.previousFocus instanceof HTMLElement&&this.previousFocus.isConnected)try{this.previousFocus.focus({preventScroll:!0})}catch{}this.onLoad=null,this.frame=null}}};var B="esaalat-checkout";var U=`
|
|
168
|
+
* { box-sizing: border-box; }
|
|
169
|
+
html, body { height: 100%; }
|
|
170
|
+
body {
|
|
171
|
+
margin: 0;
|
|
172
|
+
display: grid;
|
|
173
|
+
place-items: center;
|
|
174
|
+
padding: 24px;
|
|
175
|
+
background: #f3f0e8;
|
|
176
|
+
color: #0b0b0b;
|
|
177
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
178
|
+
-webkit-font-smoothing: antialiased;
|
|
179
|
+
}
|
|
180
|
+
.panel {
|
|
181
|
+
display: grid;
|
|
182
|
+
justify-items: center;
|
|
183
|
+
gap: 14px;
|
|
184
|
+
max-width: 34ch;
|
|
185
|
+
text-align: center;
|
|
186
|
+
}
|
|
187
|
+
.error-icon {
|
|
188
|
+
width: 54px;
|
|
189
|
+
height: 54px;
|
|
190
|
+
border-radius: 50%;
|
|
191
|
+
display: grid;
|
|
192
|
+
place-items: center;
|
|
193
|
+
background: #fef1ef;
|
|
194
|
+
border: 1px solid #f5cfca;
|
|
195
|
+
color: #b42318;
|
|
196
|
+
font-size: 24px;
|
|
197
|
+
font-weight: 800;
|
|
198
|
+
}
|
|
199
|
+
.error-title { margin: 0; font-size: 17px; line-height: 1.4; }
|
|
200
|
+
.error-message { margin: 0; font-size: 13px; line-height: 1.7; color: #716f67; }
|
|
201
|
+
.error-close {
|
|
202
|
+
margin-top: 4px;
|
|
203
|
+
min-height: 42px;
|
|
204
|
+
padding: 9px 22px;
|
|
205
|
+
border: 1px solid #ded7c8;
|
|
206
|
+
border-radius: 11px;
|
|
207
|
+
background: transparent;
|
|
208
|
+
color: #0b0b0b;
|
|
209
|
+
font: inherit;
|
|
210
|
+
font-size: 13px;
|
|
211
|
+
font-weight: 700;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
}
|
|
214
|
+
.error-close:hover { background: #e9e4d8; }
|
|
215
|
+
@media (prefers-color-scheme: dark) {
|
|
216
|
+
body { background: #141310; color: #f6f2e8; }
|
|
217
|
+
.error-message { color: #aaa59a; }
|
|
218
|
+
.error-icon { background: #2b1b19; border-color: #4b2c28; color: #f18d83; }
|
|
219
|
+
.error-close { border-color: #343028; color: #f6f2e8; }
|
|
220
|
+
.error-close:hover { background: #2b2721; }
|
|
221
|
+
}
|
|
222
|
+
`;function k(i){return i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function N(){let i=window.screen?.availWidth??480,t=window.screen?.availHeight??800,e=Math.min(480,Math.max(320,i-40)),r=Math.min(800,Math.max(480,t-40)),o=typeof window.screenX=="number"?window.screenX:0,a=typeof window.screenY=="number"?window.screenY:0,d=window.outerWidth||e,m=window.outerHeight||r,h=Math.max(0,Math.round(o+(d-e)/2)),p=Math.max(0,Math.round(a+(m-r)/2));return["popup=yes",`width=${e}`,`height=${r}`,`left=${h}`,`top=${p}`,"resizable=yes","scrollbars=yes"].join(",")}function P(i,t,e,r){return["<!doctype html>",`<html dir="${t}" lang="${e}">`,"<head>",'<meta charset="utf-8" />','<meta name="viewport" content="width=device-width, initial-scale=1" />','<meta name="robots" content="noindex" />',`<title>${k(i)}</title>`,`<style>${U}</style>`,"</head>",`<body>${r}</body>`,"</html>"].join("")}var C=class{constructor(t){this.pollId=null;this.navigated=!1;this.destroyed=!1;this.onLoad=null;let e=window.open("",B,N());if(!e)throw new Error("The browser blocked the checkout window.");this.win=e,this.onDismiss=t.onDismiss,this.title=t.title,this.labels=t.labels,this.dir=t.dir,this.lang=t.lang,this.pollId=window.setInterval(()=>this.checkClosed(),400);try{this.win.focus()}catch{}}get contentWindow(){return this.win}open(){try{this.win.focus()}catch{}}load(t){if(!this.destroyed){this.navigated=!0;try{this.win.location.replace(t)}catch{if(this.win.closed){this.dismiss();return}try{this.win.location.href=t}catch{}}this.onLoad?.()}}showError(t){if(!(this.destroyed||this.navigated)){this.renderError(P(this.title,this.dir,this.lang,`<div class="panel"><span class="error-icon" aria-hidden="true">!</span><h2 class="error-title">${k(this.labels.errorTitle)}</h2><p class="error-message">${k(t)}</p><button class="error-close" type="button">${k(this.labels.close)}</button></div>`));try{this.win.document.querySelector(".error-close")?.addEventListener("click",()=>this.onDismiss())}catch{}}}destroy(){if(!this.destroyed){this.destroyed=!0,this.stopPolling(),this.onLoad=null;try{this.win.close()}catch{}}}renderError(t){try{let e=this.win.document;e.open(),e.write(t),e.close()}catch{}}checkClosed(){if(this.destroyed){this.stopPolling();return}this.win.closed&&this.dismiss()}dismiss(){this.destroyed||(this.stopPolling(),this.onDismiss())}stopPolling(){this.pollId!==null&&(window.clearInterval(this.pollId),this.pollId=null)}};var W="https://api.esaalat.com/api/v1",H="https://pay.esaalat.com",z="0.1.0",K=2e4,V=300,F=100,j=1800,$=6e3,G=["VERIFIED","WRONG_AMOUNT","EXPIRED","CANCELLED","FAILED"],Y=["dismissed","destroyed","intent_cancelled","session_expired","session_not_found","network_error"];function T(i,t){let e=i.trim()||t,r;try{let o=typeof window<"u"?window.location?.href:void 0;r=new URL(e,o)}catch{throw new n(`Invalid URL: ${e}`,"configuration")}if(r.protocol!=="http:"&&r.protocol!=="https:")throw new n(`Unsupported URL protocol: ${r.protocol}`,"configuration");return r.toString().replace(/\/+$/,"")}function q(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{let t=Math.random()*16|0;return(i==="x"?t:t&3|8).toString(16)})}function x(i,t=""){return typeof i=="string"?i:t}function O(i){return typeof i=="string"?i:null}function X(i){if(!i||typeof i!="object")return null;let t=i;if(typeof t.detail=="string")return t.detail;if(Array.isArray(t.errors)){for(let e of t.errors)if(e&&typeof e=="object"){let r=e.detail;if(typeof r=="string")return r}}for(let e of Object.values(t)){if(typeof e=="string")return e;if(Array.isArray(e)&&typeof e[0]=="string")return e[0]}return null}function J(i){if(!i||typeof i!="object")return!1;let t=i;return typeof t.session_token=="string"&&typeof t.portal_url=="string"&&typeof t.amount=="string"&&typeof t.currency=="string"}function A(i,t){return{status:"CANCELLED",reason:t,sessionToken:i??"",intentCode:null,amount:"0.00",paidAmount:null,currency:"YER",externalOrderId:"",merchantName:""}}var g=class{constructor(t){this.active=null;this.view=null;this.portalOrigin=null;this.sessionToken=null;this.initTimer=null;this.verifiedTimer=null;this.errorTimer=null;this.handleMessage=t=>{if(!this.view||!this.portalOrigin||t.source!==this.view.contentWindow||t.origin!==this.portalOrigin)return;let e=t.data;if(!(!e||typeof e!="object")&&e.source==="esaalat-checkout"&&e.sessionToken===this.sessionToken){if(e.type==="ready"){this.clearInitTimer();return}if(e.type==="close"){this.dismiss();return}if(e.type==="result"){let r=this.parseResult(e.payload);r&&this.finish(r)}}};let e=typeof t?.publishableKey=="string"?t.publishableKey.trim():"";this.publishableKey=e,this.locale=t.locale==="en"||t.locale==="ar"?t.locale:"ar",this.apiBaseUrl=T(t.apiBaseUrl??"",W),this.portalBaseUrl=T(t.portalBaseUrl??"",H),this.zIndex=typeof t.zIndex=="number"&&Number.isFinite(t.zIndex)?t.zIndex:2147483e3,this.presentation=t.presentation==="modal"?"modal":"window";let r=t.fetch,o=typeof fetch=="function"?fetch.bind(globalThis):null;this.fetchImpl=r??o,this.defaultOnResult=t.onResult,this.defaultOnError=t.onError}get isOpen(){return this.view!==null}async open(t={}){if(this.active||this.view)throw new n("A checkout is already open. Close it before starting another.","configuration");if(!this.fetchImpl)throw new n("This browser does not support fetch. Use the SDK on a modern browser.","configuration");let e=t.locale==="en"||t.locale==="ar"?t.locale:this.locale,r=t.items??[],o=t.sessionToken?.trim()||null,a=t.amount!==void 0&&t.amount!==null&&String(t.amount).trim()!=="";if(!o&&!this.publishableKey)throw new n("A publishable key is required to create a session. Pass one, or open a server-created `sessionToken`.","configuration");if(!o&&!a&&r.length===0)throw new n("Provide an amount, items, or a server-created session token.","configuration");if(!o&&r.length>0){for(let l of r)if(!l.name||l.price===void 0||l.price===null)throw new n("Every item needs a name and a price.","configuration")}let d=e==="ar"?{close:"\u0625\u063A\u0644\u0627\u0642",loading:"\u062C\u0627\u0631\u064D \u062A\u062C\u0647\u064A\u0632 \u0627\u0644\u062F\u0641\u0639 \u0627\u0644\u0622\u0645\u0646\u2026",errorTitle:"\u062A\u0639\u0630\u0651\u0631 \u0628\u062F\u0621 \u0627\u0644\u062F\u0641\u0639"}:{close:"Close",loading:"Preparing secure checkout\u2026",errorTitle:"Couldn't start the checkout"},m=t.presentation==="window"||t.presentation==="modal"?t.presentation:this.presentation,h;try{h=this.createView(m,d,e),h.open()}catch(l){throw new n("The checkout could not be opened.","unavailable",{cause:l})}let p=new AbortController,s,u,b=new Promise((l,v)=>{s=l,u=v}),I=t.onResult??this.defaultOnResult,R=t.onError??this.defaultOnError,f={token:null,locale:e,controller:p,view:h,settled:!1,resolve:s,reject:u,onResult:I};this.active=f,this.view=h,this.sessionToken=null,this.portalOrigin=null,h.onLoad=()=>this.startInitRetry();let w;try{if(o)f.token=o,this.sessionToken=o,w=this.buildPortalUrl(o,t.portalUrl??"",e);else{let l=await this.createSession(t,e,p.signal);if(f.settled)return b;f.token=l.session_token,this.sessionToken=l.session_token,w=this.buildPortalUrl(l.session_token,l.portal_url,e)}this.portalOrigin=new URL(w).origin}catch(l){if(f.settled)return b;let v=l instanceof n?l:new n("Esaalat returned an invalid checkout URL.","session",{cause:l});f.settled=!0,this.active=null,this.removeMessageListener();try{R?.(v)}catch{}return f.reject(v),h.showError(v.message),this.errorTimer=window.setTimeout(()=>{this.view===h&&this.teardown()},$),b}return window.addEventListener("message",this.handleMessage),h.load(w),b}close(){if(this.active){this.dismiss();return}this.teardown()}destroy(){if(this.active&&!this.active.settled){this.finish(A(this.sessionToken,"destroyed"));return}this.teardown()}createView(t,e,r){let o=r==="ar"?"\u0625\u064A\u0635\u0627\u0644\u0627\u062A \u2014 \u062F\u0641\u0639 \u0622\u0645\u0646":"Esaalat \u2014 Secure checkout",a=()=>this.dismiss();if(t==="window")try{return new C({title:o,labels:e,dir:r==="ar"?"rtl":"ltr",lang:r,onDismiss:a})}catch{}return new y({zIndex:this.zIndex,title:o,labels:e,onDismiss:a})}buildPortalUrl(t,e,r){let o=e.trim()||`${this.portalBaseUrl}/checkout/${encodeURIComponent(t)}`,a=new URL(o);if(a.protocol!=="http:"&&a.protocol!=="https:")throw new Error("Unsupported portal protocol");return a.searchParams.set("lang",r),a.toString()}async createSession(t,e,r){if(!this.fetchImpl)throw new n("fetch is unavailable","configuration");let o={};t.amount!==void 0&&t.amount!==null&&(o.amount=String(t.amount)),t.items?.length&&(o.items=t.items.map(s=>({name:s.name,...s.description?{description:s.description}:{},price:String(s.price),quantity:s.quantity??1}))),t.currency&&(o.currency=t.currency),t.externalOrderId&&(o.external_order_id=t.externalOrderId),t.notes&&(o.notes=t.notes),t.wallet&&(o.wallet=t.wallet);let a=t.onBeforeCreateOrder;if(a){let s;try{s=await a(o)}catch(u){throw new n(u instanceof Error&&u.message?u.message:e==="ar"?"\u0623\u064F\u0644\u063A\u064A \u0627\u0644\u062F\u0641\u0639.":"The checkout was cancelled.","configuration",{cause:u})}s&&typeof s=="object"&&(o=s)}let d=new AbortController,m=!1,h=window.setTimeout(()=>{m=!0,d.abort()},K),p=()=>d.abort();r.addEventListener("abort",p);try{let s=await this.fetchImpl(`${this.apiBaseUrl}/checkout/sdk/sessions/`,{method:"POST",credentials:"omit",cache:"no-store",headers:{Accept:"application/json","Content-Type":"application/json","ESAALAT-PUBLISHABLE-KEY":this.publishableKey,"ESAALAT-IDEMPOTENCY-KEY":t.idempotencyKey?.trim()||q(),"ESAALAT-SDK-VERSION":z},body:JSON.stringify(o),signal:d.signal}),u=await s.json().catch(()=>null);if(!s.ok)throw new n(X(u)||(e==="ar"?"\u062A\u0639\u0630\u0651\u0631 \u0625\u0646\u0634\u0627\u0621 \u062C\u0644\u0633\u0629 \u0627\u0644\u062F\u0641\u0639.":"Couldn't create the checkout session."),"api",{status:s.status,details:u});if(!J(u))throw new n("Esaalat returned an invalid checkout session.","session",{details:u});try{t.onOrderCreated?.(u)}catch{}return u}catch(s){throw s instanceof n?s:m?new n(e==="ar"?"\u0627\u0646\u062A\u0647\u062A \u0645\u0647\u0644\u0629 \u0627\u0644\u0627\u062A\u0635\u0627\u0644 \u0628\u0625\u064A\u0635\u0627\u0644\u0627\u062A. \u062D\u0627\u0648\u0644 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.":"The connection to Esaalat timed out. Please try again.","network",{cause:s}):s instanceof DOMException&&s.name==="AbortError"?s:new n(e==="ar"?"\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u0627\u062A\u0635\u0627\u0644 \u0628\u0625\u064A\u0635\u0627\u0644\u0627\u062A. \u062A\u062D\u0642\u0642 \u0645\u0646 \u0627\u062A\u0635\u0627\u0644\u0643 \u0648\u062D\u0627\u0648\u0644 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.":"Couldn't reach Esaalat. Check your connection and try again.","network",{cause:s})}finally{window.clearTimeout(h),r.removeEventListener("abort",p)}}startInitRetry(){this.clearInitTimer();let t=0,e=()=>{!this.view||!this.portalOrigin||!this.sessionToken||(this.postInit(),t+=1,t<F?this.initTimer=window.setTimeout(e,V):this.initTimer=null)};e()}postInit(){let t=this.view?.contentWindow;if(!t||!this.portalOrigin||!this.sessionToken)return;let e={source:"esaalat-checkout-sdk",version:1,type:"init",sessionToken:this.sessionToken,parentOrigin:window.location.origin,locale:this.active?.locale??this.locale};try{t.postMessage(e,this.portalOrigin)}catch{}}clearInitTimer(){this.initTimer!==null&&(window.clearTimeout(this.initTimer),this.initTimer=null)}parseResult(t){if(!t||typeof t!="object")return null;let e=t;if(typeof e.status!="string"||!G.includes(e.status))return null;let r=typeof e.reason=="string"&&Y.includes(e.reason)?e.reason:void 0;return{status:e.status,...r?{reason:r}:{},sessionToken:this.sessionToken??"",intentCode:O(e.intentCode),amount:x(e.amount,"0.00"),paidAmount:O(e.paidAmount),currency:x(e.currency,"YER"),externalOrderId:x(e.externalOrderId),merchantName:x(e.merchantName)}}finish(t){let e=this.active;if(!(!e||e.settled)){e.settled=!0,this.active=null,this.clearInitTimer(),e.controller.abort();try{e.onResult?.(t)}catch{}if(e.resolve(t),t.reason==="dismissed"||t.reason==="destroyed"){this.teardown();return}t.status==="VERIFIED"&&(this.verifiedTimer=window.setTimeout(()=>this.teardown(),j))}}dismiss(){if(this.active&&!this.active.settled){this.finish(A(this.sessionToken,"dismissed"));return}this.teardown()}removeMessageListener(){window.removeEventListener("message",this.handleMessage)}clearTimers(){this.clearInitTimer(),this.verifiedTimer!==null&&(window.clearTimeout(this.verifiedTimer),this.verifiedTimer=null),this.errorTimer!==null&&(window.clearTimeout(this.errorTimer),this.errorTimer=null)}teardown(){this.clearTimers(),this.removeMessageListener(),this.view&&(this.view.destroy(),this.view=null),this.portalOrigin=null,this.sessionToken=null,this.active=null}};var L="esaalat-checkout-button",Q={ar:"\u0627\u062F\u0641\u0639 \u0627\u0644\u0622\u0646",en:"Pay now"},Z={VERIFIED:{handler:"onVerified",event:"esaalat-verified"},CANCELLED:{handler:"onCancelled",event:"esaalat-cancelled"},WRONG_AMOUNT:{handler:"onWrongAmount",event:"esaalat-wrong-amount"},EXPIRED:{handler:"onExpired",event:"esaalat-expired"},FAILED:{handler:"onFailed",event:"esaalat-failed"}},ee={verified:"esaalat-verified",cancelled:"esaalat-cancelled",wrongAmount:"esaalat-wrong-amount",expired:"esaalat-expired",failed:"esaalat-failed",error:"esaalat-error"},te=300;function re(i){return i instanceof Error?i:new n(String(i)||"The checkout failed.","unavailable")}var E=class extends HTMLElement{constructor(){super(...arguments);this.items=null;this.client=null;this.clientKey=null;this.button=null;this.busy=!1;this.watchTimer=null;this.slotLabel=null;this.handleClick=()=>{this.start()}}static get observedAttributes(){return["publishable-key","amount","currency","external-order-id","notes","wallet","locale","presentation","session-token","portal-url","label","disabled","items"]}connectedCallback(){this.render()}disconnectedCallback(){this.stopWatch()}attributeChangedCallback(e,r,o){if(r!==o){if(e==="publishable-key"){this.client=null,this.clientKey=null;return}if(e==="label"||e==="locale"){this.applyLabel();return}e==="disabled"&&this.syncBusy()}}async open(){await this.start()}async start(){if(this.busy||this.isCheckoutOpen())return;let e=(this.getAttribute("publishable-key")??"").trim();if(!e&&!this.attribute("session-token")){this.reportError(new n("A publishable key is required.","configuration"));return}this.setBusy(!0),this.watch();try{let r=this.readItems(),a=await this.ensureClient(e).open(this.openOptions(r));this.handleResult(a)}catch(r){this.reportError(re(r))}finally{this.setBusy(!1)}}ensureClient(e){return this.client&&this.clientKey===e?this.client:(this.clientKey=e,this.client=new g({publishableKey:e,locale:this.locale()}),this.client)}openOptions(e){return{amount:this.attribute("amount"),items:e??void 0,currency:this.attribute("currency"),externalOrderId:this.attribute("external-order-id"),notes:this.attribute("notes"),wallet:this.attribute("wallet"),sessionToken:this.attribute("session-token"),portalUrl:this.attribute("portal-url"),locale:this.locale(),presentation:this.presentation(),onBeforeCreateOrder:this.onBeforeCreateOrder,onOrderCreated:this.onOrderCreated}}handleResult(e){let r=Z[e.status];r&&(this.callSafely(this[r.handler],e),this.dispatchOutcome(r.event,e))}reportError(e){this.callSafely(this.onError,e),this.dispatchOutcome(ee.error,e)}dispatchOutcome(e,r){this.dispatchEvent(new CustomEvent(e,{detail:r,bubbles:!0,composed:!0}))}callSafely(e,r){if(e)try{e(r)}catch{}}readItems(){if(this.items&&this.items.length>0)return this.items;let e=this.attribute("items");if(!e)return null;try{let r=JSON.parse(e);if(!Array.isArray(r))throw new Error("not an array");return r}catch{throw new n("The items attribute must be a JSON array of order lines.","configuration")}}attribute(e){return(this.getAttribute(e)??"").trim()||void 0}locale(){return this.getAttribute("locale")==="en"?"en":"ar"}presentation(){return this.getAttribute("presentation")==="window"?"window":"modal"}isCheckoutOpen(){return this.client?.isOpen??!1}render(){if(!this.button){this.slotLabel===null&&(this.slotLabel=(this.textContent??"").trim());let e=this.ownerDocument.createElement("button");e.type="button",e.className="esaalat-checkout-button",e.addEventListener("click",this.handleClick),this.replaceChildren(e),this.button=e,this.applyStyles(e)}this.applyLabel(),this.syncBusy()}applyLabel(){if(!this.button)return;let e=this.attribute("label")??(this.slotLabel||void 0)??Q[this.locale()];this.button.textContent=e}applyStyles(e){e.style.appearance="none",e.style.border="1px solid var(--esaalat-button-border, transparent)",e.style.borderRadius="var(--esaalat-button-radius, 12px)",e.style.background="var(--esaalat-button-background, #1f2a24)",e.style.color="var(--esaalat-button-color, #ffffff)",e.style.font="inherit",e.style.fontSize="var(--esaalat-button-font-size, 15px)",e.style.fontWeight="600",e.style.lineHeight="1.2",e.style.padding="var(--esaalat-button-padding, 14px 22px)",e.style.cursor="pointer",e.style.transition="opacity 120ms ease-out"}setBusy(e){this.busy=e,this.syncBusy()}syncBusy(){if(!this.button)return;let e=this.busy||this.isCheckoutOpen(),r=e||this.hasAttribute("disabled");this.button.disabled=r,this.button.setAttribute("aria-busy",e?"true":"false"),this.button.style.opacity=this.hasAttribute("disabled")&&!e?"0.6":"1",this.button.style.cursor=r?"default":"pointer"}watch(){this.watchTimer!==null||typeof setInterval!="function"||(this.watchTimer=setInterval(()=>{!this.busy&&!this.isCheckoutOpen()&&this.stopWatch(),this.syncBusy()},te))}stopWatch(){this.watchTimer!==null&&(clearInterval(this.watchTimer),this.watchTimer=null)}};function S(){return typeof customElements>"u"||customElements.get(L)?!1:(customElements.define(L,E),!0)}window.EsaalatCheckout=g;window.EsaalatCheckoutError=n;S();})();
|