@inflow_pay/sdk 1.1.0 → 1.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,7 @@ Accept card payments with a secure, iframe-based payment form. This version deli
9
9
  - 🎯 **Auto 3D Secure**: SDK handles authentication modals automatically
10
10
  - 🚀 **Zero Configuration**: Works out of the box with sensible defaults
11
11
  - 🎨 **Fully Customizable**: Match your brand with comprehensive styling options
12
+ - 💳 **Save card (setup)**: Collect and tokenize a card without charging, using a `CustomerPaymentMethodRequest` ID (`setupId`)
12
13
 
13
14
  ## Installation
14
15
 
@@ -16,6 +17,12 @@ Accept card payments with a secure, iframe-based payment form. This version deli
16
17
  npm install @inflow_pay/sdk
17
18
  ```
18
19
 
20
+ **React apps** — import from `@inflow_pay/sdk/react` and ensure peer dependencies are installed:
21
+
22
+ ```bash
23
+ npm install @inflow_pay/sdk react react-dom
24
+ ```
25
+
19
26
  **CDN Option** (vanilla HTML pages only):
20
27
 
21
28
  ```html
@@ -90,6 +97,37 @@ export default function CheckoutPage() {
90
97
  }
91
98
  ```
92
99
 
100
+ ### Save card (setup) — React
101
+
102
+ Use **`setupId`** (ID returned when you create a **CustomerPaymentMethodRequest** on your server) instead of **`paymentId`**. The two are mutually exclusive. On success, `onComplete` receives a **`SaveCardResult`** with `status` and `setupId`.
103
+
104
+ ```tsx
105
+ import {
106
+ InflowPayProvider,
107
+ CardElement,
108
+ PaymentResultStatus,
109
+ type SaveCardResult,
110
+ } from "@inflow_pay/sdk/react";
111
+
112
+ export default function AddCardPage({ setupId }: { setupId: string }) {
113
+ return (
114
+ <InflowPayProvider config={{ publicKey: "inflow_pub_xxx" }}>
115
+ <CardElement
116
+ setupId={setupId}
117
+ onComplete={(result) => {
118
+ if (result.status === PaymentResultStatus.SUCCESS) {
119
+ const save = result as SaveCardResult;
120
+ console.log("Card saved for request:", save.setupId);
121
+ }
122
+ }}
123
+ />
124
+ </InflowPayProvider>
125
+ );
126
+ }
127
+ ```
128
+
129
+ Your backend should create the customer payment method request with your **private** API key and expose only the returned request ID to the browser as `setupId`.
130
+
93
131
  ### Frontend: Vanilla JavaScript
94
132
 
95
133
  ```html
