@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.
@@ -0,0 +1,1038 @@
1
+ import h from "airwallex-payment-elements";
2
+ class g {
3
+ constructor(t) {
4
+ this.baseUrl = t.replace(/\/+$/, "");
5
+ }
6
+ // ── Internal helpers ──────────────────────────────────────────────────────
7
+ /**
8
+ * Makes a JSON POST request.
9
+ * @param {string} path - API path (appended to baseUrl + /api/v1)
10
+ * @param {object} body - Request body
11
+ * @param {string|null} [token] - Optional Bearer token
12
+ * @param {object} [extraHeaders] - Additional headers
13
+ * @returns {Promise<object>}
14
+ */
15
+ async _post(t, e, i = null, o = {}) {
16
+ const s = {
17
+ "Content-Type": "application/json",
18
+ ...o
19
+ };
20
+ i && (s.Authorization = i.startsWith("Basic ") || i.startsWith("Bearer ") ? i : `Bearer ${i}`);
21
+ const r = await fetch(`${this.baseUrl}/api/v1${t}`, {
22
+ method: "POST",
23
+ headers: s,
24
+ body: JSON.stringify(e)
25
+ }), c = await r.text();
26
+ let n = null;
27
+ if (c && c.trim().length > 0)
28
+ try {
29
+ n = JSON.parse(c);
30
+ } catch {
31
+ n = null;
32
+ }
33
+ if (!r.ok) {
34
+ const l = n && (n.message || n.error) || c || r.statusText || `HTTP ${r.status}`;
35
+ throw new Error(l);
36
+ }
37
+ return n ?? {};
38
+ }
39
+ // ── Public methods ────────────────────────────────────────────────────────
40
+ /**
41
+ * Authenticate using apiKey and apiSecret via /api/v1/sdk-initialization.
42
+ * Mirrors BeneficiaryAPI authentication architecture.
43
+ *
44
+ * @param {string} apiKey - UUID-style API key
45
+ * @param {string} apiSecret - One-time API secret
46
+ * @returns {Promise<{ token: string, clientId: string, expiresIn: number, tokenType: string }>}
47
+ */
48
+ async authenticate(t, e) {
49
+ const i = btoa(`${t}:${e}`), o = await fetch(`${this.baseUrl}/api/v1/sdk-initialization`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ "x-api-version": "1.1",
54
+ Authorization: `Basic ${i}`
55
+ }
56
+ }), s = await o.text();
57
+ let r = null;
58
+ if (s && s.trim().length > 0)
59
+ try {
60
+ r = JSON.parse(s);
61
+ } catch {
62
+ r = null;
63
+ }
64
+ if (!o.ok) {
65
+ const c = r && (r.message || r.error) || s || o.statusText || `HTTP ${o.status}`;
66
+ throw new Error(c);
67
+ }
68
+ if (!r?.success || !r?.data?.token)
69
+ throw new Error("Authentication failed: Unexpected response from server");
70
+ return {
71
+ token: r.data.token,
72
+ clientId: r.data.clientId,
73
+ expiresIn: r.data.expiresIn,
74
+ tokenType: r.data.tokenType,
75
+ paymentsIndia: r.data.paymentsIndia || !1
76
+ };
77
+ }
78
+ /**
79
+ * Retrieve a client secret for Airwallex card element initialization.
80
+ *
81
+ * POST /api/v1/customer/client-secret
82
+ * Body: { customerId, accountId }
83
+ *
84
+ * @param {string} customerId
85
+ * @param {string} accountId
86
+ * @param {string|null} [token] - Bearer token if authenticated
87
+ * @returns {Promise<{ clientSecret: string }>}
88
+ */
89
+ async getClientSecret(t, e, i = null) {
90
+ console.log("🔐 CardAPI.getClientSecret()", {
91
+ customerId: t,
92
+ accountId: e,
93
+ hasToken: !!i
94
+ });
95
+ const o = await this._post(
96
+ "/customer/client-secret",
97
+ {
98
+ customerId: t,
99
+ accountId: e
100
+ },
101
+ i
102
+ ), s = o.client_secret || o.clientSecret || o?.data?.client_secret || o?.data?.clientSecret;
103
+ if (!s)
104
+ throw new Error("Failed to retrieve client secret from server");
105
+ return { clientSecret: s };
106
+ }
107
+ /**
108
+ * Save the payment consent after Airwallex createPaymentConsent succeeds.
109
+ *
110
+ * POST /api/v1/customer/save-consent
111
+ * Body: { customerId, paymentConsentId }
112
+ *
113
+ * @param {string} customerId
114
+ * @param {string} paymentConsentId
115
+ * @param {string|null} [token] - Bearer token if authenticated
116
+ * @returns {Promise<object>}
117
+ */
118
+ async saveConsent(t, e, i = null) {
119
+ return this._post(
120
+ "/customer/save-consent",
121
+ {
122
+ customerId: t,
123
+ paymentConsentId: e
124
+ },
125
+ i
126
+ );
127
+ }
128
+ }
129
+ const p = {
130
+ close: `<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
131
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.2" d="M6 18L18 6M6 6l12 12"/>
132
+ </svg>`,
133
+ check: `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
134
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20 6L9 17l-5-5"/>
135
+ </svg>`
136
+ };
137
+ class b {
138
+ constructor(t, e) {
139
+ this.shadowRoot = t, this.callbacks = e, this.state = {
140
+ loading: !0,
141
+ cardLoaded: !1,
142
+ cardComplete: !1,
143
+ submitting: !1,
144
+ error: "",
145
+ success: !1
146
+ }, this._cardMounted = !1;
147
+ }
148
+ // ── Container helper ────────────────────────────────────────────────────
149
+ _getContainer() {
150
+ let t = this.shadowRoot.querySelector("#mc-sdk-root");
151
+ if (!t) {
152
+ t = document.createElement("div"), t.id = "mc-sdk-root";
153
+ const e = this.shadowRoot.querySelector("#mc-theme-container");
154
+ e ? e.appendChild(t) : this.shadowRoot.appendChild(t);
155
+ }
156
+ return t;
157
+ }
158
+ // ══════════════════════════════════════════════════════════════════════════
159
+ // SINGLE-STEP CARD FORM
160
+ // ══════════════════════════════════════════════════════════════════════════
161
+ render() {
162
+ this.state.loading = !0;
163
+ const t = this._getContainer();
164
+ t.innerHTML = `
165
+ <div class="mc-backdrop" id="mc-backdrop">
166
+ <div class="mc-modal" id="mc-modal">
167
+ <button class="mc-close-btn" aria-label="Close" id="mc-close-btn" type="button">${p.close}</button>
168
+
169
+ <div class="mc-header">
170
+ <h1 class="mc-title">Card Details</h1>
171
+ <p class="mc-subtitle">Please enter your card details</p>
172
+ </div>
173
+
174
+ <div class="mc-body">
175
+ <!-- Error banner (initially hidden) -->
176
+ <div class="mc-error-banner" style="display:none;" id="mc-error-banner"></div>
177
+
178
+ <!-- Loading spinner (shown initially) -->
179
+ <div class="mc-loading" id="mc-loading">
180
+ <div class="mc-spinner"></div>
181
+ <span class="mc-loading-text">Loading payment form...</span>
182
+ </div>
183
+
184
+ <!-- Card wrapper (hidden until Airwallex loads) -->
185
+ <div class="mc-card-wrapper" id="mc-card-wrapper" style="display:none;">
186
+ <div id="card-root"></div>
187
+ </div>
188
+
189
+ <!-- Submit button (hidden until Airwallex loads) -->
190
+ <button class="mc-submit-btn" id="mc-submit-btn" disabled style="display:none;" type="button">
191
+ Submit
192
+ </button>
193
+ </div>
194
+
195
+ <div class="mc-powered-by">
196
+ Powered by <a href="https://mygigsters.com.au" target="_blank" rel="noopener">MyGigsters</a>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ `, this._attachListeners();
201
+ }
202
+ _attachListeners() {
203
+ this.shadowRoot.querySelectorAll(".mc-close-btn, #mc-close-btn").forEach((o) => {
204
+ o.addEventListener("click", (s) => {
205
+ s.preventDefault(), s.stopPropagation(), this.callbacks.onClose();
206
+ });
207
+ });
208
+ const e = this.shadowRoot.querySelector("#mc-backdrop") || this.shadowRoot.querySelector(".mc-backdrop");
209
+ e && e.addEventListener("click", (o) => {
210
+ o.target === e && (o.preventDefault(), o.stopPropagation(), this.callbacks.onClose());
211
+ });
212
+ const i = this.shadowRoot.querySelector("#mc-submit-btn");
213
+ i && i.addEventListener("click", (o) => {
214
+ o.preventDefault(), o.stopPropagation(), this.callbacks.onSubmit();
215
+ }), this.shadowRoot.addEventListener("click", (o) => {
216
+ const s = o.target;
217
+ s && (s.closest(".mc-close-btn") || s.closest("#mc-close-btn")) && (o.preventDefault(), o.stopPropagation(), this.callbacks.onClose());
218
+ });
219
+ }
220
+ // ── Surgical state patches (safe after card mount) ──────────────────────
221
+ /**
222
+ * Call when Airwallex card element is ready and about to be mounted.
223
+ * Hides the spinner and reveals the card wrapper + submit button.
224
+ */
225
+ setCardLoaded() {
226
+ this.state.cardLoaded = !0, this.state.loading = !1;
227
+ const t = this.shadowRoot.querySelector("#mc-loading"), e = this.shadowRoot.querySelector("#mc-card-wrapper"), i = this.shadowRoot.querySelector("#mc-submit-btn");
228
+ t && (t.style.display = "none"), e && (e.style.display = "block"), i && (i.style.display = "flex");
229
+ }
230
+ /**
231
+ * Mark the Airwallex card as mounted — after this, no full re-renders.
232
+ */
233
+ markCardMounted() {
234
+ this._cardMounted = !0;
235
+ }
236
+ /**
237
+ * Enable/disable the submit button based on card completeness.
238
+ * Surgical patch — only touches the button element.
239
+ */
240
+ setCardComplete(t) {
241
+ this.state.cardComplete = t;
242
+ const e = this.shadowRoot.querySelector("#mc-submit-btn");
243
+ e && (e.disabled = !t || this.state.submitting);
244
+ }
245
+ /**
246
+ * Show/hide the submitting state on the button.
247
+ * Surgical patch — only touches the button element.
248
+ */
249
+ setSubmitting(t) {
250
+ this.state.submitting = t;
251
+ const e = this.shadowRoot.querySelector("#mc-submit-btn");
252
+ e && (e.disabled = t || !this.state.cardComplete, e.innerHTML = t ? '<span class="mc-btn-inner"><div class="mc-spinner mc-spinner-sm"></div>Saving...</span>' : "Submit");
253
+ }
254
+ /**
255
+ * Show/hide an error banner inside the modal.
256
+ * Surgical patch — never destroys the Airwallex iframe.
257
+ */
258
+ setError(t) {
259
+ this.state.error = t;
260
+ const e = this.shadowRoot.querySelector("#mc-error-banner");
261
+ e && (t ? (e.textContent = t, e.style.display = "block") : (e.textContent = "", e.style.display = "none"));
262
+ }
263
+ /**
264
+ * Show an initialization error when the SDK fails to load.
265
+ * Only safe to call BEFORE the card element is mounted.
266
+ */
267
+ showInitError(t) {
268
+ this.state.loading = !1, this.state.error = t;
269
+ const e = this.shadowRoot.querySelector("#mc-loading");
270
+ e && (e.innerHTML = `
271
+ <div style="text-align:center; padding: 12px 0;">
272
+ <p style="color:#ef4444; font-size:14px; font-weight:600; margin-bottom:8px;">${this._escapeHtml(t)}</p>
273
+ <p style="color:#94a3b8; font-size:13px;">Please check your configuration credentials and try again.</p>
274
+ </div>
275
+ `);
276
+ }
277
+ /**
278
+ * Replace the entire modal body with a success state.
279
+ * This is safe because we no longer need the Airwallex iframe.
280
+ */
281
+ showSuccess(t) {
282
+ this.state.success = !0;
283
+ const e = this.shadowRoot.querySelector(".mc-modal");
284
+ e && (e.innerHTML = `
285
+ <button class="mc-close-btn" aria-label="Close" id="mc-close-btn" type="button">${p.close}</button>
286
+
287
+ <div class="mc-body">
288
+ <div class="mc-success-wrap">
289
+ <div class="mc-success-icon">${p.check}</div>
290
+ <h2 class="mc-success-title">Card Saved Successfully</h2>
291
+ <p class="mc-success-sub">Your card details have been securely tokenized and saved.</p>
292
+ <button class="mc-done-btn" id="mc-done-btn" type="button">Done</button>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="mc-powered-by">
297
+ Powered by <a href="https://mygigsters.com.au" target="_blank" rel="noopener">MyGigsters</a>
298
+ </div>
299
+ `, e.querySelectorAll(".mc-close-btn, #mc-close-btn").forEach((i) => {
300
+ i.addEventListener("click", (o) => {
301
+ o.preventDefault(), o.stopPropagation(), this.callbacks.onClose();
302
+ });
303
+ }), e.querySelector("#mc-done-btn")?.addEventListener("click", (i) => {
304
+ i.preventDefault(), i.stopPropagation(), this.callbacks.onClose();
305
+ }));
306
+ }
307
+ // ── Helpers ─────────────────────────────────────────────────────────────
308
+ _escapeHtml(t) {
309
+ const e = document.createElement("div");
310
+ return e.textContent = t, e.innerHTML;
311
+ }
312
+ // ── Cleanup ─────────────────────────────────────────────────────────────
313
+ destroy() {
314
+ const t = this.shadowRoot.querySelector("#mc-sdk-root");
315
+ t && (t.innerHTML = ""), this._cardMounted = !1;
316
+ }
317
+ }
318
+ const y = `
319
+ /* ── Reset inside shadow DOM ───────────────────────────────────────────────── */
320
+ *, *::before, *::after {
321
+ box-sizing: border-box;
322
+ margin: 0;
323
+ padding: 0;
324
+ }
325
+
326
+ :host {
327
+ display: block !important;
328
+ position: fixed !important;
329
+ top: 0 !important;
330
+ left: 0 !important;
331
+ right: 0 !important;
332
+ bottom: 0 !important;
333
+ width: 100vw !important;
334
+ height: 100vh !important;
335
+ z-index: 2147483647 !important;
336
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
337
+ font-size: 14px;
338
+ color: #e5e7eb;
339
+ line-height: 1.5;
340
+ pointer-events: auto !important;
341
+ }
342
+
343
+ #mc-theme-container, #mc-sdk-root {
344
+ width: 100%;
345
+ height: 100%;
346
+ position: fixed;
347
+ top: 0;
348
+ left: 0;
349
+ }
350
+
351
+ /* ══════════════════════════════════════════════════════════════════════════════
352
+ DARK THEME (DEFAULT)
353
+ ══════════════════════════════════════════════════════════════════════════════ */
354
+
355
+ /* ── Backdrop ──────────────────────────────────────────────────────────────── */
356
+ .mc-backdrop {
357
+ position: fixed;
358
+ top: 0;
359
+ left: 0;
360
+ right: 0;
361
+ bottom: 0;
362
+ width: 100vw;
363
+ height: 100vh;
364
+ background: rgba(0, 0, 0, 0.75);
365
+ backdrop-filter: blur(5px);
366
+ -webkit-backdrop-filter: blur(5px);
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: center;
370
+ z-index: 2147483647;
371
+ padding: 16px;
372
+ box-sizing: border-box;
373
+ animation: mc-fade-in 0.2s ease;
374
+ }
375
+
376
+ /* ── Modal ──────────────────────────────────────────────────────────────────── */
377
+ .mc-modal {
378
+ background: #111827;
379
+ border: 1px solid rgba(255, 255, 255, 0.12);
380
+ border-radius: 16px;
381
+ width: 100%;
382
+ max-width: 460px;
383
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.05);
384
+ overflow: hidden;
385
+ animation: mc-modal-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
386
+ position: relative;
387
+ display: flex;
388
+ flex-direction: column;
389
+ color: #ffffff;
390
+ }
391
+
392
+ /* ── Close button ──────────────────────────────────────────────────────────── */
393
+ .mc-close-btn {
394
+ position: absolute;
395
+ top: 16px;
396
+ right: 16px;
397
+ background: rgba(255, 255, 255, 0.08);
398
+ border: 1px solid rgba(255, 255, 255, 0.08);
399
+ border-radius: 8px;
400
+ width: 32px;
401
+ height: 32px;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ cursor: pointer !important;
406
+ color: #94a3b8;
407
+ transition: all 0.15s ease;
408
+ padding: 0;
409
+ line-height: 0;
410
+ z-index: 100;
411
+ outline: none;
412
+ pointer-events: auto !important;
413
+ user-select: none;
414
+ }
415
+
416
+ .mc-close-btn svg,
417
+ .mc-close-btn path {
418
+ pointer-events: none !important;
419
+ }
420
+
421
+ .mc-close-btn:hover {
422
+ background: rgba(255, 255, 255, 0.18);
423
+ color: #ffffff;
424
+ transform: scale(1.05);
425
+ }
426
+
427
+ .mc-close-btn:active {
428
+ transform: scale(0.95);
429
+ }
430
+
431
+ /* ── Header ────────────────────────────────────────────────────────────────── */
432
+ .mc-header {
433
+ text-align: center;
434
+ padding: 28px 24px 0;
435
+ }
436
+
437
+ .mc-title {
438
+ font-size: 22px;
439
+ font-weight: 700;
440
+ color: #ffffff;
441
+ margin: 0 0 6px;
442
+ line-height: 1.2;
443
+ }
444
+
445
+ .mc-subtitle {
446
+ font-size: 14px;
447
+ color: #94a3b8;
448
+ margin: 0;
449
+ font-weight: 400;
450
+ }
451
+
452
+ /* ── Body ──────────────────────────────────────────────────────────────────── */
453
+ .mc-body {
454
+ padding: 20px 24px 24px;
455
+ flex: 1;
456
+ }
457
+
458
+ /* ── Card mount area ───────────────────────────────────────────────────────── */
459
+ .mc-card-wrapper {
460
+ background: #1f2937;
461
+ border: 1px solid rgba(255, 255, 255, 0.12);
462
+ border-radius: 10px;
463
+ padding: 14px 16px;
464
+ margin-bottom: 20px;
465
+ min-height: 48px;
466
+ transition: border-color 0.2s, box-shadow 0.2s;
467
+ }
468
+
469
+ .mc-card-wrapper:focus-within {
470
+ border-color: #6366f1;
471
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
472
+ }
473
+
474
+ #card-root {
475
+ margin: 0;
476
+ padding: 0;
477
+ min-height: 40px;
478
+ width: 100%;
479
+ }
480
+
481
+ #card-root iframe {
482
+ display: block !important;
483
+ margin: 0 !important;
484
+ padding: 0 !important;
485
+ width: 100% !important;
486
+ border: none !important;
487
+ }
488
+
489
+ /* ── Loading state ─────────────────────────────────────────────────────────── */
490
+ .mc-loading {
491
+ display: flex;
492
+ flex-direction: column;
493
+ align-items: center;
494
+ justify-content: center;
495
+ padding: 32px 20px;
496
+ gap: 12px;
497
+ }
498
+
499
+ .mc-loading-text {
500
+ font-size: 13.5px;
501
+ color: #94a3b8;
502
+ font-weight: 500;
503
+ }
504
+
505
+ /* ── Spinner ────────────────────────────────────────────────────────────────── */
506
+ .mc-spinner {
507
+ width: 32px;
508
+ height: 32px;
509
+ border: 3px solid rgba(255, 255, 255, 0.15);
510
+ border-top-color: #6366f1;
511
+ border-radius: 50%;
512
+ animation: mc-spin 0.7s linear infinite;
513
+ flex-shrink: 0;
514
+ }
515
+
516
+ .mc-spinner-sm {
517
+ width: 16px;
518
+ height: 16px;
519
+ border-width: 2px;
520
+ border-color: rgba(255, 255, 255, 0.3);
521
+ border-top-color: #fff;
522
+ }
523
+
524
+ @keyframes mc-spin {
525
+ to {
526
+ transform: rotate(360deg);
527
+ }
528
+ }
529
+
530
+ /* ── Submit button ─────────────────────────────────────────────────────────── */
531
+ .mc-submit-btn {
532
+ width: 100%;
533
+ padding: 13px 24px;
534
+ background: #6366f1;
535
+ color: #ffffff;
536
+ border: none;
537
+ border-radius: 9px;
538
+ font-size: 15px;
539
+ font-weight: 600;
540
+ cursor: pointer;
541
+ font-family: inherit;
542
+ transition: all 0.2s ease;
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ gap: 8px;
547
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);
548
+ outline: none;
549
+ }
550
+
551
+ .mc-submit-btn:hover:not(:disabled) {
552
+ background: #4f46e5;
553
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.45);
554
+ transform: translateY(-1px);
555
+ }
556
+
557
+ .mc-submit-btn:active:not(:disabled) {
558
+ transform: translateY(0);
559
+ }
560
+
561
+ .mc-submit-btn:disabled {
562
+ opacity: 0.55;
563
+ cursor: not-allowed;
564
+ transform: none;
565
+ box-shadow: none;
566
+ }
567
+
568
+ .mc-btn-inner {
569
+ display: flex;
570
+ align-items: center;
571
+ justify-content: center;
572
+ gap: 8px;
573
+ }
574
+
575
+ /* ── Error banner ──────────────────────────────────────────────────────────── */
576
+ .mc-error-banner {
577
+ background: rgba(239, 68, 68, 0.12);
578
+ border: 1px solid rgba(239, 68, 68, 0.3);
579
+ border-radius: 8px;
580
+ padding: 10px 14px;
581
+ margin-bottom: 16px;
582
+ font-size: 13px;
583
+ color: #fca5a5;
584
+ line-height: 1.4;
585
+ animation: mc-fade-in 0.2s ease;
586
+ }
587
+
588
+ /* ── Success state ─────────────────────────────────────────────────────────── */
589
+ .mc-success-wrap {
590
+ text-align: center;
591
+ padding: 30px 20px 16px;
592
+ animation: mc-fade-in 0.3s ease;
593
+ }
594
+
595
+ .mc-success-icon {
596
+ width: 56px;
597
+ height: 56px;
598
+ border-radius: 50%;
599
+ background: rgba(34, 197, 94, 0.15);
600
+ border: 2px solid rgba(34, 197, 94, 0.4);
601
+ display: flex;
602
+ align-items: center;
603
+ justify-content: center;
604
+ margin: 0 auto 16px;
605
+ }
606
+
607
+ .mc-success-icon svg {
608
+ width: 28px;
609
+ height: 28px;
610
+ color: #22c55e;
611
+ }
612
+
613
+ .mc-success-title {
614
+ font-size: 20px;
615
+ font-weight: 700;
616
+ color: #f8fafc;
617
+ margin: 0 0 6px;
618
+ }
619
+
620
+ .mc-success-sub {
621
+ font-size: 13.5px;
622
+ color: #94a3b8;
623
+ margin: 0 0 24px;
624
+ }
625
+
626
+ .mc-done-btn {
627
+ display: inline-flex;
628
+ align-items: center;
629
+ justify-content: center;
630
+ padding: 11px 36px;
631
+ background: #6366f1;
632
+ color: #fff;
633
+ border: none;
634
+ border-radius: 8px;
635
+ font-size: 14px;
636
+ font-weight: 600;
637
+ cursor: pointer;
638
+ font-family: inherit;
639
+ transition: all 0.2s ease;
640
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.35);
641
+ }
642
+
643
+ .mc-done-btn:hover {
644
+ background: #4f46e5;
645
+ transform: translateY(-1px);
646
+ }
647
+
648
+ /* ── Powered by ────────────────────────────────────────────────────────────── */
649
+ .mc-powered-by {
650
+ text-align: center;
651
+ padding: 8px 0 16px;
652
+ font-size: 11.5px;
653
+ color: #64748b;
654
+ }
655
+
656
+ .mc-powered-by a {
657
+ color: #94a3b8;
658
+ text-decoration: none;
659
+ font-weight: 600;
660
+ transition: color 0.15s ease;
661
+ }
662
+
663
+ .mc-powered-by a:hover {
664
+ color: #818cf8;
665
+ }
666
+
667
+ /* ── Animations ────────────────────────────────────────────────────────────── */
668
+ @keyframes mc-modal-in {
669
+ from {
670
+ opacity: 0;
671
+ transform: translateY(12px) scale(0.97);
672
+ }
673
+ to {
674
+ opacity: 1;
675
+ transform: translateY(0) scale(1);
676
+ }
677
+ }
678
+
679
+ @keyframes mc-fade-in {
680
+ from { opacity: 0; }
681
+ to { opacity: 1; }
682
+ }
683
+
684
+ /* ══════════════════════════════════════════════════════════════════════════════
685
+ LIGHT THEME (High Contrast & Clean)
686
+ ══════════════════════════════════════════════════════════════════════════════ */
687
+
688
+ [data-theme='light'] .mc-backdrop {
689
+ background: rgba(15, 23, 42, 0.45);
690
+ backdrop-filter: blur(5px);
691
+ -webkit-backdrop-filter: blur(5px);
692
+ }
693
+
694
+ [data-theme='light'] .mc-modal {
695
+ background: #ffffff;
696
+ border: 1px solid #e2e8f0;
697
+ color: #0f172a;
698
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.12), 0 8px 10px -6px rgba(0, 0, 0, 0.06);
699
+ }
700
+
701
+ [data-theme='light'] .mc-close-btn {
702
+ background: #f1f5f9;
703
+ border: 1px solid #e2e8f0;
704
+ color: #64748b;
705
+ }
706
+
707
+ [data-theme='light'] .mc-close-btn:hover {
708
+ background: #e2e8f0;
709
+ color: #0f172a;
710
+ }
711
+
712
+ [data-theme='light'] .mc-title {
713
+ color: #0f172a;
714
+ }
715
+
716
+ [data-theme='light'] .mc-subtitle {
717
+ color: #64748b;
718
+ }
719
+
720
+ /* ── Light: Card area ─────────────────────────────────────────────────────── */
721
+ [data-theme='light'] .mc-card-wrapper {
722
+ background: #ffffff;
723
+ border: 1.5px solid #cbd5e1;
724
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
725
+ }
726
+
727
+ [data-theme='light'] .mc-card-wrapper:focus-within {
728
+ border-color: #6366f1;
729
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
730
+ }
731
+
732
+ [data-theme='light'] .mc-loading-text {
733
+ color: #64748b;
734
+ }
735
+
736
+ [data-theme='light'] .mc-spinner {
737
+ border-color: rgba(99, 102, 241, 0.2);
738
+ border-top-color: #6366f1;
739
+ }
740
+
741
+ [data-theme='light'] .mc-error-banner {
742
+ background: #fef2f2;
743
+ border-color: #fecaca;
744
+ color: #dc2626;
745
+ }
746
+
747
+ [data-theme='light'] .mc-success-icon {
748
+ background: #f0fdf4;
749
+ border-color: #bbf7d0;
750
+ }
751
+
752
+ [data-theme='light'] .mc-success-icon svg {
753
+ color: #16a34a;
754
+ }
755
+
756
+ [data-theme='light'] .mc-success-title {
757
+ color: #0f172a;
758
+ }
759
+
760
+ [data-theme='light'] .mc-success-sub {
761
+ color: #64748b;
762
+ }
763
+
764
+ [data-theme='light'] .mc-powered-by {
765
+ color: #94a3b8;
766
+ }
767
+
768
+ [data-theme='light'] .mc-powered-by a {
769
+ color: #6366f1;
770
+ }
771
+
772
+ [data-theme='light'] .mc-powered-by a:hover {
773
+ color: #4f46e5;
774
+ }
775
+ `, f = {
776
+ dev: "http://3.108.128.110:5000",
777
+ // ← Comment this line when dev server is removed
778
+ demo: "https://qa-payments.mygigsters.com.au",
779
+ // QA Demo environment
780
+ prod: "https://prod-payments.mygigsters.com.au"
781
+ // Production environment
782
+ }, w = {
783
+ dev: "demo",
784
+ demo: "demo",
785
+ prod: "prod"
786
+ };
787
+ class m {
788
+ constructor() {
789
+ 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);
790
+ }
791
+ // ─────────────────────────────────────────────────────────────────────────
792
+ // PUBLIC: init
793
+ // ─────────────────────────────────────────────────────────────────────────
794
+ /**
795
+ * Initialize the Card SDK.
796
+ *
797
+ * @param {object} config
798
+ * @param {string} config.apiKey - API key (required)
799
+ * @param {string} config.apiSecret - API secret (required)
800
+ * @param {string} config.customerId - Customer ID (required)
801
+ * @param {string} [config.myGigsterId]- MyGigster ID (required from frontend)
802
+ * @param {string} [config.accountId] - Account ID (legacy alias for myGigsterId)
803
+ * @param {string} config.containerId - ID of the DOM container
804
+ * @param {'dev'|'demo'|'prod'} [config.env='demo'] - Environment ("dev" | "demo" | "prod")
805
+ * @param {string} [config.baseUrl] - Custom base URL override
806
+ * @param {'light'|'dark'} [config.mode='dark']- UI theme
807
+ * @param {function} [config.onSuccess] - Callback on successful card save
808
+ * @param {function} [config.onError] - Callback on error
809
+ * @param {function} [config.onClose] - Callback when modal closes
810
+ */
811
+ async init(t) {
812
+ try {
813
+ if (!t)
814
+ throw new Error("Configuration object is required for MygigstersCard.init()");
815
+ if (!t.apiKey) throw new Error("apiKey is required");
816
+ if (!t.apiSecret) throw new Error("apiSecret is required");
817
+ if (!t.customerId) throw new Error("customerId is required");
818
+ const e = t.myGigsterId || t.accountId;
819
+ if (!e) throw new Error("myGigsterId is required");
820
+ if (!t.containerId) throw new Error("containerId is required");
821
+ if (!document.getElementById(t.containerId))
822
+ throw new Error(`Container element with ID '${t.containerId}' not found`);
823
+ let o = t.env || "demo";
824
+ if (o === "qa" && (o = "demo"), !f[o])
825
+ throw new Error(`Unknown env "${o}". Use "dev", "demo", or "prod".`);
826
+ const s = t.baseUrl || f[o];
827
+ this.config = {
828
+ mode: "dark",
829
+ onSuccess: () => {
830
+ },
831
+ onError: (l) => console.error("MyGigsters Card SDK Error:", l),
832
+ onClose: () => {
833
+ },
834
+ ...t,
835
+ myGigsterId: e,
836
+ accountId: e,
837
+ env: o,
838
+ baseUrl: s
839
+ };
840
+ const r = document.getElementById("mygigsters-card-sdk-host");
841
+ r?.parentNode && r.parentNode.removeChild(r), this.api = new g(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" });
842
+ const c = document.createElement("style");
843
+ c.textContent = y, this.shadowRoot.appendChild(c);
844
+ const n = document.createElement("div");
845
+ n.id = "mc-theme-container", n.setAttribute(
846
+ "data-theme",
847
+ this.config.mode === "light" ? "light" : "dark"
848
+ ), this.shadowRoot.appendChild(n), document.body.appendChild(this.host), this.renderer = new b(this.shadowRoot, {
849
+ onClose: () => this.handleClose(),
850
+ onSubmit: () => this.handleSubmit()
851
+ }), this.renderer.render(), this.isInitialized = !0, this.show(), window.addEventListener("keydown", this._handleKeyDown), await this.authenticate(), await this._initializeCard();
852
+ } catch (e) {
853
+ throw this.renderer && this.renderer.showInitError(e.message || "Initialization failed"), this.config?.onError && this.config.onError(e), e;
854
+ }
855
+ }
856
+ _handleKeyDown(t) {
857
+ t.key === "Escape" && this.isInitialized && this.handleClose();
858
+ }
859
+ // ─────────────────────────────────────────────────────────────────────────
860
+ // PUBLIC: authenticate
861
+ // ─────────────────────────────────────────────────────────────────────────
862
+ async authenticate() {
863
+ try {
864
+ console.info(
865
+ `[MyGigsters Card SDK] Authenticating in "${this.config.env}" mode → ${this.config.baseUrl}`
866
+ );
867
+ const t = await this.api.authenticate(
868
+ this.config.apiKey,
869
+ this.config.apiSecret
870
+ );
871
+ this.authToken = t.token, this.airwallexClientId = t.clientId, t.expiresIn && (this.tokenExpiresAt = Date.now() + t.expiresIn * 1e3), console.info("[MyGigsters Card SDK] Authentication successful."), (this.config.env === "demo" || this.config.env === "dev") && console.info(`ℹ️ Connected to [${this.config.env.toUpperCase()}] backend.`);
872
+ } catch (t) {
873
+ const e = t.message || "";
874
+ throw e.includes("401") || e.toLowerCase().includes("unauthorized") ? new Error(
875
+ "Invalid API credentials: Please check your apiKey and apiSecret."
876
+ ) : new Error(`Authentication failed: ${e}`);
877
+ }
878
+ }
879
+ // ─────────────────────────────────────────────────────────────────────────
880
+ // HANDLERS
881
+ // ─────────────────────────────────────────────────────────────────────────
882
+ handleClose() {
883
+ const t = this.config?.onClose;
884
+ if (this.destroy(), t)
885
+ try {
886
+ t();
887
+ } catch (e) {
888
+ console.error("Error in onClose callback:", e);
889
+ }
890
+ }
891
+ async handleSubmit() {
892
+ if (!this.isSubmitting && this.renderer.state.cardComplete) {
893
+ this.isSubmitting = !0, this.renderer.setSubmitting(!0), this.renderer.setError("");
894
+ try {
895
+ const e = (await h.createPaymentConsent({
896
+ customer_id: this.config.customerId,
897
+ client_secret: this.clientSecret,
898
+ currency: "AUD",
899
+ element: this.cardElement,
900
+ requires_cvc: !1,
901
+ next_triggered_by: "merchant"
902
+ })).payment_consent_id;
903
+ if (!e)
904
+ throw new Error("Failed to create payment consent from card element");
905
+ const i = this.config.apiKey && this.config.apiSecret ? `Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}` : this.authToken ? `Bearer ${this.authToken}` : null, s = {
906
+ ...await this.api.saveConsent(
907
+ this.config.customerId,
908
+ e,
909
+ i
910
+ ),
911
+ paymentConsentId: e,
912
+ customerId: this.config.customerId,
913
+ myGigsterId: this.config.myGigsterId,
914
+ accountId: this.config.accountId,
915
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
916
+ event: "card.saved"
917
+ };
918
+ this.renderer.showSuccess(s), this.config?.onSuccess && this.config.onSuccess(s);
919
+ } catch (t) {
920
+ const e = t.message || "Payment could not be completed. Please check your card details and try again.";
921
+ this.renderer.setError(e), this.renderer.setSubmitting(!1), this.config?.onError && this.config.onError(t);
922
+ } finally {
923
+ this.isSubmitting = !1;
924
+ }
925
+ }
926
+ }
927
+ // ─────────────────────────────────────────────────────────────────────────
928
+ // PRIVATE: Card initialization sequence
929
+ // ─────────────────────────────────────────────────────────────────────────
930
+ async _initializeCard() {
931
+ try {
932
+ const t = this.config.apiKey && this.config.apiSecret ? `Basic ${btoa(`${this.config.apiKey}:${this.config.apiSecret}`)}` : this.authToken ? `Bearer ${this.authToken}` : null, { clientSecret: e } = await this.api.getClientSecret(
933
+ this.config.customerId,
934
+ this.config.accountId,
935
+ t
936
+ );
937
+ this.clientSecret = e, await h.loadAirwallex();
938
+ const i = {
939
+ env: w[this.config.env] || "demo",
940
+ origin: window.location.origin
941
+ };
942
+ this.airwallexClientId && (i.clientId = this.airwallexClientId), h.init(i);
943
+ const o = this.config.mode === "light", s = {
944
+ color: o ? "#0f172a" : "#ffffff",
945
+ fontSize: "15px",
946
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
947
+ "::placeholder": {
948
+ color: o ? "#94a3b8" : "rgba(255, 255, 255, 0.45)",
949
+ fontSize: "15px"
950
+ }
951
+ }, r = h.createElement("card", {
952
+ style: { base: s }
953
+ });
954
+ this.cardElement = r, this.renderer.setCardLoaded(), await new Promise((n) => setTimeout(n, 0));
955
+ const c = this.shadowRoot.querySelector("#card-root");
956
+ if (!c) throw new Error("Card mount point not found");
957
+ try {
958
+ r.mount(c);
959
+ } catch {
960
+ r.mount("#card-root");
961
+ }
962
+ this.renderer.markCardMounted(), r.on("change", (n) => {
963
+ const l = n?.detail?.complete || !1;
964
+ this.renderer.setCardComplete(l);
965
+ });
966
+ } catch (t) {
967
+ const e = t.message || "Failed to load card elements";
968
+ this.renderer.showInitError(e), this.config?.onError && this.config.onError(t);
969
+ }
970
+ }
971
+ // ─────────────────────────────────────────────────────────────────────────
972
+ // PUBLIC: show / hide / destroy / setTheme / getInstance
973
+ // ─────────────────────────────────────────────────────────────────────────
974
+ show() {
975
+ if (!this.isInitialized)
976
+ throw new Error("SDK not initialized. Call init() first.");
977
+ this.host && (this.host.style.display = "block", this.host.style.width = "100vw", this.host.style.height = "100vh", this.host.style.pointerEvents = "auto");
978
+ }
979
+ hide() {
980
+ this.host && (this.host.style.display = "none", this.host.style.width = "0", this.host.style.height = "0", this.host.style.pointerEvents = "none");
981
+ }
982
+ /**
983
+ * Update the theme after initialization.
984
+ * @param {'light'|'dark'} mode
985
+ */
986
+ setTheme(t) {
987
+ if (!this.shadowRoot) return;
988
+ const e = this.shadowRoot.querySelector("#mc-theme-container");
989
+ e && e.setAttribute("data-theme", t === "light" ? "light" : "dark"), this.config && (this.config.mode = t);
990
+ }
991
+ destroy() {
992
+ if (window.removeEventListener("keydown", this._handleKeyDown), this.cardElement)
993
+ try {
994
+ this.cardElement.unmount();
995
+ } catch {
996
+ }
997
+ this.renderer && this.renderer.destroy(), this.host?.parentNode && this.host.parentNode.removeChild(this.host);
998
+ const t = document.getElementById("mygigsters-card-sdk-host");
999
+ t?.parentNode && t.parentNode.removeChild(t), 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;
1000
+ }
1001
+ }
1002
+ let a = null;
1003
+ const u = {
1004
+ init: async (d) => (a && (a.destroy(), a = null), a = new m(), await a.init(d), a),
1005
+ show: () => {
1006
+ a?.show();
1007
+ },
1008
+ hide: () => {
1009
+ a?.hide();
1010
+ },
1011
+ destroy: () => {
1012
+ a && (a.destroy(), a = null);
1013
+ },
1014
+ setTheme: (d) => {
1015
+ a?.setTheme(d);
1016
+ },
1017
+ getInstance: () => a
1018
+ };
1019
+ typeof window < "u" && (window.MygigstersCard = u, window.MygigstersCardSDK = m, document.addEventListener("DOMContentLoaded", () => {
1020
+ const d = document.querySelector(
1021
+ 'script[data-mygigsters-config][src*="card-sdk"]'
1022
+ );
1023
+ if (d)
1024
+ try {
1025
+ const t = JSON.parse(d.dataset.mygigstersConfig);
1026
+ window.MygigstersCard.init(t).catch((e) => {
1027
+ console.error("MyGigsters Card SDK auto-init failed:", e);
1028
+ });
1029
+ } catch (t) {
1030
+ console.error("Failed to parse MyGigsters Card SDK config:", t);
1031
+ }
1032
+ }));
1033
+ typeof window < "u" && (window.MygigstersCard = u, window.MygigstersCardSDK = m);
1034
+ export {
1035
+ u as MygigstersCard,
1036
+ m as MygigstersCardSDK,
1037
+ m as default
1038
+ };