esaalat 0.1.0 → 0.2.1
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 +49 -1
- package/dist/esaalat.global.js +3 -3
- package/dist/index.cjs +321 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +194 -2
- package/dist/index.d.ts +194 -2
- package/dist/index.js +316 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ await checkout.open({
|
|
|
94
94
|
|
|
95
95
|
| Option | Default | Notes |
|
|
96
96
|
|---|---|---|
|
|
97
|
-
| `publishableKey` | — | Required
|
|
97
|
+
| `publishableKey` | — | Required unless every `open()` passes a `sessionToken`. Safe to expose. |
|
|
98
98
|
| `locale` | `"ar"` | Portal language (`"ar"` / `"en"`). |
|
|
99
99
|
| `apiBaseUrl` | `https://api.esaalat.com/api/v1` | Override for sandbox/local. |
|
|
100
100
|
| `portalBaseUrl` | `https://pay.esaalat.com` | Fallback only; the API's `portal_url` wins. |
|
|
@@ -119,6 +119,16 @@ await checkout.open({
|
|
|
119
119
|
| `idempotencyKey` | Stable retry key; generated per call when omitted. |
|
|
120
120
|
| `locale` | Overrides the client locale for this checkout. |
|
|
121
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
|
+
```
|
|
122
132
|
|
|
123
133
|
`open()` rejects with `EsaalatCheckoutError` when the session cannot be created
|
|
124
134
|
or reached. Check `.code` (`configuration`, `network`, `api`, `session`,
|
|
@@ -166,6 +176,44 @@ const checkout = new EsaalatCheckout({
|
|
|
166
176
|
});
|
|
167
177
|
```
|
|
168
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
|
+
|
|
169
217
|
## CSP
|
|
170
218
|
|
|
171
219
|
If your site uses a Content-Security-Policy, allow the SDK's two destinations:
|
package/dist/esaalat.global.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
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
2
|
:host { all: initial; }
|
|
3
3
|
*, *::before, *::after { box-sizing: border-box; }
|
|
4
4
|
.overlay {
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
.overlay, .dialog { animation: none; }
|
|
165
165
|
.spinner { animation-duration: 0.01ms; }
|
|
166
166
|
}
|
|
167
|
-
`;function c(
|
|
167
|
+
`;function c(o,t){let e=document.createElement(o);return t&&(e.className=t),e}function _(){let o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.setAttribute("viewBox","0 0 24 24"),o.setAttribute("fill","none"),o.setAttribute("stroke","currentColor"),o.setAttribute("stroke-width","1.8"),o.setAttribute("stroke-linecap","round"),o.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"),o.append(t),o}function D(){let o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.setAttribute("viewBox","0 0 24 24"),o.setAttribute("fill","none"),o.setAttribute("stroke","currentColor"),o.setAttribute("stroke-width","1.8"),o.setAttribute("stroke-linecap","round"),o.setAttribute("stroke-linejoin","round"),o.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"),o.append(t),o}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"),i=c("span","mark");i.textContent="E",i.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(i,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 i=c("h2","error-title");i.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,i,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
168
|
* { box-sizing: border-box; }
|
|
169
169
|
html, body { height: 100%; }
|
|
170
170
|
body {
|
|
@@ -219,4 +219,4 @@
|
|
|
219
219
|
.error-close { border-color: #343028; color: #f6f2e8; }
|
|
220
220
|
.error-close:hover { background: #2b2721; }
|
|
221
221
|
}
|
|
222
|
-
`;function b(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function _(){let r=window.screen?.availWidth??480,e=window.screen?.availHeight??800,t=Math.min(480,Math.max(320,r-40)),o=Math.min(800,Math.max(480,e-40)),i=typeof window.screenX=="number"?window.screenX:0,a=typeof window.screenY=="number"?window.screenY:0,d=window.outerWidth||t,f=window.outerHeight||o,u=Math.max(0,Math.round(i+(d-t)/2)),n=Math.max(0,Math.round(a+(f-o)/2));return["popup=yes",`width=${t}`,`height=${o}`,`left=${u}`,`top=${n}`,"resizable=yes","scrollbars=yes"].join(",")}function U(r,e,t,o){return["<!doctype html>",`<html dir="${e}" lang="${t}">`,"<head>",'<meta charset="utf-8" />','<meta name="viewport" content="width=device-width, initial-scale=1" />','<meta name="robots" content="noindex" />',`<title>${b(r)}</title>`,`<style>${D}</style>`,"</head>",`<body>${o}</body>`,"</html>"].join("")}var y=class{constructor(e){this.pollId=null;this.navigated=!1;this.destroyed=!1;this.onLoad=null;let t=window.open("",M,_());if(!t)throw new Error("The browser blocked the checkout window.");this.win=t,this.onDismiss=e.onDismiss,this.title=e.title,this.labels=e.labels,this.dir=e.dir,this.lang=e.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(e){if(!this.destroyed){this.navigated=!0;try{this.win.location.replace(e)}catch{if(this.win.closed){this.dismiss();return}try{this.win.location.href=e}catch{}}this.onLoad?.()}}showError(e){if(!(this.destroyed||this.navigated)){this.renderError(U(this.title,this.dir,this.lang,`<div class="panel"><span class="error-icon" aria-hidden="true">!</span><h2 class="error-title">${b(this.labels.errorTitle)}</h2><p class="error-message">${b(e)}</p><button class="error-close" type="button">${b(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(e){try{let t=this.win.document;t.open(),t.write(e),t.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 P="https://api.esaalat.com/api/v1",W="https://pay.esaalat.com",N="0.1.0",H=2e4,z=300,B=100,V=1800,$=6e3,j=["VERIFIED","WRONG_AMOUNT","EXPIRED","CANCELLED","FAILED"],F=["dismissed","destroyed","intent_cancelled","session_expired","session_not_found","network_error"];function C(r,e){let t=r.trim()||e,o;try{let i=typeof window<"u"?window.location?.href:void 0;o=new URL(t,i)}catch{throw new s(`Invalid URL: ${t}`,"configuration")}if(o.protocol!=="http:"&&o.protocol!=="https:")throw new s(`Unsupported URL protocol: ${o.protocol}`,"configuration");return o.toString().replace(/\/+$/,"")}function K(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return(r==="x"?e:e&3|8).toString(16)})}function k(r,e=""){return typeof r=="string"?r:e}function T(r){return typeof r=="string"?r:null}function Y(r){if(!r||typeof r!="object")return null;let e=r;if(typeof e.detail=="string")return e.detail;if(Array.isArray(e.errors)){for(let t of e.errors)if(t&&typeof t=="object"){let o=t.detail;if(typeof o=="string")return o}}for(let t of Object.values(e)){if(typeof t=="string")return t;if(Array.isArray(t)&&typeof t[0]=="string")return t[0]}return null}function G(r){if(!r||typeof r!="object")return!1;let e=r;return typeof e.session_token=="string"&&typeof e.portal_url=="string"&&typeof e.amount=="string"&&typeof e.currency=="string"}function I(r,e){return{status:"CANCELLED",reason:e,sessionToken:r??"",intentCode:null,amount:"0.00",paidAmount:null,currency:"YER",externalOrderId:"",merchantName:""}}var x=class{constructor(e){this.active=null;this.view=null;this.portalOrigin=null;this.sessionToken=null;this.initTimer=null;this.verifiedTimer=null;this.errorTimer=null;this.handleMessage=e=>{if(!this.view||!this.portalOrigin||e.source!==this.view.contentWindow||e.origin!==this.portalOrigin)return;let t=e.data;if(!(!t||typeof t!="object")&&t.source==="esaalat-checkout"&&t.sessionToken===this.sessionToken){if(t.type==="ready"){this.clearInitTimer();return}if(t.type==="close"){this.dismiss();return}if(t.type==="result"){let o=this.parseResult(t.payload);o&&this.finish(o)}}};if(!e||typeof e.publishableKey!="string")throw new s("A publishable key is required.","configuration");let t=e.publishableKey.trim();if(!t)throw new s("A publishable key is required.","configuration");this.publishableKey=t,this.locale=e.locale==="en"||e.locale==="ar"?e.locale:"ar",this.apiBaseUrl=C(e.apiBaseUrl??"",P),this.portalBaseUrl=C(e.portalBaseUrl??"",W),this.zIndex=typeof e.zIndex=="number"&&Number.isFinite(e.zIndex)?e.zIndex:2147483e3,this.presentation=e.presentation==="modal"?"modal":"window";let o=e.fetch,i=typeof fetch=="function"?fetch.bind(globalThis):null;this.fetchImpl=o??i,this.defaultOnResult=e.onResult,this.defaultOnError=e.onError}get isOpen(){return this.view!==null}async open(e={}){if(this.active||this.view)throw new s("A checkout is already open. Close it before starting another.","configuration");if(!this.fetchImpl)throw new s("This browser does not support fetch. Use the SDK on a modern browser.","configuration");let t=e.locale==="en"||e.locale==="ar"?e.locale:this.locale,o=e.items??[],i=e.sessionToken?.trim()||null,a=e.amount!==void 0&&e.amount!==null&&String(e.amount).trim()!=="";if(!i&&!a&&o.length===0)throw new s("Provide an amount, items, or a server-created session token.","configuration");if(!i&&o.length>0){for(let l of o)if(!l.name||l.price===void 0||l.price===null)throw new s("Every item needs a name and a price.","configuration")}let d=t==="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"},f=e.presentation==="window"||e.presentation==="modal"?e.presentation:this.presentation,u;try{u=this.createView(f,d,t),u.open()}catch(l){throw new s("The checkout could not be opened.","unavailable",{cause:l})}let n=new AbortController,h,E,g=new Promise((l,m)=>{h=l,E=m}),S=e.onResult??this.defaultOnResult,A=e.onError??this.defaultOnError,p={token:null,locale:t,controller:n,view:u,settled:!1,resolve:h,reject:E,onResult:S};this.active=p,this.view=u,this.sessionToken=null,this.portalOrigin=null,u.onLoad=()=>this.startInitRetry();let w;try{if(i)p.token=i,this.sessionToken=i,w=this.buildPortalUrl(i,e.portalUrl??"",t);else{let l=await this.createSession(e,t,n.signal);if(p.settled)return g;p.token=l.session_token,this.sessionToken=l.session_token,w=this.buildPortalUrl(l.session_token,l.portal_url,t)}this.portalOrigin=new URL(w).origin}catch(l){if(p.settled)return g;let m=l instanceof s?l:new s("Esaalat returned an invalid checkout URL.","session",{cause:l});p.settled=!0,this.active=null,this.removeMessageListener();try{A?.(m)}catch{}return p.reject(m),u.showError(m.message),this.errorTimer=window.setTimeout(()=>{this.view===u&&this.teardown()},$),g}return window.addEventListener("message",this.handleMessage),u.load(w),g}close(){if(this.active){this.dismiss();return}this.teardown()}destroy(){if(this.active&&!this.active.settled){this.finish(I(this.sessionToken,"destroyed"));return}this.teardown()}createView(e,t,o){let i=o==="ar"?"\u0625\u064A\u0635\u0627\u0644\u0627\u062A \u2014 \u062F\u0641\u0639 \u0622\u0645\u0646":"Esaalat \u2014 Secure checkout",a=()=>this.dismiss();if(e==="window")try{return new y({title:i,labels:t,dir:o==="ar"?"rtl":"ltr",lang:o,onDismiss:a})}catch{}return new v({zIndex:this.zIndex,title:i,labels:t,onDismiss:a})}buildPortalUrl(e,t,o){let i=t.trim()||`${this.portalBaseUrl}/checkout/${encodeURIComponent(e)}`,a=new URL(i);if(a.protocol!=="http:"&&a.protocol!=="https:")throw new Error("Unsupported portal protocol");return a.searchParams.set("lang",o),a.toString()}async createSession(e,t,o){if(!this.fetchImpl)throw new s("fetch is unavailable","configuration");let i={};e.amount!==void 0&&e.amount!==null&&(i.amount=String(e.amount)),e.items?.length&&(i.items=e.items.map(n=>({name:n.name,...n.description?{description:n.description}:{},price:String(n.price),quantity:n.quantity??1}))),e.currency&&(i.currency=e.currency),e.externalOrderId&&(i.external_order_id=e.externalOrderId),e.notes&&(i.notes=e.notes),e.wallet&&(i.wallet=e.wallet);let a=new AbortController,d=!1,f=window.setTimeout(()=>{d=!0,a.abort()},H),u=()=>a.abort();o.addEventListener("abort",u);try{let n=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":e.idempotencyKey?.trim()||K(),"ESAALAT-SDK-VERSION":N},body:JSON.stringify(i),signal:a.signal}),h=await n.json().catch(()=>null);if(!n.ok)throw new s(Y(h)||(t==="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:n.status,details:h});if(!G(h))throw new s("Esaalat returned an invalid checkout session.","session",{details:h});return h}catch(n){throw n instanceof s?n:d?new s(t==="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:n}):n instanceof DOMException&&n.name==="AbortError"?n:new s(t==="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:n})}finally{window.clearTimeout(f),o.removeEventListener("abort",u)}}startInitRetry(){this.clearInitTimer();let e=0,t=()=>{!this.view||!this.portalOrigin||!this.sessionToken||(this.postInit(),e+=1,e<B?this.initTimer=window.setTimeout(t,z):this.initTimer=null)};t()}postInit(){let e=this.view?.contentWindow;if(!e||!this.portalOrigin||!this.sessionToken)return;let t={source:"esaalat-checkout-sdk",version:1,type:"init",sessionToken:this.sessionToken,parentOrigin:window.location.origin,locale:this.active?.locale??this.locale};try{e.postMessage(t,this.portalOrigin)}catch{}}clearInitTimer(){this.initTimer!==null&&(window.clearTimeout(this.initTimer),this.initTimer=null)}parseResult(e){if(!e||typeof e!="object")return null;let t=e;if(typeof t.status!="string"||!j.includes(t.status))return null;let o=typeof t.reason=="string"&&F.includes(t.reason)?t.reason:void 0;return{status:t.status,...o?{reason:o}:{},sessionToken:this.sessionToken??"",intentCode:T(t.intentCode),amount:k(t.amount,"0.00"),paidAmount:T(t.paidAmount),currency:k(t.currency,"YER"),externalOrderId:k(t.externalOrderId),merchantName:k(t.merchantName)}}finish(e){let t=this.active;if(!(!t||t.settled)){t.settled=!0,this.active=null,this.clearInitTimer(),t.controller.abort();try{t.onResult?.(e)}catch{}if(t.resolve(e),e.reason==="dismissed"||e.reason==="destroyed"){this.teardown();return}e.status==="VERIFIED"&&(this.verifiedTimer=window.setTimeout(()=>this.teardown(),V))}}dismiss(){if(this.active&&!this.active.settled){this.finish(I(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}};window.EsaalatCheckout=x;window.EsaalatCheckoutError=s;})();
|
|
222
|
+
`;function k(o){return o.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function H(){let o=window.screen?.availWidth??480,t=window.screen?.availHeight??800,e=Math.min(480,Math.max(320,o-40)),r=Math.min(800,Math.max(480,t-40)),i=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(i+(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 N(o,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(o)}</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,H());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(N(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 P="https://api.esaalat.com/api/v1",W="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(o,t){let e=o.trim()||t,r;try{let i=typeof window<"u"?window.location?.href:void 0;r=new URL(e,i)}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,o=>{let t=Math.random()*16|0;return(o==="x"?t:t&3|8).toString(16)})}function x(o,t=""){return typeof o=="string"?o:t}function O(o){return typeof o=="string"?o:null}function X(o){if(!o||typeof o!="object")return null;let t=o;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(o){if(!o||typeof o!="object")return!1;let t=o;return typeof t.session_token=="string"&&typeof t.portal_url=="string"&&typeof t.amount=="string"&&typeof t.currency=="string"}function A(o,t){return{status:"CANCELLED",reason:t,sessionToken:o??"",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??"",P),this.portalBaseUrl=T(t.portalBaseUrl??"",W),this.zIndex=typeof t.zIndex=="number"&&Number.isFinite(t.zIndex)?t.zIndex:2147483e3,this.presentation=t.presentation==="modal"?"modal":"window";let r=t.fetch,i=typeof fetch=="function"?fetch.bind(globalThis):null;this.fetchImpl=r??i,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??[],i=t.sessionToken?.trim()||null,a=t.amount!==void 0&&t.amount!==null&&String(t.amount).trim()!=="";if(!i&&!this.publishableKey)throw new n("A publishable key is required to create a session. Pass one, or open a server-created `sessionToken`.","configuration");if(!i&&!a&&r.length===0)throw new n("Provide an amount, items, or a server-created session token.","configuration");if(!i&&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(i)f.token=i,this.sessionToken=i,w=this.buildPortalUrl(i,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 i=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:i,labels:e,dir:r==="ar"?"rtl":"ltr",lang:r,onDismiss:a})}catch{}return new y({zIndex:this.zIndex,title:i,labels:e,onDismiss:a})}buildPortalUrl(t,e,r){let i=e.trim()||`${this.portalBaseUrl}/checkout/${encodeURIComponent(t)}`,a=new URL(i);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 i={};t.amount!==void 0&&t.amount!==null&&(i.amount=String(t.amount)),t.items?.length&&(i.items=t.items.map(s=>({name:s.name,...s.description?{description:s.description}:{},price:String(s.price),quantity:s.quantity??1}))),t.currency&&(i.currency=t.currency),t.externalOrderId&&(i.external_order_id=t.externalOrderId),t.notes&&(i.notes=t.notes),t.wallet&&(i.wallet=t.wallet);let a=t.onBeforeCreateOrder;if(a){let s;try{s=await a(i)}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"&&(i=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(i),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(o){return o instanceof Error?o:new n(String(o)||"The checkout failed.","unavailable")}var oe=typeof HTMLElement>"u"?class{}:HTMLElement,E=class extends oe{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,i){if(r!==i){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();})();
|
package/dist/index.cjs
CHANGED
|
@@ -20,9 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ESAALAT_CHECKOUT_BUTTON_TAG: () => ESAALAT_CHECKOUT_BUTTON_TAG,
|
|
24
|
+
ESAALAT_CHECKOUT_EVENTS: () => ESAALAT_CHECKOUT_EVENTS,
|
|
23
25
|
EsaalatCheckout: () => EsaalatCheckout,
|
|
26
|
+
EsaalatCheckoutButtonElement: () => EsaalatCheckoutButtonElement,
|
|
24
27
|
EsaalatCheckoutError: () => EsaalatCheckoutError,
|
|
25
|
-
default: () => EsaalatCheckout
|
|
28
|
+
default: () => EsaalatCheckout,
|
|
29
|
+
registerEsaalatCheckoutButton: () => registerEsaalatCheckoutButton
|
|
26
30
|
});
|
|
27
31
|
module.exports = __toCommonJS(index_exports);
|
|
28
32
|
|
|
@@ -702,19 +706,7 @@ var EsaalatCheckout = class {
|
|
|
702
706
|
if (result) this.finish(result);
|
|
703
707
|
}
|
|
704
708
|
};
|
|
705
|
-
|
|
706
|
-
throw new EsaalatCheckoutError(
|
|
707
|
-
"A publishable key is required.",
|
|
708
|
-
"configuration"
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
const key = options.publishableKey.trim();
|
|
712
|
-
if (!key) {
|
|
713
|
-
throw new EsaalatCheckoutError(
|
|
714
|
-
"A publishable key is required.",
|
|
715
|
-
"configuration"
|
|
716
|
-
);
|
|
717
|
-
}
|
|
709
|
+
const key = typeof options?.publishableKey === "string" ? options.publishableKey.trim() : "";
|
|
718
710
|
this.publishableKey = key;
|
|
719
711
|
this.locale = options.locale === "en" || options.locale === "ar" ? options.locale : "ar";
|
|
720
712
|
this.apiBaseUrl = normalizeBase(options.apiBaseUrl ?? "", DEFAULT_API_BASE);
|
|
@@ -757,6 +749,12 @@ var EsaalatCheckout = class {
|
|
|
757
749
|
const items = options.items ?? [];
|
|
758
750
|
const existingToken = options.sessionToken?.trim() || null;
|
|
759
751
|
const hasAmount = options.amount !== void 0 && options.amount !== null && String(options.amount).trim() !== "";
|
|
752
|
+
if (!existingToken && !this.publishableKey) {
|
|
753
|
+
throw new EsaalatCheckoutError(
|
|
754
|
+
"A publishable key is required to create a session. Pass one, or open a server-created `sessionToken`.",
|
|
755
|
+
"configuration"
|
|
756
|
+
);
|
|
757
|
+
}
|
|
760
758
|
if (!existingToken && !hasAmount && items.length === 0) {
|
|
761
759
|
throw new EsaalatCheckoutError(
|
|
762
760
|
"Provide an amount, items, or a server-created session token.",
|
|
@@ -920,24 +918,38 @@ var EsaalatCheckout = class {
|
|
|
920
918
|
if (!this.fetchImpl) {
|
|
921
919
|
throw new EsaalatCheckoutError("fetch is unavailable", "configuration");
|
|
922
920
|
}
|
|
923
|
-
|
|
921
|
+
let payload = {};
|
|
924
922
|
if (options.amount !== void 0 && options.amount !== null) {
|
|
925
|
-
|
|
923
|
+
payload.amount = String(options.amount);
|
|
926
924
|
}
|
|
927
925
|
if (options.items?.length) {
|
|
928
|
-
|
|
926
|
+
payload.items = options.items.map((item) => ({
|
|
929
927
|
name: item.name,
|
|
930
928
|
...item.description ? { description: item.description } : {},
|
|
931
929
|
price: String(item.price),
|
|
932
930
|
quantity: item.quantity ?? 1
|
|
933
931
|
}));
|
|
934
932
|
}
|
|
935
|
-
if (options.currency)
|
|
933
|
+
if (options.currency) payload.currency = options.currency;
|
|
936
934
|
if (options.externalOrderId) {
|
|
937
|
-
|
|
935
|
+
payload.external_order_id = options.externalOrderId;
|
|
936
|
+
}
|
|
937
|
+
if (options.notes) payload.notes = options.notes;
|
|
938
|
+
if (options.wallet) payload.wallet = options.wallet;
|
|
939
|
+
const beforeCreate = options.onBeforeCreateOrder;
|
|
940
|
+
if (beforeCreate) {
|
|
941
|
+
let updated;
|
|
942
|
+
try {
|
|
943
|
+
updated = await beforeCreate(payload);
|
|
944
|
+
} catch (error) {
|
|
945
|
+
throw new EsaalatCheckoutError(
|
|
946
|
+
error instanceof Error && error.message ? error.message : locale === "ar" ? "\u0623\u064F\u0644\u063A\u064A \u0627\u0644\u062F\u0641\u0639." : "The checkout was cancelled.",
|
|
947
|
+
"configuration",
|
|
948
|
+
{ cause: error }
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
if (updated && typeof updated === "object") payload = updated;
|
|
938
952
|
}
|
|
939
|
-
if (options.notes) body.notes = options.notes;
|
|
940
|
-
if (options.wallet) body.wallet = options.wallet;
|
|
941
953
|
const controller = new AbortController();
|
|
942
954
|
let timedOut = false;
|
|
943
955
|
const timeout = window.setTimeout(() => {
|
|
@@ -960,26 +972,30 @@ var EsaalatCheckout = class {
|
|
|
960
972
|
"ESAALAT-IDEMPOTENCY-KEY": options.idempotencyKey?.trim() || requestId(),
|
|
961
973
|
"ESAALAT-SDK-VERSION": SDK_VERSION
|
|
962
974
|
},
|
|
963
|
-
body: JSON.stringify(
|
|
975
|
+
body: JSON.stringify(payload),
|
|
964
976
|
signal: controller.signal
|
|
965
977
|
}
|
|
966
978
|
);
|
|
967
|
-
const
|
|
979
|
+
const session = await response.json().catch(() => null);
|
|
968
980
|
if (!response.ok) {
|
|
969
981
|
throw new EsaalatCheckoutError(
|
|
970
|
-
errorDetail(
|
|
982
|
+
errorDetail(session) || (locale === "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."),
|
|
971
983
|
"api",
|
|
972
|
-
{ status: response.status, details:
|
|
984
|
+
{ status: response.status, details: session }
|
|
973
985
|
);
|
|
974
986
|
}
|
|
975
|
-
if (!isCheckoutSession(
|
|
987
|
+
if (!isCheckoutSession(session)) {
|
|
976
988
|
throw new EsaalatCheckoutError(
|
|
977
989
|
"Esaalat returned an invalid checkout session.",
|
|
978
990
|
"session",
|
|
979
|
-
{ details:
|
|
991
|
+
{ details: session }
|
|
980
992
|
);
|
|
981
993
|
}
|
|
982
|
-
|
|
994
|
+
try {
|
|
995
|
+
options.onOrderCreated?.(session);
|
|
996
|
+
} catch {
|
|
997
|
+
}
|
|
998
|
+
return session;
|
|
983
999
|
} catch (error) {
|
|
984
1000
|
if (error instanceof EsaalatCheckoutError) throw error;
|
|
985
1001
|
if (timedOut) {
|
|
@@ -1111,9 +1127,285 @@ var EsaalatCheckout = class {
|
|
|
1111
1127
|
this.active = null;
|
|
1112
1128
|
}
|
|
1113
1129
|
};
|
|
1130
|
+
|
|
1131
|
+
// src/element.ts
|
|
1132
|
+
var ESAALAT_CHECKOUT_BUTTON_TAG = "esaalat-checkout-button";
|
|
1133
|
+
var DEFAULT_LABELS = {
|
|
1134
|
+
ar: "\u0627\u062F\u0641\u0639 \u0627\u0644\u0622\u0646",
|
|
1135
|
+
en: "Pay now"
|
|
1136
|
+
};
|
|
1137
|
+
var OUTCOMES = {
|
|
1138
|
+
VERIFIED: { handler: "onVerified", event: "esaalat-verified" },
|
|
1139
|
+
CANCELLED: { handler: "onCancelled", event: "esaalat-cancelled" },
|
|
1140
|
+
WRONG_AMOUNT: { handler: "onWrongAmount", event: "esaalat-wrong-amount" },
|
|
1141
|
+
EXPIRED: { handler: "onExpired", event: "esaalat-expired" },
|
|
1142
|
+
FAILED: { handler: "onFailed", event: "esaalat-failed" }
|
|
1143
|
+
};
|
|
1144
|
+
var ESAALAT_CHECKOUT_EVENTS = {
|
|
1145
|
+
verified: "esaalat-verified",
|
|
1146
|
+
cancelled: "esaalat-cancelled",
|
|
1147
|
+
wrongAmount: "esaalat-wrong-amount",
|
|
1148
|
+
expired: "esaalat-expired",
|
|
1149
|
+
failed: "esaalat-failed",
|
|
1150
|
+
error: "esaalat-error"
|
|
1151
|
+
};
|
|
1152
|
+
var WATCH_INTERVAL_MS = 300;
|
|
1153
|
+
function asError(error) {
|
|
1154
|
+
if (error instanceof Error) return error;
|
|
1155
|
+
return new EsaalatCheckoutError(String(error) || "The checkout failed.", "unavailable");
|
|
1156
|
+
}
|
|
1157
|
+
var HTMLElementBase = typeof HTMLElement === "undefined" ? class {
|
|
1158
|
+
} : HTMLElement;
|
|
1159
|
+
var EsaalatCheckoutButtonElement = class extends HTMLElementBase {
|
|
1160
|
+
constructor() {
|
|
1161
|
+
super(...arguments);
|
|
1162
|
+
/**
|
|
1163
|
+
* Order lines as data. The `items` attribute carries the same as JSON, for
|
|
1164
|
+
* HTML templates that have no way to set a property.
|
|
1165
|
+
*/
|
|
1166
|
+
this.items = null;
|
|
1167
|
+
this.client = null;
|
|
1168
|
+
this.clientKey = null;
|
|
1169
|
+
this.button = null;
|
|
1170
|
+
this.busy = false;
|
|
1171
|
+
this.watchTimer = null;
|
|
1172
|
+
/** Text written between the tags, captured before the button is injected. */
|
|
1173
|
+
this.slotLabel = null;
|
|
1174
|
+
this.handleClick = () => {
|
|
1175
|
+
void this.start();
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
static get observedAttributes() {
|
|
1179
|
+
return [
|
|
1180
|
+
"publishable-key",
|
|
1181
|
+
"amount",
|
|
1182
|
+
"currency",
|
|
1183
|
+
"external-order-id",
|
|
1184
|
+
"notes",
|
|
1185
|
+
"wallet",
|
|
1186
|
+
"locale",
|
|
1187
|
+
"presentation",
|
|
1188
|
+
"session-token",
|
|
1189
|
+
"portal-url",
|
|
1190
|
+
"label",
|
|
1191
|
+
"disabled",
|
|
1192
|
+
"items"
|
|
1193
|
+
];
|
|
1194
|
+
}
|
|
1195
|
+
connectedCallback() {
|
|
1196
|
+
this.render();
|
|
1197
|
+
}
|
|
1198
|
+
disconnectedCallback() {
|
|
1199
|
+
this.stopWatch();
|
|
1200
|
+
}
|
|
1201
|
+
attributeChangedCallback(name, previous, value) {
|
|
1202
|
+
if (previous === value) return;
|
|
1203
|
+
if (name === "publishable-key") {
|
|
1204
|
+
this.client = null;
|
|
1205
|
+
this.clientKey = null;
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
if (name === "label" || name === "locale") {
|
|
1209
|
+
this.applyLabel();
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
if (name === "disabled") {
|
|
1213
|
+
this.syncBusy();
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/** Open the checkout, exactly as a click does (for programmatic use). */
|
|
1217
|
+
async open() {
|
|
1218
|
+
await this.start();
|
|
1219
|
+
}
|
|
1220
|
+
async start() {
|
|
1221
|
+
if (this.busy || this.isCheckoutOpen()) return;
|
|
1222
|
+
const key = (this.getAttribute("publishable-key") ?? "").trim();
|
|
1223
|
+
if (!key && !this.attribute("session-token")) {
|
|
1224
|
+
this.reportError(
|
|
1225
|
+
new EsaalatCheckoutError("A publishable key is required.", "configuration")
|
|
1226
|
+
);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
this.setBusy(true);
|
|
1230
|
+
this.watch();
|
|
1231
|
+
try {
|
|
1232
|
+
const items = this.readItems();
|
|
1233
|
+
const client = this.ensureClient(key);
|
|
1234
|
+
const result = await client.open(this.openOptions(items));
|
|
1235
|
+
this.handleResult(result);
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
this.reportError(asError(error));
|
|
1238
|
+
} finally {
|
|
1239
|
+
this.setBusy(false);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
ensureClient(key) {
|
|
1243
|
+
if (this.client && this.clientKey === key) return this.client;
|
|
1244
|
+
this.clientKey = key;
|
|
1245
|
+
this.client = new EsaalatCheckout({
|
|
1246
|
+
publishableKey: key,
|
|
1247
|
+
locale: this.locale()
|
|
1248
|
+
});
|
|
1249
|
+
return this.client;
|
|
1250
|
+
}
|
|
1251
|
+
openOptions(items) {
|
|
1252
|
+
return {
|
|
1253
|
+
amount: this.attribute("amount"),
|
|
1254
|
+
items: items ?? void 0,
|
|
1255
|
+
currency: this.attribute("currency"),
|
|
1256
|
+
externalOrderId: this.attribute("external-order-id"),
|
|
1257
|
+
notes: this.attribute("notes"),
|
|
1258
|
+
wallet: this.attribute("wallet"),
|
|
1259
|
+
sessionToken: this.attribute("session-token"),
|
|
1260
|
+
portalUrl: this.attribute("portal-url"),
|
|
1261
|
+
locale: this.locale(),
|
|
1262
|
+
presentation: this.presentation(),
|
|
1263
|
+
onBeforeCreateOrder: this.onBeforeCreateOrder,
|
|
1264
|
+
onOrderCreated: this.onOrderCreated
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
handleResult(result) {
|
|
1268
|
+
const outcome = OUTCOMES[result.status];
|
|
1269
|
+
if (!outcome) return;
|
|
1270
|
+
this.callSafely(this[outcome.handler], result);
|
|
1271
|
+
this.dispatchOutcome(outcome.event, result);
|
|
1272
|
+
}
|
|
1273
|
+
reportError(error) {
|
|
1274
|
+
this.callSafely(this.onError, error);
|
|
1275
|
+
this.dispatchOutcome(ESAALAT_CHECKOUT_EVENTS.error, error);
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* DOM events carry the same detail as the callbacks, so templates that bind
|
|
1279
|
+
* events instead of properties (`@esaalat-verified`, `(esaalat-verified)`)
|
|
1280
|
+
* need no extra JS.
|
|
1281
|
+
*/
|
|
1282
|
+
dispatchOutcome(name, detail) {
|
|
1283
|
+
this.dispatchEvent(
|
|
1284
|
+
new CustomEvent(name, { detail, bubbles: true, composed: true })
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
callSafely(handler, argument) {
|
|
1288
|
+
if (!handler) return;
|
|
1289
|
+
try {
|
|
1290
|
+
handler(argument);
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
readItems() {
|
|
1295
|
+
if (this.items && this.items.length > 0) return this.items;
|
|
1296
|
+
const raw = this.attribute("items");
|
|
1297
|
+
if (!raw) return null;
|
|
1298
|
+
try {
|
|
1299
|
+
const parsed = JSON.parse(raw);
|
|
1300
|
+
if (!Array.isArray(parsed)) throw new Error("not an array");
|
|
1301
|
+
return parsed;
|
|
1302
|
+
} catch {
|
|
1303
|
+
throw new EsaalatCheckoutError(
|
|
1304
|
+
"The items attribute must be a JSON array of order lines.",
|
|
1305
|
+
"configuration"
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
attribute(name) {
|
|
1310
|
+
const value = (this.getAttribute(name) ?? "").trim();
|
|
1311
|
+
return value || void 0;
|
|
1312
|
+
}
|
|
1313
|
+
locale() {
|
|
1314
|
+
return this.getAttribute("locale") === "en" ? "en" : "ar";
|
|
1315
|
+
}
|
|
1316
|
+
presentation() {
|
|
1317
|
+
return this.getAttribute("presentation") === "window" ? "window" : "modal";
|
|
1318
|
+
}
|
|
1319
|
+
isCheckoutOpen() {
|
|
1320
|
+
return this.client?.isOpen ?? false;
|
|
1321
|
+
}
|
|
1322
|
+
// -- rendering -----------------------------------------------------------
|
|
1323
|
+
render() {
|
|
1324
|
+
if (!this.button) {
|
|
1325
|
+
if (this.slotLabel === null) {
|
|
1326
|
+
this.slotLabel = (this.textContent ?? "").trim();
|
|
1327
|
+
}
|
|
1328
|
+
const button = this.ownerDocument.createElement("button");
|
|
1329
|
+
button.type = "button";
|
|
1330
|
+
button.className = "esaalat-checkout-button";
|
|
1331
|
+
button.addEventListener("click", this.handleClick);
|
|
1332
|
+
this.replaceChildren(button);
|
|
1333
|
+
this.button = button;
|
|
1334
|
+
this.applyStyles(button);
|
|
1335
|
+
}
|
|
1336
|
+
this.applyLabel();
|
|
1337
|
+
this.syncBusy();
|
|
1338
|
+
}
|
|
1339
|
+
applyLabel() {
|
|
1340
|
+
if (!this.button) return;
|
|
1341
|
+
const label = this.attribute("label") ?? (this.slotLabel || void 0) ?? DEFAULT_LABELS[this.locale()];
|
|
1342
|
+
this.button.textContent = label;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Defaults live on the inner button and read CSS custom properties, so a
|
|
1346
|
+
* merchant themes the control from their own stylesheet without the element
|
|
1347
|
+
* shipping a global stylesheet or a shadow root that would trap their CSS.
|
|
1348
|
+
*/
|
|
1349
|
+
applyStyles(button) {
|
|
1350
|
+
button.style.appearance = "none";
|
|
1351
|
+
button.style.border = "1px solid var(--esaalat-button-border, transparent)";
|
|
1352
|
+
button.style.borderRadius = "var(--esaalat-button-radius, 12px)";
|
|
1353
|
+
button.style.background = "var(--esaalat-button-background, #1f2a24)";
|
|
1354
|
+
button.style.color = "var(--esaalat-button-color, #ffffff)";
|
|
1355
|
+
button.style.font = "inherit";
|
|
1356
|
+
button.style.fontSize = "var(--esaalat-button-font-size, 15px)";
|
|
1357
|
+
button.style.fontWeight = "600";
|
|
1358
|
+
button.style.lineHeight = "1.2";
|
|
1359
|
+
button.style.padding = "var(--esaalat-button-padding, 14px 22px)";
|
|
1360
|
+
button.style.cursor = "pointer";
|
|
1361
|
+
button.style.transition = "opacity 120ms ease-out";
|
|
1362
|
+
}
|
|
1363
|
+
setBusy(busy) {
|
|
1364
|
+
this.busy = busy;
|
|
1365
|
+
this.syncBusy();
|
|
1366
|
+
}
|
|
1367
|
+
syncBusy() {
|
|
1368
|
+
if (!this.button) return;
|
|
1369
|
+
const busy = this.busy || this.isCheckoutOpen();
|
|
1370
|
+
const locked = busy || this.hasAttribute("disabled");
|
|
1371
|
+
this.button.disabled = locked;
|
|
1372
|
+
this.button.setAttribute("aria-busy", busy ? "true" : "false");
|
|
1373
|
+
this.button.style.opacity = this.hasAttribute("disabled") && !busy ? "0.6" : "1";
|
|
1374
|
+
this.button.style.cursor = locked ? "default" : "pointer";
|
|
1375
|
+
}
|
|
1376
|
+
// -- state watcher -------------------------------------------------------
|
|
1377
|
+
/**
|
|
1378
|
+
* A result resolves `open()`, but the portal stays on screen for a moment
|
|
1379
|
+
* (`VERIFIED` auto-closes; every other outcome waits for the buyer to close
|
|
1380
|
+
* it). The button has to stay locked until then — a click in that window
|
|
1381
|
+
* would only reach "a checkout is already open".
|
|
1382
|
+
*/
|
|
1383
|
+
watch() {
|
|
1384
|
+
if (this.watchTimer !== null || typeof setInterval !== "function") return;
|
|
1385
|
+
this.watchTimer = setInterval(() => {
|
|
1386
|
+
if (!this.busy && !this.isCheckoutOpen()) this.stopWatch();
|
|
1387
|
+
this.syncBusy();
|
|
1388
|
+
}, WATCH_INTERVAL_MS);
|
|
1389
|
+
}
|
|
1390
|
+
stopWatch() {
|
|
1391
|
+
if (this.watchTimer === null) return;
|
|
1392
|
+
clearInterval(this.watchTimer);
|
|
1393
|
+
this.watchTimer = null;
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
function registerEsaalatCheckoutButton() {
|
|
1397
|
+
if (typeof customElements === "undefined") return false;
|
|
1398
|
+
if (customElements.get(ESAALAT_CHECKOUT_BUTTON_TAG)) return false;
|
|
1399
|
+
customElements.define(ESAALAT_CHECKOUT_BUTTON_TAG, EsaalatCheckoutButtonElement);
|
|
1400
|
+
return true;
|
|
1401
|
+
}
|
|
1114
1402
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1115
1403
|
0 && (module.exports = {
|
|
1404
|
+
ESAALAT_CHECKOUT_BUTTON_TAG,
|
|
1405
|
+
ESAALAT_CHECKOUT_EVENTS,
|
|
1116
1406
|
EsaalatCheckout,
|
|
1117
|
-
|
|
1407
|
+
EsaalatCheckoutButtonElement,
|
|
1408
|
+
EsaalatCheckoutError,
|
|
1409
|
+
registerEsaalatCheckoutButton
|
|
1118
1410
|
});
|
|
1119
1411
|
//# sourceMappingURL=index.cjs.map
|