@@ -118,6 +156,29 @@ export default function CheckoutPage() {
118
156
  </script>
119
157
  ```
120
158
 
159
+ ### Save card (setup) — Vanilla JavaScript
160
+
161
+ ```javascript
162
+ const provider = new InflowPaySDK.InflowPayProvider({
163
+ config: { publicKey: "inflow_pub_xxx" },
164
+ });
165
+
166
+ fetch("/api/create-card-setup", { method: "POST" })
167
+ .then((res) => res.json())
168
+ .then((data) => {
169
+ const cardElement = provider.createCardElement({
170
+ setupId: data.setupId,
171
+ container: "#card-container",
172
+ onComplete: (result) => {
173
+ if (result.status === InflowPaySDK.PaymentResultStatus.SUCCESS) {
174
+ console.log("setupId:", result.setupId);
175
+ }
176
+ },
177
+ });
178
+ cardElement.mount();
179
+ });
180
+ ```
181
+
121
182
  ## Built-in Success UI Explained
122
183
 
123
184
  The SDK includes a polished success screen that appears automatically after successful payment:
@@ -181,14 +242,23 @@ return (
181
242
  );
182
243
  ```
183
244
 
184
- ## Payment Flow
245
+ ## Payment flow
246
+
247
+ **Charge (payment)**
185
248
 
186
- 1. Backend creates payment → returns `paymentId`
187
- 2. Frontend initializes SDK with `paymentId` and public key
188
- 3. User enters card details in secure iframe
189
- 4. SDK tokenizes card and processes payment
190
- 5. 3D Secure authentication (if required) - automatic modal
191
- 6. `onComplete` callback fires → redirect or update UI
249
+ 1. Backend creates a payment → returns `paymentId`
250
+ 2. Frontend mounts `CardElement` with `paymentId` and your public key
251
+ 3. User enters card details in the secure iframe
252
+ 4. SDK tokenizes the card and processes the payment
253
+ 5. 3D Secure (if required) handled automatically
254
+ 6. `onComplete` fires with a **`PaymentResult`** → redirect or update UI
255
+
256
+ **Save card (setup)**
257
+
258
+ 1. Backend creates a **CustomerPaymentMethodRequest** → returns its ID as `setupId`
259
+ 2. Frontend mounts `CardElement` with **`setupId`** (not `paymentId`)
260
+ 3. User completes the card form; 3DS may still run when the issuer requires it
261
+ 4. `onComplete` fires with a **`SaveCardResult`** (`setupId` + `status`, and `error` on failure)
192
262
 
193
263
  ## API Reference
194
264
 
@@ -207,9 +277,12 @@ const provider = new InflowPayProvider({
207
277
 
208
278
  ### CardElement
209
279
 
280
+ Pass exactly one of **`paymentId`** (checkout) or **`setupId`** (save card). Passing both or neither throws.
281
+
210
282
  ```javascript
211
283
  const cardElement = provider.createCardElement({
212
- paymentId: "pay_xxx", // Required - from your backend
284
+ paymentId: "pay_xxx", // XOR setupId from your backend
285
+ // setupId: "cus_pm_req_xxx", // save-card flow (CustomerPaymentMethodRequest ID)
213
286
  container: "#card-container", // Required - CSS selector or HTMLElement
214
287
 
215
288
  // Callbacks
@@ -254,7 +327,7 @@ const cardElement = provider.createCardElement({
254
327
  cardElement.mount();
255
328
  ```
256
329
 
257
- ### Payment Result
330
+ ### Payment and save-card results
258
331
 
259
332
  ```typescript
260
333
  interface PaymentResult {
@@ -266,8 +339,20 @@ interface PaymentResult {
266
339
  retryable: boolean; // Can retry?
267
340
  };
268
341
  }
342
+
343
+ interface SaveCardResult {
344
+ status: "SUCCESS" | "FAILED";
345
+ setupId: string;
346
+ error?: {
347
+ code: string;
348
+ message: string;
349
+ retryable: boolean;
350
+ };
351
+ }
269
352
  ```
270
353
 
354
+ In **React**, `onComplete` is typed as `(result: PaymentResult | SaveCardResult) => void`. Use `result.status` and narrow with `'paymentId' in result` vs `'setupId' in result`, or cast when you know which mode you mounted.
355
+
271
356
  ### CardElement Methods
272
357
 
273
358
  ```javascript
@@ -375,17 +460,28 @@ onComplete: (result) => {
375
460
 
376
461
  ## TypeScript
377
462
 
378
- Full type definitions included:
463
+ Full type definitions are included for both entry points:
379
464
 
380
465
  ```typescript
381
- import { InflowPayProvider, PaymentResultStatus } from "@inflow_pay/sdk";
382
- import type { PaymentResult, PaymentError } from "@inflow_pay/sdk";
466
+ // Vanilla (InflowPayProvider, PaymentSDK, CardElement)
467
+ import {
468
+ InflowPayProvider,
469
+ PaymentResultStatus,
470
+ } from "@inflow_pay/sdk";
471
+ import type { PaymentResult, SaveCardResult, PaymentError } from "@inflow_pay/sdk";
383
472
 
384
- const provider = new InflowPayProvider({
385
- config: { publicKey: "inflow_pub_xxx" },
386
- });
473
+ // React components and hooks
474
+ import {
475
+ InflowPayProvider,
476
+ CardElement,
477
+ useInflowPay,
478
+ PaymentResultStatus,
479
+ } from "@inflow_pay/sdk/react";
480
+ import type { PaymentResult, SaveCardResult } from "@inflow_pay/sdk/react";
387
481
  ```
388
482
 
483
+ `useInflowPay()` returns the shared `PaymentSDK` instance from `InflowPayProvider` (e.g. for advanced use).
484
+
389
485
  ## Features
390
486
 
391
487
  - ✅ Dynamic height adjustment
@@ -0,0 +1,169 @@
1
+ let e=require(`react`),t=require(`react/jsx-runtime`);var n=function(e){return e.SUCCESS=`SUCCESS`,e.FAILED=`FAILED`,e}({}),r=function(e){return e.THREE_DS_FAILED=`THREE_DS_FAILED`,e.PAYMENT_PROCESSING_ERROR=`PAYMENT_PROCESSING_ERROR`,e}({}),i=(0,e.createContext)(null);function a({config:n,children:r}){let[a]=(0,e.useState)(()=>new q({publicKey:n.publicKey,locale:n.locale}));return(0,t.jsx)(i.Provider,{value:{sdk:a},children:r})}function o(){let t=(0,e.useContext)(i);if(!t)throw Error(`useInflowPay must be used within InflowPayProvider`);return t.sdk}function s(r){let{paymentId:a,setupId:o,onReady:s,onChange:c,onComplete:l,onError:u,style:d,buttonText:f,placeholders:p,showDefaultSuccessUI:m,config:h}=r,g=(0,e.useContext)(i),_=(0,e.useRef)(null),v=(0,e.useRef)(null),[y,b]=(0,e.useState)(!1),x=g?.sdk||(h?new q({publicKey:h.publicKey,locale:h.locale}):null);if(!x)throw Error(`CardElement must be used within InflowPayProvider or have a config prop`);return(0,e.useEffect)(()=>{if(!_.current)return;_.current.id||(_.current.id=`inflowpay-card-element-${Date.now()}`);let e={container:_.current,...a&&{paymentId:a},...o&&{setupId:o},...d&&{style:d},...f&&{buttonText:f},...p&&{placeholders:p},...m!==void 0&&{showDefaultSuccessUI:m},onComplete:e=>{l&&l(e)},onError:e=>{u?u(e):l&&l(o?{status:n.FAILED,setupId:o,error:e}:{status:n.FAILED,paymentId:a,error:e})},onClose:()=>{}},t=x.createCardElement(e);if(v.current=t,t.mount(),b(!0),s){let e=setTimeout(()=>{s()},100);return()=>clearTimeout(e)}},[a,o,x,l,u,s,d,f,p,m]),(0,e.useEffect)(()=>{c&&y&&c({complete:!1})},[y,c]),(0,e.useEffect)(()=>()=>{v.current&&(v.current.destroy(),v.current=null)},[]),(0,t.jsx)(`div`,{ref:_,style:{width:d?.fillParent?`100%`:`344px`}})}var c={OVERLAY:999999,MODAL:1e6,LOADER:1e3},l={MIN_HEIGHT:196,CONTAINER_MAX_WIDTH:`500px`,CONTAINER_WIDTH_PERCENT:`90%`,CONTAINER_HEIGHT_PERCENT:`90%`,CONTAINER_MAX_HEIGHT:`600px`,BUTTON_SIZE:30,DEFAULT_IFRAME_WIDTH:`344px`},u=[`https://dev.api.inflowpay.xyz`,`https://pre-prod.api.inflowpay.xyz`,`https://api.inflowpay.xyz`],d={OVERLAY:`inflowpay-sdk-overlay`,LOADER:`inflowpay-loader`,LOADER_STYLES:`inflowpay-loader-styles`,THREE_DS_OVERLAY:`inflowpay-3ds-overlay`,THREE_DS_CLOSE:`inflowpay-3ds-close`},f={PRODUCTION:`https://iframe.inflowpay.com/iframe/checkout`,PREPROD:`https://preprod.iframe.inflowpay.com/iframe/checkout`,DEVELOPMENT:`https://dev.iframe.inflowpay.com/iframe/checkout`,LOCAL:`http://localhost:3010/iframe/checkout`};function p(e){return{inputBgColor:e?`#2d2d2d`:`#F5F5F5`,shimmerBase:e?`#3d3d3d`:`#E5E5E5`,shimmerLight:e?`#4d4d4d`:`#F0F0F0`}}function m(e){return`linear-gradient(90deg, ${e.shimmerBase} 25%, ${e.shimmerLight} 50%, ${e.shimmerBase} 75%)`}function h(){return`
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background-color: rgba(0, 0, 0, 0.5);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: ${c.OVERLAY};
12
+ `}function g(){return`
13
+ position: relative;
14
+ width: ${l.CONTAINER_WIDTH_PERCENT};
15
+ max-width: ${l.CONTAINER_MAX_WIDTH};
16
+ height: ${l.CONTAINER_HEIGHT_PERCENT};
17
+ max-height: ${l.CONTAINER_MAX_HEIGHT};
18
+ background: white;
19
+ border-radius: 8px;
20
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
21
+ `}function _(){return`
22
+ position: absolute;
23
+ top: 10px;
24
+ right: 10px;
25
+ width: ${l.BUTTON_SIZE}px;
26
+ height: ${l.BUTTON_SIZE}px;
27
+ border: none;
28
+ background: transparent;
29
+ font-size: 24px;
30
+ cursor: pointer;
31
+ z-index: ${c.MODAL};
32
+ color: #333;
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ `}function v(){return`
37
+ width: 100%;
38
+ height: 100%;
39
+ border: none;
40
+ border-radius: 8px;
41
+ `}function y(e){return`
42
+ width: ${e?`100%`:l.DEFAULT_IFRAME_WIDTH};
43
+ max-width: ${e?`none`:`100%`};
44
+ height: ${l.MIN_HEIGHT}px;
45
+ min-height: ${l.MIN_HEIGHT}px;
46
+ border: none;
47
+ display: block;
48
+ transition: height 0.2s ease;
49
+ `}function b(){return`
50
+ position: absolute;
51
+ top: 0;
52
+ left: 0;
53
+ width: 100%;
54
+ height: 100%;
55
+ z-index: ${c.LOADER};
56
+ padding: 20px;
57
+ box-sizing: border-box;
58
+ display: flex;
59
+ flex-direction: column;
60
+ align-items: center;
61
+ `}function x(e){return`
62
+ width: ${e?`100%`:l.DEFAULT_IFRAME_WIDTH};
63
+ max-width: 100%;
64
+ margin: 0 auto;
65
+ `}function S(e){return`
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ gap: 6px;
70
+ background-color: ${e};
71
+ padding: 8px;
72
+ border-radius: 8px;
73
+ margin-bottom: 20px;
74
+ `}function C(e){return`
75
+ background: ${e};
76
+ background-size: 200% 100%;
77
+ animation: inflowpay-shimmer 1.5s infinite;
78
+ `}function w(e){return`
79
+ flex: 1;
80
+ min-width: 0;
81
+ height: 32px;
82
+ border-radius: 6px;
83
+ ${C(e)}
84
+ `}function T(e){return`
85
+ width: 21.5%;
86
+ flex-shrink: 0;
87
+ height: 32px;
88
+ border-radius: 6px;
89
+ ${C(e)}
90
+ `}function E(e){return`
91
+ width: 17.5%;
92
+ flex-shrink: 0;
93
+ height: 32px;
94
+ border-radius: 6px;
95
+ ${C(e)}
96
+ `}function D(e){return`
97
+ width: 100%;
98
+ height: 42px;
99
+ border-radius: 8px;
100
+ ${C(e)}
101
+ margin-bottom: 16px;
102
+ `}function O(){return`
103
+ display: flex;
104
+ flex-direction: column;
105
+ align-items: center;
106
+ gap: 4px;
107
+ width: 100%;
108
+ margin-top: 16px;
109
+ `}function k(e){return`
110
+ width: 10px;
111
+ height: 10px;
112
+ border-radius: 50%;
113
+ ${C(e)}
114
+ `}function A(e){return`
115
+ width: 80%;
116
+ height: 16px;
117
+ border-radius: 4px;
118
+ ${C(e)}
119
+ `}var j=`
120
+ @keyframes inflowpay-shimmer {
121
+ 0% {
122
+ background-position: -200% 0;
123
+ }
124
+ 100% {
125
+ background-position: 200% 0;
126
+ }
127
+ }
128
+ `;function M(){return`
129
+ position: fixed;
130
+ top: 0;
131
+ left: 0;
132
+ width: 100%;
133
+ height: 100%;
134
+ background-color: rgba(0, 0, 0, 0.7);
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ z-index: ${c.OVERLAY};
139
+ `}function N(){return`
140
+ position: relative;
141
+ width: ${l.CONTAINER_WIDTH_PERCENT};
142
+ max-width: ${l.CONTAINER_MAX_WIDTH};
143
+ height: ${l.CONTAINER_HEIGHT_PERCENT};
144
+ max-height: ${l.CONTAINER_MAX_HEIGHT};
145
+ background: white;
146
+ border-radius: 16px;
147
+ overflow: hidden;
148
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
149
+ display: flex;
150
+ flex-direction: column;
151
+ `}function P(){return`
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: space-between;
155
+ padding: 15px 20px;
156
+ border-bottom: 1px solid #e5e5e5;
157
+ `}function F(){return`
158
+ flex: 1;
159
+ position: relative;
160
+ overflow-y: auto;
161
+ overflow-x: hidden;
162
+ `}function I(){return`
163
+ width: 100%;
164
+ height: 100%;
165
+ border: none;
166
+ `}var L=class{constructor(e,t={}){this.loaderElement=null,this.container=e,this.config=t}show(e){e&&(e.style.display=`none`);let t=p(window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches),n=m(t),r=document.createElement(`div`);r.id=d.LOADER,r.style.cssText=b();let i=document.createElement(`div`);i.style.cssText=x(this.config.fillParent||!1);let a=document.createElement(`div`);a.style.cssText=S(t.inputBgColor);let o=document.createElement(`div`);o.className=`inflowpay-skeleton`,o.style.cssText=w(n);let s=document.createElement(`div`);s.className=`inflowpay-skeleton`,s.style.cssText=T(n);let c=document.createElement(`div`);c.className=`inflowpay-skeleton`,c.style.cssText=E(n),a.appendChild(o),a.appendChild(s),a.appendChild(c);let l=document.createElement(`div`);l.className=`inflowpay-skeleton`,l.style.cssText=D(n);let u=document.createElement(`div`);u.style.cssText=O();let f=document.createElement(`div`);f.className=`inflowpay-skeleton`,f.style.cssText=k(n);let h=document.createElement(`div`);h.className=`inflowpay-skeleton`,h.style.cssText=A(n),u.appendChild(f),u.appendChild(h),i.appendChild(a),i.appendChild(l),i.appendChild(u),r.appendChild(i),this.injectShimmerStyles(),this.container.appendChild(r),this.loaderElement=r}hide(e){let t=document.getElementById(d.LOADER);t&&t.remove(),this.loaderElement=null,e&&(e.style.display=``)}injectShimmerStyles(){if(!document.getElementById(d.LOADER_STYLES)){let e=document.createElement(`style`);e.id=d.LOADER_STYLES,e.textContent=j,document.head.appendChild(e)}}},R,z=class e{constructor(e){this.config=e}open(t,n,r){return this.config.debug&&(console.log(`[SDK] open3DSModal called with URL:`,t),console.log(`[SDK] Session ID:`,n,`Payment ID:`,r)),e.activeModal&&(console.warn(`[SDK] Closing existing modal before opening new one`),e.activeModal.overlay.remove(),e.activeModal=null),new Promise(i=>{let a=document.createElement(`div`);a.id=d.THREE_DS_OVERLAY,a.style.cssText=M();let o=document.createElement(`div`);o.style.cssText=N();let s=document.createElement(`div`);s.style.cssText=P(),s.innerHTML=`
167
+ <h3 style="margin: 0; font-size: 18px; font-weight: 600;">Secure Payment Authentication</h3>
168
+ `;let c=document.createElement(`div`);c.style.cssText=F();let l=document.createElement(`iframe`);l.src=t,l.style.cssText=I(),l.setAttribute(`allow`,`payment`),l.setAttribute(`sandbox`,`allow-forms allow-scripts allow-same-origin allow-popups`),c.appendChild(l),o.appendChild(s),o.appendChild(c),a.appendChild(o),document.body.appendChild(a),e.activeModal={overlay:a,sessionId:n,paymentId:r};let u=e=>{if(!e.data)return;if(!this.isAllowedOrigin(e.origin)){this.config.debug&&console.warn(`[SDK] Rejected 3DS message from unauthorized origin:`,e.origin);return}let t=e.data,n=t.type===`THREE_DS_COMPLETE`||t.type===`3ds-complete`,r=t.status===`success`,o=t.status===`failed`||t.status===`failure`;if(n&&r){a.remove(),window.removeEventListener(`message`,u),i(!0);return}if(r&&!n){a.remove(),window.removeEventListener(`message`,u),i(!0);return}if(n&&o||t.type===`3ds-failed`||o){a.remove(),window.removeEventListener(`message`,u),i(!1);return}};window.addEventListener(`message`,u)})}static close(t){if(!e.activeModal){console.log(`[SDK] No active modal to close`);return}if(t&&e.activeModal.sessionId!==t){console.warn(`[SDK] Session ID mismatch: webhook for ${t}, but modal is for ${e.activeModal.sessionId}. Ignoring close request.`);return}console.log(`[SDK] Closing modal for session:`,t||e.activeModal.sessionId),e.activeModal.overlay.remove(),e.activeModal=null}static isModalOpen(){return e.activeModal!==null}static getCurrentSessionId(){return e.activeModal?.sessionId||null}isAllowedOrigin(e){return!!(u.includes(e)||(this.config.environment===`sandbox`||this.config.environment===`development`)&&(e.includes(`localhost`)||e.includes(`127.0.0.1`)))}};R=z,R.activeModal=null;var B=class{static createOverlay(e,t){let n=document.createElement(`div`);n.id=d.OVERLAY,n.style.cssText=h();let r=document.createElement(`div`);r.style.cssText=g();let i=document.createElement(`button`);i.innerHTML=`×`,i.style.cssText=_(),i.onclick=t;let a=document.createElement(`iframe`);return a.src=e,a.style.cssText=v(),a.setAttribute(`allow`,`payment`),r.appendChild(i),r.appendChild(a),n.appendChild(r),n.addEventListener(`click`,e=>{e.target===n&&t()}),{overlay:n,container:r,iframe:a}}static removeOverlay(){let e=document.getElementById(d.OVERLAY);e&&e.remove()}},V=[`en`,`de`,`es`,`fr`,`it`,`nl`,`pl`,`pt`];function H(){if(typeof window>`u`)return`en`;try{let e=(navigator.language||navigator.userLanguage||``).split(`-`)[0].toLowerCase();if(V.includes(e))return e}catch{}return`en`}function U(e){return e.includes(`_local_`)||e.startsWith(`inflow_local_`)?`sandbox`:e.includes(`_prod_`)&&!e.includes(`_preprod_`)?`production`:e.includes(`_preprod_`)||e.startsWith(`inflow_preprod_`)?`preprod`:e.includes(`_dev_`)?`development`:`sandbox`}function W(e){switch(U(e)){case`production`:return f.PRODUCTION;case`preprod`:return f.PREPROD;case`development`:return f.DEVELOPMENT;default:return f.LOCAL}}var G=class{constructor(e){if(this.iframe=null,this.messageListener=null,this.containerElement=null,this.loaderManager=null,this.config=e,this.iframeUrl=e.iframeUrl||W(e.publicKey||``),this.environment=U(e.publicKey||``),this.usePopup=!e.container,e.container)if(typeof e.container==`string`){if(this.containerElement=document.querySelector(e.container),!this.containerElement)throw Error(`Container not found: ${e.container}`)}else this.containerElement=e.container;this.modalManager=new z({environment:this.environment,debug:this.config.debug})}init(){this.iframe||(this.createIframe(),this.addMessageListener())}createIframe(){let e=new URL(this.iframeUrl);this.config.publicKey&&e.searchParams.set(`publicKey`,this.config.publicKey),this.config.config?.paymentId&&e.searchParams.set(`paymentId`,this.config.config.paymentId),this.config.config?.setupId&&e.searchParams.set(`setupId`,this.config.config.setupId),this.config.locale&&e.searchParams.set(`locale`,this.config.locale);let t=e.toString();if(this.usePopup){let{overlay:e,container:n,iframe:r}=B.createOverlay(t,()=>this.close());this.iframe=r,document.body.appendChild(e),this.loaderManager=new L(n,{fillParent:this.config.config?.style?.fillParent}),this.loaderManager.show(this.iframe)}else{if(!this.containerElement)throw Error(`Container element is required for inline mode`);if(this.containerElement.innerHTML=``,this.containerElement instanceof HTMLElement){let e=this.containerElement.getAttribute(`style`)||``;e.includes(`min-height`)||(this.containerElement.style.minHeight=`${l.MIN_HEIGHT}px`),e.includes(`position`)||(this.containerElement.style.position=`relative`),e.includes(`overflow`)||(this.containerElement.style.overflow=`hidden`)}this.iframe=document.createElement(`iframe`),this.iframe.src=t,this.iframe.style.cssText=y(this.config.config?.style?.fillParent||!1),this.iframe.setAttribute(`allow`,`payment`),this.containerElement.appendChild(this.iframe),this.loaderManager=new L(this.containerElement,{fillParent:this.config.config?.style?.fillParent}),this.loaderManager.show(this.iframe)}}addMessageListener(){this.messageListener=e=>{let t=new URL(this.iframeUrl).origin,n=e.origin===t;if(n||((this.environment===`sandbox`||this.environment===`development`)&&(n=(e.origin.includes(`localhost`)||e.origin.includes(`127.0.0.1`))&&(t.includes(`localhost`)||t.includes(`127.0.0.1`))),n||(n=e.origin===`https://dev.iframe.inflowpay.com`||e.origin===`https://pre-prod.iframe.inflowpay.xyz`||e.origin===`https://iframe.inflowpay.xyz`)),!n){this.config.debug&&console.warn(`[SDK] Rejected message from unauthorized origin:`,e.origin);return}let r=e.data;if(!(!r||!r.type))switch(r.type){case`iframe-ready`:this.loaderManager&&this.loaderManager.hide(this.iframe||void 0),this.sendConfigToIframe();break;case`content-height`:r.height&&this.iframe&&(this.iframe.style.height=`${r.height}px`,this.containerElement&&(this.containerElement.style.minHeight=`${r.height}px`));break;case`close`:this.close();break;case`success`:this.config.onSuccess&&this.config.onSuccess(r.data);break;case`error`:this.config.onError&&this.config.onError(r.data);break;case`3ds-required`:if(this.config.debug&&(console.log(`[SDK] Received 3DS request:`,r.threeDsSessionUrl),console.log(`[SDK] Session ID:`,r.sessionId)),r.threeDsSessionUrl){let e=r.sessionId,t=r.paymentId||r.setupId||this.config.config?.paymentId||this.config.config?.setupId||``;r.sessionId||console.warn(`[SDK] No session ID provided by iframe`),this.config.debug&&console.log(`[SDK] Opening 3DS modal with session ID:`,e),this.modalManager.open(r.threeDsSessionUrl,e,t).then(e=>{this.config.debug&&console.log(`[SDK] 3DS modal closed, result:`,e)})}else this.config.debug&&console.error(`[SDK] 3DS required but no threeDsSessionUrl provided`);break;case`3ds-success`:this.config.debug&&(console.log(`[SDK] 3DS completed, closing modal...`),console.log(`[SDK] Session ID from webhook:`,r.sessionId)),z.close(r.sessionId);break;case`3ds-failed`:this.config.debug&&(console.log(`[SDK] 3DS failed, closing modal...`),console.log(`[SDK] Session ID from webhook:`,r.sessionId)),z.close(r.sessionId);break;default:this.config.debug&&console.log(`SDK: Received message:`,r)}},window.addEventListener(`message`,this.messageListener)}sendConfigToIframe(){if(!this.iframe||!this.iframe.contentWindow){this.iframe&&(this.iframe.onload=()=>{this.sendConfigToIframe()});return}let e={type:`sdkData`,config:{...this.config.config||{},paymentId:this.config.config?.paymentId,setupId:this.config.config?.setupId},data:{publicKey:this.config.publicKey}},t=this.getTargetOrigin();this.iframe.contentWindow.postMessage(e,t)}close(){this.config.onClose&&this.config.onClose(),this.loaderManager&&(this.loaderManager.hide(this.iframe||void 0),this.loaderManager=null),this.messageListener&&(window.removeEventListener(`message`,this.messageListener),this.messageListener=null),this.usePopup?B.removeOverlay():this.containerElement&&(this.containerElement.innerHTML=``),this.iframe=null}switchToSuccessState(e){if(this.loaderManager&&(this.loaderManager.hide(this.iframe||void 0),this.loaderManager=null),this.usePopup){this.messageListener&&(window.removeEventListener(`message`,this.messageListener),this.messageListener=null),B.removeOverlay(),this.iframe=null;return}!(this.config.config?.showDefaultSuccessUI??!0)&&this.containerElement&&(this.containerElement.innerHTML=``,this.iframe=null)}getTargetOrigin(){return this.environment===`production`||this.environment===`preprod`?new URL(this.iframeUrl).origin:`*`}destroy(){this.close()}},K=class{constructor(e,t){if(this.mounted=!1,!t.paymentId&&!t.setupId)throw Error(`CardElement: paymentId or setupId is required`);if(t.paymentId&&t.setupId)throw Error(`CardElement: paymentId and setupId are mutually exclusive`);let r;if(typeof t.container==`string`){if(r=document.querySelector(t.container),!r)throw Error(`Container not found: ${t.container}`)}else r=t.container;this.container=r;let i=!!t.setupId;this.sdk=new G({publicKey:e.publicKey,container:this.container,locale:e.locale??H(),config:{...t.paymentId&&{paymentId:t.paymentId},...t.setupId&&{setupId:t.setupId},...t.style&&{style:t.style},...t.buttonText&&{buttonText:t.buttonText},...t.placeholders&&{placeholders:t.placeholders},...t.showDefaultSuccessUI!==void 0&&{showDefaultSuccessUI:t.showDefaultSuccessUI}},onSuccess:e=>{t.onComplete&&(i?t.onComplete({status:n.SUCCESS,setupId:t.setupId}):t.onComplete({status:n.SUCCESS,paymentId:t.paymentId})),this.sdk.switchToSuccessState(e)},onError:e=>{t.onError?t.onError(e):t.onComplete&&(i?t.onComplete({status:n.FAILED,setupId:t.setupId,error:e}):t.onComplete({status:n.FAILED,paymentId:t.paymentId,error:e}))},onClose:()=>{t.onClose&&t.onClose()}})}mount(){if(this.mounted)throw Error(`CardElement is already mounted`);this.sdk.init(),this.mounted=!0}destroy(){this.mounted&&(this.sdk.destroy(),this.mounted=!1)}},q=class{constructor(e){if(!e.publicKey||typeof e.publicKey!=`string`)throw Error(`API key is required`);this.config=e,this.timeout=3e4,this.iframeUrl=W(e.publicKey);let t=U(e.publicKey);this.debug=t===`sandbox`||t===`development`}createCardElement(e){return new K({publicKey:this.config.publicKey,iframeUrl:this.iframeUrl,timeout:this.timeout,debug:this.debug,locale:this.config.locale},e)}getIframeUrl(){return this.iframeUrl}getApiKey(){return this.config.publicKey}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return K}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return q}});
169
+ //# sourceMappingURL=payment-sdk-DhJS0NRo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-sdk-DhJS0NRo.js","names":[],"sources":["../src/types.ts","../src/react/index.tsx","../src/constants/sdk-constants.ts","../src/ui/styles.ts","../src/ui/loader-manager.ts","../src/ui/modal-manager.ts","../src/ui/overlay-manager.ts","../src/utils.ts","../src/sdk.ts","../src/card-element.ts","../src/payment-sdk.ts"],"sourcesContent":["/**\n * Type definitions for SDK v2\n */\n\n// ============================================================================\n// Locale Types\n// ============================================================================\n\nexport type Locale = 'en' | 'de' | 'es' | 'fr' | 'it' | 'nl' | 'pl' | 'pt';\n\n// ============================================================================\n// Style Types\n// ============================================================================\n\nexport type FontFamily =\n | 'DM Sans'\n | 'Inter'\n | 'Poppins'\n | 'Nunito'\n | 'Work Sans'\n | 'Manrope'\n | 'Rubik'\n | 'Karla'\n | 'Figtree'\n | 'Outfit'\n | 'Space Grotesk'\n | 'Urbanist';\n\nexport type FontWeight =\n | 100\n | 200\n | 300\n | 400\n | 500\n | 600\n | 700\n | 800\n | 900\n | 'normal'\n | 'bold'\n | 'lighter'\n | 'bolder';\n\nexport type Opacity = number | string; \n\nexport interface InputContainerStyles {\n backgroundColor?: string;\n borderColor?: string;\n borderEnabled?: boolean;\n borderRadius?: string;\n}\n\nexport interface InputStyles {\n backgroundColor?: string;\n borderColor?: string;\n borderEnabled?: boolean;\n borderRadius?: string;\n textColor?: string;\n placeholderColor?: string;\n}\n\nexport interface ButtonBaseStyles {\n backgroundColor?: string;\n textColor?: string;\n borderRadius?: string;\n borderColor?: string;\n borderEnabled?: boolean;\n fontSize?: string;\n fontWeight?: FontWeight;\n opacity?: Opacity;\n}\n\nexport interface ButtonStyles extends ButtonBaseStyles {\n hover?: ButtonBaseStyles;\n disabled?: ButtonBaseStyles;\n loaderColor?: string;\n}\n\nexport interface GeneralMessageStyles {\n textColor?: string;\n backgroundColor?: string;\n borderEnabled?: boolean;\n borderRadius?: string;\n borderColor?: string;\n}\n\nexport interface SuccessUIStyles {\n backgroundColor?: string;\n primaryTextColor?: string;\n secondaryTextColor?: string;\n}\n\nexport interface ThemeStyles {\n inputContainer?: InputContainerStyles;\n input?: InputStyles;\n button?: ButtonStyles;\n disclaimerColor?: string;\n fieldErrorColor?: string;\n generalError?: GeneralMessageStyles;\n /** @deprecated generalSuccess was removed from the sdk; this has no effect. Will be removed in the next major version. */\n generalSuccess?: GeneralMessageStyles;\n successUI?: SuccessUIStyles;\n}\n\nexport interface CSSProperties {\n fontFamily?: FontFamily;\n fillParent?: boolean;\n inputContainer?: InputContainerStyles;\n input?: InputStyles;\n button?: ButtonStyles;\n disclaimerColor?: string;\n fieldErrorColor?: string;\n generalError?: GeneralMessageStyles;\n /** @deprecated generalSuccess was removed from the sdk; this has no effect. Will be removed in the next major version. */\n generalSuccess?: GeneralMessageStyles;\n successUI?: SuccessUIStyles;\n // Theme-specific overrides\n dark?: ThemeStyles;\n}\n\n// ============================================================================\n// SDK Config Types\n// ============================================================================\n\nexport interface SDKConfig {\n /** Public API key */\n publicKey?: string;\n /** Payment configuration */\n config?: PaymentConfig;\n /** Container element or selector where to mount the iframe (if not provided, creates a popup) */\n container?: string | HTMLElement;\n /** Locale for the UI. Defaults to 'en' */\n locale?: Locale;\n /** Callback when payment succeeds */\n onSuccess?: (data: TransactionData) => void;\n /** Callback when payment fails */\n onError?: (error: any) => void;\n /** Callback when user closes the payment modal */\n onClose?: () => void;\n /** Show default success UI after payment (default true). If false, only unmount iframe. */\n showDefaultSuccessUI?: boolean;\n}\n\nexport interface PaymentConfig {\n name?: string;\n amount?: number;\n currency?: string;\n paymentId?: string;\n /** ID of a CustomerPaymentMethodRequest – activates save-card mode in the iframe */\n setupId?: string;\n /** Custom styling for the card element */\n style?: CSSProperties;\n /** Custom button text (default: \"Complete Payment\") */\n buttonText?: string;\n /** Custom placeholder text for inputs */\n placeholders?: {\n cardNumber?: string;\n expiry?: string;\n cvc?: string;\n };\n /** Show default success UI after payment (default true). If false, only unmount iframe. */\n showDefaultSuccessUI?: boolean;\n [key: string]: any;\n}\n\nexport interface IframeMessage {\n type: 'sdkData' | 'success' | 'error' | 'close' | '3ds-required' | 'iframe-ready' | 'content-height' | '3ds-failed' | '3ds-success';\n data?: any;\n config?: PaymentConfig;\n threeDsSessionUrl?: string; // URL for 3DS challenge when type is '3ds-required'\n paymentId?: string; // Payment ID for payment flow\n setupId?: string; // CustomerPaymentMethodRequest ID for save-card flow\n sessionId?: string; // 3DS session ID when type is '3ds-completed'\n success?: boolean; // 3DS result when type is '3ds-result'\n height?: number; // Content height when type is 'content-height'\n}\n\nexport interface TransactionData {\n transaction?: {\n id: string;\n amount: number;\n currency: string;\n status: string;\n created_at: string;\n [key: string]: any;\n };\n [key: string]: any;\n}\n\n// ============================================================================\n// Payment Result Types\n// ============================================================================\n\nexport enum PaymentResultStatus {\n SUCCESS = 'SUCCESS',\n FAILED = 'FAILED',\n}\n\nexport enum PaymentResultErrorCode {\n THREE_DS_FAILED = 'THREE_DS_FAILED',\n PAYMENT_PROCESSING_ERROR = 'PAYMENT_PROCESSING_ERROR',\n}\n\nexport interface PaymentError {\n code: PaymentResultErrorCode;\n message: string;\n retryable: boolean;\n}\n\nexport interface PaymentResult {\n status: PaymentResultStatus;\n paymentId: string;\n error?: PaymentError;\n}\n\nexport interface SaveCardResult {\n status: PaymentResultStatus;\n setupId: string;\n error?: PaymentError;\n}\n\nexport interface CardElementState {\n complete: boolean;\n}\n\n","/**\n * InflowPay React SDK v2 - React Components\n * \n * React components that use the iframe-based SDK v2\n * Same API as the original React SDK for easy migration\n */\n\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useEffect, useRef, useState } from 'react';\nimport type { CardElement as CardElementClass, CardElementOptions } from '../card-element';\nimport type { PaymentSDKConfig } from '../payment-sdk';\nimport { PaymentSDK } from '../payment-sdk';\nimport type {\n CardElementState,\n CSSProperties,\n Locale,\n PaymentError,\n PaymentResult,\n SaveCardResult,\n} from '../types';\nimport { PaymentResultStatus } from '../types';\n\nexport interface SDKConfig {\n publicKey: string;\n /** Locale for the payment UI. Defaults to 'en' */\n locale?: Locale;\n}\n\nexport type {\n CardElementState, PaymentError,\n PaymentResult,\n SaveCardResult,\n} from '../types';\n\nexport { PaymentResultErrorCode, PaymentResultStatus } from '../types';\n\nexport interface CardElementProps {\n /** Payment ID for a payment transaction. Mutually exclusive with setupId. */\n paymentId?: string;\n /** CustomerPaymentMethodRequest ID for a save-card session. Mutually exclusive with paymentId. */\n setupId?: string;\n onReady?: () => void;\n onChange?: (state: CardElementState) => void;\n onComplete?: (result: PaymentResult | SaveCardResult) => void;\n onError?: (error: PaymentError) => void;\n style?: CSSProperties;\n buttonText?: string;\n placeholders?: {\n cardNumber?: string;\n expiry?: string;\n cvc?: string;\n };\n /** Show default success UI after completion (default true). If false, only unmount iframe. */\n showDefaultSuccessUI?: boolean;\n}\n\ninterface InflowPayContextValue {\n sdk: PaymentSDK;\n}\n\nconst InflowPayContext = createContext<InflowPayContextValue | null>(null);\n\n/**\n * InflowPayProvider - React component\n * \n * Same API as the original React SDK\n * \n * @example\n * ```tsx\n * <InflowPayProvider config={{ apiKey: 'inflow_pub_xxx' }}>\n * <CardElement\n * paymentId=\"pay_xxx\"\n * onComplete={(result) => {\n * if (result.status === 'CHECKOUT_SUCCESS') {\n * window.location.href = '/success';\n * }\n * }}\n * />\n * </InflowPayProvider>\n * ```\n */\nexport function InflowPayProvider({\n config,\n children,\n}: {\n config: SDKConfig;\n children: ReactNode;\n}) {\n const [sdk] = useState(() => {\n const sdkConfig: PaymentSDKConfig = {\n publicKey: config.publicKey,\n locale: config.locale,\n };\n return new PaymentSDK(sdkConfig);\n });\n\n return (\n <InflowPayContext.Provider value={{ sdk }}>\n {children}\n </InflowPayContext.Provider>\n );\n}\n\n/**\n * useInflowPay - Hook to access InflowPay SDK instance\n * \n * @example\n * ```tsx\n * function CustomComponent() {\n * const inflow = useInflowPay();\n * \n * const checkStatus = async () => {\n * const status = await inflow.getPaymentStatus('pay_xxx');\n * console.log(status);\n * };\n * \n * return <button onClick={checkStatus}>Check Status</button>;\n * }\n * ```\n */\nexport function useInflowPay(): PaymentSDK {\n const context = useContext(InflowPayContext);\n if (!context) {\n throw new Error('useInflowPay must be used within InflowPayProvider');\n }\n return context.sdk;\n}\n\n/**\n * CardElement - React component\n * \n * Same API as the original React SDK\n * \n * @example\n * ```tsx\n * <CardElement\n * paymentId=\"pay_xxx\"\n * onComplete={(result) => {\n * if (result.status === 'CHECKOUT_SUCCESS') {\n * window.location.href = '/success';\n * }\n * }}\n * />\n * ```\n */\nexport function CardElement(props: CardElementProps & { config?: SDKConfig }) {\n const {\n paymentId,\n setupId,\n onReady,\n onChange,\n onComplete,\n onError,\n style,\n buttonText,\n placeholders,\n showDefaultSuccessUI,\n config: propConfig,\n } = props;\n const context = useContext(InflowPayContext);\n const containerRef = useRef<HTMLDivElement>(null);\n const cardElementRef = useRef<CardElementClass | null>(null);\n const [mounted, setMounted] = useState(false);\n\n const sdk = context?.sdk || (propConfig ? new PaymentSDK({\n publicKey: propConfig.publicKey,\n locale: propConfig.locale,\n }) : null);\n\n if (!sdk) {\n throw new Error('CardElement must be used within InflowPayProvider or have a config prop');\n }\n\n useEffect(() => {\n if (!containerRef.current) {\n return;\n }\n\n if (!containerRef.current.id) {\n containerRef.current.id = `inflowpay-card-element-${Date.now()}`;\n }\n\n const cardElementOptions: CardElementOptions = {\n container: containerRef.current,\n ...(paymentId && { paymentId }),\n ...(setupId && { setupId }),\n ...(style && { style }),\n ...(buttonText && { buttonText }),\n ...(placeholders && { placeholders }),\n ...(showDefaultSuccessUI !== undefined && { showDefaultSuccessUI }),\n onComplete: (result: PaymentResult | SaveCardResult) => {\n if (onComplete) {\n onComplete(result);\n }\n },\n onError: (error) => {\n if (onError) {\n onError(error);\n } else if (onComplete) {\n if (setupId) {\n onComplete({\n status: PaymentResultStatus.FAILED,\n setupId,\n error,\n });\n } else {\n onComplete({\n status: PaymentResultStatus.FAILED,\n paymentId: paymentId as string,\n error,\n });\n }\n }\n },\n onClose: () => {\n },\n };\n\n const cardElement = sdk.createCardElement(cardElementOptions);\n cardElementRef.current = cardElement;\n cardElement.mount();\n setMounted(true);\n\n if (onReady) {\n const timer = setTimeout(() => {\n onReady();\n }, 100);\n return () => clearTimeout(timer);\n }\n }, [paymentId, setupId, sdk, onComplete, onError, onReady, style, buttonText, placeholders, showDefaultSuccessUI]);\n\n useEffect(() => {\n if (onChange && mounted) {\n onChange({ complete: false });\n }\n }, [mounted, onChange]);\n\n useEffect(() => {\n return () => {\n if (cardElementRef.current) {\n cardElementRef.current.destroy();\n cardElementRef.current = null;\n }\n };\n }, []);\n\n return (\n <div\n ref={containerRef}\n style={{\n width: style?.fillParent ? \"100%\" : \"344px\"\n }}\n />\n );\n}\n","/**\n * SDK Constants\n * Centralized constants for z-indices, dimensions, and configuration values\n */\n\nexport const Z_INDEX = {\n OVERLAY: 999999,\n MODAL: 1000000,\n LOADER: 1000,\n} as const;\n\nexport const DIMENSIONS = {\n MIN_HEIGHT: 196,\n CONTAINER_MAX_WIDTH: '500px',\n CONTAINER_WIDTH_PERCENT: '90%',\n CONTAINER_HEIGHT_PERCENT: '90%',\n CONTAINER_MAX_HEIGHT: '600px',\n BUTTON_SIZE: 30,\n DEFAULT_IFRAME_WIDTH: '344px',\n} as const;\n\nexport const ALLOWED_3DS_ORIGINS = [\n 'https://dev.api.inflowpay.xyz',\n 'https://pre-prod.api.inflowpay.xyz',\n 'https://api.inflowpay.xyz',\n] as const;\n\nexport const ELEMENT_IDS = {\n OVERLAY: 'inflowpay-sdk-overlay',\n LOADER: 'inflowpay-loader',\n LOADER_STYLES: 'inflowpay-loader-styles',\n THREE_DS_OVERLAY: 'inflowpay-3ds-overlay',\n THREE_DS_CLOSE: 'inflowpay-3ds-close',\n} as const;\n\nexport const IFRAME_URLS = {\n PRODUCTION: 'https://iframe.inflowpay.com/iframe/checkout',\n PREPROD: 'https://preprod.iframe.inflowpay.com/iframe/checkout',\n DEVELOPMENT: 'https://dev.iframe.inflowpay.com/iframe/checkout',\n LOCAL: 'http://localhost:3010/iframe/checkout',\n} as const;\n","/**\n * UI Styles\n * Centralized CSS styles for SDK components\n */\n\nimport { Z_INDEX, DIMENSIONS } from '../constants/sdk-constants';\n\n/**\n * Color scheme for dark mode support\n */\nexport interface ColorScheme {\n inputBgColor: string;\n shimmerBase: string;\n shimmerLight: string;\n}\n\n/**\n * Get color scheme based on dark mode preference\n */\nexport function getColorScheme(isDarkMode: boolean): ColorScheme {\n return {\n inputBgColor: isDarkMode ? '#2d2d2d' : '#F5F5F5',\n shimmerBase: isDarkMode ? '#3d3d3d' : '#E5E5E5',\n shimmerLight: isDarkMode ? '#4d4d4d' : '#F0F0F0',\n };\n}\n\n/**\n * Get shimmer gradient for skeleton loader\n */\nexport function getShimmerGradient(colors: ColorScheme): string {\n return `linear-gradient(90deg, ${colors.shimmerBase} 25%, ${colors.shimmerLight} 50%, ${colors.shimmerBase} 75%)`;\n}\n\n/**\n * Overlay styles for popup mode\n */\nexport function getOverlayStyles(): string {\n return `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: ${Z_INDEX.OVERLAY};\n `;\n}\n\n/**\n * Container styles for popup mode\n */\nexport function getContainerStyles(): string {\n return `\n position: relative;\n width: ${DIMENSIONS.CONTAINER_WIDTH_PERCENT};\n max-width: ${DIMENSIONS.CONTAINER_MAX_WIDTH};\n height: ${DIMENSIONS.CONTAINER_HEIGHT_PERCENT};\n max-height: ${DIMENSIONS.CONTAINER_MAX_HEIGHT};\n background: white;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n `;\n}\n\n/**\n * Close button styles\n */\nexport function getCloseButtonStyles(): string {\n return `\n position: absolute;\n top: 10px;\n right: 10px;\n width: ${DIMENSIONS.BUTTON_SIZE}px;\n height: ${DIMENSIONS.BUTTON_SIZE}px;\n border: none;\n background: transparent;\n font-size: 24px;\n cursor: pointer;\n z-index: ${Z_INDEX.MODAL};\n color: #333;\n display: flex;\n align-items: center;\n justify-content: center;\n `;\n}\n\n/**\n * Iframe styles for popup mode\n */\nexport function getPopupIframeStyles(): string {\n return `\n width: 100%;\n height: 100%;\n border: none;\n border-radius: 8px;\n `;\n}\n\n/**\n * Iframe styles for inline mode\n */\nexport function getInlineIframeStyles(fillParent: boolean): string {\n const width = fillParent ? '100%' : DIMENSIONS.DEFAULT_IFRAME_WIDTH;\n const maxWidth = fillParent ? 'none' : '100%';\n \n return `\n width: ${width};\n max-width: ${maxWidth};\n height: ${DIMENSIONS.MIN_HEIGHT}px;\n min-height: ${DIMENSIONS.MIN_HEIGHT}px;\n border: none;\n display: block;\n transition: height 0.2s ease;\n `;\n}\n\n/**\n * Loader container styles\n */\nexport function getLoaderContainerStyles(): string {\n return `\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: ${Z_INDEX.LOADER};\n padding: 20px;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n `;\n}\n\n/**\n * Skeleton card element styles\n */\nexport function getSkeletonCardStyles(fillParent: boolean): string {\n const width = fillParent ? '100%' : DIMENSIONS.DEFAULT_IFRAME_WIDTH;\n \n return `\n width: ${width};\n max-width: 100%;\n margin: 0 auto;\n `;\n}\n\n/**\n * Input wrapper styles for skeleton loader\n */\nexport function getInputWrapStyles(bgColor: string): string {\n return `\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n background-color: ${bgColor};\n padding: 8px;\n border-radius: 8px;\n margin-bottom: 20px;\n `;\n}\n\n/**\n * Skeleton element base styles\n */\nexport function getSkeletonBaseStyles(shimmerGradient: string): string {\n return `\n background: ${shimmerGradient};\n background-size: 200% 100%;\n animation: inflowpay-shimmer 1.5s infinite;\n `;\n}\n\n/**\n * Card number skeleton styles\n */\nexport function getCardNumberSkeletonStyles(shimmerGradient: string): string {\n return `\n flex: 1;\n min-width: 0;\n height: 32px;\n border-radius: 6px;\n ${getSkeletonBaseStyles(shimmerGradient)}\n `;\n}\n\n/**\n * Expiry skeleton styles\n */\nexport function getExpirySkeletonStyles(shimmerGradient: string): string {\n return `\n width: 21.5%;\n flex-shrink: 0;\n height: 32px;\n border-radius: 6px;\n ${getSkeletonBaseStyles(shimmerGradient)}\n `;\n}\n\n/**\n * CVC skeleton styles\n */\nexport function getCvcSkeletonStyles(shimmerGradient: string): string {\n return `\n width: 17.5%;\n flex-shrink: 0;\n height: 32px;\n border-radius: 6px;\n ${getSkeletonBaseStyles(shimmerGradient)}\n `;\n}\n\n/**\n * Button skeleton styles\n */\nexport function getButtonSkeletonStyles(shimmerGradient: string): string {\n return `\n width: 100%;\n height: 42px;\n border-radius: 8px;\n ${getSkeletonBaseStyles(shimmerGradient)}\n margin-bottom: 16px;\n `;\n}\n\n/**\n * Disclaimer skeleton container styles\n */\nexport function getDisclaimerSkeletonStyles(): string {\n return `\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n width: 100%;\n margin-top: 16px;\n `;\n}\n\n/**\n * Disclaimer icon skeleton styles\n */\nexport function getDisclaimerIconSkeletonStyles(shimmerGradient: string): string {\n return `\n width: 10px;\n height: 10px;\n border-radius: 50%;\n ${getSkeletonBaseStyles(shimmerGradient)}\n `;\n}\n\n/**\n * Disclaimer text skeleton styles\n */\nexport function getDisclaimerTextSkeletonStyles(shimmerGradient: string): string {\n return `\n width: 80%;\n height: 16px;\n border-radius: 4px;\n ${getSkeletonBaseStyles(shimmerGradient)}\n `;\n}\n\n/**\n * Shimmer animation keyframes\n */\nexport const SHIMMER_ANIMATION = `\n @keyframes inflowpay-shimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n }\n`;\n\n/**\n * 3DS overlay styles\n */\nexport function get3DSOverlayStyles(): string {\n return `\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.7);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: ${Z_INDEX.OVERLAY};\n `;\n}\n\n/**\n * 3DS modal styles\n */\nexport function get3DSModalStyles(): string {\n return `\n position: relative;\n width: ${DIMENSIONS.CONTAINER_WIDTH_PERCENT};\n max-width: ${DIMENSIONS.CONTAINER_MAX_WIDTH};\n height: ${DIMENSIONS.CONTAINER_HEIGHT_PERCENT};\n max-height: ${DIMENSIONS.CONTAINER_MAX_HEIGHT};\n background: white;\n border-radius: 16px;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n `;\n}\n\n/**\n * 3DS header styles\n */\nexport function get3DSHeaderStyles(): string {\n return `\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 15px 20px;\n border-bottom: 1px solid #e5e5e5;\n `;\n}\n\n/**\n * 3DS content styles\n */\nexport function get3DSContentStyles(): string {\n return `\n flex: 1;\n position: relative;\n overflow-y: auto;\n overflow-x: hidden;\n `;\n}\n\n/**\n * 3DS iframe styles\n */\nexport function get3DSIframeStyles(): string {\n return `\n width: 100%;\n height: 100%;\n border: none;\n `;\n}\n","/**\n * Loader Manager\n * Handles skeleton loading UI while iframe is initializing\n */\n\nimport { ELEMENT_IDS } from '../constants/sdk-constants';\nimport {\n getColorScheme,\n getShimmerGradient,\n getLoaderContainerStyles,\n getSkeletonCardStyles,\n getInputWrapStyles,\n getCardNumberSkeletonStyles,\n getExpirySkeletonStyles,\n getCvcSkeletonStyles,\n getButtonSkeletonStyles,\n getDisclaimerSkeletonStyles,\n getDisclaimerIconSkeletonStyles,\n getDisclaimerTextSkeletonStyles,\n SHIMMER_ANIMATION,\n} from './styles';\n\nexport interface LoaderConfig {\n fillParent?: boolean;\n}\n\nexport class LoaderManager {\n private container: HTMLElement;\n private config: LoaderConfig;\n private loaderElement: HTMLElement | null = null;\n\n constructor(container: HTMLElement, config: LoaderConfig = {}) {\n this.container = container;\n this.config = config;\n }\n\n /**\n * Show skeleton loader\n */\n show(iframe?: HTMLIFrameElement): void {\n // Hide iframe while loader is showing\n if (iframe) {\n iframe.style.display = 'none';\n }\n\n // Detect dark mode\n const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;\n\n // Get color scheme\n const colors = getColorScheme(isDarkMode);\n const shimmerGradient = getShimmerGradient(colors);\n\n // Create loader container\n const loader = document.createElement('div');\n loader.id = ELEMENT_IDS.LOADER;\n loader.style.cssText = getLoaderContainerStyles();\n\n // Create skeleton card element\n const skeletonCard = document.createElement('div');\n skeletonCard.style.cssText = getSkeletonCardStyles(this.config.fillParent || false);\n\n // Create input wrapper\n const inputWrap = document.createElement('div');\n inputWrap.style.cssText = getInputWrapStyles(colors.inputBgColor);\n\n // Create card number skeleton\n const cardNumberSkeleton = document.createElement('div');\n cardNumberSkeleton.className = 'inflowpay-skeleton';\n cardNumberSkeleton.style.cssText = getCardNumberSkeletonStyles(shimmerGradient);\n\n // Create expiry skeleton\n const expirySkeleton = document.createElement('div');\n expirySkeleton.className = 'inflowpay-skeleton';\n expirySkeleton.style.cssText = getExpirySkeletonStyles(shimmerGradient);\n\n // Create CVC skeleton\n const cvcSkeleton = document.createElement('div');\n cvcSkeleton.className = 'inflowpay-skeleton';\n cvcSkeleton.style.cssText = getCvcSkeletonStyles(shimmerGradient);\n\n // Assemble input wrapper\n inputWrap.appendChild(cardNumberSkeleton);\n inputWrap.appendChild(expirySkeleton);\n inputWrap.appendChild(cvcSkeleton);\n\n // Create button skeleton\n const buttonSkeleton = document.createElement('div');\n buttonSkeleton.className = 'inflowpay-skeleton';\n buttonSkeleton.style.cssText = getButtonSkeletonStyles(shimmerGradient);\n\n // Create disclaimer skeleton\n const disclaimerSkeleton = document.createElement('div');\n disclaimerSkeleton.style.cssText = getDisclaimerSkeletonStyles();\n\n const disclaimerIconSkeleton = document.createElement('div');\n disclaimerIconSkeleton.className = 'inflowpay-skeleton';\n disclaimerIconSkeleton.style.cssText = getDisclaimerIconSkeletonStyles(shimmerGradient);\n\n const disclaimerTextSkeleton = document.createElement('div');\n disclaimerTextSkeleton.className = 'inflowpay-skeleton';\n disclaimerTextSkeleton.style.cssText = getDisclaimerTextSkeletonStyles(shimmerGradient);\n\n disclaimerSkeleton.appendChild(disclaimerIconSkeleton);\n disclaimerSkeleton.appendChild(disclaimerTextSkeleton);\n\n // Assemble skeleton card\n skeletonCard.appendChild(inputWrap);\n skeletonCard.appendChild(buttonSkeleton);\n skeletonCard.appendChild(disclaimerSkeleton);\n\n // Add to loader\n loader.appendChild(skeletonCard);\n\n // Inject shimmer animation styles if not already present\n this.injectShimmerStyles();\n\n // Add loader to container\n this.container.appendChild(loader);\n this.loaderElement = loader;\n }\n\n /**\n * Hide loader\n */\n hide(iframe?: HTMLIFrameElement): void {\n const loader = document.getElementById(ELEMENT_IDS.LOADER);\n if (loader) {\n loader.remove();\n }\n this.loaderElement = null;\n\n // Show iframe again when loader is hidden\n if (iframe) {\n iframe.style.display = '';\n }\n }\n\n /**\n * Inject shimmer animation styles into document head\n */\n private injectShimmerStyles(): void {\n if (!document.getElementById(ELEMENT_IDS.LOADER_STYLES)) {\n const style = document.createElement('style');\n style.id = ELEMENT_IDS.LOADER_STYLES;\n style.textContent = SHIMMER_ANIMATION;\n document.head.appendChild(style);\n }\n }\n}\n","/**\n * Modal Manager\n * Handles 3DS authentication modal\n */\n\nimport { ALLOWED_3DS_ORIGINS, DIMENSIONS, ELEMENT_IDS } from '../constants/sdk-constants';\nimport {\n get3DSContentStyles,\n get3DSHeaderStyles,\n get3DSIframeStyles,\n get3DSModalStyles,\n get3DSOverlayStyles,\n getCloseButtonStyles,\n} from './styles';\n\nexport interface ModalConfig {\n environment: 'sandbox' | 'production' | 'development' | 'preprod';\n debug?: boolean;\n}\n\nexport class ModalManager {\n private config: ModalConfig;\n private static activeModal: {\n overlay: HTMLElement;\n sessionId: string;\n paymentId: string;\n } | null = null;\n\n constructor(config: ModalConfig) {\n this.config = config;\n }\n\n /**\n * Open 3DS authentication modal\n * Returns a Promise that resolves with the authentication result\n */\n open(challengeUrl: string, sessionId: string, paymentId: string): Promise<boolean> {\n if (this.config.debug) {\n console.log('[SDK] open3DSModal called with URL:', challengeUrl);\n console.log('[SDK] Session ID:', sessionId, 'Payment ID:', paymentId);\n }\n\n // If there's already a modal open, close it first\n if (ModalManager.activeModal) {\n console.warn('[SDK] Closing existing modal before opening new one');\n ModalManager.activeModal.overlay.remove();\n ModalManager.activeModal = null;\n }\n\n return new Promise((resolve) => {\n // Create overlay\n const overlay = document.createElement('div');\n overlay.id = ELEMENT_IDS.THREE_DS_OVERLAY;\n overlay.style.cssText = get3DSOverlayStyles();\n\n // Create modal\n const modal = document.createElement('div');\n modal.style.cssText = get3DSModalStyles();\n\n // Create header\n const header = document.createElement('div');\n header.style.cssText = get3DSHeaderStyles();\n header.innerHTML = `\n <h3 style=\"margin: 0; font-size: 18px; font-weight: 600;\">Secure Payment Authentication</h3>\n `;\n\n // Create content with iframe\n const content = document.createElement('div');\n content.style.cssText = get3DSContentStyles();\n \n const iframe = document.createElement('iframe');\n iframe.src = challengeUrl;\n iframe.style.cssText = get3DSIframeStyles();\n iframe.setAttribute('allow', 'payment');\n iframe.setAttribute('sandbox', 'allow-forms allow-scripts allow-same-origin allow-popups');\n content.appendChild(iframe);\n\n // Assemble modal\n modal.appendChild(header);\n modal.appendChild(content);\n overlay.appendChild(modal);\n document.body.appendChild(overlay);\n\n // Store as THE active modal\n ModalManager.activeModal = {\n overlay,\n sessionId,\n paymentId\n };\n\n // Message handler for 3DS completion\n const messageHandler = (event: MessageEvent) => {\n if (!event.data) return;\n\n // Validate origin\n if (!this.isAllowedOrigin(event.origin)) {\n if (this.config.debug) {\n console.warn('[SDK] Rejected 3DS message from unauthorized origin:', event.origin);\n }\n return;\n }\n\n const data = event.data;\n const is3DSComplete = data.type === 'THREE_DS_COMPLETE' || data.type === '3ds-complete';\n const isSuccess = data.status === 'success';\n const isFailure = data.status === 'failed' || data.status === 'failure';\n\n // Success case\n if (is3DSComplete && isSuccess) {\n overlay.remove();\n window.removeEventListener('message', messageHandler);\n resolve(true);\n return;\n }\n\n // Also handle legacy format\n if (isSuccess && !is3DSComplete) {\n overlay.remove();\n window.removeEventListener('message', messageHandler);\n resolve(true);\n return;\n }\n\n // Failure case\n if ((is3DSComplete && isFailure) || data.type === '3ds-failed' || isFailure) {\n overlay.remove();\n window.removeEventListener('message', messageHandler);\n resolve(false);\n return;\n }\n };\n\n window.addEventListener('message', messageHandler);\n });\n }\n\n /**\n * Close 3DS modal (called from external events like WebSocket)\n * @param sessionId - Optional session ID to validate before closing\n */\n static close(sessionId?: string): void {\n if (!ModalManager.activeModal) {\n // No modal is open - nothing to do\n console.log('[SDK] No active modal to close');\n return;\n }\n\n // If sessionId is provided, validate it matches the current modal\n if (sessionId && ModalManager.activeModal.sessionId !== sessionId) {\n console.warn(\n `[SDK] Session ID mismatch: webhook for ${sessionId}, but modal is for ${ModalManager.activeModal.sessionId}. Ignoring close request.`\n );\n return;\n }\n\n // Close the modal\n console.log('[SDK] Closing modal for session:', sessionId || ModalManager.activeModal.sessionId);\n ModalManager.activeModal.overlay.remove();\n ModalManager.activeModal = null;\n }\n\n /**\n * Check if a modal is currently open\n */\n static isModalOpen(): boolean {\n return ModalManager.activeModal !== null;\n }\n\n /**\n * Get current session ID\n */\n static getCurrentSessionId(): string | null {\n return ModalManager.activeModal?.sessionId || null;\n }\n\n /**\n * Check if origin is allowed for 3DS messages\n */\n private isAllowedOrigin(origin: string): boolean {\n // Check exact match with allowed origins\n if ((ALLOWED_3DS_ORIGINS as readonly string[]).includes(origin)) {\n return true;\n }\n\n // Allow localhost in dev/sandbox environments\n if (this.config.environment === 'sandbox' || this.config.environment === 'development') {\n if (origin.includes('localhost') || origin.includes('127.0.0.1')) {\n return true;\n }\n }\n\n return false;\n }\n}\n","/**\n * Overlay Manager\n * Handles popup overlay UI for payment iframe\n */\n\nimport { ELEMENT_IDS } from '../constants/sdk-constants';\nimport {\n getOverlayStyles,\n getContainerStyles,\n getCloseButtonStyles,\n getPopupIframeStyles,\n} from './styles';\n\nexport interface OverlayElements {\n overlay: HTMLElement;\n container: HTMLElement;\n iframe: HTMLIFrameElement;\n}\n\nexport class OverlayManager {\n /**\n * Create popup overlay with iframe\n */\n static createOverlay(\n iframeSrc: string,\n onClose: () => void\n ): OverlayElements {\n // Create overlay\n const overlay = document.createElement('div');\n overlay.id = ELEMENT_IDS.OVERLAY;\n overlay.style.cssText = getOverlayStyles();\n\n // Create iframe container\n const container = document.createElement('div');\n container.style.cssText = getContainerStyles();\n\n // Create close button\n const closeButton = document.createElement('button');\n closeButton.innerHTML = '×';\n closeButton.style.cssText = getCloseButtonStyles();\n closeButton.onclick = onClose;\n\n // Create iframe\n const iframe = document.createElement('iframe');\n iframe.src = iframeSrc;\n iframe.style.cssText = getPopupIframeStyles();\n iframe.setAttribute('allow', 'payment');\n\n // Assemble structure\n container.appendChild(closeButton);\n container.appendChild(iframe);\n overlay.appendChild(container);\n\n // Close on overlay click (but not on container click)\n overlay.addEventListener('click', (e) => {\n if (e.target === overlay) {\n onClose();\n }\n });\n\n return { overlay, container, iframe };\n }\n\n /**\n * Remove overlay from DOM\n */\n static removeOverlay(): void {\n const overlay = document.getElementById(ELEMENT_IDS.OVERLAY);\n if (overlay) {\n overlay.remove();\n }\n }\n}\n","/**\n * Utility functions\n */\n\nimport { IFRAME_URLS } from './constants/sdk-constants';\nimport type { Locale } from './types';\n\nexport const SUPPORTED_LOCALES: Locale[] = ['en', 'de', 'es', 'fr', 'it', 'nl', 'pl', 'pt'];\n\n/**\n * Detects the browser's language and returns a supported locale if available.\n * Falls back to 'en' if the browser language is not supported.\n */\nexport function detectBrowserLocale(): Locale {\n if (typeof window === 'undefined') {\n return 'en';\n }\n\n try {\n const browserLang =\n navigator.language ||\n (navigator as { userLanguage?: string }).userLanguage ||\n '';\n\n const primaryLang = browserLang.split('-')[0].toLowerCase();\n\n if (SUPPORTED_LOCALES.includes(primaryLang as Locale)) {\n return primaryLang as Locale;\n }\n } catch {\n // If detection fails, fall back to default\n }\n\n return 'en';\n}\n\n/**\n * Determines the environment from the API key format\n */\nexport function getEnvironmentFromApiKey(publicKey: string): 'sandbox' | 'production' | 'development' | 'preprod' {\n if (publicKey.includes('_local_') || publicKey.startsWith('inflow_local_')) {\n return 'sandbox';\n } else if (publicKey.includes('_prod_') && !publicKey.includes('_preprod_')) {\n return 'production';\n } else if (publicKey.includes('_preprod_') || publicKey.startsWith('inflow_preprod_')) {\n return 'preprod';\n } else if (publicKey.includes('_dev_')) {\n return 'development';\n }\n return 'sandbox';\n}\n\n/**\n * Gets the iframe URL based on the API key environment\n * This function auto-detects the correct iframe URL from the API key format\n * \n * @param publicKey - The public API key\n * @returns The iframe URL for the detected environment\n * \n * @example\n * ```typescript\n * const url = getIframeUrlFromApiKey('inflow_pub_prod_xxx');\n * // Returns: 'https://api.inflowpay.xyz/iframe/checkout'\n * \n * const url = getIframeUrlFromApiKey('inflow_pub_local_xxx');\n * // Returns: 'http://localhost:3000/iframe/checkout'\n * ```\n */\nexport function getIframeUrlFromApiKey(publicKey: string): string {\n const environment = getEnvironmentFromApiKey(publicKey);\n \n switch (environment) {\n case 'production':\n return IFRAME_URLS.PRODUCTION;\n case 'preprod':\n return IFRAME_URLS.PREPROD;\n case 'development':\n return IFRAME_URLS.DEVELOPMENT;\n case 'sandbox':\n default:\n return IFRAME_URLS.LOCAL;\n }\n}\n","/**\n * InflowPay SDK v2 - Iframe-based Payment SDK\n * \n * This SDK creates an iframe and communicates with a React payment application\n * using postMessage API for secure cross-origin communication.\n */\n\nimport { DIMENSIONS } from './constants/sdk-constants';\nimport type { IframeMessage, SDKConfig, TransactionData } from './types';\nimport { LoaderManager } from './ui/loader-manager';\nimport { ModalManager } from './ui/modal-manager';\nimport { OverlayManager } from './ui/overlay-manager';\nimport { getInlineIframeStyles } from './ui/styles';\nimport { getEnvironmentFromApiKey, getIframeUrlFromApiKey } from './utils';\nexport class SDK {\n private iframe: HTMLIFrameElement | null = null;\n private iframeUrl: string;\n private config: SDKConfig & { iframeUrl?: string; debug?: boolean };\n private messageListener: ((event: MessageEvent) => void) | null = null;\n private containerElement: HTMLElement | null = null;\n private usePopup: boolean;\n private environment: 'sandbox' | 'production' | 'development' | 'preprod';\n private loaderManager: LoaderManager | null = null;\n private modalManager: ModalManager;\n\n constructor(config: SDKConfig & { iframeUrl?: string; debug?: boolean }) {\n this.config = config;\n\n // Auto-detect iframe URL from API key, or use provided iframeUrl (internal use only)\n this.iframeUrl = config.iframeUrl || getIframeUrlFromApiKey(config.publicKey || '');\n this.environment = getEnvironmentFromApiKey(config.publicKey || '');\n\n // Determine if we should use popup or inline\n this.usePopup = !config.container;\n\n // Resolve container if provided\n if (config.container) {\n if (typeof config.container === 'string') {\n this.containerElement = document.querySelector(config.container);\n if (!this.containerElement) {\n throw new Error(`Container not found: ${config.container}`);\n }\n } else {\n this.containerElement = config.container;\n }\n }\n\n // Initialize modal manager\n this.modalManager = new ModalManager({\n environment: this.environment,\n debug: this.config.debug,\n });\n }\n\n /**\n * Initialize and open the payment iframe\n */\n init(): void {\n if (this.iframe) {\n return;\n }\n\n this.createIframe();\n this.addMessageListener();\n }\n\n /**\n * Create and append the iframe to the document\n */\n private createIframe(): void {\n // Build iframe URL with API key and paymentId and locale as query parameters\n const url = new URL(this.iframeUrl);\n if (this.config.publicKey) {\n url.searchParams.set('publicKey', this.config.publicKey);\n }\n if (this.config.config?.paymentId) {\n url.searchParams.set('paymentId', this.config.config.paymentId);\n }\n if (this.config.config?.setupId) {\n url.searchParams.set('setupId', this.config.config.setupId);\n }\n if (this.config.locale) {\n url.searchParams.set('locale', this.config.locale);\n }\n const iframeSrc = url.toString();\n\n if (this.usePopup) {\n // Use OverlayManager to create popup UI\n const { overlay, container, iframe } = OverlayManager.createOverlay(\n iframeSrc,\n () => this.close()\n );\n\n this.iframe = iframe;\n document.body.appendChild(overlay);\n\n // Show loader\n this.loaderManager = new LoaderManager(container, {\n fillParent: this.config.config?.style?.fillParent,\n });\n this.loaderManager.show(this.iframe);\n } else {\n // Inline mode - mount directly in container\n if (!this.containerElement) {\n throw new Error('Container element is required for inline mode');\n }\n\n // Clear container\n this.containerElement.innerHTML = '';\n\n // Set container styles for seamless integration\n if (this.containerElement instanceof HTMLElement) {\n const currentStyle = this.containerElement.getAttribute('style') || '';\n if (!currentStyle.includes('min-height')) {\n this.containerElement.style.minHeight = `${DIMENSIONS.MIN_HEIGHT}px`;\n }\n if (!currentStyle.includes('position')) {\n this.containerElement.style.position = 'relative';\n }\n if (!currentStyle.includes('overflow')) {\n this.containerElement.style.overflow = 'hidden';\n }\n }\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = iframeSrc;\n this.iframe.style.cssText = getInlineIframeStyles(\n this.config.config?.style?.fillParent || false\n );\n this.iframe.setAttribute('allow', 'payment');\n\n // Append to container\n this.containerElement.appendChild(this.iframe);\n\n // Show loader\n this.loaderManager = new LoaderManager(this.containerElement, {\n fillParent: this.config.config?.style?.fillParent,\n });\n this.loaderManager.show(this.iframe);\n }\n }\n\n /**\n * Add message listener for communication with iframe\n */\n private addMessageListener(): void {\n this.messageListener = (event: MessageEvent) => {\n const allowedOrigin = new URL(this.iframeUrl).origin;\n const isExactMatch = event.origin === allowedOrigin;\n\n let isAllowedOrigin = isExactMatch;\n\n if (!isAllowedOrigin) {\n if (this.environment === 'sandbox' || this.environment === 'development') {\n const isLocalhostDev =\n (event.origin.includes('localhost') || event.origin.includes('127.0.0.1')) &&\n (allowedOrigin.includes('localhost') || allowedOrigin.includes('127.0.0.1'));\n isAllowedOrigin = isLocalhostDev;\n }\n\n if (!isAllowedOrigin) {\n const isAllowedApiOrigin =\n event.origin === 'https://dev.iframe.inflowpay.com' ||\n event.origin === 'https://pre-prod.iframe.inflowpay.xyz' ||\n event.origin === 'https://iframe.inflowpay.xyz';\n isAllowedOrigin = isAllowedApiOrigin;\n }\n }\n\n if (!isAllowedOrigin) {\n if (this.config.debug) {\n console.warn('[SDK] Rejected message from unauthorized origin:', event.origin);\n }\n return;\n }\n\n const data = event.data as IframeMessage;\n\n if (!data || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'iframe-ready':\n // Wait for iframe's javascript to be ready before sending config\n if (this.loaderManager) {\n this.loaderManager.hide(this.iframe || undefined);\n }\n this.sendConfigToIframe();\n break;\n\n case 'content-height':\n // Adjust iframe height based on content\n if (data.height && this.iframe) {\n this.iframe.style.height = `${data.height}px`;\n if (this.containerElement) {\n this.containerElement.style.minHeight = `${data.height}px`;\n }\n }\n break;\n\n case 'close':\n this.close();\n break;\n\n case 'success':\n if (this.config.onSuccess) {\n this.config.onSuccess(data.data);\n }\n break;\n\n case 'error':\n if (this.config.onError) {\n this.config.onError(data.data);\n }\n break;\n\n case '3ds-required':\n // Iframe requests SDK to open 3DS popup\n if (this.config.debug) {\n console.log('[SDK] Received 3DS request:', data.threeDsSessionUrl);\n console.log('[SDK] Session ID:', data.sessionId);\n }\n if (data.threeDsSessionUrl) {\n const sessionId = data.sessionId;\n const requestId =\n data.paymentId ||\n data.setupId ||\n this.config.config?.paymentId ||\n this.config.config?.setupId ||\n '';\n\n if (!data.sessionId) {\n console.warn('[SDK] No session ID provided by iframe');\n }\n\n if (this.config.debug) {\n console.log('[SDK] Opening 3DS modal with session ID:', sessionId);\n }\n\n this.modalManager.open(data.threeDsSessionUrl, sessionId as string, requestId).then((success) => {\n if (this.config.debug) {\n console.log('[SDK] 3DS modal closed, result:', success);\n }\n // No longer send message back to iframe - unidirectional flow\n });\n } else {\n if (this.config.debug) {\n console.error('[SDK] 3DS required but no threeDsSessionUrl provided');\n }\n }\n break;\n\n case '3ds-success':\n // 3DS challenge completed via WebSocket, close modal automatically\n if (this.config.debug) {\n console.log('[SDK] 3DS completed, closing modal...');\n console.log('[SDK] Session ID from webhook:', data.sessionId);\n }\n // Pass session ID to validate before closing\n ModalManager.close(data.sessionId);\n break;\n\n case '3ds-failed':\n // 3DS challenge completed via WebSocket, close modal automatically\n if (this.config.debug) {\n console.log('[SDK] 3DS failed, closing modal...');\n console.log('[SDK] Session ID from webhook:', data.sessionId);\n }\n // Pass session ID to validate before closing\n ModalManager.close(data.sessionId);\n break;\n\n default:\n if (this.config.debug) {\n console.log('SDK: Received message:', data);\n }\n\n\n }\n };\n\n window.addEventListener('message', this.messageListener);\n }\n\n /**\n * Send configuration to the iframe\n */\n private sendConfigToIframe(): void {\n if (!this.iframe || !this.iframe.contentWindow) {\n // Wait for iframe to load\n if (this.iframe) {\n this.iframe.onload = () => {\n this.sendConfigToIframe();\n };\n }\n return;\n }\n\n const message: IframeMessage = {\n type: 'sdkData',\n config: {\n ...(this.config.config || {}),\n paymentId: this.config.config?.paymentId,\n setupId: this.config.config?.setupId,\n },\n data: {\n publicKey: this.config.publicKey,\n },\n };\n\n const targetOrigin = this.getTargetOrigin();\n this.iframe.contentWindow.postMessage(message, targetOrigin);\n }\n\n\n /**\n * Close the iframe and cleanup\n */\n private close(): void {\n if (this.config.onClose) {\n this.config.onClose();\n }\n\n // Hide loader\n if (this.loaderManager) {\n this.loaderManager.hide(this.iframe || undefined);\n this.loaderManager = null;\n }\n\n // Remove message listener\n if (this.messageListener) {\n window.removeEventListener('message', this.messageListener);\n this.messageListener = null;\n }\n\n if (this.usePopup) {\n // Remove overlay\n OverlayManager.removeOverlay();\n } else {\n // Clear container\n if (this.containerElement) {\n this.containerElement.innerHTML = '';\n }\n }\n\n this.iframe = null;\n }\n\n /**\n * Handle success state: when showDefaultSuccessUI is true, iframe shows success UI\n * and we keep it mounted; when false, we unmount so the parent can render their own.\n */\n public switchToSuccessState(_result: TransactionData): void {\n // Hide loader\n if (this.loaderManager) {\n this.loaderManager.hide(this.iframe || undefined);\n this.loaderManager = null;\n }\n\n if (this.usePopup) {\n if (this.messageListener) {\n window.removeEventListener('message', this.messageListener);\n this.messageListener = null;\n }\n OverlayManager.removeOverlay();\n this.iframe = null;\n return;\n }\n\n const showDefault = this.config.config?.showDefaultSuccessUI ?? true;\n\n if (!showDefault && this.containerElement) {\n this.containerElement.innerHTML = '';\n this.iframe = null;\n }\n // When showDefault is true, keep iframe mounted (success UI is shown inside iframe)\n }\n\n /**\n * Get target origin for postMessage based on environment\n * In production/pre-prod: use exact origin for security\n * In dev/sandbox: use wildcard for development flexibility\n */\n private getTargetOrigin(): string {\n if (this.environment === 'production' || this.environment === 'preprod') {\n return new URL(this.iframeUrl).origin;\n }\n return '*';\n }\n\n /**\n * Public method to close the iframe\n */\n public destroy(): void {\n this.close();\n }\n}\n\n","/**\n * CardElement - Iframe-based payment element\n * \n * Mounts an iframe with the payment checkout form\n */\n\nimport { PaymentResult, PaymentResultStatus } from './react';\nimport { SDK } from './sdk';\n\nimport type { CSSProperties, Locale, SaveCardResult } from './types';\nimport { detectBrowserLocale } from './utils';\n\nexport interface CardElementOptions {\n /** Container element or CSS selector where the iframe will be mounted */\n container: string | HTMLElement;\n /** Payment ID for a payment transaction. Mutually exclusive with setupId. */\n paymentId?: string;\n /** CustomerPaymentMethodRequest ID for a save-card session. Mutually exclusive with paymentId. */\n setupId?: string;\n /** Callback when the operation completes (payment or card setup) */\n onComplete?: (result: PaymentResult | SaveCardResult) => void;\n /** Callback when the operation fails */\n onError?: (error: any) => void;\n /** Callback when user closes the element */\n onClose?: () => void;\n /** Custom styling for the card element */\n style?: CSSProperties;\n /** Custom button text (default: \"Complete Payment\") */\n buttonText?: string;\n /** Custom placeholder text for inputs */\n placeholders?: {\n cardNumber?: string;\n expiry?: string;\n cvc?: string;\n };\n /** Show default success UI after completion (default true). If false, only unmount iframe. */\n showDefaultSuccessUI?: boolean;\n}\n\ninterface InternalSDKConfig {\n publicKey: string;\n iframeUrl: string;\n timeout: number;\n debug: boolean;\n locale?: Locale;\n}\n\nexport class CardElement {\n private sdk: SDK;\n private container: HTMLElement;\n private mounted: boolean = false;\n\n constructor(\n config: InternalSDKConfig,\n options: CardElementOptions\n ) {\n if (!options.paymentId && !options.setupId) {\n throw new Error('CardElement: paymentId or setupId is required');\n }\n if (options.paymentId && options.setupId) {\n throw new Error('CardElement: paymentId and setupId are mutually exclusive');\n }\n\n let containerElement: HTMLElement | null;\n if (typeof options.container === 'string') {\n containerElement = document.querySelector(options.container);\n if (!containerElement) {\n throw new Error(`Container not found: ${options.container}`);\n }\n } else {\n containerElement = options.container;\n }\n this.container = containerElement;\n\n const isSaveCardMode = Boolean(options.setupId);\n\n this.sdk = new SDK({\n publicKey: config.publicKey,\n container: this.container,\n locale: config.locale ?? detectBrowserLocale(),\n config: {\n ...(options.paymentId && { paymentId: options.paymentId }),\n ...(options.setupId && { setupId: options.setupId }),\n ...(options.style && { style: options.style }),\n ...(options.buttonText && { buttonText: options.buttonText }),\n ...(options.placeholders && { placeholders: options.placeholders }),\n ...(options.showDefaultSuccessUI !== undefined && {\n showDefaultSuccessUI: options.showDefaultSuccessUI,\n }),\n },\n onSuccess: (data) => {\n if (options.onComplete) {\n if (isSaveCardMode) {\n options.onComplete({\n status: PaymentResultStatus.SUCCESS,\n setupId: options.setupId as string,\n } satisfies SaveCardResult);\n } else {\n options.onComplete({\n status: PaymentResultStatus.SUCCESS,\n paymentId: options.paymentId as string,\n } satisfies PaymentResult);\n }\n }\n this.sdk.switchToSuccessState(data);\n },\n onError: (error) => {\n if (options.onError) {\n options.onError(error);\n } else if (options.onComplete) {\n if (isSaveCardMode) {\n options.onComplete({\n status: PaymentResultStatus.FAILED,\n setupId: options.setupId as string,\n error,\n } satisfies SaveCardResult);\n } else {\n options.onComplete({\n status: PaymentResultStatus.FAILED,\n paymentId: options.paymentId as string,\n error,\n } satisfies PaymentResult);\n }\n }\n },\n onClose: () => {\n if (options.onClose) {\n options.onClose();\n }\n },\n });\n }\n\n /**\n * Mount the CardElement to the DOM\n * This will create and display the iframe\n */\n mount(): void {\n if (this.mounted) {\n throw new Error('CardElement is already mounted');\n }\n\n this.sdk.init();\n this.mounted = true;\n }\n\n /**\n * Destroy the CardElement and cleanup\n */\n destroy(): void {\n if (this.mounted) {\n this.sdk.destroy();\n this.mounted = false;\n }\n }\n}\n\n","/**\n * InflowPay Payment SDK v2\n * \n * Provider class that manages global SDK configuration\n * Similar to the original SDK but uses iframe-based payment flow\n */\n\nimport { CardElement } from './card-element';\nimport type { PaymentResult } from './react';\nimport type { CSSProperties, Locale, SaveCardResult } from './types';\nimport { getEnvironmentFromApiKey, getIframeUrlFromApiKey } from './utils';\n\nexport interface PaymentSDKConfig {\n /** Public API key */\n publicKey: string;\n /** Locale for the UI. Defaults to 'en' */\n locale?: Locale;\n}\n\nexport class PaymentSDK {\n private config: PaymentSDKConfig;\n private iframeUrl: string;\n private timeout: number;\n private debug: boolean;\n\n /**\n * Initialize the InflowPay Payment SDK\n * \n * @param config - SDK configuration\n * \n * @example\n * ```typescript\n * const sdk = new PaymentSDK({\n * apiKey: 'inflow_pub_local_xxx'\n * });\n * ```\n */\n constructor(config: PaymentSDKConfig) {\n // Validate API key\n if (!config.publicKey || typeof config.publicKey !== 'string') {\n throw new Error('API key is required');\n }\n\n this.config = config;\n this.timeout = 30000;\n\n // Auto-detect iframe URL from API key using utility function\n this.iframeUrl = getIframeUrlFromApiKey(config.publicKey);\n\n // Auto-enable debug in development and sandbox environments\n const environment = getEnvironmentFromApiKey(config.publicKey);\n this.debug = environment === 'sandbox' || environment === 'development';\n }\n\n /**\n * Create a CardElement for iframe-based payment or save-card UI.\n *\n * Pass `paymentId` for a payment transaction, or `setupId` to save\n * a card without charging. The two options are mutually exclusive.\n *\n * @param options - CardElement configuration\n * @returns CardElement instance\n *\n * @example – payment\n * ```typescript\n * const cardElement = sdk.createCardElement({\n * container: '#card-container',\n * paymentId: 'pay_123',\n * onComplete: (result) => { ... }\n * });\n * cardElement.mount();\n * ```\n *\n * @example – save card\n * ```typescript\n * const cardElement = sdk.createCardElement({\n * container: '#card-container',\n * setupId: 'cus_pm_req_123',\n * onComplete: (result) => { ... }\n * });\n * cardElement.mount();\n * ```\n */\n createCardElement(options: {\n container: string | HTMLElement;\n /** Payment ID – use for payment flow */\n paymentId?: string;\n /** CustomerPaymentMethodRequest ID – use for save-card flow */\n setupId?: string;\n style?: CSSProperties;\n buttonText?: string;\n placeholders?: {\n cardNumber?: string;\n expiry?: string;\n cvc?: string;\n };\n onComplete?: (result: PaymentResult | SaveCardResult) => void;\n onError?: (error: any) => void;\n onClose?: () => void;\n showDefaultSuccessUI?: boolean;\n }): CardElement {\n return new CardElement(\n {\n publicKey: this.config.publicKey,\n iframeUrl: this.iframeUrl,\n timeout: this.timeout,\n debug: this.debug,\n locale: this.config.locale,\n },\n options,\n );\n }\n\n /**\n * Get the iframe URL being used\n */\n getIframeUrl(): string {\n return this.iframeUrl;\n }\n\n /**\n * Get the API key\n */\n getApiKey(): string {\n return this.config.publicKey;\n }\n}\n\n"],"mappings":"sDAiMA,IAAY,EAAL,SAAA,EAAA,OACL,GAAA,QAAA,UACA,EAAA,OAAA,eACD,CAEW,EAAL,SAAA,EAAA,OACL,GAAA,gBAAA,kBACA,EAAA,yBAAA,iCACD,CC7IK,GAAA,EAAA,EAAA,eAA+D,KAAK,CAqB1E,SAAgB,EAAkB,CAChC,SACA,YAIC,CACD,GAAM,CAAC,IAAA,EAAA,EAAA,cAKE,IAAI,EAJyB,CAClC,UAAW,EAAO,UAClB,OAAQ,EAAO,OAChB,CAC+B,CAChC,CAEF,OACE,EAAA,EAAA,KAAC,EAAiB,SAAlB,CAA2B,MAAO,CAAE,MAAK,CACtC,WACyB,CAAA,CAqBhC,SAAgB,GAA2B,CACzC,IAAM,GAAA,EAAA,EAAA,YAAqB,EAAiB,CAC5C,GAAI,CAAC,EACH,MAAU,MAAM,qDAAqD,CAEvE,OAAO,EAAQ,IAoBjB,SAAgB,EAAY,EAAkD,CAC5E,GAAM,CACJ,YACA,UACA,UACA,WACA,aACA,UACA,QACA,aACA,eACA,uBACA,OAAQ,GACN,EACE,GAAA,EAAA,EAAA,YAAqB,EAAiB,CACtC,GAAA,EAAA,EAAA,QAAsC,KAAK,CAC3C,GAAA,EAAA,EAAA,QAAiD,KAAK,CACtD,CAAC,EAAS,IAAA,EAAA,EAAA,UAAuB,GAAM,CAEvC,EAAM,GAAS,MAAQ,EAAa,IAAI,EAAW,CACvD,UAAW,EAAW,UACtB,OAAQ,EAAW,OACpB,CAAC,CAAG,MAEL,GAAI,CAAC,EACH,MAAU,MAAM,0EAA0E,CA4E5F,OAzEA,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAa,QAChB,OAGG,EAAa,QAAQ,KACxB,EAAa,QAAQ,GAAK,0BAA0B,KAAK,KAAK,IAGhE,IAAM,EAAyC,CAC7C,UAAW,EAAa,QACxB,GAAI,GAAa,CAAE,YAAW,CAC9B,GAAI,GAAW,CAAE,UAAS,CAC1B,GAAI,GAAS,CAAE,QAAO,CACtB,GAAI,GAAc,CAAE,aAAY,CAChC,GAAI,GAAgB,CAAE,eAAc,CACpC,GAAI,IAAyB,IAAA,IAAa,CAAE,uBAAsB,CAClE,WAAa,GAA2C,CAClD,GACF,EAAW,EAAO,EAGtB,QAAU,GAAU,CACd,EACF,EAAQ,EAAM,CACL,GAEP,EADE,EACS,CACT,OAAQ,EAAoB,OAC5B,UACA,QACD,CAEU,CACT,OAAQ,EAAoB,OACjB,YACX,QACD,CAAC,EAIR,YAAe,GAEhB,CAEK,EAAc,EAAI,kBAAkB,EAAmB,CAK7D,GAJA,EAAe,QAAU,EACzB,EAAY,OAAO,CACnB,EAAW,GAAK,CAEZ,EAAS,CACX,IAAM,EAAQ,eAAiB,CAC7B,GAAS,EACR,IAAI,CACP,UAAa,aAAa,EAAM,GAEjC,CAAC,EAAW,EAAS,EAAK,EAAY,EAAS,EAAS,EAAO,EAAY,EAAc,EAAqB,CAAC,EAElH,EAAA,EAAA,eAAgB,CACV,GAAY,GACd,EAAS,CAAE,SAAU,GAAO,CAAC,EAE9B,CAAC,EAAS,EAAS,CAAC,EAEvB,EAAA,EAAA,mBACe,CACP,EAAe,UACjB,EAAe,QAAQ,SAAS,CAChC,EAAe,QAAU,OAG5B,EAAE,CAAC,EAGJ,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,MAAO,CACL,MAAO,GAAO,WAAa,OAAS,QACrC,CACD,CAAA,CCvPN,IAAa,EAAU,CACrB,QAAS,OACT,MAAO,IACP,OAAQ,IACT,CAEY,EAAa,CACxB,WAAY,IACZ,oBAAqB,QACrB,wBAAyB,MACzB,yBAA0B,MAC1B,qBAAsB,QACtB,YAAa,GACb,qBAAsB,QACvB,CAEY,EAAsB,CACjC,gCACA,qCACA,4BACD,CAEY,EAAc,CACzB,QAAS,wBACT,OAAQ,mBACR,cAAe,0BACf,iBAAkB,wBAClB,eAAgB,sBACjB,CAEY,EAAc,CACzB,WAAY,+CACZ,QAAS,uDACT,YAAa,mDACb,MAAO,wCACR,CCrBD,SAAgB,EAAe,EAAkC,CAC/D,MAAO,CACL,aAAc,EAAa,UAAY,UACvC,YAAa,EAAa,UAAY,UACtC,aAAc,EAAa,UAAY,UACxC,CAMH,SAAgB,EAAmB,EAA6B,CAC9D,MAAO,0BAA0B,EAAO,YAAY,QAAQ,EAAO,aAAa,QAAQ,EAAO,YAAY,OAM7G,SAAgB,GAA2B,CACzC,MAAO;;;;;;;;;;eAUM,EAAQ,QAAQ;IAO/B,SAAgB,GAA6B,CAC3C,MAAO;;aAEI,EAAW,wBAAwB;iBAC/B,EAAW,oBAAoB;cAClC,EAAW,yBAAyB;kBAChC,EAAW,qBAAqB;;;;IAUlD,SAAgB,GAA+B,CAC7C,MAAO;;;;aAII,EAAW,YAAY;cACtB,EAAW,YAAY;;;;;eAKtB,EAAQ,MAAM;;;;;IAW7B,SAAgB,GAA+B,CAC7C,MAAO;;;;;IAWT,SAAgB,EAAsB,EAA6B,CAIjE,MAAO;aAHO,EAAa,OAAS,EAAW,qBAI9B;iBAHA,EAAa,OAAS,OAIf;cACZ,EAAW,WAAW;kBAClB,EAAW,WAAW;;;;IAUxC,SAAgB,GAAmC,CACjD,MAAO;;;;;;eAMM,EAAQ,OAAO;;;;;;IAY9B,SAAgB,EAAsB,EAA6B,CAGjE,MAAO;aAFO,EAAa,OAAS,EAAW,qBAG9B;;;IASnB,SAAgB,EAAmB,EAAyB,CAC1D,MAAO;;;;;wBAKe,EAAQ;;;;IAUhC,SAAgB,EAAsB,EAAiC,CACrE,MAAO;kBACS,EAAgB;;;IASlC,SAAgB,EAA4B,EAAiC,CAC3E,MAAO;;;;;MAKH,EAAsB,EAAgB,CAAC;IAO7C,SAAgB,EAAwB,EAAiC,CACvE,MAAO;;;;;MAKH,EAAsB,EAAgB,CAAC;IAO7C,SAAgB,EAAqB,EAAiC,CACpE,MAAO;;;;;MAKH,EAAsB,EAAgB,CAAC;IAO7C,SAAgB,EAAwB,EAAiC,CACvE,MAAO;;;;MAIH,EAAsB,EAAgB,CAAC;;IAQ7C,SAAgB,GAAsC,CACpD,MAAO;;;;;;;IAaT,SAAgB,EAAgC,EAAiC,CAC/E,MAAO;;;;MAIH,EAAsB,EAAgB,CAAC;IAO7C,SAAgB,EAAgC,EAAiC,CAC/E,MAAO;;;;MAIH,EAAsB,EAAgB,CAAC;IAO7C,IAAa,EAAoB;;;;;;;;;EAcjC,SAAgB,GAA8B,CAC5C,MAAO;;;;;;;;;;eAUM,EAAQ,QAAQ;IAO/B,SAAgB,GAA4B,CAC1C,MAAO;;aAEI,EAAW,wBAAwB;iBAC/B,EAAW,oBAAoB;cAClC,EAAW,yBAAyB;kBAChC,EAAW,qBAAqB;;;;;;;IAalD,SAAgB,GAA6B,CAC3C,MAAO;;;;;;IAYT,SAAgB,GAA8B,CAC5C,MAAO;;;;;IAWT,SAAgB,GAA6B,CAC3C,MAAO;;;;ICnUT,IAAa,EAAb,KAA2B,CAKzB,YAAY,EAAwB,EAAuB,EAAE,CAAE,oBAFnB,KAG1C,KAAK,UAAY,EACjB,KAAK,OAAS,EAMhB,KAAK,EAAkC,CAEjC,IACF,EAAO,MAAM,QAAU,QAOzB,IAAM,EAAS,EAHI,OAAO,YAAc,OAAO,WAAW,+BAA+B,CAAC,QAGjD,CACnC,EAAkB,EAAmB,EAAO,CAG5C,EAAS,SAAS,cAAc,MAAM,CAC5C,EAAO,GAAK,EAAY,OACxB,EAAO,MAAM,QAAU,GAA0B,CAGjD,IAAM,EAAe,SAAS,cAAc,MAAM,CAClD,EAAa,MAAM,QAAU,EAAsB,KAAK,OAAO,YAAc,GAAM,CAGnF,IAAM,EAAY,SAAS,cAAc,MAAM,CAC/C,EAAU,MAAM,QAAU,EAAmB,EAAO,aAAa,CAGjE,IAAM,EAAqB,SAAS,cAAc,MAAM,CACxD,EAAmB,UAAY,qBAC/B,EAAmB,MAAM,QAAU,EAA4B,EAAgB,CAG/E,IAAM,EAAiB,SAAS,cAAc,MAAM,CACpD,EAAe,UAAY,qBAC3B,EAAe,MAAM,QAAU,EAAwB,EAAgB,CAGvE,IAAM,EAAc,SAAS,cAAc,MAAM,CACjD,EAAY,UAAY,qBACxB,EAAY,MAAM,QAAU,EAAqB,EAAgB,CAGjE,EAAU,YAAY,EAAmB,CACzC,EAAU,YAAY,EAAe,CACrC,EAAU,YAAY,EAAY,CAGlC,IAAM,EAAiB,SAAS,cAAc,MAAM,CACpD,EAAe,UAAY,qBAC3B,EAAe,MAAM,QAAU,EAAwB,EAAgB,CAGvE,IAAM,EAAqB,SAAS,cAAc,MAAM,CACxD,EAAmB,MAAM,QAAU,GAA6B,CAEhE,IAAM,EAAyB,SAAS,cAAc,MAAM,CAC5D,EAAuB,UAAY,qBACnC,EAAuB,MAAM,QAAU,EAAgC,EAAgB,CAEvF,IAAM,EAAyB,SAAS,cAAc,MAAM,CAC5D,EAAuB,UAAY,qBACnC,EAAuB,MAAM,QAAU,EAAgC,EAAgB,CAEvF,EAAmB,YAAY,EAAuB,CACtD,EAAmB,YAAY,EAAuB,CAGtD,EAAa,YAAY,EAAU,CACnC,EAAa,YAAY,EAAe,CACxC,EAAa,YAAY,EAAmB,CAG5C,EAAO,YAAY,EAAa,CAGhC,KAAK,qBAAqB,CAG1B,KAAK,UAAU,YAAY,EAAO,CAClC,KAAK,cAAgB,EAMvB,KAAK,EAAkC,CACrC,IAAM,EAAS,SAAS,eAAe,EAAY,OAAO,CACtD,GACF,EAAO,QAAQ,CAEjB,KAAK,cAAgB,KAGjB,IACF,EAAO,MAAM,QAAU,IAO3B,qBAAoC,CAClC,GAAI,CAAC,SAAS,eAAe,EAAY,cAAc,CAAE,CACvD,IAAM,EAAQ,SAAS,cAAc,QAAQ,CAC7C,EAAM,GAAK,EAAY,cACvB,EAAM,YAAc,EACpB,SAAS,KAAK,YAAY,EAAM,MC7HzB,EAAb,MAAa,CAAa,CAQxB,YAAY,EAAqB,CAC/B,KAAK,OAAS,EAOhB,KAAK,EAAsB,EAAmB,EAAqC,CAajF,OAZI,KAAK,OAAO,QACd,QAAQ,IAAI,sCAAuC,EAAa,CAChE,QAAQ,IAAI,oBAAqB,EAAW,cAAe,EAAU,EAInE,EAAa,cACf,QAAQ,KAAK,sDAAsD,CACnE,EAAa,YAAY,QAAQ,QAAQ,CACzC,EAAa,YAAc,MAGtB,IAAI,QAAS,GAAY,CAE9B,IAAM,EAAU,SAAS,cAAc,MAAM,CAC7C,EAAQ,GAAK,EAAY,iBACzB,EAAQ,MAAM,QAAU,GAAqB,CAG7C,IAAM,EAAQ,SAAS,cAAc,MAAM,CAC3C,EAAM,MAAM,QAAU,GAAmB,CAGzC,IAAM,EAAS,SAAS,cAAc,MAAM,CAC5C,EAAO,MAAM,QAAU,GAAoB,CAC3C,EAAO,UAAY;;QAKnB,IAAM,EAAU,SAAS,cAAc,MAAM,CAC7C,EAAQ,MAAM,QAAU,GAAqB,CAE7C,IAAM,EAAS,SAAS,cAAc,SAAS,CAC/C,EAAO,IAAM,EACb,EAAO,MAAM,QAAU,GAAoB,CAC3C,EAAO,aAAa,QAAS,UAAU,CACvC,EAAO,aAAa,UAAW,2DAA2D,CAC1F,EAAQ,YAAY,EAAO,CAG3B,EAAM,YAAY,EAAO,CACzB,EAAM,YAAY,EAAQ,CAC1B,EAAQ,YAAY,EAAM,CAC1B,SAAS,KAAK,YAAY,EAAQ,CAGlC,EAAa,YAAc,CACzB,UACA,YACA,YACD,CAGD,IAAM,EAAkB,GAAwB,CAC9C,GAAI,CAAC,EAAM,KAAM,OAGjB,GAAI,CAAC,KAAK,gBAAgB,EAAM,OAAO,CAAE,CACnC,KAAK,OAAO,OACd,QAAQ,KAAK,uDAAwD,EAAM,OAAO,CAEpF,OAGF,IAAM,EAAO,EAAM,KACb,EAAgB,EAAK,OAAS,qBAAuB,EAAK,OAAS,eACnE,EAAY,EAAK,SAAW,UAC5B,EAAY,EAAK,SAAW,UAAY,EAAK,SAAW,UAG9D,GAAI,GAAiB,EAAW,CAC9B,EAAQ,QAAQ,CAChB,OAAO,oBAAoB,UAAW,EAAe,CACrD,EAAQ,GAAK,CACb,OAIF,GAAI,GAAa,CAAC,EAAe,CAC/B,EAAQ,QAAQ,CAChB,OAAO,oBAAoB,UAAW,EAAe,CACrD,EAAQ,GAAK,CACb,OAIF,GAAK,GAAiB,GAAc,EAAK,OAAS,cAAgB,EAAW,CAC3E,EAAQ,QAAQ,CAChB,OAAO,oBAAoB,UAAW,EAAe,CACrD,EAAQ,GAAM,CACd,SAIJ,OAAO,iBAAiB,UAAW,EAAe,EAClD,CAOJ,OAAO,MAAM,EAA0B,CACrC,GAAI,CAAC,EAAa,YAAa,CAE7B,QAAQ,IAAI,iCAAiC,CAC7C,OAIF,GAAI,GAAa,EAAa,YAAY,YAAc,EAAW,CACjE,QAAQ,KACN,0CAA0C,EAAU,qBAAqB,EAAa,YAAY,UAAU,2BAC7G,CACD,OAIF,QAAQ,IAAI,mCAAoC,GAAa,EAAa,YAAY,UAAU,CAChG,EAAa,YAAY,QAAQ,QAAQ,CACzC,EAAa,YAAc,KAM7B,OAAO,aAAuB,CAC5B,OAAO,EAAa,cAAgB,KAMtC,OAAO,qBAAqC,CAC1C,OAAO,EAAa,aAAa,WAAa,KAMhD,gBAAwB,EAAyB,CAa/C,MANA,GALK,EAA0C,SAAS,EAAO,GAK3D,KAAK,OAAO,cAAgB,WAAa,KAAK,OAAO,cAAgB,iBACnE,EAAO,SAAS,YAAY,EAAI,EAAO,SAAS,YAAY,uBAhKzD,KCPb,IAAa,EAAb,KAA4B,CAI1B,OAAO,cACL,EACA,EACiB,CAEjB,IAAM,EAAU,SAAS,cAAc,MAAM,CAC7C,EAAQ,GAAK,EAAY,QACzB,EAAQ,MAAM,QAAU,GAAkB,CAG1C,IAAM,EAAY,SAAS,cAAc,MAAM,CAC/C,EAAU,MAAM,QAAU,GAAoB,CAG9C,IAAM,EAAc,SAAS,cAAc,SAAS,CACpD,EAAY,UAAY,IACxB,EAAY,MAAM,QAAU,GAAsB,CAClD,EAAY,QAAU,EAGtB,IAAM,EAAS,SAAS,cAAc,SAAS,CAiB/C,MAhBA,GAAO,IAAM,EACb,EAAO,MAAM,QAAU,GAAsB,CAC7C,EAAO,aAAa,QAAS,UAAU,CAGvC,EAAU,YAAY,EAAY,CAClC,EAAU,YAAY,EAAO,CAC7B,EAAQ,YAAY,EAAU,CAG9B,EAAQ,iBAAiB,QAAU,GAAM,CACnC,EAAE,SAAW,GACf,GAAS,EAEX,CAEK,CAAE,UAAS,YAAW,SAAQ,CAMvC,OAAO,eAAsB,CAC3B,IAAM,EAAU,SAAS,eAAe,EAAY,QAAQ,CACxD,GACF,EAAQ,QAAQ,GC9DT,EAA8B,CAAC,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,CAM3F,SAAgB,GAA8B,CAC5C,GAAI,OAAO,OAAW,IACpB,MAAO,KAGT,GAAI,CAMF,IAAM,GAJJ,UAAU,UACT,UAAwC,cACzC,IAE8B,MAAM,IAAI,CAAC,GAAG,aAAa,CAE3D,GAAI,EAAkB,SAAS,EAAsB,CACnD,OAAO,OAEH,EAIR,MAAO,KAMT,SAAgB,EAAyB,EAAyE,CAUhH,OATI,EAAU,SAAS,UAAU,EAAI,EAAU,WAAW,gBAAgB,CACjE,UACE,EAAU,SAAS,SAAS,EAAI,CAAC,EAAU,SAAS,YAAY,CAClE,aACE,EAAU,SAAS,YAAY,EAAI,EAAU,WAAW,kBAAkB,CAC5E,UACE,EAAU,SAAS,QAAQ,CAC7B,cAEF,UAmBT,SAAgB,EAAuB,EAA2B,CAGhE,OAFoB,EAAyB,EAAU,CAEvD,CACE,IAAK,aACH,OAAO,EAAY,WACrB,IAAK,UACH,OAAO,EAAY,QACrB,IAAK,cACH,OAAO,EAAY,YAErB,QACE,OAAO,EAAY,OClEzB,IAAa,EAAb,KAAiB,CAWf,YAAY,EAA6D,CAWvE,eArByC,0BAGuB,2BACnB,wBAGD,KAI5C,KAAK,OAAS,EAGd,KAAK,UAAY,EAAO,WAAa,EAAuB,EAAO,WAAa,GAAG,CACnF,KAAK,YAAc,EAAyB,EAAO,WAAa,GAAG,CAGnE,KAAK,SAAW,CAAC,EAAO,UAGpB,EAAO,UACT,GAAI,OAAO,EAAO,WAAc,SAE9B,IADA,KAAK,iBAAmB,SAAS,cAAc,EAAO,UAAU,CAC5D,CAAC,KAAK,iBACR,MAAU,MAAM,wBAAwB,EAAO,YAAY,MAG7D,KAAK,iBAAmB,EAAO,UAKnC,KAAK,aAAe,IAAI,EAAa,CACnC,YAAa,KAAK,YAClB,MAAO,KAAK,OAAO,MACpB,CAAC,CAMJ,MAAa,CACP,KAAK,SAIT,KAAK,cAAc,CACnB,KAAK,oBAAoB,EAM3B,cAA6B,CAE3B,IAAM,EAAM,IAAI,IAAI,KAAK,UAAU,CAC/B,KAAK,OAAO,WACd,EAAI,aAAa,IAAI,YAAa,KAAK,OAAO,UAAU,CAEtD,KAAK,OAAO,QAAQ,WACtB,EAAI,aAAa,IAAI,YAAa,KAAK,OAAO,OAAO,UAAU,CAE7D,KAAK,OAAO,QAAQ,SACtB,EAAI,aAAa,IAAI,UAAW,KAAK,OAAO,OAAO,QAAQ,CAEzD,KAAK,OAAO,QACd,EAAI,aAAa,IAAI,SAAU,KAAK,OAAO,OAAO,CAEpD,IAAM,EAAY,EAAI,UAAU,CAEhC,GAAI,KAAK,SAAU,CAEjB,GAAM,CAAE,UAAS,YAAW,UAAW,EAAe,cACpD,MACM,KAAK,OAAO,CACnB,CAED,KAAK,OAAS,EACd,SAAS,KAAK,YAAY,EAAQ,CAGlC,KAAK,cAAgB,IAAI,EAAc,EAAW,CAChD,WAAY,KAAK,OAAO,QAAQ,OAAO,WACxC,CAAC,CACF,KAAK,cAAc,KAAK,KAAK,OAAO,KAC/B,CAEL,GAAI,CAAC,KAAK,iBACR,MAAU,MAAM,gDAAgD,CAOlE,GAHA,KAAK,iBAAiB,UAAY,GAG9B,KAAK,4BAA4B,YAAa,CAChD,IAAM,EAAe,KAAK,iBAAiB,aAAa,QAAQ,EAAI,GAC/D,EAAa,SAAS,aAAa,GACtC,KAAK,iBAAiB,MAAM,UAAY,GAAG,EAAW,WAAW,KAE9D,EAAa,SAAS,WAAW,GACpC,KAAK,iBAAiB,MAAM,SAAW,YAEpC,EAAa,SAAS,WAAW,GACpC,KAAK,iBAAiB,MAAM,SAAW,UAK3C,KAAK,OAAS,SAAS,cAAc,SAAS,CAC9C,KAAK,OAAO,IAAM,EAClB,KAAK,OAAO,MAAM,QAAU,EAC1B,KAAK,OAAO,QAAQ,OAAO,YAAc,GAC1C,CACD,KAAK,OAAO,aAAa,QAAS,UAAU,CAG5C,KAAK,iBAAiB,YAAY,KAAK,OAAO,CAG9C,KAAK,cAAgB,IAAI,EAAc,KAAK,iBAAkB,CAC5D,WAAY,KAAK,OAAO,QAAQ,OAAO,WACxC,CAAC,CACF,KAAK,cAAc,KAAK,KAAK,OAAO,EAOxC,oBAAmC,CACjC,KAAK,gBAAmB,GAAwB,CAC9C,IAAM,EAAgB,IAAI,IAAI,KAAK,UAAU,CAAC,OAG1C,EAFiB,EAAM,SAAW,EAqBtC,GAjBK,KACC,KAAK,cAAgB,WAAa,KAAK,cAAgB,iBAIzD,GAFG,EAAM,OAAO,SAAS,YAAY,EAAI,EAAM,OAAO,SAAS,YAAY,IACxE,EAAc,SAAS,YAAY,EAAI,EAAc,SAAS,YAAY,GAI1E,IAKH,EAHE,EAAM,SAAW,oCACjB,EAAM,SAAW,yCACjB,EAAM,SAAW,iCAKnB,CAAC,EAAiB,CAChB,KAAK,OAAO,OACd,QAAQ,KAAK,mDAAoD,EAAM,OAAO,CAEhF,OAGF,IAAM,EAAO,EAAM,KAEf,MAAC,GAAQ,CAAC,EAAK,MAInB,OAAQ,EAAK,KAAb,CACE,IAAK,eAEC,KAAK,eACP,KAAK,cAAc,KAAK,KAAK,QAAU,IAAA,GAAU,CAEnD,KAAK,oBAAoB,CACzB,MAEF,IAAK,iBAEC,EAAK,QAAU,KAAK,SACtB,KAAK,OAAO,MAAM,OAAS,GAAG,EAAK,OAAO,IACtC,KAAK,mBACP,KAAK,iBAAiB,MAAM,UAAY,GAAG,EAAK,OAAO,MAG3D,MAEF,IAAK,QACH,KAAK,OAAO,CACZ,MAEF,IAAK,UACC,KAAK,OAAO,WACd,KAAK,OAAO,UAAU,EAAK,KAAK,CAElC,MAEF,IAAK,QACC,KAAK,OAAO,SACd,KAAK,OAAO,QAAQ,EAAK,KAAK,CAEhC,MAEF,IAAK,eAMH,GAJI,KAAK,OAAO,QACd,QAAQ,IAAI,8BAA+B,EAAK,kBAAkB,CAClE,QAAQ,IAAI,oBAAqB,EAAK,UAAU,EAE9C,EAAK,kBAAmB,CAC1B,IAAM,EAAY,EAAK,UACjB,EACJ,EAAK,WACL,EAAK,SACL,KAAK,OAAO,QAAQ,WACpB,KAAK,OAAO,QAAQ,SACpB,GAEG,EAAK,WACR,QAAQ,KAAK,yCAAyC,CAGpD,KAAK,OAAO,OACd,QAAQ,IAAI,2CAA4C,EAAU,CAGpE,KAAK,aAAa,KAAK,EAAK,kBAAmB,EAAqB,EAAU,CAAC,KAAM,GAAY,CAC3F,KAAK,OAAO,OACd,QAAQ,IAAI,kCAAmC,EAAQ,EAGzD,MAEE,KAAK,OAAO,OACd,QAAQ,MAAM,uDAAuD,CAGzE,MAEF,IAAK,cAEC,KAAK,OAAO,QACd,QAAQ,IAAI,wCAAwC,CACpD,QAAQ,IAAI,iCAAkC,EAAK,UAAU,EAG/D,EAAa,MAAM,EAAK,UAAU,CAClC,MAEF,IAAK,aAEC,KAAK,OAAO,QACd,QAAQ,IAAI,qCAAqC,CACjD,QAAQ,IAAI,iCAAkC,EAAK,UAAU,EAG/D,EAAa,MAAM,EAAK,UAAU,CAClC,MAEF,QACM,KAAK,OAAO,OACd,QAAQ,IAAI,yBAA0B,EAAK,GAOnD,OAAO,iBAAiB,UAAW,KAAK,gBAAgB,CAM1D,oBAAmC,CACjC,GAAI,CAAC,KAAK,QAAU,CAAC,KAAK,OAAO,cAAe,CAE1C,KAAK,SACP,KAAK,OAAO,WAAe,CACzB,KAAK,oBAAoB,GAG7B,OAGF,IAAM,EAAyB,CAC7B,KAAM,UACN,OAAQ,CACN,GAAI,KAAK,OAAO,QAAU,EAAE,CAC5B,UAAW,KAAK,OAAO,QAAQ,UAC/B,QAAS,KAAK,OAAO,QAAQ,QAC9B,CACD,KAAM,CACJ,UAAW,KAAK,OAAO,UACxB,CACF,CAEK,EAAe,KAAK,iBAAiB,CAC3C,KAAK,OAAO,cAAc,YAAY,EAAS,EAAa,CAO9D,OAAsB,CAChB,KAAK,OAAO,SACd,KAAK,OAAO,SAAS,CAInB,KAAK,gBACP,KAAK,cAAc,KAAK,KAAK,QAAU,IAAA,GAAU,CACjD,KAAK,cAAgB,MAInB,KAAK,kBACP,OAAO,oBAAoB,UAAW,KAAK,gBAAgB,CAC3D,KAAK,gBAAkB,MAGrB,KAAK,SAEP,EAAe,eAAe,CAG1B,KAAK,mBACP,KAAK,iBAAiB,UAAY,IAItC,KAAK,OAAS,KAOhB,qBAA4B,EAAgC,CAO1D,GALI,KAAK,gBACP,KAAK,cAAc,KAAK,KAAK,QAAU,IAAA,GAAU,CACjD,KAAK,cAAgB,MAGnB,KAAK,SAAU,CACb,KAAK,kBACP,OAAO,oBAAoB,UAAW,KAAK,gBAAgB,CAC3D,KAAK,gBAAkB,MAEzB,EAAe,eAAe,CAC9B,KAAK,OAAS,KACd,OAKE,EAFgB,KAAK,OAAO,QAAQ,sBAAwB,KAE5C,KAAK,mBACvB,KAAK,iBAAiB,UAAY,GAClC,KAAK,OAAS,MAUlB,iBAAkC,CAIhC,OAHI,KAAK,cAAgB,cAAgB,KAAK,cAAgB,UACrD,IAAI,IAAI,KAAK,UAAU,CAAC,OAE1B,IAMT,SAAuB,CACrB,KAAK,OAAO,GC7VH,EAAb,KAAyB,CAKvB,YACE,EACA,EACA,CACA,gBANyB,GAMrB,CAAC,EAAQ,WAAa,CAAC,EAAQ,QACjC,MAAU,MAAM,gDAAgD,CAElE,GAAI,EAAQ,WAAa,EAAQ,QAC/B,MAAU,MAAM,4DAA4D,CAG9E,IAAI,EACJ,GAAI,OAAO,EAAQ,WAAc,SAE/B,IADA,EAAmB,SAAS,cAAc,EAAQ,UAAU,CACxD,CAAC,EACH,MAAU,MAAM,wBAAwB,EAAQ,YAAY,MAG9D,EAAmB,EAAQ,UAE7B,KAAK,UAAY,EAEjB,IAAM,EAAiB,EAAQ,EAAQ,QAEvC,KAAK,IAAM,IAAI,EAAI,CACjB,UAAW,EAAO,UAClB,UAAW,KAAK,UAChB,OAAQ,EAAO,QAAU,GAAqB,CAC9C,OAAQ,CACN,GAAI,EAAQ,WAAa,CAAE,UAAW,EAAQ,UAAW,CACzD,GAAI,EAAQ,SAAW,CAAE,QAAS,EAAQ,QAAS,CACnD,GAAI,EAAQ,OAAS,CAAE,MAAO,EAAQ,MAAO,CAC7C,GAAI,EAAQ,YAAc,CAAE,WAAY,EAAQ,WAAY,CAC5D,GAAI,EAAQ,cAAgB,CAAE,aAAc,EAAQ,aAAc,CAClE,GAAI,EAAQ,uBAAyB,IAAA,IAAa,CAChD,qBAAsB,EAAQ,qBAC/B,CACF,CACD,UAAY,GAAS,CACf,EAAQ,aACN,EACF,EAAQ,WAAW,CACjB,OAAQ,EAAoB,QAC5B,QAAS,EAAQ,QAClB,CAA0B,CAE3B,EAAQ,WAAW,CACjB,OAAQ,EAAoB,QAC5B,UAAW,EAAQ,UACpB,CAAyB,EAG9B,KAAK,IAAI,qBAAqB,EAAK,EAErC,QAAU,GAAU,CACd,EAAQ,QACV,EAAQ,QAAQ,EAAM,CACb,EAAQ,aACb,EACF,EAAQ,WAAW,CACjB,OAAQ,EAAoB,OAC5B,QAAS,EAAQ,QACjB,QACD,CAA0B,CAE3B,EAAQ,WAAW,CACjB,OAAQ,EAAoB,OAC5B,UAAW,EAAQ,UACnB,QACD,CAAyB,GAIhC,YAAe,CACT,EAAQ,SACV,EAAQ,SAAS,EAGtB,CAAC,CAOJ,OAAc,CACZ,GAAI,KAAK,QACP,MAAU,MAAM,iCAAiC,CAGnD,KAAK,IAAI,MAAM,CACf,KAAK,QAAU,GAMjB,SAAgB,CACV,KAAK,UACP,KAAK,IAAI,SAAS,CAClB,KAAK,QAAU,MCrIR,EAAb,KAAwB,CAkBtB,YAAY,EAA0B,CAEpC,GAAI,CAAC,EAAO,WAAa,OAAO,EAAO,WAAc,SACnD,MAAU,MAAM,sBAAsB,CAGxC,KAAK,OAAS,EACd,KAAK,QAAU,IAGf,KAAK,UAAY,EAAuB,EAAO,UAAU,CAGzD,IAAM,EAAc,EAAyB,EAAO,UAAU,CAC9D,KAAK,MAAQ,IAAgB,WAAa,IAAgB,cAgC5D,kBAAkB,EAiBF,CACd,OAAO,IAAI,EACT,CACE,UAAW,KAAK,OAAO,UACvB,UAAW,KAAK,UAChB,QAAS,KAAK,QACd,MAAO,KAAK,MACZ,OAAQ,KAAK,OAAO,OACrB,CACD,EACD,CAMH,cAAuB,CACrB,OAAO,KAAK,UAMd,WAAoB,CAClB,OAAO,KAAK,OAAO"}