@swirepay-developer/swirepay-card-sdk 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swirepay-developer/swirepay-card-sdk",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Swirepay Card Payment SDK (Web Component with Modal UI)",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -108,133 +108,8 @@ const USA_STATES = [
108
108
  ];
109
109
 
110
110
  const COUNTRIES_LIST = [
111
- { "name": "Afghanistan", "code": "AF" },
112
- { "name": "Albania", "code": "AL" },
113
- { "name": "Algeria", "code": "DZ" },
114
- { "name": "Andorra", "code": "AD" },
115
- { "name": "Angola", "code": "AO" },
116
- { "name": "Antigua and Barbuda", "code": "AG" },
117
- { "name": "Argentina", "code": "AR" },
118
- { "name": "Armenia", "code": "AM" },
119
- { "name": "Australia", "code": "AU" },
120
- { "name": "Austria", "code": "AT" },
121
- { "name": "Azerbaijan", "code": "AZ" },
122
- { "name": "Bahamas", "code": "BS" },
123
- { "name": "Bahrain", "code": "BH" },
124
- { "name": "Bangladesh", "code": "BD" },
125
- { "name": "Barbados", "code": "BB" },
126
- { "name": "Belarus", "code": "BY" },
127
- { "name": "Belgium", "code": "BE" },
128
- { "name": "Belize", "code": "BZ" },
129
- { "name": "Benin", "code": "BJ" },
130
- { "name": "Bhutan", "code": "BT" },
131
- { "name": "Bolivia", "code": "BO" },
132
- { "name": "Bosnia and Herzegovina", "code": "BA" },
133
- { "name": "Botswana", "code": "BW" },
134
- { "name": "Brazil", "code": "BR" },
135
- { "name": "Brunei", "code": "BN" },
136
- { "name": "Bulgaria", "code": "BG" },
137
- { "name": "Burkina Faso", "code": "BF" },
138
- { "name": "Burundi", "code": "BI" },
139
- { "name": "Cambodia", "code": "KH" },
140
- { "name": "Cameroon", "code": "CM" },
141
- { "name": "Canada", "code": "CA" },
142
- { "name": "Cape Verde", "code": "CV" },
143
- { "name": "Central African Republic", "code": "CF" },
144
- { "name": "Chad", "code": "TD" },
145
- { "name": "Chile", "code": "CL" },
146
- { "name": "China", "code": "CN" },
147
- { "name": "Colombia", "code": "CO" },
148
- { "name": "Comoros", "code": "KM" },
149
- { "name": "Congo", "code": "CG" },
150
- { "name": "Costa Rica", "code": "CR" },
151
- { "name": "Croatia", "code": "HR" },
152
- { "name": "Cuba", "code": "CU" },
153
- { "name": "Cyprus", "code": "CY" },
154
- { "name": "Czech Republic", "code": "CZ" },
155
- { "name": "Denmark", "code": "DK" },
156
- { "name": "Djibouti", "code": "DJ" },
157
- { "name": "Dominica", "code": "DM" },
158
- { "name": "Dominican Republic", "code": "DO" },
159
- { "name": "Ecuador", "code": "EC" },
160
- { "name": "Egypt", "code": "EG" },
161
- { "name": "El Salvador", "code": "SV" },
162
- { "name": "Equatorial Guinea", "code": "GQ" },
163
- { "name": "Eritrea", "code": "ER" },
164
- { "name": "Estonia", "code": "EE" },
165
- { "name": "Ethiopia", "code": "ET" },
166
- { "name": "Fiji", "code": "FJ" },
167
- { "name": "Finland", "code": "FI" },
168
- { "name": "France", "code": "FR" },
169
- { "name": "Gabon", "code": "GA" },
170
- { "name": "Gambia", "code": "GM" },
171
- { "name": "Georgia", "code": "GE" },
172
- { "name": "Germany", "code": "DE" },
173
- { "name": "Ghana", "code": "GH" },
174
- { "name": "Greece", "code": "GR" },
175
- { "name": "Grenada", "code": "GD" },
176
- { "name": "Guatemala", "code": "GT" },
177
- { "name": "Guinea", "code": "GN" },
178
- { "name": "Guyana", "code": "GY" },
179
- { "name": "Haiti", "code": "HT" },
180
- { "name": "Honduras", "code": "HN" },
181
- { "name": "Hungary", "code": "HU" },
182
- { "name": "Iceland", "code": "IS" },
183
111
  { "name": "India", "code": "IN" },
184
- { "name": "Indonesia", "code": "ID" },
185
- { "name": "Iran", "code": "IR" },
186
- { "name": "Iraq", "code": "IQ" },
187
- { "name": "Ireland", "code": "IE" },
188
- { "name": "Israel", "code": "IL" },
189
- { "name": "Italy", "code": "IT" },
190
- { "name": "Jamaica", "code": "JM" },
191
- { "name": "Japan", "code": "JP" },
192
- { "name": "Jordan", "code": "JO" },
193
- { "name": "Kazakhstan", "code": "KZ" },
194
- { "name": "Kenya", "code": "KE" },
195
- { "name": "Korea, South", "code": "KR" },
196
- { "name": "Kuwait", "code": "KW" },
197
- { "name": "Laos", "code": "LA" },
198
- { "name": "Latvia", "code": "LV" },
199
- { "name": "Lebanon", "code": "LB" },
200
- { "name": "Libya", "code": "LY" },
201
- { "name": "Lithuania", "code": "LT" },
202
- { "name": "Luxembourg", "code": "LU" },
203
- { "name": "Malaysia", "code": "MY" },
204
- { "name": "Maldives", "code": "MV" },
205
- { "name": "Mexico", "code": "MX" },
206
- { "name": "Monaco", "code": "MC" },
207
- { "name": "Morocco", "code": "MA" },
208
- { "name": "Nepal", "code": "NP" },
209
- { "name": "Netherlands", "code": "NL" },
210
- { "name": "New Zealand", "code": "NZ" },
211
- { "name": "Nigeria", "code": "NG" },
212
- { "name": "Norway", "code": "NO" },
213
- { "name": "Oman", "code": "OM" },
214
- { "name": "Pakistan", "code": "PK" },
215
- { "name": "Panama", "code": "PA" },
216
- { "name": "Peru", "code": "PE" },
217
- { "name": "Philippines", "code": "PH" },
218
- { "name": "Poland", "code": "PL" },
219
- { "name": "Portugal", "code": "PT" },
220
- { "name": "Qatar", "code": "QA" },
221
- { "name": "Romania", "code": "RO" },
222
- { "name": "Russia", "code": "RU" },
223
- { "name": "Saudi Arabia", "code": "SA" },
224
- { "name": "Singapore", "code": "SG" },
225
- { "name": "South Africa", "code": "ZA" },
226
- { "name": "Spain", "code": "ES" },
227
- { "name": "Sri Lanka", "code": "LK" },
228
- { "name": "Sweden", "code": "SE" },
229
- { "name": "Switzerland", "code": "CH" },
230
- { "name": "Thailand", "code": "TH" },
231
- { "name": "Turkey", "code": "TR" },
232
- { "name": "Ukraine", "code": "UA" },
233
- { "name": "United Arab Emirates", "code": "AE" },
234
- { "name": "United Kingdom", "code": "GB" },
235
112
  { "name": "United States", "code": "US" },
236
- { "name": "Vietnam", "code": "VN" },
237
- { "name": "Zimbabwe", "code": "ZW" }
238
113
  ];
