@mygigsters/card-sdk 1.0.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 ADDED
@@ -0,0 +1,613 @@
1
+ # @mygigsters/card-sdk
2
+
3
+ > Embed a secure, single-step card saving form into any web application — powered by Airwallex Payment Elements.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@mygigsters/card-sdk)](https://www.npmjs.com/package/@mygigsters/card-sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ - [Overview](#overview)
13
+ - [Features](#features)
14
+ - [Requirements](#requirements)
15
+ - [Installation](#installation)
16
+ - [Get Client Info (API)](#get-client-info-api)
17
+ - [Quick Start](#quick-start)
18
+ - [Module Formats](#module-formats)
19
+ - [Usage Examples](#usage-examples)
20
+ - [ES Modules (Recommended)](#es-modules-recommended)
21
+ - [CommonJS](#commonjs)
22
+ - [CDN / Browser Script Tag](#cdn--browser-script-tag)
23
+ - [React (useEffect)](#react-useeffect)
24
+ - [Configuration Reference](#configuration-reference)
25
+ - [API Reference](#api-reference)
26
+ - [init(config)](#initconfig)
27
+ - [show()](#show)
28
+ - [hide()](#hide)
29
+ - [setTheme(mode)](#setthememode)
30
+ - [destroy()](#destroy)
31
+ - [getInstance()](#getinstance-cdn-only)
32
+ - [Success Response](#success-response)
33
+ - [Callback Reference](#callback-reference)
34
+ - [Environments](#environments)
35
+ - [Theming](#theming)
36
+ - [Error Handling](#error-handling)
37
+ - [Security](#security)
38
+ - [TypeScript](#typescript)
39
+ - [Troubleshooting](#troubleshooting)
40
+ - [Changelog](#changelog)
41
+
42
+ ---
43
+
44
+ ## Overview
45
+
46
+ The **MyGigsters Card SDK** renders a secure, self-contained modal that lets your customers save their payment card in a single step. The SDK:
47
+
48
+ - Authenticates with the MyGigsters backend using your API credentials.
49
+ - Embeds an **Airwallex Payment Element** inside a **closed Shadow DOM** — your host page styles never leak in and vice versa.
50
+ - Returns a `paymentConsentId` on success — no raw card data ever touches your server.
51
+
52
+ ---
53
+
54
+ ## Features
55
+
56
+ - **Airwallex-powered card tokenization** — PCI-compliant card element embedded in an isolated iframe.
57
+ - **Shadow DOM isolation** — No CSS conflicts with your app.
58
+ - **Light & Dark themes** — Toggle at init time or at runtime.
59
+ - **Zero React dependency** — Works in any JavaScript app (React, Vue, Angular, plain HTML).
60
+ - **Multiple module formats** — ESM, CJS, CDN (UMD).
61
+ - **TypeScript** — Full type definitions included (`dist/index.d.ts`).
62
+ - **Webhook support** — Optionally POST the success payload to your own endpoint.
63
+
64
+ ---
65
+
66
+ ## Requirements
67
+
68
+ | Requirement | Version |
69
+ |-------------|---------|
70
+ | Browser | Chrome 80+, Firefox 78+, Safari 14+, Edge 80+ |
71
+ | Node.js (build/dev only) | ≥ 14.0.0 |
72
+ | React (optional) | ≥ 18.0.0 |
73
+
74
+ > **Note:** The SDK must be served over **HTTPS** (or `localhost` for development). Airwallex payment elements require a secure context.
75
+
76
+ ---
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ # npm
82
+ npm install @mygigsters/card-sdk
83
+
84
+ # yarn
85
+ yarn add @mygigsters/card-sdk
86
+
87
+ # pnpm
88
+ pnpm add @mygigsters/card-sdk
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Get Client Info (API)
94
+
95
+ Before initializing the Card SDK, you need your **`myGigsterId`** and the customer's **`customerId`**. Call the `GET /api/v1/client/me` endpoint using your API credentials (HTTP Basic Auth `uuid:randomPasswordShowOnce`) to retrieve your client account profile and `myGigsterId`. Obtain the `customerId` by creating a customer via `POST /api/v1/customer`.
96
+
97
+ ### Endpoint
98
+
99
+ ```http
100
+ GET /api/v1/client/me
101
+ ```
102
+
103
+ | Environment | URL |
104
+ |-------------|-----|
105
+ | Local Development | `http://localhost:5000/api/v1/client/me` |
106
+ | Demo / QA | `https://qa-payments.mygigsters.com.au/api/v1/client/me` |
107
+ | Production | `https://prod-payments.mygigsters.com.au/api/v1/client/me` |
108
+
109
+ ### Headers
110
+
111
+ ```http
112
+ Content-Type: application/json
113
+ x-api-version: 1.2
114
+ Authorization: Basic {base64_encoded_combination of uuid:randomPasswordShowOnce}
115
+ ```
116
+
117
+ ### Example Request
118
+
119
+ ```bash
120
+ curl --location 'https://qa-payments.mygigsters.com.au/api/v1/client/me' \
121
+ --header 'Content-Type: application/json' \
122
+ --header 'x-api-version: 1.2' \
123
+ --header 'Authorization: Basic MGI3YjFiMTctOTJhZC00Yjc4LWFhN2ItNjI2ODBhOWUzOGExOkt1aFNEa3NCVmFXQFNPQyhZQCFh'
124
+ ```
125
+
126
+ ### Example Response (200 OK)
127
+
128
+ ```json
129
+ {
130
+ "success": true,
131
+ "status": 200,
132
+ "data": {
133
+ "id": 1,
134
+ "myGigsterId": "4407c3ba-10c9-4822-9f51-c2c96692f8d7",
135
+ "fullName": "Nitesh ",
136
+ "email": "nitesh@megamindcreations.com",
137
+ "businessName": "MG Nitesh Agrwal Deswal",
138
+ "address": null,
139
+ "acn": "123456789",
140
+ "phone": "9876543210"
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Response Fields for Card SDK
146
+
147
+ | Field | Type | Description |
148
+ |-------|------|-------------|
149
+ | `data.myGigsterId` | `string` | **Required by SDK:** Your unique MyGigsters client account identifier. Pass as `myGigsterId` in `init()`. |
150
+ | `data.fullName` | `string` | Registered full name of the client. |
151
+ | `data.email` | `string` | Registered contact email address. |
152
+ | `data.businessName` | `string` | Registered business or entity name. |
153
+ | `data.phone` | `string` | Contact phone number. |
154
+ | `data.acn` | `string` | Australian Company Number (if applicable). |
155
+
156
+ ---
157
+
158
+ ## Quick Start
159
+
160
+ **1. Add a container element in your HTML:**
161
+
162
+ ```html
163
+ <div id="mygigsters-card"></div>
164
+ ```
165
+
166
+ **2. Initialize the SDK:**
167
+
168
+ ```javascript
169
+ import { MygigstersCardSDK } from '@mygigsters/card-sdk';
170
+
171
+ const sdk = new MygigstersCardSDK();
172
+
173
+ await sdk.init({
174
+ apiKey: 'your_api_key',
175
+ apiSecret: 'your_api_secret',
176
+ customerId: 'cus_hkdmcdjshhknkk4lc2t',
177
+ myGigsterId: '4407c3ba-10c9-4822-9f51-c2c96692f8d7',
178
+ containerId: 'mygigsters-card',
179
+ env: 'demo',
180
+ onSuccess: (res) => console.log('Card saved! Consent ID:', res.paymentConsentId),
181
+ onError: (err) => console.error('Error:', err.message),
182
+ onClose: () => console.log('Modal closed'),
183
+ });
184
+ ```
185
+
186
+ The card modal opens automatically after `init()` resolves.
187
+
188
+ ---
189
+
190
+ ## Module Formats
191
+
192
+ | Format | Path | Use Case |
193
+ |--------|------|----------|
194
+ | **ESM** | `dist/index.esm.js` | Modern bundlers (Vite, Webpack 5, Rollup) |
195
+ | **CommonJS** | `dist/index.cjs.js` | Node.js / `require()` environments |
196
+ | **CDN (UMD)** | `cdn/card-sdk-entry.js` | Browser `<script>` tag / self-hosted CDN |
197
+ | **TypeScript** | `dist/index.d.ts` | Type definitions |
198
+
199
+ ---
200
+
201
+ ## Usage Examples
202
+
203
+ ### ES Modules (Recommended)
204
+
205
+ ```javascript
206
+ import { MygigstersCardSDK } from '@mygigsters/card-sdk';
207
+
208
+ const sdk = new MygigstersCardSDK();
209
+
210
+ await sdk.init({
211
+ apiKey: 'your_api_key',
212
+ apiSecret: 'your_api_secret',
213
+ customerId: 'cus_hkdmcdjshhknkk4lc2t',
214
+ myGigsterId: '4407c3ba-10c9-4822-9f51-c2c96692f8d7',
215
+ containerId: 'mygigsters-card',
216
+ env: 'demo', // 'demo' | 'prod'
217
+ mode: 'dark', // optional: 'dark' (default) | 'light'
218
+ onSuccess: (res) => {
219
+ console.log('✅ Card saved:', res.paymentConsentId);
220
+ // Pass res.paymentConsentId to the MyGigsters API to charge the customer
221
+ },
222
+ onError: (err) => {
223
+ console.error('❌ Error:', err.message);
224
+ },
225
+ onClose: () => {
226
+ console.log('Modal closed by user');
227
+ },
228
+ });
229
+ ```
230
+
231
+ ---
232
+
233
+ ### CommonJS
234
+
235
+ ```javascript
236
+ const { MygigstersCardSDK } = require('@mygigsters/card-sdk');
237
+
238
+ const sdk = new MygigstersCardSDK();
239
+
240
+ sdk.init({
241
+ apiKey: 'your_api_key',
242
+ apiSecret: 'your_api_secret',
243
+ customerId: 'cus_hkdmcdjshhknkk4lc2t',
244
+ myGigsterId: '4407c3ba-10c9-4822-9f51-c2c96692f8d7',
245
+ containerId: 'mygigsters-card',
246
+ onSuccess: (res) => console.log('Card saved:', res.paymentConsentId),
247
+ onError: (err) => console.error('Error:', err.message),
248
+ onClose: () => console.log('Closed'),
249
+ });
250
+ ```
251
+
252
+ ---
253
+
254
+ ### CDN / Browser Script Tag
255
+
256
+ ```html
257
+ <!DOCTYPE html>
258
+ <html lang="en">
259
+ <head>
260
+ <meta charset="UTF-8" />
261
+ <title>My App</title>
262
+ </head>
263
+ <body>
264
+
265
+ <!-- 1. Mount target -->
266
+ <div id="mygigsters-card"></div>
267
+
268
+ <!-- 2. Trigger button (optional) -->
269
+ <button onclick="openCardForm()">Save Card</button>
270
+
271
+ <!-- 3. Load the SDK -->
272
+ <script src="https://qa-payments.mygigsters.com.au/card-sdk-entry.js"></script>
273
+
274
+ <script>
275
+ async function openCardForm() {
276
+ await MygigstersCard.init({
277
+ apiKey: 'your_api_key',
278
+ apiSecret: 'your_api_secret',
279
+ customerId: 'cus_hkdmcdjshhknkk4lc2t',
280
+ myGigsterId: '4407c3ba-10c9-4822-9f51-c2c96692f8d7',
281
+ containerId: 'mygigsters-card',
282
+ env: 'demo',
283
+ mode: 'dark',
284
+ onSuccess: (res) => console.log('✅ Card saved:', res.paymentConsentId),
285
+ onError: (err) => console.error('❌ Error:', err.message),
286
+ onClose: () => console.log('Closed'),
287
+ });
288
+ }
289
+ </script>
290
+
291
+ </body>
292
+ </html>
293
+ ```
294
+
295
+ > **CDN global:** The CDN build exposes `window.MygigstersCard` (not `MygigstersCardSDK`).
296
+
297
+ ---
298
+
299
+ ### React (useEffect)
300
+
301
+ ```jsx
302
+ import { useEffect } from 'react';
303
+ import { MygigstersCardSDK } from '@mygigsters/card-sdk';
304
+
305
+ function SaveCardForm({ customerId, myGigsterId }) {
306
+ useEffect(() => {
307
+ const sdk = new MygigstersCardSDK();
308
+ sdk.init({
309
+ apiKey: 'your_api_key',
310
+ apiSecret: 'your_api_secret',
311
+ customerId,
312
+ myGigsterId,
313
+ containerId: 'mygigsters-card',
314
+ env: 'demo',
315
+ mode: 'dark',
316
+ onSuccess: (res) => console.log('Card saved!', res.paymentConsentId),
317
+ onError: (err) => console.error(err),
318
+ onClose: () => console.log('Closed'),
319
+ });
320
+
321
+ // Cleanup on unmount
322
+ return () => sdk.destroy();
323
+ }, [customerId, myGigsterId]);
324
+
325
+ return <div id="mygigsters-card" />;
326
+ }
327
+ ```
328
+
329
+ ---
330
+
331
+ ## Configuration Reference
332
+
333
+ Pass these options to `init()`:
334
+
335
+ | Parameter | Type | Required | Default | Description |
336
+ |-----------|------|:--------:|---------|-------------|
337
+ | `apiKey` | `string` | ✅ | — | UUID-style API key issued by MyGigsters |
338
+ | `apiSecret` | `string` | ✅ | — | One-time API secret (shown once at creation) |
339
+ | `customerId` | `string` | ✅ | — | MyGigsters customer ID for whom the card is saved (from `POST /api/v1/customer`) |
340
+ | `myGigsterId` | `string` | ✅ | — | Unique client account identifier (from `GET /api/v1/client/me`) |
341
+ | `containerId` | `string` | ✅ | — | `id` of the DOM element to mount the SDK into |
342
+ | `env` | `'demo' \| 'prod'` | ❌ | `'demo'` | Target environment |
343
+ | `baseUrl` | `string` | ❌ | — | Custom base URL override (for self-hosted deployments) |
344
+ | `mode` | `'light' \| 'dark'` | ❌ | `'dark'` | UI color theme |
345
+ | `onSuccess` | `(res) => void` | ❌ | no-op | Fired after the card is saved successfully |
346
+ | `onError` | `(error) => void` | ❌ | `console.error` | Fired on any SDK or network error |
347
+ | `onClose` | `() => void` | ❌ | no-op | Fired when the user closes the modal |
348
+ | `webhookUrl` | `string` | ❌ | — | URL to POST the success payload to automatically |
349
+ | `onWebhook` | `(payload) => void` | ❌ | no-op | JS callback for receiving the webhook payload in real-time |
350
+
351
+ ---
352
+
353
+ ## API Reference
354
+
355
+ ### `init(config)`
356
+
357
+ Authenticates with the MyGigsters backend, builds the Shadow DOM modal, initializes the Airwallex card element, and automatically calls `show()`.
358
+
359
+ ```javascript
360
+ import { MygigstersCardSDK } from '@mygigsters/card-sdk';
361
+
362
+ const sdk = new MygigstersCardSDK();
363
+ await sdk.init(config);
364
+
365
+ // or, via CDN:
366
+ await MygigstersCard.init(config);
367
+ ```
368
+
369
+ - **Returns:** `Promise<void>`
370
+ - **Throws:** if `apiKey`, `apiSecret`, `customerId`, `myGigsterId`, or `containerId` are missing; if the container element is not found; or if authentication fails.
371
+ - Each SDK instance maintains its own internal state.
372
+ - CDN builds expose a singleton instance automatically.
373
+
374
+ ---
375
+
376
+ ### `show()`
377
+
378
+ Shows the card modal. Called automatically by `init()`, but can be called again after `hide()`.
379
+
380
+ ```javascript
381
+ sdk.show();
382
+ ```
383
+
384
+ Throws `Error: SDK not initialized. Call init() first.` if called before `init()`.
385
+
386
+ ---
387
+
388
+ ### `hide()`
389
+
390
+ Hides the modal without destroying the SDK instance. The Airwallex card element state is preserved.
391
+
392
+ ```javascript
393
+ sdk.hide();
394
+ ```
395
+
396
+ ---
397
+
398
+ ### `setTheme(mode)`
399
+
400
+ Switch the UI theme at runtime — no need to re-initialize.
401
+
402
+ ```javascript
403
+ sdk.setTheme('dark'); // or 'light'
404
+ ```
405
+
406
+ ```javascript
407
+ // Example: sync with OS preference
408
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
409
+ mq.addEventListener('change', (e) => {
410
+ sdk.setTheme(e.matches ? 'dark' : 'light');
411
+ });
412
+ ```
413
+
414
+ ---
415
+
416
+ ### `destroy()`
417
+
418
+ Removes the modal from the DOM and resets all internal state. Call this when navigating away or unmounting the host component.
419
+
420
+ ```javascript
421
+ sdk.destroy();
422
+ ```
423
+
424
+ ---
425
+
426
+ ### `getInstance()` *(CDN only)*
427
+
428
+ Returns the underlying SDK instance for advanced use.
429
+
430
+ ```javascript
431
+ const sdk = MygigstersCard.getInstance();
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Success Response
437
+
438
+ The `onSuccess` callback receives a `CardSuccessResponse` object:
439
+
440
+ ```javascript
441
+ onSuccess: (res) => {
442
+ // res.paymentConsentId — Airwallex payment consent ID
443
+ // Pass this to the MyGigsters API when creating a charge
444
+ // res.customerId — Customer ID associated with this consent
445
+ // res.myGigsterId — MyGigster ID associated with this consent
446
+ // res.timestamp — ISO-8601 timestamp of the save event
447
+ // res.event — 'card.saved'
448
+
449
+ console.log('Payment Consent ID:', res.paymentConsentId);
450
+ }
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Callback Reference
456
+
457
+ ### `onSuccess(res)`
458
+
459
+ Called after the card is saved and the Airwallex element confirms the payment consent.
460
+
461
+ ```javascript
462
+ onSuccess: (res) => {
463
+ // res.paymentConsentId → use to charge the customer via Payment Intents API
464
+ console.log('Saved!', res);
465
+ }
466
+ ```
467
+
468
+ ### `onError(error)`
469
+
470
+ Called when any error occurs — authentication failures, Airwallex element errors, or server rejections.
471
+
472
+ ```javascript
473
+ onError: (error) => {
474
+ // error is a standard JS Error object
475
+ console.error(error.message);
476
+ }
477
+ ```
478
+
479
+ ### `onClose()`
480
+
481
+ Called when the user dismisses the modal (close button ×, backdrop click, or Escape key).
482
+
483
+ ```javascript
484
+ onClose: () => {
485
+ // Re-show a trigger button, update UI state, etc.
486
+ }
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Environments
492
+
493
+ | Environment | SDK Script URL | `env` Value |
494
+ |-------------|----------------|-------------|
495
+ | Demo / QA | `https://qa-payments.mygigsters.com.au/card-sdk-entry.js` | `"demo"` |
496
+ | Production | `https://prod-payments.mygigsters.com.au/card-sdk-entry.js` | `"prod"` |
497
+
498
+ ---
499
+
500
+ ## Theming
501
+
502
+ Set `mode: 'light'` or `mode: 'dark'` in the config object (defaults to `'dark'`).
503
+
504
+ You can also switch themes after initialization using [`setTheme()`](#setthememode).
505
+
506
+ ```javascript
507
+ // Toggle dark mode on a button click
508
+ const sdk = new MygigstersCardSDK();
509
+
510
+ document.getElementById('toggle-theme').addEventListener('click', () => {
511
+ sdk.setTheme(
512
+ document.body.classList.toggle('dark') ? 'dark' : 'light'
513
+ );
514
+ });
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Error Handling
520
+
521
+ Wrap `init()` in a `try/catch` to handle initialization errors:
522
+
523
+ ```javascript
524
+ try {
525
+ const sdk = new MygigstersCardSDK();
526
+
527
+ await sdk.init({ ... });
528
+ } catch (err) {
529
+ // Common errors:
530
+ // "apiKey is required"
531
+ // "customerId is required"
532
+ // "myGigsterId is required"
533
+ // "Container element with ID '...' not found"
534
+ // "Invalid API credentials: Please check your apiKey and apiSecret."
535
+ // "Authentication failed: ..."
536
+ console.error('SDK failed to initialize:', err.message);
537
+ }
538
+ ```
539
+
540
+ Runtime errors (Airwallex element failures, network issues, server rejections) are surfaced via the `onError` callback and displayed inline in the modal.
541
+
542
+ ---
543
+
544
+ ## Security
545
+
546
+ - **Credentials are never stored** in `localStorage` or `sessionStorage`. The auth token lives in memory only.
547
+ - **Authentication** uses HTTP Basic Auth (`base64(apiKey:apiSecret)`) over HTTPS. Never expose your `apiSecret` in client-side code committed to a public repository — use environment variables or a backend-for-frontend pattern.
548
+ - **PCI-compliant card handling** — Raw card data is handled exclusively by Airwallex's PCI-certified payment element embedded inside an Airwallex-controlled iframe. Your server never receives raw card numbers or CVVs.
549
+ - **Shadow DOM** (`mode: 'closed'`) prevents host-page scripts from reaching into the SDK's DOM.
550
+
551
+ ---
552
+
553
+ ## TypeScript
554
+
555
+ Type definitions are included at `dist/index.d.ts`. Import the SDK as usual:
556
+
557
+ ```typescript
558
+ import { MygigstersCardSDK, MygigstersCardConfig, CardSuccessResponse } from '@mygigsters/card-sdk';
559
+
560
+ const config: MygigstersCardConfig = {
561
+ apiKey: process.env.MYGIGSTERS_API_KEY!,
562
+ apiSecret: process.env.MYGIGSTERS_API_SECRET!,
563
+ customerId: 'cus_hkdmcdjshhknkk4lc2t',
564
+ myGigsterId: '4407c3ba-10c9-4822-9f51-c2c96692f8d7',
565
+ containerId: 'mygigsters-card',
566
+ env: 'prod',
567
+ mode: 'dark',
568
+ onSuccess: (res: CardSuccessResponse) => {
569
+ console.log('Consent ID:', res.paymentConsentId);
570
+ },
571
+ };
572
+
573
+ const sdk = new MygigstersCardSDK();
574
+ await sdk.init(config);
575
+ ```
576
+
577
+ ---
578
+
579
+ ## Troubleshooting
580
+
581
+ **`Container element with ID '...' not found`**
582
+ Make sure the target `<div>` exists in the DOM before calling `init()`. If you are using a framework, call `init()` after the component mounts (e.g., inside `useEffect` or `mounted()`).
583
+
584
+ **`Invalid API credentials`**
585
+ Double-check that `apiKey` and `apiSecret` match the values shown in your MyGigsters dashboard. The secret is shown only once — if lost, generate a new one.
586
+
587
+ **`myGigsterId is required`**
588
+ Make sure `myGigsterId` is provided in the configuration object passed to `init()`. You can retrieve your `myGigsterId` by calling `GET /api/v1/client/me`. To obtain `customerId`, create a customer via `POST /api/v1/customer`.
589
+
590
+ **Airwallex card element fails to load**
591
+ Ensure your page is served over **HTTPS**. Also verify that your Airwallex account has card tokenization enabled, and that the `env` setting matches the environment your account credentials belong to.
592
+
593
+ **Card modal appears behind other elements**
594
+ The modal is mounted on `<body>` with `z-index: 2147483647`. If another element on your page has a higher stacking context, adjust accordingly. Because the SDK uses Shadow DOM, your global CSS reset will not affect it.
595
+
596
+ ---
597
+
598
+ ## Changelog
599
+
600
+ ### 1.0.0
601
+ - Initial public release
602
+ - Single-step card saving via Airwallex Payment Elements
603
+ - Shadow DOM isolation
604
+ - Light / dark theming with runtime `setTheme()` support
605
+ - ESM, CJS, and CDN (UMD) builds
606
+ - TypeScript definitions
607
+ - Webhook support via `webhookUrl` and `onWebhook` options
608
+
609
+ ---
610
+
611
+ ## License
612
+
613
+ MIT © [MyGigsters](https://www.mygigsters.com.au)
@@ -0,0 +1,2 @@
1
+ !function(n){"function"==typeof define&&define.amd?define(n):n()}(function(){"use strict";class n{constructor(n){this.baseUrl=n.replace(/\/+$/,"")}async _post(n,e,t=null,o={}){const i={"Content-Type":"application/json",...o};t&&(i.Authorization=t.startsWith("Basic ")||t.startsWith("Bearer ")?t:`Bearer ${t}`);const r=await fetch(`${this.baseUrl}/api/v1${n}`,{method:"POST",headers:i,body:JSON.stringify(e)}),s=await r.text();let a=null;if(s&&s.trim().length>0)try{a=JSON.parse(s)}catch{a=null}if(!r.ok){const n=a&&(a.message||a.error)||s||r.statusText||`HTTP ${r.status}`;throw new Error(n)}return a??{}}async authenticate(n,e){const t=btoa(`${n}:${e}`),o=await fetch(`${this.baseUrl}/api/v1/sdk-initialization`,{method:"POST",headers:{"Content-Type":"application/json","x-api-version":"1.1",Authorization:`Basic ${t}`}}),i=await o.text();let r=null;if(i&&i.trim().length>0)try{r=JSON.parse(i)}catch{r=null}if(!o.ok){const n=r&&(r.message||r.error)||i||o.statusText||`HTTP ${o.status}`;throw new Error(n)}if(!r?.success||!r?.data?.token)throw new Error("Authentication failed: Unexpected response from server");return{token:r.data.token,clientId:r.data.clientId,expiresIn:r.data.expiresIn,tokenType:r.data.tokenType,paymentsIndia:r.data.paymentsIndia||!1}}async getClientSecret(n,e,t=null){console.log("🔐 CardAPI.getClientSecret()",{customerId:n,accountId:e,hasToken:!!t});const o=await this._post("/customer/client-secret",{customerId:n,accountId:e},t),i=o.client_secret||o.clientSecret||o?.data?.client_secret||o?.data?.clientSecret;if(!i)throw new Error("Failed to retrieve client secret from server");return{clientSecret:i}}async saveConsent(n,e,t=null){return this._post("/customer/save-consent",{customerId:n,paymentConsentId:e},t)}}const e='<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2" d="M6 18L18 6M6 6l12 12"/>\n </svg>',t='<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">\n <path stroke-linecap="round" stroke-linejoin="round" d="M20 6L9 17l-5-5"/>\n </svg>';class o{constructor(n,e){this.shadowRoot=n,this.callbacks=e,this.state={loading:!0,cardLoaded:!1,cardComplete:!1,submitting:!1,error:"",success:!1},this._cardMounted=!1}_getContainer(){let n=this.shadowRoot.querySelector("#mc-sdk-root");if(!n){n=document.createElement("div"),n.id="mc-sdk-root";const e=this.shadowRoot.querySelector("#mc-theme-container");e?e.appendChild(n):this.shadowRoot.appendChild(n)}return n}render(){this.state.loading=!0;this._getContainer().innerHTML=`\n <div class="mc-backdrop" id="mc-backdrop">\n <div class="mc-modal" id="mc-modal">\n <button class="mc-close-btn" aria-label="Close" id="mc-close-btn" type="button">${e}</button>\n\n <div class="mc-header">\n <h1 class="mc-title">Card Details</h1>\n <p class="mc-subtitle">Please enter your card details</p>\n </div>\n\n <div class="mc-body">\n \x3c!-- Error banner (initially hidden) --\x3e\n <div class="mc-error-banner" style="display:none;" id="mc-error-banner"></div>\n\n \x3c!-- Loading spinner (shown initially) --\x3e\n <div class="mc-loading" id="mc-loading">\n <div class="mc-spinner"></div>\n <span class="mc-loading-text">Loading payment form...</span>\n </div>\n\n \x3c!-- Card wrapper (hidden until Airwallex loads) --\x3e\n <div class="mc-card-wrapper" id="mc-card-wrapper" style="display:none;">\n <div id="card-root"></div>\n </div>\n\n \x3c!-- Submit button (hidden until Airwallex loads) --\x3e\n <button class="mc-submit-btn" id="mc-submit-btn" disabled style="display:none;" type="button">\n Submit\n </button>\n </div>\n\n <div class="mc-powered-by">\n Powered by <a href="https://mygigsters.com.au" target="_blank" rel="noopener">MyGigsters</a>\n </div>\n </div>\n </div>\n `,this._attachListeners()}_attachListeners(){this.shadowRoot.querySelectorAll(".mc-close-btn, #mc-close-btn").forEach(n=>{n.addEventListener("click",n=>{n.preventDefault(),n.stopPropagation(),this.callbacks.onClose()})});const n=this.shadowRoot.querySelector("#mc-backdrop")||this.shadowRoot.querySelector(".mc-backdrop");n&&n.addEventListener("click",e=>{e.target===n&&(e.preventDefault(),e.stopPropagation(),this.callbacks.onClose())});const e=this.shadowRoot.querySelector("#mc-submit-btn");e&&e.addEventListener("click",n=>{n.preventDefault(),n.stopPropagation(),this.callbacks.onSubmit()}),this.shadowRoot.addEventListener("click",n=>{const e=n.target;e&&(e.closest(".mc-close-btn")||e.closest("#mc-close-btn"))&&(n.preventDefault(),n.stopPropagation(),this.callbacks.onClose())})}setCardLoaded(){this.state.cardLoaded=!0,this.state.loading=!1;const n=this.shadowRoot.querySelector("#mc-loading"),e=this.shadowRoot.querySelector("#mc-card-wrapper"),t=this.shadowRoot.querySelector("#mc-submit-btn");n&&(n.style.display="none"),e&&(e.style.display="block"),t&&(t.style.display="flex")}markCardMounted(){this._cardMounted=!0}setCardComplete(n){this.state.cardComplete=n;const e=this.shadowRoot.querySelector("#mc-submit-btn");e&&(e.disabled=!n||this.state.submitting)}setSubmitting(n){this.state.submitting=n;const e=this.shadowRoot.querySelector("#mc-submit-btn");e&&(e.disabled=n||!this.state.cardComplete,e.innerHTML=n?'<span class="mc-btn-inner"><div class="mc-spinner mc-spinner-sm"></div>Saving...</span>':"Submit")}setError(n){this.state.error=n;const e=this.shadowRoot.querySelector("#mc-error-banner");e&&(n?(e.textContent=n,e.style.display="block"):(e.textContent="",e.style.display="none"))}showInitError(n){this.state.loading=!1,this.state.error=n;const e=this.shadowRoot.querySelector("#mc-loading");e&&(e.innerHTML=`\n <div style="text-align:center; padding: 12px 0;">\n <p style="color:#ef4444; font-size:14px; font-weight:600; margin-bottom:8px;">${this._escapeHtml(n)}</p>\n <p style="color:#94a3b8; font-size:13px;">Please check your configuration credentials and try again.</p>\n </div>\n `)}showSuccess(n){this.state.success=!0;const o=this.shadowRoot.querySelector(".mc-modal");o&&(o.innerHTML=`\n <button class="mc-close-btn" aria-label="Close" id="mc-close-btn" type="button">${e}</button>\n\n <div class="mc-body">\n <div class="mc-success-wrap">\n <div class="mc-success-icon">${t}</div>\n <h2 class="mc-success-title">Card Saved Successfully</h2>\n <p class="mc-success-sub">Your card details have been securely tokenized and saved.</p>\n <button class="mc-done-btn" id="mc-done-btn" type="button">Done</button>\n </div>\n </div>\n\n <div class="mc-powered-by">\n Powered by <a href="https://mygigsters.com.au" target="_blank" rel="noopener">MyGigsters</a>\n </div>\n `,o.querySelectorAll(".mc-close-btn, #mc-close-btn").forEach(n=>{n.addEventListener("click",n=>{n.preventDefault(),n.stopPropagation(),this.callbacks.onClose()})}),o.querySelector("#mc-done-btn")?.addEventListener("click",n=>{n.preventDefault(),n.stopPropagation(),this.callbacks.onClose()}))}_escapeHtml(n){const e=document.createElement("div");return e.textContent=n,e.innerHTML}destroy(){const n=this.shadowRoot.querySelector("#mc-sdk-root");n&&(n.innerHTML=""),this._cardMounted=!1}}const i={prod:"checkout.airwallex.com",demo:"checkout-demo.airwallex.com",staging:"checkout-staging.airwallex.com",dev:"checkout-dev.airwallex.com",local:"localhost:3000"},r=n=>`https://${i[n]||i.prod}`,s="/assets/elements.bundle.min.js?version=1.160.0",a=n=>{const e=document.createElement("script");e.src=`${n}${s}`,e.crossOrigin="anonymous";const t=document.head||document.body;if(!t)throw new Error("Airwallex payment scripts requires a <head> or <body> html element in order to be loaded.");return t.appendChild(e),e};var c=async n=>{if("undefined"==typeof window)return null;if(window.Airwallex)return window.Airwallex;let e=0;const t=async()=>{const e=document.querySelector(`script[src="${s}"], script[src="${s}/"]`)||a(r((null==n?void 0:n.env)||"prod"));return new Promise((t,o)=>{e.addEventListener("load",()=>{window.Airwallex?(window.Airwallex.init(n),t(window.Airwallex)):o(new Error("Failed to load Airwallex on load event"))}),e.addEventListener("error",()=>{o(new Error("Failed to load Airwallex scripts")),e.remove&&e.remove()})})};for(;e<3;)try{return await t()}catch(o){e++,await new Promise(n=>window.setTimeout(n,500))}return null},d=n=>{window.Airwallex?window.Airwallex.init(n):console.error("Please loadAirwallex() before init();")},l=(n,e)=>window.Airwallex?window.Airwallex.createElement(n,e):(console.error("Please loadAirwallex() before createElement();"),null),h=async n=>{if(window.Airwallex)return window.Airwallex.createPaymentConsent(n);{const n="Please loadAirwallex() before createPaymentConsent();";throw console.error(n),new Error(n)}};const m={dev:"http://3.108.128.110:5000",demo:"https://qa-payments.mygigsters.com.au",prod:"https://prod-payments.mygigsters.com.au"},p={dev:"demo",demo:"demo",prod:"prod"};class u{constructor(){this.config=null,this.api=null,this.renderer=null,this.host=null,this.shadowRoot=null,this.cardElement=null,this.clientSecret=null,this.authToken=null,this.tokenExpiresAt=null,this.airwallexClientId=null,this.isInitialized=!1,this.isSubmitting=!1,this._handleKeyDown=this._handleKeyDown.bind(this)}async init(e){try{if(!e)throw new Error("Configuration object is required for MygigstersCard.init()");if(!e.apiKey)throw new Error("apiKey is required");if(!e.apiSecret)throw new Error("apiSecret is required");if(!e.customerId)throw new Error("customerId is required");const t=e.myGigsterId||e.accountId;if(!t)throw new Error("myGigsterId is required");if(!e.containerId)throw new Error("containerId is required");if(!document.getElementById(e.containerId))throw new Error(`Container element with ID '${e.containerId}' not found`);let i=e.env||"demo";if("qa"===i&&(i="demo"),!m[i])throw new Error(`Unknown env "${i}". Use "dev", "demo", or "prod".`);const r=e.baseUrl||m[i];this.config={mode:"dark",onSuccess:()=>{},onError:n=>console.error("MyGigsters Card SDK Error:",n),onClose:()=>{},...e,myGigsterId:t,accountId:t,env:i,baseUrl:r};const s=document.getElementById("mygigsters-card-sdk-host");s?.parentNode&&s.parentNode.removeChild(s),this.api=new n(this.config.baseUrl),this.host=document.createElement("div"),this.host.id="mygigsters-card-sdk-host",this.host.style.cssText="display:block;position:fixed;top:0;left:0;right:0;bottom:0;width:100vw;height:100vh;z-index:2147483647;pointer-events:auto;",this.shadowRoot=this.host.attachShadow({mode:"closed"});const a=document.createElement("style");a.textContent="\n/* ── Reset inside shadow DOM ───────────────────────────────────────────────── */\n*, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\n:host {\n display: block !important;\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n right: 0 !important;\n bottom: 0 !important;\n width: 100vw !important;\n height: 100vh !important;\n z-index: 2147483647 !important;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n font-size: 14px;\n color: #e5e7eb;\n line-height: 1.5;\n pointer-events: auto !important;\n}\n\n#mc-theme-container, #mc-sdk-root {\n width: 100%;\n height: 100%;\n position: fixed;\n top: 0;\n left: 0;\n}\n\n/* ══════════════════════════════════════════════════════════════════════════════\n DARK THEME (DEFAULT)\n ══════════════════════════════════════════════════════════════════════════════ */\n\n/* ── Backdrop ──────────────────────────────────────────────────────────────── */\n.mc-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100vw;\n height: 100vh;\n background: rgba(0, 0, 0, 0.75);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2147483647;\n padding: 16px;\n box-sizing: border-box;\n animation: mc-fade-in 0.2s ease;\n}\n\n/* ── Modal ──────────────────────────────────────────────────────────────────── */\n.mc-modal {\n background: #111827;\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n width: 100%;\n max-width: 460px;\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);\n overflow: hidden;\n animation: mc-modal-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);\n position: relative;\n display: flex;\n flex-direction: column;\n color: #ffffff;\n}\n\n/* ── Close button ──────────────────────────────────────────────────────────── */\n.mc-close-btn {\n position: absolute;\n top: 16px;\n right: 16px;\n background: rgba(255, 255, 255, 0.08);\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 8px;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer !important;\n color: #94a3b8;\n transition: all 0.15s ease;\n padding: 0;\n line-height: 0;\n z-index: 100;\n outline: none;\n pointer-events: auto !important;\n user-select: none;\n}\n\n.mc-close-btn svg,\n.mc-close-btn path {\n pointer-events: none !important;\n}\n\n.mc-close-btn:hover {\n background: rgba(255, 255, 255, 0.18);\n color: #ffffff;\n transform: scale(1.05);\n}\n\n.mc-close-btn:active {\n transform: scale(0.95);\n}\n\n/* ── Header ────────────────────────────────────────────────────────────────── */\n.mc-header {\n text-align: center;\n padding: 28px 24px 0;\n}\n\n.mc-title {\n font-size: 22px;\n font-weight: 700;\n color: #ffffff;\n margin: 0 0 6px;\n line-height: 1.2;\n}\n\n.mc-subtitle {\n font-size: 14px;\n color: #94a3b8;\n margin: 0;\n font-weight: 400;\n}\n\n/* ── Body ──────────────────────────────────────────────────────────────────── */\n.mc-body {\n padding: 20px 24px 24px;\n flex: 1;\n}\n\n/* ── Card mount area ───────────────────────────────────────────────────────── */\n.mc-card-wrapper {\n background: #1f2937;\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 10px;\n padding: 14px 16px;\n margin-bottom: 20px;\n min-height: 48px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.mc-card-wrapper:focus-within {\n border-color: #6366f1;\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);\n}\n\n#card-root {\n margin: 0;\n padding: 0;\n min-height: 40px;\n width: 100%;\n}\n\n#card-root iframe {\n display: block !important;\n margin: 0 !important;\n padding: 0 !important;\n width: 100% !important;\n border: none !important;\n}\n\n/* ── Loading state ─────────────────────────────────────────────────────────── */\n.mc-loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 32px 20px;\n gap: 12px;\n}\n\n.mc-loading-text {\n font-size: 13.5px;\n color: #94a3b8;\n font-weight: 500;\n}\n\n/* ── Spinner ────────────────────────────────────────────────────────────────── */\n.mc-spinner {\n width: 32px;\n height: 32px;\n border: 3px solid rgba(255, 255, 255, 0.15);\n border-top-color: #6366f1;\n border-radius: 50%;\n animation: mc-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n\n.mc-spinner-sm {\n width: 16px;\n height: 16px;\n border-width: 2px;\n border-color: rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n}\n\n@keyframes mc-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* ── Submit button ─────────────────────────────────────────────────────────── */\n.mc-submit-btn {\n width: 100%;\n padding: 13px 24px;\n background: #6366f1;\n color: #ffffff;\n border: none;\n border-radius: 9px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n font-family: inherit;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);\n outline: none;\n}\n\n.mc-submit-btn:hover:not(:disabled) {\n background: #4f46e5;\n box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45);\n transform: translateY(-1px);\n}\n\n.mc-submit-btn:active:not(:disabled) {\n transform: translateY(0);\n}\n\n.mc-submit-btn:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n\n.mc-btn-inner {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n/* ── Error banner ──────────────────────────────────────────────────────────── */\n.mc-error-banner {\n background: rgba(239, 68, 68, 0.12);\n border: 1px solid rgba(239, 68, 68, 0.3);\n border-radius: 8px;\n padding: 10px 14px;\n margin-bottom: 16px;\n font-size: 13px;\n color: #fca5a5;\n line-height: 1.4;\n animation: mc-fade-in 0.2s ease;\n}\n\n/* ── Success state ─────────────────────────────────────────────────────────── */\n.mc-success-wrap {\n text-align: center;\n padding: 30px 20px 16px;\n animation: mc-fade-in 0.3s ease;\n}\n\n.mc-success-icon {\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: rgba(34, 197, 94, 0.15);\n border: 2px solid rgba(34, 197, 94, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 16px;\n}\n\n.mc-success-icon svg {\n width: 28px;\n height: 28px;\n color: #22c55e;\n}\n\n.mc-success-title {\n font-size: 20px;\n font-weight: 700;\n color: #f8fafc;\n margin: 0 0 6px;\n}\n\n.mc-success-sub {\n font-size: 13.5px;\n color: #94a3b8;\n margin: 0 0 24px;\n}\n\n.mc-done-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 11px 36px;\n background: #6366f1;\n color: #fff;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n font-family: inherit;\n transition: all 0.2s ease;\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);\n}\n\n.mc-done-btn:hover {\n background: #4f46e5;\n transform: translateY(-1px);\n}\n\n/* ── Powered by ────────────────────────────────────────────────────────────── */\n.mc-powered-by {\n text-align: center;\n padding: 8px 0 16px;\n font-size: 11.5px;\n color: #64748b;\n}\n\n.mc-powered-by a {\n color: #94a3b8;\n text-decoration: none;\n font-weight: 600;\n transition: color 0.15s ease;\n}\n\n.mc-powered-by a:hover {\n color: #818cf8;\n}\n\n/* ── Animations ────────────────────────────────────────────────────────────── */\n@keyframes mc-modal-in {\n from {\n opacity: 0;\n transform: translateY(12px) scale(0.97);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes mc-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ══════════════════════════════════════════════════════════════════════════════\n LIGHT THEME (High Contrast & Clean)\n ══════════════════════════════════════════════════════════════════════════════ */\n\n[data-theme='light'] .mc-backdrop {\n background: rgba(15, 23, 42, 0.45);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n\n[data-theme='light'] .mc-modal {\n background: #ffffff;\n border: 1px solid #e2e8f0;\n color: #0f172a;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.12), 0 8px 10px -6px rgba(0, 0, 0, 0.06);\n}\n\n[data-theme='light'] .mc-close-btn {\n background: #f1f5f9;\n border: 1px solid #e2e8f0;\n color: #64748b;\n}\n\n[data-theme='light'] .mc-close-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n}\n\n[data-theme='light'] .mc-title {\n color: #0f172a;\n}\n\n[data-theme='light'] .mc-subtitle {\n color: #64748b;\n}\n\n/* ── Light: Card area ─────────────────────────────────────────────────────── */\n[data-theme='light'] .mc-card-wrapper {\n background: #ffffff;\n border: 1.5px solid #cbd5e1;\n box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);\n}\n\n[data-theme='light'] .mc-card-wrapper:focus-within {\n border-color: #6366f1;\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);\n}\n\n[data-theme='light'] .mc-loading-text {\n color: #64748b;\n}\n\n[data-theme='light'] .mc-spinner {\n border-color: rgba(99, 102, 241, 0.2);\n border-top-color: #6366f1;\n}\n\n[data-theme='light'] .mc-error-banner {\n background: #fef2f2;\n border-color: #fecaca;\n color: #dc2626;\n}\n\n[data-theme='light'] .mc-success-icon {\n background: #f0fdf4;\n border-color: #bbf7d0;\n}\n\n[data-theme='light'] .mc-success-icon svg {\n color: #16a34a;\n}\n\n[data-theme='light'] .mc-success-title {\n color: #0f172a;\n}\n\n[data-theme='light'] .mc-success-sub {\n color: #64748b;\n}\n\n[data-theme='light'] .mc-powered-by {\n color: #94a3b8;\n}\n\n[data-theme='light'] .mc-powered-by a {\n color: #6366f1;\n}\n\n[data-theme='light'] .mc-powered-by a:hover {\n color: #4f46e5;\n}\n",this.shadowRoot.appendChild(a);const c=document.createElement("div");c.id="mc-theme-container",c.setAttribute("data-theme","light"===this.config.mode?"light":"dark"),this.shadowRoot.appendChild(c),document.body.appendChild(this.host),this.renderer=new o(this.shadowRoot,{onClose:()=>this.handleClose(),onSubmit:()=>this.handleSubmit()}),this.renderer.render(),this.isInitialized=!0,this.show(),window.addEventListener("keydown",this._handleKeyDown),await this.authenticate(),await this._initializeCard()}catch(t){throw this.renderer&&this.renderer.showInitError(t.message||"Initialization failed"),this.config?.onError&&this.config.onError(t),t}}_handleKeyDown(n){"Escape"===n.key&&this.isInitialized&&this.handleClose()}async authenticate(){try{console.info(`[MyGigsters Card SDK] Authenticating in "${this.config.env}" mode → ${this.config.baseUrl}`);const n=await this.api.authenticate(this.config.apiKey,this.config.apiSecret);this.authToken=n.token,this.airwallexClientId=n.clientId,n.expiresIn&&(this.tokenExpiresAt=Date.now()+1e3*n.expiresIn),console.info("[MyGigsters Card SDK] Authentication successful."),"demo"!==this.config.env&&"dev"!==this.config.env||console.info(`ℹ️ Connected to [${this.config.env.toUpperCase()}] backend.`)}catch(n){const e=n.message||"";if(e.includes("401")||e.toLowerCase().includes("unauthorized"))throw new Error("Invalid API credentials: Please check your apiKey and apiSecret.");throw new Error(`Authentication failed: ${e}`)}}handleClose(){const n=this.config?.onClose;if(this.destroy(),n)try{n()}catch(e){console.error("Error in onClose callback:",e)}}async handleSubmit(){if(!this.isSubmitting&&this.renderer.state.cardComplete){this.isSubmitting=!0,this.renderer.setSubmitting(!0),this.renderer.setError("");try{const n=(await h({customer_id:this.config.customerId,client_secret:this.clientSecret,currency:"AUD",element:this.cardElement,requires_cvc:!1,next_triggered_by:"merchant"})).payment_consent_id;if(!n)throw new Error("Failed to create payment consent from card element");const e=this.config.apiKey&&this.config.apiSecret?`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`:this.authToken?`Bearer ${this.authToken}`:null,t={...await this.api.saveConsent(this.config.customerId,n,e),paymentConsentId:n,customerId:this.config.customerId,myGigsterId:this.config.myGigsterId,accountId:this.config.accountId,timestamp:(new Date).toISOString(),event:"card.saved"};this.renderer.showSuccess(t),this.config?.onSuccess&&this.config.onSuccess(t)}catch(n){const e=n.message||"Payment could not be completed. Please check your card details and try again.";this.renderer.setError(e),this.renderer.setSubmitting(!1),this.config?.onError&&this.config.onError(n)}finally{this.isSubmitting=!1}}}async _initializeCard(){try{const e=this.config.apiKey&&this.config.apiSecret?`Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}`:this.authToken?`Bearer ${this.authToken}`:null,{clientSecret:t}=await this.api.getClientSecret(this.config.customerId,this.config.accountId,e);this.clientSecret=t,await c();const o={env:p[this.config.env]||"demo",origin:window.location.origin};this.airwallexClientId&&(o.clientId=this.airwallexClientId),d(o);const i="light"===this.config.mode,r=l("card",{style:{base:{color:i?"#0f172a":"#ffffff",fontSize:"15px",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',"::placeholder":{color:i?"#94a3b8":"rgba(255, 255, 255, 0.45)",fontSize:"15px"}}}});this.cardElement=r,this.renderer.setCardLoaded(),await new Promise(n=>setTimeout(n,0));const s=this.shadowRoot.querySelector("#card-root");if(!s)throw new Error("Card mount point not found");try{r.mount(s)}catch(n){r.mount("#card-root")}this.renderer.markCardMounted(),r.on("change",n=>{const e=n?.detail?.complete||!1;this.renderer.setCardComplete(e)})}catch(e){const n=e.message||"Failed to load card elements";this.renderer.showInitError(n),this.config?.onError&&this.config.onError(e)}}show(){if(!this.isInitialized)throw new Error("SDK not initialized. Call init() first.");this.host&&(this.host.style.display="block",this.host.style.width="100vw",this.host.style.height="100vh",this.host.style.pointerEvents="auto")}hide(){this.host&&(this.host.style.display="none",this.host.style.width="0",this.host.style.height="0",this.host.style.pointerEvents="none")}setTheme(n){if(!this.shadowRoot)return;const e=this.shadowRoot.querySelector("#mc-theme-container");e&&e.setAttribute("data-theme","light"===n?"light":"dark"),this.config&&(this.config.mode=n)}destroy(){if(window.removeEventListener("keydown",this._handleKeyDown),this.cardElement)try{this.cardElement.unmount()}catch(e){}this.renderer&&this.renderer.destroy(),this.host?.parentNode&&this.host.parentNode.removeChild(this.host);const n=document.getElementById("mygigsters-card-sdk-host");n?.parentNode&&n.parentNode.removeChild(n),this.isInitialized=!1,this.isSubmitting=!1,this.config=null,this.api=null,this.renderer=null,this.host=null,this.shadowRoot=null,this.cardElement=null,this.clientSecret=null,this.authToken=null,this.tokenExpiresAt=null,this.airwallexClientId=null}}let g=null;const f={init:async n=>(g&&(g.destroy(),g=null),g=new u,await g.init(n),g),show:()=>{g?.show()},hide:()=>{g?.hide()},destroy:()=>{g&&(g.destroy(),g=null)},setTheme:n=>{g?.setTheme(n)},getInstance:()=>g};"undefined"!=typeof window&&(window.MygigstersCard=f,window.MygigstersCardSDK=u,document.addEventListener("DOMContentLoaded",()=>{const n=document.querySelector('script[data-mygigsters-config][src*="card-sdk"]');if(n)try{const e=JSON.parse(n.dataset.mygigstersConfig);window.MygigstersCard.init(e).catch(n=>{console.error("MyGigsters Card SDK auto-init failed:",n)})}catch(e){console.error("Failed to parse MyGigsters Card SDK config:",e)}}))});
2
+ //# sourceMappingURL=card-sdk-entry.js.map