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 CHANGED
@@ -94,7 +94,7 @@ await checkout.open({
94
94
 
95
95
  | Option | Default | Notes |
96
96
  |---|---|---|
97
- | `publishableKey` | — | Required. Safe to expose. |
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:
@@ -1,4 +1,4 @@
1
- "use strict";(()=>{var s=class extends Error{constructor(e,t,o={}){super(e),this.name="EsaalatCheckoutError",this.code=t,this.status=o.status,this.details=o.details,o.cause!==void 0&&(this.cause=o.cause)}};var L=`
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(r,e){let t=document.createElement(r);return e&&(t.className=e),t}function O(){let r=document.createElementNS("http://www.w3.org/2000/svg","svg");r.setAttribute("viewBox","0 0 24 24"),r.setAttribute("fill","none"),r.setAttribute("stroke","currentColor"),r.setAttribute("stroke-width","1.8"),r.setAttribute("stroke-linecap","round"),r.setAttribute("aria-hidden","true");let e=document.createElementNS("http://www.w3.org/2000/svg","path");return e.setAttribute("d","M18 6 6 18M6 6l12 12"),r.append(e),r}function R(){let r=document.createElementNS("http://www.w3.org/2000/svg","svg");r.setAttribute("viewBox","0 0 24 24"),r.setAttribute("fill","none"),r.setAttribute("stroke","currentColor"),r.setAttribute("stroke-width","1.8"),r.setAttribute("stroke-linecap","round"),r.setAttribute("stroke-linejoin","round"),r.setAttribute("aria-hidden","true");let e=document.createElementNS("http://www.w3.org/2000/svg","path");return e.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"),r.append(e),r}var v=class{constructor(e){this.options=e;this.frame=null;this.previousOverflow="";this.previousFocus=null;this.destroyed=!1;this.onLoad=null;this.handleKeyDown=e=>{e.key==="Escape"&&(e.preventDefault(),this.onDismiss())};this.onDismiss=e.onDismiss,this.host=document.createElement("div"),this.host.setAttribute("data-esaalat-checkout",""),this.host.style.cssText=`all: initial; position: fixed; inset: 0; z-index: ${e.zIndex}; pointer-events: none;`,this.shadow=this.host.attachShadow({mode:"open"});let t=c("style");t.textContent=L,this.shadow.append(t),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",e.title);let o=c("div","chrome"),i=c("span","mark");i.textContent="E",i.setAttribute("aria-hidden","true");let a=c("span","chrome-title");a.textContent=e.title,this.closeButton=c("button","close"),this.closeButton.type="button",this.closeButton.setAttribute("aria-label",e.labels.close),this.closeButton.append(O()),this.closeButton.addEventListener("click",()=>this.onDismiss()),o.append(i,a,this.closeButton),this.content=c("div","content"),this.showLoading(),this.dialog.append(o,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(e){if(this.destroyed)return;this.dialog.classList.add("is-frame"),this.content.replaceChildren();let t=document.createElement("iframe");t.className="frame",t.src=e,t.title=this.options.title,t.setAttribute("allow","clipboard-write"),t.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),t.setAttribute("loading","eager"),t.addEventListener("load",()=>{this.onLoad?.();try{t.focus({preventScroll:!0})}catch{}}),this.frame=t,this.content.append(t)}get contentWindow(){return this.frame?.contentWindow??null}showError(e){if(this.destroyed)return;this.content.replaceChildren();let t=c("div","panel"),o=c("span","error-icon");o.append(R());let i=c("h2","error-title");i.textContent=this.options.labels.errorTitle;let a=c("p","error-message");a.textContent=e;let d=c("button","error-close");d.type="button",d.textContent=this.options.labels.close,d.addEventListener("click",()=>this.onDismiss()),t.append(o,i,a,d),this.content.append(t),this.dialog.classList.remove("is-frame"),d.focus({preventScroll:!0})}showLoading(){this.content.replaceChildren();let e=c("div","panel"),t=c("span","spinner");t.setAttribute("aria-hidden","true");let o=c("p","loading");o.textContent=this.options.labels.loading,e.append(t,o),this.content.append(e)}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 M="esaalat-checkout";var D=`
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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
- if (!options || typeof options.publishableKey !== "string") {
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
- const body = {};
921
+ let payload = {};
924
922
  if (options.amount !== void 0 && options.amount !== null) {
925
- body.amount = String(options.amount);
923
+ payload.amount = String(options.amount);
926
924
  }
927
925
  if (options.items?.length) {
928
- body.items = options.items.map((item) => ({
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) body.currency = options.currency;
933
+ if (options.currency) payload.currency = options.currency;
936
934
  if (options.externalOrderId) {
937
- body.external_order_id = options.externalOrderId;
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(body),
975
+ body: JSON.stringify(payload),
964
976
  signal: controller.signal
965
977
  }
966
978
  );
967
- const payload = await response.json().catch(() => null);
979
+ const session = await response.json().catch(() => null);
968
980
  if (!response.ok) {
969
981
  throw new EsaalatCheckoutError(
970
- errorDetail(payload) || (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."),
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: payload }
984
+ { status: response.status, details: session }
973
985
  );
974
986
  }
975
- if (!isCheckoutSession(payload)) {
987
+ if (!isCheckoutSession(session)) {
976
988
  throw new EsaalatCheckoutError(
977
989
  "Esaalat returned an invalid checkout session.",
978
990
  "session",
979
- { details: payload }
991
+ { details: session }
980
992
  );
981
993
  }
982
- return payload;
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
- EsaalatCheckoutError
1407
+ EsaalatCheckoutButtonElement,
1408
+ EsaalatCheckoutError,
1409
+ registerEsaalatCheckoutButton
1118
1410
  });
1119
1411
  //# sourceMappingURL=index.cjs.map