239
114
 
240
115
  const DEFAULT_THEME = {
@@ -279,12 +154,59 @@ export class SwirepayCheckout extends HTMLElement {
279
154
  this.theme = DEFAULT_THEME;
280
155
  }
281
156
 
157
+ getEndpoints() {
158
+ if (this.test) {
159
+ return { gateway: 'https://staging-backend.swirepay.com' };
160
+ }
161
+ return { gateway: 'https://api.swirepay.com' };
162
+ }
163
+
164
+ async getSplitUpDetails(inventoryOrder) {
165
+ const payload = {
166
+ shopGid: inventoryOrder?.shopGid,
167
+ items: inventoryOrder?.inventoryOrderLineItems,
168
+ tipType: "AMOUNT",
169
+ tip: inventoryOrder?.totalTip
170
+ }
171
+ const gateway = this.getEndpoints().gateway;
172
+ let url = `${gateway}/v1/inventory/order/inventory-order-split-up`;
173
+ const response = await fetch(url, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'application/json',
177
+ 'x-api-key': this.apiKey,
178
+ },
179
+ body: JSON.stringify(payload)
180
+ })
181
+ return await response.json();
182
+ }
183
+
282
184
  async connectedCallback() {
283
185
  this.amount = parseInt(this.getAttribute("amount")) || 0;
284
186
  this.test = this.getAttribute("mode") === "test";
285
- this.amount = parseInt(this.getAttribute("amount")) || 0;
286
187
  this.currencyCode = this.getAttribute("currencyCode");
287
188
  this.apiKey = this.getAttribute("api-key");
189
+ this.isAddressRequired = this.getAttribute("isAddressRequired") === "true";
190
+ this.inventory = this.getAttribute("inventory") === "true";
191
+ this.planGid = this.getAttribute("planGid");
192
+ this.productName = this.getAttribute("productName");
193
+ this.frequency = this.getAttribute("frequency");
194
+ this.description = this.getAttribute("description");
195
+ this.totalPayments = this.getAttribute('totalPayments');
196
+ try {
197
+ const customerAttr = this.getAttribute("customer");
198
+ this.customer = customerAttr ? JSON.parse(customerAttr) : {};
199
+ const inventoryOrders = this.getAttribute("inventoryOrders");
200
+ this.inventoryOrder = inventoryOrders ? JSON.parse(inventoryOrders) : {};
201
+ if (this.inventory) {
202
+ const splitUpInfo = await this.getSplitUpDetails(this.inventoryOrder);
203
+ this.splitUp = splitUpInfo?.entity;
204
+ }
205
+ } catch (e) {
206
+ console.error("Invalid JSON", e);
207
+ this.customer = {};
208
+ this.inventoryOrder = {};
209
+ }
288
210
  await Promise.all([
289
211
  loadScript(SDK_CONFIG.elliptic),
290
212
  loadScript(SDK_CONFIG.crypto)
@@ -293,6 +215,112 @@ export class SwirepayCheckout extends HTMLElement {
293
215
  this.render();
294
216
  }
295
217
 
218
+ static get observedAttributes() {
219
+ return [
220
+ "amount",
221
+ "mode",
222
+ "currencycode",
223
+ "api-key",
224
+ "isaddressrequired",
225
+ "inventory",
226
+ "plangid",
227
+ "productname",
228
+ "frequency",
229
+ "description",
230
+ "totalpayments",
231
+ "customer",
232
+ "inventoryorders"
233
+ ];
234
+ }
235
+
236
+ attributeChangedCallback(name, oldValue, newValue) {
237
+ if (oldValue === newValue) return;
238
+
239
+ try {
240
+ switch (name) {
241
+ case "amount":
242
+ this.amount = parseInt(newValue) || 0;
243
+ break;
244
+
245
+ case "mode":
246
+ this.test = newValue === "test";
247
+ break;
248
+
249
+ case "currencycode":
250
+ this.currencyCode = newValue;
251
+ break;
252
+
253
+ case "api-key":
254
+ this.apiKey = newValue;
255
+ break;
256
+
257
+ case "isaddressrequired":
258
+ this.isAddressRequired = newValue === "true";
259
+ break;
260
+
261
+ case "inventory":
262
+ this.inventory = newValue === "true";
263
+ break;
264
+
265
+ case "plangid":
266
+ this.planGid = newValue;
267
+ break;
268
+
269
+ case "productname":
270
+ this.productName = newValue;
271
+ break;
272
+
273
+ case "frequency":
274
+ this.frequency = newValue;
275
+ break;
276
+
277
+ case "description":
278
+ this.description = newValue;
279
+ break;
280
+
281
+ case "totalpayments":
282
+ this.totalPayments = newValue;
283
+ break;
284
+
285
+ case "customer":
286
+ this.customer = newValue ? JSON.parse(newValue) : {};
287
+ break;
288
+
289
+ case "inventoryorders":
290
+ this.inventoryOrder = newValue ? JSON.parse(newValue) : {};
291
+ break;
292
+
293
+ default:
294
+ break;
295
+ }
296
+ if (this.shadowRoot) {
297
+ this.render();
298
+ }
299
+
300
+ } catch (e) {
301
+ console.error(`Invalid value for ${name}`, e);
302
+ }
303
+ }
304
+
305
+ setError(id, message) {
306
+ const input = this.shadow.getElementById(id);
307
+ const errorEl = this.shadow.getElementById(`error-${id}`);
308
+
309
+ if (errorEl) errorEl.textContent = message || "";
310
+
311
+ if (input) {
312
+ if (message) input.classList.add("input-error");
313
+ else input.classList.remove("input-error");
314
+ }
315
+ }
316
+
317
+ clearErrors() {
318
+ this.shadow.querySelectorAll(".error").forEach(e => e.textContent = "");
319
+ this.shadow.querySelectorAll("input, select").forEach(el => {
320
+ el.classList.remove("input-error");
321
+ });
322
+ }
323
+
296
324
  async encryptData(data, serverPublicKey, clientPrivateKey, keyId) {
297
325
  try {
298
326
  const EC = window.elliptic.ec;
@@ -388,6 +416,108 @@ export class SwirepayCheckout extends HTMLElement {
388
416
  return await response.json();
389
417
  }
390
418
 
419
+ async createOrder() {
420
+ const gateway = this.getEndpoints().gateway;
421
+ let url = `${gateway}/v1/inventory/order`;
422
+ const response = await fetch(url, {
423
+ method: 'POST',
424
+ headers: {
425
+ 'Content-Type': 'application/json',
426
+ 'x-api-key': this.apiKey,
427
+ },
428
+ body: JSON.stringify(this.inventoryOrder)
429
+ })
430
+ return await response.json();
431
+ }
432
+
433
+ async getSubScription(payload) {
434
+ const gateway = this.getEndpoints().gateway;
435
+ let url = `${gateway}/v2/subscription`;
436
+ const response = await fetch(url, {
437
+ method: 'POST',
438
+ headers: {
439
+ 'Content-Type': 'application/json',
440
+ 'x-api-key': this.apiKey,
441
+ },
442
+ body: JSON.stringify(payload)
443
+ })
444
+ return await response.json();
445
+ }
446
+
447
+ async getPlan() {
448
+ const gateway = this.getEndpoints().gateway;
449
+ let url = `${gateway}/v2/plan?isInternal.EQ=false&name.EQ=Recurring%20Plan`;
450
+ const response = await fetch(url, {
451
+ method: 'GET',
452
+ headers: {
453
+ 'Content-Type': 'application/json',
454
+ 'x-api-key': this.apiKey,
455
+ }
456
+ })
457
+ return await response.json();
458
+ }
459
+
460
+ async addedPlanInfo() {
461
+ const payload = {
462
+ "currencyCode": this.currencyCode,
463
+ "name": "Recurring Plan",
464
+ "description": "Recurring Plan",
465
+ "note": "Recurring Plan",
466
+ "billingFrequency": this.frequency,
467
+ "billingPeriod": 1
468
+ }
469
+ const gateway = this.getEndpoints().gateway;
470
+ let url = `${gateway}/v2/plan`;
471
+ const response = await fetch(url, {
472
+ method: 'POST',
473
+ headers: {
474
+ 'Content-Type': 'application/json',
475
+ 'x-api-key': this.apiKey,
476
+ },
477
+ body: JSON.stringify(payload)
478
+ })
479
+ return await response.json();
480
+ }
481
+
482
+ async getItemInfo() {
483
+ const gateway = this.getEndpoints().gateway;
484
+ let url = `${gateway}/v1/inventory/item?name.EQ=Recurring%20item&priceType.EQ=VARIABLE&isRecurring.EQ=true`;
485
+ const response = await fetch(url, {
486
+ method: 'GET',
487
+ headers: {
488
+ 'Content-Type': 'application/json',
489
+ 'x-api-key': this.apiKey,
490
+ },
491
+ })
492
+ return await response.json();
493
+ }
494
+
495
+ async addedItemInfo() {
496
+ const payload = {
497
+ "name": "Recurring item",
498
+ "alternateName": "Recurring item",
499
+ "onlineName": "Recurring item",
500
+ "onlineEnabled": true,
501
+ "price": 100,
502
+ "priceType": "VARIABLE",
503
+ "includeTax": true,
504
+ "available": true,
505
+ "recurring": true,
506
+ "onKiosk": true
507
+ }
508
+ const gateway = this.getEndpoints().gateway;
509
+ let url = `${gateway}/v1/inventory/item`;
510
+ const response = await fetch(url, {
511
+ method: 'POST',
512
+ headers: {
513
+ 'Content-Type': 'application/json',
514
+ 'x-api-key': this.apiKey,
515
+ },
516
+ body: JSON.stringify(payload)
517
+ })
518
+ return await response.json();
519
+ }
520
+
391
521
  async getServerEncription() {
392
522
  try {
393
523
  const gateway = this.getEndpoints().gateway;
@@ -443,9 +573,11 @@ export class SwirepayCheckout extends HTMLElement {
443
573
  return v.replace(/\D/g, "").slice(0, 10);
444
574
  }
445
575
  handleCountryChange() {
446
- const country = this.shadow.getElementById("country").value;
576
+ const countryEl = this.shadow.getElementById("country");
447
577
  const phoneCodeEl = this.shadow.getElementById("phone-code");
448
578
  const stateEl = this.shadow.getElementById("state");
579
+ if (!countryEl || !phoneCodeEl || !stateEl) return;
580
+ const country = countryEl.value;
449
581
  phoneCodeEl.value = COUNTRY_PHONE_MAP[country] || "";
450
582
  const states = getStates(country);
451
583
  stateEl.innerHTML = states.map(
@@ -453,77 +585,173 @@ export class SwirepayCheckout extends HTMLElement {
453
585
  ).join("");
454
586
  }
455
587
 
588
+ detectCardType(number) {
589
+ const n = number.replace(/\s/g, "");
590
+
591
+ if (/^4/.test(n)) return "VISA";
592
+ if (/^5[1-5]/.test(n)) return "MASTERCARD";
593
+ if (/^3[47]/.test(n)) return "AMEX";
594
+ if (/^6(?:011|5)/.test(n)) return "DISCOVER";
595
+
596
+ return "";
597
+ }
598
+
599
+ getAddressFields() {
600
+ if (!this.isAddressRequired) return "";
601
+
602
+ return `
603
+ <div class="section">
604
+ <div class="section-title">Address</div>
605
+
606
+ <div class="field">
607
+ <input id="street" required placeholder=" " />
608
+ <label>Street *</label>
609
+ </div>
610
+ <div class="error" id="error-street"></div>
611
+
612
+ <div class="field">
613
+ <input id="city" required placeholder=" " />
614
+ <label>City *</label>
615
+ </div>
616
+ <div class="error" id="error-city"></div>
617
+
618
+ <div class="field">
619
+ <select id="state"></select>
620
+ <label class="floating">State *</label>
621
+ </div>
622
+ <div class="error" id="error-state"></div>
623
+
624
+ <div class="field">
625
+ <select id="country" required>
626
+ ${COUNTRIES_LIST.map(c =>
627
+ `<option value="${c.code}" ${c.code === "US" ? "selected" : ""}>${c.name}</option>`
628
+ ).join("")}
629
+ </select>
630
+ <label class="floating">Country *</label>
631
+ </div>
632
+ <div class="error" id="error-country"></div>
633
+
634
+ <div class="field">
635
+ <input id="zip" required placeholder=" " />
636
+ <label>ZIP Code *</label>
637
+ </div>
638
+ <div class="error" id="error-zip"></div>
639
+
640
+ </div>
641
+ `;
642
+ }
643
+
456
644
  validate() {
457
- const get = id => this.shadow.getElementById(id)?.value?.trim();
645
+ this.clearErrors();
646
+ let isValid = true;
458
647
 
459
- const name = get("name");
460
- const email = get("email");
461
- const phone = get("phone");
462
- const code = get("phone-code");
463
- const zip = get("zip");
464
- const country = get("country");
465
-
466
- const cardName = get("card-name");
467
- const cardNumber = get("card")?.replace(/\s/g, "");
468
- const expiry = get("expiry");
469
- const cvv = get("cvv");
470
- const street = get("street");
471
- if (!name) return "Name required";
472
- if (!email) return "Email required";
473
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return "Invalid email address";
474
- if (!cardName) return "Card holder name required";
475
- if (!cardNumber || !/^\d{12,19}$/.test(cardNumber)) {
476
- return "Invalid card number";
648
+ const get = id => this.shadow.getElementById(id)?.value?.trim();
649
+
650
+ if (!get("name")) {
651
+ this.setError("name", "Name required");
652
+ isValid = false;
653
+ }
654
+
655
+ const email = get("email");
656
+ if (!email) {
657
+ this.setError("email", "Email required");
658
+ isValid = false;
659
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
660
+ this.setError("email", "Invalid email");
661
+ isValid = false;
662
+ }
663
+
664
+ if (!get("card-name")) {
665
+ this.setError("card-name", "Card holder name required");
666
+ isValid = false;
667
+ }
668
+
669
+ const cardNumber = get("card")?.replace(/\s/g, "");
670
+ if (!cardNumber || !/^\d{12,19}$/.test(cardNumber)) {
671
+ this.setError("card", "Invalid card number");
672
+ isValid = false;
673
+ }
674
+
675
+ const expiry = get("expiry");
676
+ if (!expiry || !/^\d{2}\/\d{2}$/.test(expiry)) {
677
+ this.setError("expiry", "Invalid expiry");
678
+ isValid = false;
679
+ } else {
680
+ const [mm, yy] = expiry.split("/").map(Number);
681
+ const now = new Date();
682
+ const currentYear = now.getFullYear() % 100;
683
+ const currentMonth = now.getMonth() + 1;
684
+
685
+ if (mm < 1 || mm > 12) {
686
+ this.setError("expiry", "Invalid month");
687
+ isValid = false;
688
+ } else if (yy < currentYear || (yy === currentYear && mm < currentMonth)) {
689
+ this.setError("expiry", "Card expired");
690
+ isValid = false;
477
691
  }
478
- if (!expiry || !/^\d{2}\/\d{2}$/.test(expiry)) {
479
- return "Invalid expiry format (MM/YY)";
692
+ }
693
+
694
+ const cvv = get("cvv");
695
+ if (!cvv || !/^\d{3,4}$/.test(cvv)) {
696
+ this.setError("cvv", "Invalid CVV");
697
+ isValid = false;
698
+ }
699
+
700
+ const phone = get("phone")?.replace(/\D/g, "");
701
+ const code = get("phone-code");
702
+ if (!phone || !code) {
703
+ this.setError("phone", "Phone number required");
704
+ isValid = false;
480
705
  } else {
481
- const [mm, yy] = expiry.split("/").map(Number);
482
- if (mm < 1 || mm > 12) return "Invalid expiry month";
483
- const now = new Date();
484
- const currentYear = now.getFullYear() % 100;
485
- const currentMonth = now.getMonth() + 1;
486
-
487
- if (yy < currentYear || (yy === currentYear && mm < currentMonth)) {
488
- return "Card expired";
706
+ const fullNumber = `${code}${phone}`;
707
+ if (!isValidPhoneNumber(fullNumber)) {
708
+ this.setError("phone", "Invalid phone number");
709
+ isValid = false;
489
710
  }
490
711
  }
491
- if (!cvv || !/^\d{3,4}$/.test(cvv)) {
492
- return "Invalid CVV";
493
- }
494
- if (!street) return "Street address required";
495
- if (phone && !isValidPhoneNumber(code + phone)) {
496
- return "Invalid phone";
712
+
713
+ if (this.isAddressRequired) {
714
+ if (!get("street")) {
715
+ this.setError("street", "Street required");
716
+ isValid = false;
497
717
  }
498
- if (zip && postalCodes.validate(country, zip) !== true) {
499
- return "Invalid ZIP";
718
+
719
+ if (!get("city")) {
720
+ this.setError("city", "City required");
721
+ isValid = false;
500
722
  }
501
723
 
502
- return null;
503
- }
724
+ if (!get("state")) {
725
+ this.setError("state", "State required");
726
+ isValid = false;
727
+ }
504
728
 
505
- async handleSubmit(e) {
506
- e.preventDefault();
729
+ if (!get("country")) {
730
+ this.setError("country", "Country required");
731
+ isValid = false;
732
+ }
507
733
 
508
- const payButton = this.shadow.getElementById("form").querySelector(".btn");
509
- payButton.disabled = true;
510
- payButton.textContent = "Processing...";
734
+ const zip = get("zip");
511
735
 
512
- const err = this.validate();
513
- if (err) {
514
- alert(err);
515
- payButton.disabled = false;
516
- payButton.textContent = `Pay $${(this.amount / 100).toFixed(2)}`;
517
- return;
736
+ if (!zip) {
737
+ this.setError("zip", "ZIP required");
738
+ isValid = false;
739
+ } else if (postalCodes.validate(get("country"), zip) !== true) {
740
+ this.setError("zip", "Invalid ZIP code");
741
+ isValid = false;
518
742
  }
743
+ }
744
+
745
+ return isValid;
746
+ }
519
747
 
748
+ async oneTimePayment() {
520
749
  try {
521
750
  const values = await this.getServerEncription();
522
751
  if (values?.public_key) {
523
752
  const keys = await this.generateClientKeyPair();
524
753
  if (keys?.privateKey) {
525
754
  const getVal = id => this.shadow.getElementById(id)?.value?.trim();
526
-
527
755
  const phoneCode = getVal("phone-code") || "";
528
756
  const phone = getVal("phone");
529
757
 
@@ -538,13 +766,17 @@ export class SwirepayCheckout extends HTMLElement {
538
766
  phone: phone ? `${phoneCode}${phone}` : null
539
767
  };
540
768
 
541
- const address = {
542
- street: getVal("street"),
543
- city: getVal("city"),
544
- state: getVal("state"),
545
- countryCode: getVal("country"),
546
- postalCode: getVal("zip")
547
- };
769
+ let address = null;
770
+
771
+ if (this.isAddressRequired) {
772
+ address = {
773
+ street: getVal("street"),
774
+ city: getVal("city"),
775
+ state: getVal("state"),
776
+ countryCode: getVal("country"),
777
+ postalCode: getVal("zip")
778
+ };
779
+ }
548
780
 
549
781
  const encriptedData = await this.encryptData(JSON.stringify(customer), values.public_key, keys.privateKey, values.key_id);
550
782
  const payload = {
@@ -565,7 +797,7 @@ export class SwirepayCheckout extends HTMLElement {
565
797
  cvv: cvv,
566
798
  name: cardHolderName
567
799
  },
568
- postalCode: address?.zip,
800
+ postalCode: address?.postalCode,
569
801
  paymentMethodBillingAddress: address,
570
802
  customerGid: customerGid
571
803
  };
@@ -611,20 +843,282 @@ export class SwirepayCheckout extends HTMLElement {
611
843
  body: JSON.stringify({ data: payload2?.data })
612
844
  });
613
845
  const resData = await res.json();
614
- if (resData?.entity?.errorCode === null){
615
- this.events.emit("success", resData);
616
- this.close();
617
- } else if (resData?.entity?.errorCode !== null){
618
- console.error("Payment Error:", resData?.entity?.errorDescription);
619
- this.events.emit("error", resData?.entity?.errorDescription);
620
- alert(resData?.entity?.errorDescription || "Payment failed");
621
- }
846
+ return resData;
847
+ }
848
+ }
849
+ } catch (error) {
850
+ console.error("Payment Error:", error);
851
+ this.events.emit("error", error);
852
+ }
853
+ }
854
+
855
+ async recurringPayment () {
856
+ try {
857
+ const values = await this.getServerEncription();
858
+ if (values?.public_key) {
859
+ const keys = await this.generateClientKeyPair();
860
+ if (keys?.privateKey) {
861
+ const getVal = id => this.shadow.getElementById(id)?.value?.trim();
862
+ const phoneCode = getVal("phone-code") || "";
863
+ const phone = getVal("phone");
864
+
865
+ const cardNumber = getVal("card")?.replace(/\s/g, "");
866
+ const expiry = getVal("expiry")?.split("/") || [];
867
+ const cvv = getVal("cvv");
868
+ const cardHolderName = getVal("card-name");
869
+
870
+ const customer = {
871
+ name: getVal("name"),
872
+ email: getVal("email"),
873
+ phone: phone ? `${phoneCode}${phone}` : null
874
+ };
875
+
876
+ let address = null;
877
+
878
+ if (this.isAddressRequired) {
879
+ address = {
880
+ street: getVal("street"),
881
+ city: getVal("city"),
882
+ state: getVal("state"),
883
+ countryCode: getVal("country"),
884
+ postalCode: getVal("zip")
885
+ };
886
+ }
887
+
888
+ const encriptedData = await this.encryptData(JSON.stringify(customer), values.public_key, keys.privateKey, values.key_id);
889
+ const payload = {
890
+ publicKey: keys?.publicKey,
891
+ keyId: encriptedData?.keyId,
892
+ iv: encriptedData?.iv,
893
+ data: encriptedData?.encryptedData,
894
+ }
895
+ const newCustomer = await this.createCustomer(payload);
896
+ const customerGid = newCustomer.entity?.gid;
897
+ let product;
898
+ const itemInfo = await this.getItemInfo();
899
+ if (itemInfo?.entity?.content[0]?.gid) {
900
+ product = itemInfo?.entity?.content[0];
901
+ } else {
902
+ const newItemInfo = await this.addedItemInfo();
903
+ product = newItemInfo?.entity;
904
+ }
905
+ let plan;
906
+ const planInfo = await this.getPlan();
907
+ if (planInfo?.entity?.content[0]?.gid) {
908
+ plan = planInfo?.entity?.content[0];
909
+ } else {
910
+ const newPlanInfo = await this.addedPlanInfo();
911
+ plan = newPlanInfo?.entity;
912
+ }
913
+
914
+ const subscriptionData = {
915
+ currencyCode: this.currencyCode,
916
+ customerGid: customerGid,
917
+ plan2Gid: plan?.gid,
918
+ plan2BillingPeriod: 1,
919
+ plan2BillingFrequency: this.frequency,
920
+ plan2StartDate: new Date().toISOString(),
921
+ plan2TotalPayments: this.totalPayments,
922
+ description: this.description,
923
+ notificationType: "ALL",
924
+ emailRecipientList: [
925
+ customer.email
926
+ ],
927
+ canCustomerCancelOrPause: true,
928
+ subscription2LineItems: [
929
+ {
930
+ itemGid: product?.gid,
931
+ quantity: 1,
932
+ upfront: false,
933
+ currencyCode: this.currencyCode,
934
+ amount: this.amount
935
+ }
936
+ ]
937
+ }
938
+ const subscriptionDetails = await this.getSubScription(subscriptionData);
939
+ const subscription = subscriptionDetails?.entity;
940
+ const sessionData = {
941
+ link: subscription?.subscription2Links[0]?.link,
942
+ customer: subscription?.customer,
943
+ type: 'CARD',
944
+ card: {
945
+ number: cardNumber,
946
+ expiryMonth: parseInt(expiry[0]),
947
+ expiryYear: 2000 + parseInt(expiry[1]),
948
+ cvv: cvv,
949
+ name: cardHolderName
950
+ },
951
+ postalCode: address?.postalCode,
952
+ paymentMethodBillingAddress: address
953
+ }
954
+ const encriptedData4 = await this.encryptData(JSON.stringify(sessionData), values.public_key, keys.privateKey, values.key_id);
955
+ const payload2 = {
956
+ publicKey: keys?.publicKey,
957
+ keyId: encriptedData4?.keyId,
958
+ iv: encriptedData4?.iv,
959
+ data: encriptedData4?.encryptedData,
960
+ }
961
+ const gateway = this.getEndpoints().gateway;
962
+ const url = `${gateway}/v2/subscription-link/${subscription?.subscription2Links[0]?.gid}/payment-method`;
963
+ const res = await fetch(url, {
964
+ method: 'POST',
965
+ headers: {
966
+ 'x-enc-public-key': payload2?.publicKey,
967
+ 'x-enc-key-id': payload2?.keyId,
968
+ 'x-enc-iv': payload2?.iv,
969
+ 'Content-Type': 'application/json',
970
+ 'x-api-key': this.apiKey,
971
+ },
972
+ body: JSON.stringify({ data: payload2?.data })
973
+ });
974
+ const resData = await res.json();
975
+ return resData;
976
+ }
977
+ }
978
+ } catch (error) {
979
+ console.error("Payment Error:", error);
980
+ this.events.emit("error", error);
981
+ }
982
+ }
983
+
984
+ async inventoryCharge() {
985
+ try {
986
+ const values = await this.getServerEncription();
987
+ if (values?.public_key) {
988
+ const keys = await this.generateClientKeyPair();
989
+ if (keys?.privateKey) {
990
+ const getVal = id => this.shadow.getElementById(id)?.value?.trim();
991
+ const phoneCode = getVal("phone-code") || "";
992
+ const phone = getVal("phone");
993
+
994
+ const cardNumber = getVal("card")?.replace(/\s/g, "");
995
+ const expiry = getVal("expiry")?.split("/") || [];
996
+ const cvv = getVal("cvv");
997
+ const cardHolderName = getVal("card-name");
998
+
999
+ const customer = {
1000
+ name: getVal("name"),
1001
+ email: getVal("email"),
1002
+ phone: phone ? `${phoneCode}${phone}` : null
1003
+ };
1004
+
1005
+ let address = null;
1006
+
1007
+ if (this.isAddressRequired) {
1008
+ address = {
1009
+ street: getVal("street"),
1010
+ city: getVal("city"),
1011
+ state: getVal("state"),
1012
+ countryCode: getVal("country"),
1013
+ postalCode: getVal("zip")
1014
+ };
1015
+ }
1016
+
1017
+ const encriptedData = await this.encryptData(JSON.stringify(customer), values.public_key, keys.privateKey, values.key_id);
1018
+ const payload = {
1019
+ publicKey: keys?.publicKey,
1020
+ keyId: encriptedData?.keyId,
1021
+ iv: encriptedData?.iv,
1022
+ data: encriptedData?.encryptedData,
1023
+ }
1024
+ const newCustomer = await this.createCustomer(payload);
1025
+ const customerGid = newCustomer.entity?.gid;
1026
+ const orderDetails = await this.createOrder();
1027
+ const order = orderDetails?.entity;
1028
+ const sessionData = {
1029
+ customer: customer,
1030
+ customerGid: customerGid,
1031
+ paymentMethod: {
1032
+ card: {
1033
+ number: cardNumber,
1034
+ expiryMonth: parseInt(expiry[0]),
1035
+ expiryYear: 2000 + parseInt(expiry[1]),
1036
+ cvv: cvv,
1037
+ name: cardHolderName
1038
+ },
1039
+ postalCode: address?.postalCode,
1040
+ type: "CARD",
1041
+ paymentMethodBillingAddress: address,
1042
+ }
1043
+ }
1044
+ const encriptedData1 = await this.encryptData(JSON.stringify(sessionData), values.public_key, keys.privateKey, values.key_id);
1045
+ const payload2 = {
1046
+ publicKey: keys?.publicKey,
1047
+ keyId: encriptedData1?.keyId,
1048
+ iv: encriptedData1?.iv,
1049
+ data: encriptedData1?.encryptedData,
1050
+ }
1051
+ const gateway = this.getEndpoints().gateway;
1052
+ const url = `${gateway}/v1/inventory/order-link/${order?.inventoryOrderLink?.gid}/charge`;
1053
+ const res = await fetch(url, {
1054
+ method: 'POST',
1055
+ headers: {
1056
+ 'x-enc-public-key': payload2?.publicKey,
1057
+ 'x-enc-key-id': payload2?.keyId,
1058
+ 'x-enc-iv': payload2?.iv,
1059
+ 'Content-Type': 'application/json',
1060
+ 'x-api-key': this.apiKey,
1061
+ },
1062
+ body: JSON.stringify({ data: payload2?.data })
1063
+ });
1064
+ const resData = await res.json();
1065
+ return resData;
1066
+ }
1067
+ }
1068
+
1069
+ } catch (error) {
1070
+ console.error("Payment Error:", error);
1071
+ this.events.emit("error", error);
1072
+ }
1073
+ }
1074
+
1075
+ async handleSubmit(e) {
1076
+ e.preventDefault();
1077
+
1078
+ const payButton = this.shadow.getElementById("form").querySelector(".btn");
1079
+ payButton.disabled = true;
1080
+ payButton.textContent = "Processing...";
1081
+
1082
+ const isValid = this.validate();
1083
+ if (!isValid) {
1084
+ payButton.disabled = false;
1085
+ payButton.textContent = `Pay $${(this.amount / 100).toFixed(2)}`;
1086
+ return;
1087
+ }
1088
+ try {
1089
+ if (this.frequency === "ONE-TIME" && !this.inventory) {
1090
+ const resData = await this.oneTimePayment();
1091
+ if (resData?.entity?.errorCode === null) {
1092
+ this.events.emit("success", resData);
1093
+ this.close();
1094
+ } else {
1095
+ const errorMsg = resData?.entity?.errorDescription || "Payment failed";
1096
+ console.error("Payment Error:", errorMsg);
1097
+ this.events.emit("error", errorMsg);
1098
+ }
1099
+ } else if (this.frequency !== "ONE-TIME" && !this.inventory) {
1100
+ const resData = await this.recurringPayment();
1101
+ if (resData?.entity?.setupSession?.errorCode === null) {
1102
+ this.events.emit("success", resData);
1103
+ this.close();
1104
+ } else {
1105
+ const errorMsg = resData?.entity?.setupSession?.errorDescription || "Payment failed";
1106
+ console.error("Payment Error:", errorMsg);
1107
+ this.events.emit("error", errorMsg);
1108
+ }
1109
+ } else if (this.inventory) {
1110
+ const resData = await this.inventoryCharge();
1111
+ if (resData?.responseCode === 200) {
1112
+ this.events.emit("success", resData);
1113
+ this.close();
1114
+ } else {
1115
+ console.error("Payment Error:", resData?.message || 'Payment failed');
1116
+ this.events.emit("error", resData?.message);
622
1117
  }
623
1118
  }
624
1119
  } catch (error) {
625
1120
  console.error("Payment Error:", error);
626
1121
  this.events.emit("error", error);
627
- alert(error.message || "Payment failed");
628
1122
  } finally {
629
1123
  payButton.disabled = false;
630
1124
  payButton.textContent = `Pay $${(this.amount / 100).toFixed(2)}`;
@@ -633,6 +1127,7 @@ export class SwirepayCheckout extends HTMLElement {
633
1127
  render() {
634
1128
  const t = this.theme;
635
1129
  const amountText = (this.amount / 100).toFixed(2);
1130
+ const inventoryAmountText = ((this.splitUp?.netPayable) / 100).toFixed(2);
636
1131
 
637
1132
  this.shadow.innerHTML = `
638
1133
  <style>
@@ -654,26 +1149,45 @@ export class SwirepayCheckout extends HTMLElement {
654
1149
  background:rgba(0,0,0,0.6);
655
1150
  justify-content:center;
656
1151
  align-items:center;
1152
+ z-index:9999;
657
1153
  }
658
1154
 
659
1155
  .modal {
660
- width:420px;
661
- background:var(--bg);
662
- color:var(--text);
663
- border-radius:16px;
664
- padding:20px;
665
- font-family:sans-serif;
1156
+ width: 520px;
1157
+ height: auto;
1158
+ max-height: 650px;
1159
+ background: var(--bg);
1160
+ color: var(--text);
1161
+ border-radius: 16px;
1162
+ padding: 28px;
1163
+ font-family: sans-serif;
1164
+ }
1165
+
1166
+ .modal-body {
1167
+ overflow-y: auto;
1168
+ flex: 1;
1169
+ height: auto;
1170
+ max-height: 530px;
1171
+ }
1172
+
1173
+ form {
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ gap: 16px;
666
1177
  }
667
1178
 
668
1179
  input, select {
669
- width:100%;
670
- padding:10px;
671
- margin:6px 0;
672
- border:1px solid var(--border);
673
- background: white;
674
- color: black;
675
- border-radius:8px;
676
- outline:none;
1180
+ width: 100%;
1181
+ padding: 12px;
1182
+ border: 1px solid var(--border);
1183
+ background: #ffffff;
1184
+ color: #000000;
1185
+ border-radius: 8px;
1186
+ outline: none;
1187
+ height: 44px;
1188
+ font-size: 14px;
1189
+ box-sizing: border-box;
1190
+ margin: 0 !important;
677
1191
  }
678
1192
 
679
1193
  input::placeholder {
@@ -696,7 +1210,7 @@ input::placeholder {
696
1210
  select {
697
1211
  width:100%;
698
1212
  padding:10px;
699
- margin:6px 0;
1213
+ margin: 0;
700
1214
  border:1px solid var(--border);
701
1215
  border-radius:8px;
702
1216
 
@@ -712,67 +1226,199 @@ select option {
712
1226
  color: #000000;
713
1227
  }
714
1228
 
715
- .row { display:flex; gap:10px; }
1229
+ .row {
1230
+ display: flex;
1231
+ gap: 16px;
1232
+ align-items: center;
1233
+ }
1234
+ .phone-code {
1235
+ flex: 0 0 70px !important;
1236
+ }
1237
+
1238
+ .row input,
1239
+ .row select {
1240
+ flex: 1;
1241
+ }
716
1242
 
717
- .phone-code { width:70px; }
1243
+ input, select {
1244
+ box-sizing: border-box;
1245
+ height: 40px;
1246
+ padding: 10px;
1247
+ font-size: 14px;
1248
+ }
718
1249
 
719
1250
  .btn {
720
- background:var(--primary);
721
- color:#fff;
722
- padding:12px;
723
- border:none;
724
- border-radius:10px;
725
- cursor:pointer;
726
- width:100%;
1251
+ background: var(--primary);
1252
+ color: #fff;
1253
+ padding: 14px;
1254
+ border: none;
1255
+ border-radius: 10px;
1256
+ cursor: pointer;
1257
+ width: 100%;
1258
+ height: 48px;
1259
+ font-size: 15px;
727
1260
  }
728
1261
 
729
1262
  .close {
730
1263
  float:right;
731
1264
  cursor:pointer;
732
1265
  }
733
- </style>
734
1266
 
735
- <div class="overlay" id="overlay">
736
- <div class="modal">
737
- <span class="close" id="close">✕</span>
1267
+ h3 {
1268
+ margin-bottom: 16px;
1269
+ }
738
1270
 
739
- <h3>Pay with Card</h3>
1271
+ .section {
1272
+ display: flex;
1273
+ flex-direction: column;
1274
+ gap: 16px;
1275
+ }
740
1276
 
741
- <form id="form">
742
- <input id="name" placeholder="Name"/>
743
- <input id="email" placeholder="Email"/>
1277
+ .section-title {
1278
+ font-size: 14px;
1279
+ font-weight: 600;
1280
+ margin: 0;
1281
+ }
744
1282
 
745
- <div class="row">
746
- <input class="phone-code" id="phone-code" value="+1"/>
747
- <input id="phone" placeholder="Phone"/>
748
- </div>
1283
+ .card-wrapper {
1284
+ position: relative;
1285
+ }
749
1286
 
750
- <input id="card-name" placeholder="Card Holder Name"/>
751
- <input id="card" placeholder="Card Number"/>
1287
+ #card-icon {
1288
+ position: absolute;
1289
+ right: 10px;
1290
+ top: 50%;
1291
+ transform: translateY(-50%);
1292
+ height: 24px;
1293
+ }
1294
+ .error {
1295
+ font-size: 12px;
1296
+ color: #dc2626;
1297
+ margin-top: 0px;
1298
+ margin-bottom: 0px;
1299
+ }
752
1300
 
753
- <div class="row">
754
- <input id="expiry" placeholder="MM/YY"/>
755
- <input id="cvv" type="password" placeholder="CVV"/>
756
- </div>
1301
+ .field {
1302
+ position: relative;
1303
+ width: 100%;
1304
+ }
757
1305
 
758
- <input id="street" placeholder="Street Address"/>
759
- <input id="city" placeholder="City"/>
1306
+ .field input,
1307
+ .field select {
1308
+ width: 100%;
1309
+ padding: 14px 12px;
1310
+ border: 1px solid var(--border);
1311
+ border-radius: 8px;
1312
+ font-size: 14px;
1313
+ background: #fff;
1314
+ outline: none;
1315
+ }
1316
+ .field label {
1317
+ position: absolute;
1318
+ left: 0px;
1319
+ top: 50%;
1320
+ transform: translateY(-50%);
1321
+ padding: 0 4px;
1322
+ color: #6b7280;
1323
+ font-size: 14px;
1324
+ pointer-events: none;
1325
+ transition: 0.2s ease;
1326
+ }
1327
+ .field input:focus + label,
1328
+ .field input:not(:placeholder-shown) + label {
1329
+ top: -8px;
1330
+ font-size: 12px;
1331
+ color: var(--primary);
1332
+ }
1333
+ .field select {
1334
+ width: 100%;
1335
+ height: 44px;
1336
+ padding: 0 12px;
1337
+ border: 1px solid var(--border);
1338
+ border-radius: 8px;
1339
+ background: #fff;
1340
+ color: #000;
1341
+ font-size: 14px;
1342
+ outline: none;
1343
+ appearance: none;
1344
+ }
1345
+ .input-error {
1346
+ border-color: #dc2626 !important;
1347
+ }
760
1348
 
761
- <!-- STATE FIRST -->
762
- <select id="state"></select>
1349
+ .field input.input-error + label {
1350
+ color: #dc2626;
1351
+ }
763
1352
 
764
- <!-- COUNTRY AFTER -->
765
- <select id="country">
766
- ${COUNTRIES_LIST.map(c =>
767
- `<option value="${c.code}" ${c.code === "US" ? "selected" : ""}>
768
- ${c.name}
769
- </option>`
770
- ).join("")}
771
- </select>
1353
+ .field label.floating {
1354
+ top: -8px;
1355
+ font-size: 12px;
1356
+ color: var(--primary);
1357
+ }
1358
+ </style>
772
1359
 
773
- <input id="zip" placeholder="ZIP Code"/>
1360
+ <div class="overlay" id="overlay">
1361
+ <div class="modal">
1362
+ <div class="modal-header">
1363
+ <span class="close" id="close">✕</span>
1364
+ <h3>Pay with Card</h3>
1365
+ </div>
1366
+ <div class="modal-body">
1367
+ <form id="form">
1368
+ <div class="section">
1369
+ <div class="section-title">Customer Details</div>
1370
+ <div class="field">
1371
+ <input id="name" required placeholder=" " />
1372
+ <label>Name *</label>
1373
+ </div>
1374
+ <div class="error" id="error-name"></div>
1375
+ <div class="field">
1376
+ <input id="email" required placeholder=" " />
1377
+ <label>Email *</label>
1378
+ </div>
1379
+ <div class="error" id="error-email"></div>
1380
+ <div class="row">
1381
+ <div class="field phone-code">
1382
+ <input id="phone-code" placeholder=" " />
1383
+ <label>Code</label>
1384
+ </div>
1385
+ <div class="field">
1386
+ <input id="phone" required placeholder=" " />
1387
+ <label>Phone *</label>
1388
+ </div>
1389
+ </div>
1390
+ <div class="error" id="error-phone"></div>
1391
+ </div>
1392
+ <div class="section">
1393
+ <div class="section-title">Card Details</div>
1394
+ <div class="field">
1395
+ <input id="card" required placeholder=" " />
1396
+ <label>Card Number *</label>
1397
+ </div>
1398
+ <div class="error" id="error-card"></div>
774
1399
 
775
- <button class="btn">Pay $${amountText}</button>
1400
+ <div class="row">
1401
+ <div class="field">
1402
+ <input id="expiry" required placeholder=" " />
1403
+ <label>MM/YY *</label>
1404
+ </div>
1405
+
1406
+ <div class="field">
1407
+ <input id="cvv" type="password" required placeholder=" " />
1408
+ <label>CVV *</label>
1409
+ </div>
1410
+ </div>
1411
+ <div class="error" id="error-expiry"></div>
1412
+ <div class="error" id="error-cvv"></div>
1413
+ <div class="field">
1414
+ <input id="card-name" required placeholder=" " />
1415
+ <label>Card Holder Name *</label>
1416
+ </div>
1417
+ <div class="error" id="error-card-name"></div>
1418
+ </div>
1419
+ ${this.getAddressFields()}
1420
+ <button class="btn">Pay $${this.inventory ? inventoryAmountText : amountText}</button>
1421
+ </div>
776
1422
  </form>
777
1423
  </div>
778
1424
  </div>
@@ -780,16 +1426,64 @@ select option {
780
1426
 
781
1427
  const $ = id => this.shadow.getElementById(id);
782
1428
 
1429
+ if (this.customer) {
1430
+ if (this.customer.name) $("name").value = this.customer.name;
1431
+ if (this.customer.email) $("email").value = this.customer.email;
1432
+
1433
+ if (this.customer.phone) {
1434
+ let phone = this.customer.phone;
1435
+ let code = "";
1436
+ let number = phone;
1437
+
1438
+ if (phone.startsWith("+")) {
1439
+ if (phone.startsWith("+1")) {
1440
+ code = "+1";
1441
+ number = phone.slice(2);
1442
+ } else if (phone.startsWith("+91")) {
1443
+ code = "+91";
1444
+ number = phone.slice(3);
1445
+ }
1446
+ }
1447
+
1448
+ if ($("phone-code")) $("phone-code").value = code;
1449
+ if ($("phone")) $("phone").value = number;
1450
+ }
1451
+ }
1452
+
783
1453
  $("close").onclick = () => this.close();
784
1454
  $("form").onsubmit = e => this.handleSubmit(e);
785
1455
 
786
- $("country").onchange = () => this.handleCountryChange();
787
-
788
1456
  const countryEl = $("country");
789
- countryEl.value = "US";
790
- this.handleCountryChange();
1457
+ if (countryEl) {
1458
+ countryEl.onchange = () => this.handleCountryChange();
1459
+ if (!this.customer?.countryCode) {
1460
+ countryEl.value = "US";
1461
+ }
1462
+ this.handleCountryChange();
1463
+ }
1464
+ const phoneCodeEl = $("phone-code");
1465
+ if (phoneCodeEl) {
1466
+ phoneCodeEl.dataset.auto = "true";
1467
+ }
791
1468
 
792
- $("card").oninput = e => e.target.value = this.formatCard(e.target.value);
1469
+ const icons = {
1470
+ VISA: `<svg width="40" viewBox="0 0 48 16"><text x="0" y="14">VISA</text></svg>`,
1471
+ MASTERCARD: `<svg width="40"><text x="0" y="14">MC</text></svg>`,
1472
+ AMEX: `<svg width="40"><text x="0" y="14">AMEX</text></svg>`,
1473
+ DISCOVER: `<svg width="40"><text x="0" y="14">DISC</text></svg>`
1474
+ };
1475
+ $("card").oninput = e => {
1476
+ const formatted = this.formatCard(e.target.value);
1477
+ e.target.value = formatted;
1478
+
1479
+ const type = this.detectCardType(formatted);
1480
+
1481
+ const iconEl = $("card-icon");
1482
+ if (iconEl) {
1483
+ iconEl.src = icons[type] || "";
1484
+ iconEl.style.display = type ? "block" : "none";
1485
+ }
1486
+ };
793
1487
  $("expiry").oninput = e => e.target.value = this.formatExpiry(e.target.value);
794
1488
  $("phone").oninput = e => e.target.value = this.formatPhone(e.target.value);
795
1489
  }