@one-payments/web-components 1.7.0 → 1.7.2

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.
@@ -5,9 +5,10 @@
5
5
  import { __decorate } from "tslib";
6
6
  import { LitElement, html, css } from 'lit';
7
7
  import { customElement, property, state } from 'lit/decorators.js';
8
- import { PaymentSDK, PAYMENT_METHODS, PAYNOW_ICON_DATA_URL, PROMPTPAY_ICON_DATA_URL, DUITNOW_ICON_DATA_URL, GOPAY_ICON_DATA_URL, BOOST_ICON_DATA_URL, SHOPEEPAY_ICON_DATA_URL, ATOM_ICON_DATA_URL, DANA_ICON_DATA_URL, TNG_ICON_DATA_URL, ALIPAYCN_ICON_DATA_URL, ALIPAYHK_ICON_DATA_URL, GCASH_ICON_DATA_URL, KONBINI_ICON_DATA_URL, PAYEASY_ICON_DATA_URL } from '@one-payments/core';
8
+ import { PaymentSDK, PAYMENT_METHODS, PAYNOW_ICON_DATA_URL, PROMPTPAY_ICON_DATA_URL, DUITNOW_ICON_DATA_URL, GOPAY_ICON_DATA_URL, BOOST_ICON_DATA_URL, SHOPEEPAY_ICON_DATA_URL, ATOM_ICON_DATA_URL, DANA_ICON_DATA_URL, TNG_ICON_DATA_URL, ALIPAYCN_ICON_DATA_URL, ALIPAYHK_ICON_DATA_URL, GCASH_ICON_DATA_URL, KONBINI_ICON_DATA_URL, PAYEASY_ICON_DATA_URL, GRABPAY_ICON_DATA_URL, FPX_ICON_DATA_URL, } from '@one-payments/core';
9
9
  import CleaveConstructor from 'cleave.js';
10
10
  import QRCode from 'qrcode';
11
+ import { AsYouType, isValidPhoneNumber, parsePhoneNumber, } from 'libphonenumber-js';
11
12
  /**
12
13
  * Main payment component - framework-agnostic web component with EXACT original design
13
14
  *
@@ -74,6 +75,15 @@ let OnePayment = class OnePayment extends LitElement {
74
75
  this.qrCodeDataUrl = null; // Generated QR image
75
76
  this.qrAutoResumeTimer = null;
76
77
  this.qrPollingInProgress = false; // Track if QR polling is in progress
78
+ // Bank selection state for FPX and similar methods
79
+ this.availableBanks = [];
80
+ this.selectedBank = null;
81
+ this.banksLoading = false;
82
+ this.bankSearchQuery = '';
83
+ // Phone number input state for methods that require phone entry
84
+ this.phoneInputValue = '';
85
+ this.phoneInputError = '';
86
+ this.selectedCountry = 'SG'; // Default to Singapore
77
87
  this.sdk = null;
78
88
  this.isInitialized = false;
79
89
  this.cardNumberCleave = null;
@@ -122,6 +132,33 @@ let OnePayment = class OnePayment extends LitElement {
122
132
  }
123
133
  }
124
134
  };
135
+ // --- Phone Number Helper Methods ---
136
+ /**
137
+ * Minimum phone number length (digits only)
138
+ */
139
+ this.MIN_PHONE_LENGTH = 8;
140
+ /**
141
+ * Japan-only payment methods - phone must start with +81
142
+ * These methods require the country selector to be locked to Japan
143
+ */
144
+ this.JAPAN_ONLY_PHONE_METHODS = [
145
+ PAYMENT_METHODS.KONBINI,
146
+ PAYMENT_METHODS.PAYEASY,
147
+ ];
148
+ /**
149
+ * Country options for phone number selection
150
+ * Uses ISO 3166-1 alpha-2 country codes (uppercase for libphonenumber-js)
151
+ */
152
+ this.COUNTRY_OPTIONS = [
153
+ { code: 'JP', name: 'Japan', flag: '🇯🇵', dialCode: '+81' },
154
+ { code: 'SG', name: 'Singapore', flag: '🇸🇬', dialCode: '+65' },
155
+ { code: 'MY', name: 'Malaysia', flag: '🇲🇾', dialCode: '+60' },
156
+ { code: 'ID', name: 'Indonesia', flag: '🇮🇩', dialCode: '+62' },
157
+ { code: 'TH', name: 'Thailand', flag: '🇹🇭', dialCode: '+66' },
158
+ { code: 'PH', name: 'Philippines', flag: '🇵🇭', dialCode: '+63' },
159
+ { code: 'HK', name: 'Hong Kong', flag: '🇭🇰', dialCode: '+852' },
160
+ { code: 'CN', name: 'China', flag: '🇨🇳', dialCode: '+86' },
161
+ ];
125
162
  }
126
163
  /**
127
164
  * Check if all required props are present and valid.
@@ -145,7 +182,9 @@ let OnePayment = class OnePayment extends LitElement {
145
182
  if (!this.email || typeof this.email !== 'string' || this.email.trim() === '') {
146
183
  return false;
147
184
  }
148
- if (!this.phoneNumber || typeof this.phoneNumber !== 'string' || this.phoneNumber.trim() === '') {
185
+ if (!this.phoneNumber ||
186
+ typeof this.phoneNumber !== 'string' ||
187
+ this.phoneNumber.trim() === '') {
149
188
  return false;
150
189
  }
151
190
  return true;
@@ -323,11 +362,12 @@ let OnePayment = class OnePayment extends LitElement {
323
362
  }
324
363
  // --- Methods ---
325
364
  /**
326
- * Get filtered and validated payment methods
327
- * Applies three layers of filtering:
328
- * 1. API availability (from available-methods endpoint)
365
+ * Get filtered payment methods
366
+ * Applies filtering:
367
+ * 1. API availability (from available-methods endpoint with currency filtering)
329
368
  * 2. User exclusions (excludePaymentMethods prop)
330
- * 3. Currency validation (PayNow only for SGD)
369
+ *
370
+ * Note: Currency validation is now handled server-side by the available-methods API
331
371
  */
332
372
  getFilteredPaymentMethods() {
333
373
  // Get available methods from SDK state (from API)
@@ -342,7 +382,7 @@ let OnePayment = class OnePayment extends LitElement {
342
382
  if (availableMethods.length === 0) {
343
383
  return [];
344
384
  }
345
- // Filter by exclusions and currency
385
+ // Filter by user exclusions only - currency filtering is done server-side
346
386
  return availableMethods
347
387
  .filter((method) => {
348
388
  // Apply user exclusions
@@ -351,180 +391,10 @@ let OnePayment = class OnePayment extends LitElement {
351
391
  }
352
392
  return true;
353
393
  })
354
- .map((method) => {
355
- // Check currency validation for PayNow
356
- if (method.id === PAYMENT_METHODS.PAYNOW) {
357
- const transactionData = this.getTransactionData();
358
- const currency = transactionData.currency;
359
- if (currency !== 'SGD') {
360
- return {
361
- id: method.id,
362
- enabled: false,
363
- disabledReason: 'PayNow is only available for SGD currency',
364
- };
365
- }
366
- }
367
- // Check currency validation for Prompt Pay
368
- if (method.id === PAYMENT_METHODS.PROMPTPAY) {
369
- const transactionData = this.getTransactionData();
370
- const currency = transactionData.currency;
371
- if (currency !== 'THB') {
372
- return {
373
- id: method.id,
374
- enabled: false,
375
- disabledReason: 'PromptPay is only available for THB currency',
376
- };
377
- }
378
- }
379
- // Check currency validation for DuitNow
380
- if (method.id === PAYMENT_METHODS.DUITNOW) {
381
- const transactionData = this.getTransactionData();
382
- const currency = transactionData.currency;
383
- if (currency !== 'MYR') {
384
- return {
385
- id: method.id,
386
- enabled: false,
387
- disabledReason: 'DuitNow is only available for MYR currency',
388
- };
389
- }
390
- }
391
- // Check currency validation for GoPay
392
- if (method.id === PAYMENT_METHODS.GOPAY) {
393
- const transactionData = this.getTransactionData();
394
- const currency = transactionData.currency;
395
- if (currency !== 'IDR') {
396
- return {
397
- id: method.id,
398
- enabled: false,
399
- disabledReason: 'GoPay is only available for IDR currency',
400
- };
401
- }
402
- }
403
- // Check currency validation for Boost
404
- if (method.id === PAYMENT_METHODS.BOOST) {
405
- const transactionData = this.getTransactionData();
406
- const currency = transactionData.currency;
407
- if (currency !== 'MYR') {
408
- return {
409
- id: method.id,
410
- enabled: false,
411
- disabledReason: 'Boost is only available for MYR currency',
412
- };
413
- }
414
- }
415
- // Check currency validation for ShopeePay
416
- if (method.id === PAYMENT_METHODS.SHOPEEPAY) {
417
- const transactionData = this.getTransactionData();
418
- const currency = transactionData.currency;
419
- if (currency !== 'MYR') {
420
- return {
421
- id: method.id,
422
- enabled: false,
423
- disabledReason: 'ShopeePay is only available for MYR currency',
424
- };
425
- }
426
- }
427
- // Check currency validation for Atom (SGD or MYR only)
428
- if (method.id === PAYMENT_METHODS.ATOM) {
429
- const transactionData = this.getTransactionData();
430
- const currency = transactionData.currency;
431
- if (currency !== 'SGD' && currency !== 'MYR') {
432
- return {
433
- id: method.id,
434
- enabled: false,
435
- disabledReason: 'Atom is only available for SGD and MYR currencies',
436
- };
437
- }
438
- }
439
- // Check currency validation for Dana (IDR only)
440
- if (method.id === PAYMENT_METHODS.DANA) {
441
- const transactionData = this.getTransactionData();
442
- const currency = transactionData.currency;
443
- if (currency !== 'IDR') {
444
- return {
445
- id: method.id,
446
- enabled: false,
447
- disabledReason: 'Dana is only available for IDR currency',
448
- };
449
- }
450
- }
451
- // Check currency validation for Touch 'n Go (MYR only)
452
- if (method.id === PAYMENT_METHODS.TNG) {
453
- const transactionData = this.getTransactionData();
454
- const currency = transactionData.currency;
455
- if (currency !== 'MYR') {
456
- return {
457
- id: method.id,
458
- enabled: false,
459
- disabledReason: "Touch 'n Go is only available for MYR currency",
460
- };
461
- }
462
- }
463
- // Check currency validation for Alipay CN (CNY only)
464
- if (method.id === PAYMENT_METHODS.ALIPAYCN) {
465
- const transactionData = this.getTransactionData();
466
- const currency = transactionData.currency;
467
- if (currency !== 'CNY') {
468
- return {
469
- id: method.id,
470
- enabled: false,
471
- disabledReason: 'Alipay is only available for CNY currency',
472
- };
473
- }
474
- }
475
- // Check currency validation for Alipay HK (HKD only)
476
- if (method.id === PAYMENT_METHODS.ALIPAYHK) {
477
- const transactionData = this.getTransactionData();
478
- const currency = transactionData.currency;
479
- if (currency !== 'HKD') {
480
- return {
481
- id: method.id,
482
- enabled: false,
483
- disabledReason: 'AlipayHK is only available for HKD currency',
484
- };
485
- }
486
- }
487
- // Check currency validation for GCash (PHP only)
488
- if (method.id === PAYMENT_METHODS.GCASH) {
489
- const transactionData = this.getTransactionData();
490
- const currency = transactionData.currency;
491
- if (currency !== 'PHP') {
492
- return {
493
- id: method.id,
494
- enabled: false,
495
- disabledReason: 'GCash is only available for PHP currency',
496
- };
497
- }
498
- }
499
- // Check currency validation for Konbini (JPY only)
500
- if (method.id === PAYMENT_METHODS.KONBINI) {
501
- const transactionData = this.getTransactionData();
502
- const currency = transactionData.currency;
503
- if (currency !== 'JPY') {
504
- return {
505
- id: method.id,
506
- enabled: false,
507
- disabledReason: 'Konbini is only available for JPY currency',
508
- };
509
- }
510
- }
511
- // Check currency validation for Pay-Easy (JPY only)
512
- if (method.id === PAYMENT_METHODS.PAYEASY) {
513
- const transactionData = this.getTransactionData();
514
- const currency = transactionData.currency;
515
- if (currency !== 'JPY') {
516
- return {
517
- id: method.id,
518
- enabled: false,
519
- disabledReason: 'Pay-Easy is only available for JPY currency',
520
- };
521
- }
522
- }
523
- return {
524
- id: method.id,
525
- enabled: true,
526
- };
527
- });
394
+ .map((method) => ({
395
+ id: method.id,
396
+ enabled: true,
397
+ }));
528
398
  }
529
399
  initializeSDK() {
530
400
  // Validate required configuration props
@@ -782,6 +652,22 @@ let OnePayment = class OnePayment extends LitElement {
782
652
  window.location.href = redirectUrl;
783
653
  }
784
654
  });
655
+ // Handle GrabPay SG redirect
656
+ this.sdk.on('grabpay_sg_redirect', (event) => {
657
+ const { redirectUrl } = event.payload;
658
+ if (redirectUrl) {
659
+ // Redirect the entire page to the GrabPay payment page
660
+ window.location.href = redirectUrl;
661
+ }
662
+ });
663
+ // Handle FPX redirect
664
+ this.sdk.on('fpx_redirect', (event) => {
665
+ const { redirectUrl } = event.payload;
666
+ if (redirectUrl) {
667
+ // Redirect the entire page to the bank's payment page
668
+ window.location.href = redirectUrl;
669
+ }
670
+ });
785
671
  this.sdk.initialize({
786
672
  amount: this.amount,
787
673
  currency: this.currency,
@@ -976,13 +862,15 @@ let OnePayment = class OnePayment extends LitElement {
976
862
  const expiryMonth = (expiryParts[0] || '').trim();
977
863
  const expiryYearShort = (expiryParts[1] || '').trim().substring(0, 2); // Take only first 2 digits
978
864
  const expiryYear = `20${expiryYearShort}`; // Convert to 4-digit year
865
+ // Format names to UPPERCASE for API
866
+ const formattedNames = this.formatNamesUppercase();
979
867
  this.sdk.submitPayment(PAYMENT_METHODS.CARD, {
980
868
  cardNumber: this.cardFormData.cardNumber.replace(/\s/g, ''),
981
869
  expiryMonth,
982
870
  expiryYear,
983
871
  cvv: this.cardFormData.cvv,
984
- firstName: this.firstName,
985
- lastName: this.lastName,
872
+ firstName: formattedNames.firstName,
873
+ lastName: formattedNames.lastName,
986
874
  email: this.email,
987
875
  phoneNumber: this.phoneNumber,
988
876
  });
@@ -993,10 +881,12 @@ let OnePayment = class OnePayment extends LitElement {
993
881
  }
994
882
  // Lock payment methods to prevent switching
995
883
  this.paymentLocked = true;
884
+ // Format names to UPPERCASE for API
885
+ const formattedNames = this.formatNamesUppercase();
996
886
  // Use customer info from props
997
887
  this.sdk.submitPayment(PAYMENT_METHODS.PAYNOW, {
998
- firstName: this.firstName,
999
- lastName: this.lastName,
888
+ firstName: formattedNames.firstName,
889
+ lastName: formattedNames.lastName,
1000
890
  email: this.email,
1001
891
  phoneNumber: this.phoneNumber,
1002
892
  });
@@ -1008,11 +898,13 @@ let OnePayment = class OnePayment extends LitElement {
1008
898
  }
1009
899
  // Lock payment methods to prevent switching
1010
900
  this.paymentLocked = true;
901
+ // Format names to UPPERCASE for API
902
+ const formattedNames = this.formatNamesUppercase();
1011
903
  // Use customer info from props
1012
904
  // SDK will redirect to external payment page when nextActionUrl is received
1013
905
  this.sdk.submitPayment(PAYMENT_METHODS.PROMPTPAY, {
1014
- firstName: this.firstName,
1015
- lastName: this.lastName,
906
+ firstName: formattedNames.firstName,
907
+ lastName: formattedNames.lastName,
1016
908
  email: this.email,
1017
909
  phoneNumber: this.phoneNumber,
1018
910
  });
@@ -1023,11 +915,13 @@ let OnePayment = class OnePayment extends LitElement {
1023
915
  }
1024
916
  // Lock payment methods to prevent switching
1025
917
  this.paymentLocked = true;
918
+ // Format names to UPPERCASE for API
919
+ const formattedNames = this.formatNamesUppercase();
1026
920
  // Use customer info from props
1027
921
  // SDK will redirect to external payment page when nextActionUrl is received
1028
922
  this.sdk.submitPayment(PAYMENT_METHODS.DUITNOW, {
1029
- firstName: this.firstName,
1030
- lastName: this.lastName,
923
+ firstName: formattedNames.firstName,
924
+ lastName: formattedNames.lastName,
1031
925
  email: this.email,
1032
926
  phoneNumber: this.phoneNumber,
1033
927
  });
@@ -1038,11 +932,13 @@ let OnePayment = class OnePayment extends LitElement {
1038
932
  }
1039
933
  // Lock payment methods to prevent switching
1040
934
  this.paymentLocked = true;
935
+ // Format names to UPPERCASE for API
936
+ const formattedNames = this.formatNamesUppercase();
1041
937
  // Use customer info from props
1042
938
  // SDK will redirect to external payment page when nextActionUrl is received
1043
939
  this.sdk.submitPayment(PAYMENT_METHODS.GOPAY, {
1044
- firstName: this.firstName,
1045
- lastName: this.lastName,
940
+ firstName: formattedNames.firstName,
941
+ lastName: formattedNames.lastName,
1046
942
  email: this.email,
1047
943
  phoneNumber: this.phoneNumber,
1048
944
  });
@@ -1051,152 +947,499 @@ let OnePayment = class OnePayment extends LitElement {
1051
947
  if (!this.sdk || this.currentState.status !== 'ready') {
1052
948
  return;
1053
949
  }
950
+ // Validate phone input if filled, otherwise use prop
951
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
952
+ return;
953
+ }
1054
954
  // Lock payment methods to prevent switching
1055
955
  this.paymentLocked = true;
1056
- // Use customer info from props
956
+ // Format names to UPPERCASE for API
957
+ const formattedNames = this.formatNamesUppercase();
958
+ // Use phone from input or prop
959
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.BOOST);
1057
960
  // SDK will redirect to external payment page when nextActionUrl is received
1058
961
  this.sdk.submitPayment(PAYMENT_METHODS.BOOST, {
1059
- firstName: this.firstName,
1060
- lastName: this.lastName,
962
+ firstName: formattedNames.firstName,
963
+ lastName: formattedNames.lastName,
1061
964
  email: this.email,
1062
- phoneNumber: this.phoneNumber,
965
+ phoneNumber,
1063
966
  });
1064
967
  }
1065
968
  handleShopeePayPayment() {
1066
969
  if (!this.sdk || this.currentState.status !== 'ready') {
1067
970
  return;
1068
971
  }
972
+ // Validate phone input if filled, otherwise use prop
973
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
974
+ return;
975
+ }
1069
976
  // Lock payment methods to prevent switching
1070
977
  this.paymentLocked = true;
1071
- // Use customer info from props
978
+ // Format names to UPPERCASE for API
979
+ const formattedNames = this.formatNamesUppercase();
980
+ // Use phone from input or prop
981
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.SHOPEEPAY);
1072
982
  // SDK will redirect to external payment page when nextActionUrl is received
1073
983
  this.sdk.submitPayment(PAYMENT_METHODS.SHOPEEPAY, {
1074
- firstName: this.firstName,
1075
- lastName: this.lastName,
984
+ firstName: formattedNames.firstName,
985
+ lastName: formattedNames.lastName,
1076
986
  email: this.email,
1077
- phoneNumber: this.phoneNumber,
987
+ phoneNumber,
1078
988
  });
1079
989
  }
1080
990
  handleAtomPayment() {
1081
991
  if (!this.sdk || this.currentState.status !== 'ready') {
1082
992
  return;
1083
993
  }
994
+ // Validate phone input if filled, otherwise use prop
995
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
996
+ return;
997
+ }
1084
998
  // Lock payment methods to prevent switching
1085
999
  this.paymentLocked = true;
1086
- // Use customer info from props
1000
+ // Format names to UPPERCASE for API
1001
+ const formattedNames = this.formatNamesUppercase();
1002
+ // Use phone from input or prop
1003
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.ATOM);
1087
1004
  // SDK will redirect to external payment page when nextActionUrl is received
1088
1005
  this.sdk.submitPayment(PAYMENT_METHODS.ATOM, {
1089
- firstName: this.firstName,
1090
- lastName: this.lastName,
1006
+ firstName: formattedNames.firstName,
1007
+ lastName: formattedNames.lastName,
1091
1008
  email: this.email,
1092
- phoneNumber: this.phoneNumber,
1009
+ phoneNumber,
1093
1010
  });
1094
1011
  }
1095
1012
  handleDanaPayment() {
1096
1013
  if (!this.sdk || this.currentState.status !== 'ready') {
1097
1014
  return;
1098
1015
  }
1016
+ // Validate phone input if filled, otherwise use prop
1017
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1018
+ return;
1019
+ }
1099
1020
  // Lock payment methods to prevent switching
1100
1021
  this.paymentLocked = true;
1101
- // Use customer info from props
1022
+ // Format names to UPPERCASE for API
1023
+ const formattedNames = this.formatNamesUppercase();
1024
+ // Use phone from input or prop
1025
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.DANA);
1102
1026
  // SDK will redirect to external payment page when nextActionUrl is received
1103
1027
  this.sdk.submitPayment(PAYMENT_METHODS.DANA, {
1104
- firstName: this.firstName,
1105
- lastName: this.lastName,
1028
+ firstName: formattedNames.firstName,
1029
+ lastName: formattedNames.lastName,
1106
1030
  email: this.email,
1107
- phoneNumber: this.phoneNumber,
1031
+ phoneNumber,
1108
1032
  });
1109
1033
  }
1110
1034
  handleTngPayment() {
1111
1035
  if (!this.sdk || this.currentState.status !== 'ready') {
1112
1036
  return;
1113
1037
  }
1038
+ // Validate phone input if filled, otherwise use prop
1039
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1040
+ return;
1041
+ }
1114
1042
  // Lock payment methods to prevent switching
1115
1043
  this.paymentLocked = true;
1116
- // Use customer info from props
1044
+ // Format names to UPPERCASE for API
1045
+ const formattedNames = this.formatNamesUppercase();
1046
+ // Use phone from input or prop
1047
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.TNG);
1117
1048
  // SDK will redirect to external payment page when nextActionUrl is received
1118
1049
  this.sdk.submitPayment(PAYMENT_METHODS.TNG, {
1119
- firstName: this.firstName,
1120
- lastName: this.lastName,
1050
+ firstName: formattedNames.firstName,
1051
+ lastName: formattedNames.lastName,
1121
1052
  email: this.email,
1122
- phoneNumber: this.phoneNumber,
1053
+ phoneNumber,
1123
1054
  });
1124
1055
  }
1125
1056
  handleAlipayCNPayment() {
1126
1057
  if (!this.sdk || this.currentState.status !== 'ready') {
1127
1058
  return;
1128
1059
  }
1060
+ // Validate phone input if filled, otherwise use prop
1061
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1062
+ return;
1063
+ }
1129
1064
  // Lock payment methods to prevent switching
1130
1065
  this.paymentLocked = true;
1131
- // Use customer info from props
1066
+ // Format names to UPPERCASE for API
1067
+ const formattedNames = this.formatNamesUppercase();
1068
+ // Use phone from input or prop
1069
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.ALIPAYCN);
1132
1070
  // SDK will redirect to external payment page when nextActionUrl is received
1133
1071
  this.sdk.submitPayment(PAYMENT_METHODS.ALIPAYCN, {
1134
- firstName: this.firstName,
1135
- lastName: this.lastName,
1072
+ firstName: formattedNames.firstName,
1073
+ lastName: formattedNames.lastName,
1136
1074
  email: this.email,
1137
- phoneNumber: this.phoneNumber,
1075
+ phoneNumber,
1138
1076
  });
1139
1077
  }
1140
1078
  handleAlipayHKPayment() {
1141
1079
  if (!this.sdk || this.currentState.status !== 'ready') {
1142
1080
  return;
1143
1081
  }
1082
+ // Validate phone input if filled, otherwise use prop
1083
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1084
+ return;
1085
+ }
1144
1086
  // Lock payment methods to prevent switching
1145
1087
  this.paymentLocked = true;
1146
- // Use customer info from props
1088
+ // Format names to UPPERCASE for API
1089
+ const formattedNames = this.formatNamesUppercase();
1090
+ // Use phone from input or prop
1091
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.ALIPAYHK);
1147
1092
  // SDK will redirect to external payment page when nextActionUrl is received
1148
1093
  this.sdk.submitPayment(PAYMENT_METHODS.ALIPAYHK, {
1149
- firstName: this.firstName,
1150
- lastName: this.lastName,
1094
+ firstName: formattedNames.firstName,
1095
+ lastName: formattedNames.lastName,
1151
1096
  email: this.email,
1152
- phoneNumber: this.phoneNumber,
1097
+ phoneNumber,
1153
1098
  });
1154
1099
  }
1155
1100
  handleGCashPayment() {
1156
1101
  if (!this.sdk || this.currentState.status !== 'ready') {
1157
1102
  return;
1158
1103
  }
1104
+ // Validate phone input if filled, otherwise use prop
1105
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1106
+ return;
1107
+ }
1159
1108
  // Lock payment methods to prevent switching
1160
1109
  this.paymentLocked = true;
1161
- // Use customer info from props
1110
+ // Format names to UPPERCASE for API
1111
+ const formattedNames = this.formatNamesUppercase();
1112
+ // Use phone from input or prop
1113
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.GCASH);
1162
1114
  // SDK will redirect to external payment page when nextActionUrl is received
1163
1115
  this.sdk.submitPayment(PAYMENT_METHODS.GCASH, {
1164
- firstName: this.firstName,
1165
- lastName: this.lastName,
1116
+ firstName: formattedNames.firstName,
1117
+ lastName: formattedNames.lastName,
1166
1118
  email: this.email,
1167
- phoneNumber: this.phoneNumber,
1119
+ phoneNumber,
1168
1120
  });
1169
1121
  }
1170
1122
  handleKonbiniPayment() {
1171
1123
  if (!this.sdk || this.currentState.status !== 'ready') {
1172
1124
  return;
1173
1125
  }
1126
+ // Validate phone input - required for Japan method
1127
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1128
+ return;
1129
+ }
1174
1130
  // Lock payment methods to prevent switching
1175
1131
  this.paymentLocked = true;
1176
- // Use customer info from props
1132
+ // Format names to UPPERCASE for API
1133
+ const formattedNames = this.formatNamesUppercase();
1134
+ // Use phone from input (formatted for Japan: +81 -> 0) or prop
1135
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.KONBINI);
1177
1136
  // SDK will redirect to convenience store payment page when nextActionUrl is received
1178
1137
  this.sdk.submitPayment(PAYMENT_METHODS.KONBINI, {
1179
- firstName: this.firstName,
1180
- lastName: this.lastName,
1138
+ firstName: formattedNames.firstName,
1139
+ lastName: formattedNames.lastName,
1181
1140
  email: this.email,
1182
- phoneNumber: this.phoneNumber,
1141
+ phoneNumber,
1183
1142
  });
1184
1143
  }
1185
1144
  handlePayEasyPayment() {
1186
1145
  if (!this.sdk || this.currentState.status !== 'ready') {
1187
1146
  return;
1188
1147
  }
1148
+ // Validate phone input - required for Japan method
1149
+ if (this.phoneInputValue && !this.validatePhoneInput()) {
1150
+ return;
1151
+ }
1189
1152
  // Lock payment methods to prevent switching
1190
1153
  this.paymentLocked = true;
1191
- // Use customer info from props
1154
+ // Use phone from input (formatted for Japan: +81 -> 0) or prop
1155
+ const phoneNumber = this.getEffectivePhoneNumber(PAYMENT_METHODS.PAYEASY);
1156
+ // Format names for Pay-easy (max 9 chars combined)
1157
+ const formattedNames = this.formatNamesForPayEasy(this.firstName, this.lastName);
1192
1158
  // SDK will redirect to bank transfer/ATM payment page when nextActionUrl is received
1193
1159
  this.sdk.submitPayment(PAYMENT_METHODS.PAYEASY, {
1194
- firstName: this.firstName,
1195
- lastName: this.lastName,
1160
+ firstName: formattedNames.firstName,
1161
+ lastName: formattedNames.lastName,
1162
+ email: this.email,
1163
+ phoneNumber,
1164
+ });
1165
+ }
1166
+ handleGrabPaySGPayment() {
1167
+ if (!this.sdk || this.currentState.status !== 'ready') {
1168
+ return;
1169
+ }
1170
+ // Lock payment methods to prevent switching
1171
+ this.paymentLocked = true;
1172
+ // Format names to UPPERCASE for API
1173
+ const formattedNames = this.formatNamesUppercase();
1174
+ // Use customer info from props
1175
+ // SDK will redirect to GrabPay payment page when nextActionUrl is received
1176
+ this.sdk.submitPayment(PAYMENT_METHODS.GRABPAY_SG, {
1177
+ firstName: formattedNames.firstName,
1178
+ lastName: formattedNames.lastName,
1196
1179
  email: this.email,
1197
1180
  phoneNumber: this.phoneNumber,
1198
1181
  });
1199
1182
  }
1183
+ handleFPXPayment() {
1184
+ if (!this.sdk || this.currentState.status !== 'ready') {
1185
+ return;
1186
+ }
1187
+ // Validate bank selection
1188
+ if (!this.selectedBank) {
1189
+ return;
1190
+ }
1191
+ // Lock payment methods to prevent switching
1192
+ this.paymentLocked = true;
1193
+ // Format names to UPPERCASE for API
1194
+ const formattedNames = this.formatNamesUppercase();
1195
+ // Use customer info from props plus selected bank
1196
+ // SDK will redirect to bank's payment page when nextActionUrl is received
1197
+ this.sdk.submitPayment(PAYMENT_METHODS.FPX, {
1198
+ firstName: formattedNames.firstName,
1199
+ lastName: formattedNames.lastName,
1200
+ email: this.email,
1201
+ phoneNumber: this.phoneNumber,
1202
+ bankName: this.selectedBank.bankName,
1203
+ });
1204
+ }
1205
+ /**
1206
+ * Fetch available banks for a payment method
1207
+ */
1208
+ async fetchBanks(paymentMethodType) {
1209
+ if (!this.sdk)
1210
+ return;
1211
+ this.banksLoading = true;
1212
+ this.availableBanks = [];
1213
+ this.selectedBank = null;
1214
+ this.bankSearchQuery = '';
1215
+ try {
1216
+ const banks = await this.sdk.getBanks(paymentMethodType);
1217
+ this.availableBanks = banks;
1218
+ }
1219
+ catch (error) {
1220
+ console.error('Error fetching banks:', error);
1221
+ this.availableBanks = [];
1222
+ }
1223
+ finally {
1224
+ this.banksLoading = false;
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Handle bank selection
1229
+ */
1230
+ handleBankSelect(bank) {
1231
+ this.selectedBank = bank;
1232
+ this.bankSearchQuery = bank.displayName;
1233
+ }
1234
+ /**
1235
+ * Get filtered banks based on search query
1236
+ */
1237
+ getFilteredBanks() {
1238
+ if (!this.bankSearchQuery.trim()) {
1239
+ return this.availableBanks;
1240
+ }
1241
+ const query = this.bankSearchQuery.toLowerCase();
1242
+ return this.availableBanks.filter((bank) => bank.displayName.toLowerCase().includes(query) ||
1243
+ bank.bankName.toLowerCase().includes(query));
1244
+ }
1245
+ /**
1246
+ * Check if payment method requires Japan-only phone
1247
+ */
1248
+ isJapanOnlyPhoneMethod(methodId) {
1249
+ return this.JAPAN_ONLY_PHONE_METHODS.includes(methodId);
1250
+ }
1251
+ /**
1252
+ * Get the full phone number with country code in international format
1253
+ * For display: "+81 90 1234 5678"
1254
+ * For API: depends on payment method
1255
+ */
1256
+ getFormattedPhoneWithCountryCode() {
1257
+ if (!this.phoneInputValue.trim())
1258
+ return '';
1259
+ try {
1260
+ const parsed = parsePhoneNumber(this.phoneInputValue, this.selectedCountry);
1261
+ if (parsed) {
1262
+ return parsed.formatInternational();
1263
+ }
1264
+ }
1265
+ catch {
1266
+ // Fallback: prepend dial code
1267
+ }
1268
+ const country = this.COUNTRY_OPTIONS.find((c) => c.code === this.selectedCountry);
1269
+ const dialCode = country?.dialCode || '+65';
1270
+ const digits = this.phoneInputValue.replace(/\D/g, '');
1271
+ return `${dialCode} ${digits}`;
1272
+ }
1273
+ /**
1274
+ * Get effective phone number for API submission
1275
+ * - Japan methods (konbini, payeasy): Convert +81 to local format (0XXXXXXXXXX)
1276
+ * - Other methods: Keep international format (+XX XXXX XXXX)
1277
+ */
1278
+ getEffectivePhoneNumber(methodId) {
1279
+ // If phone input is filled, use it
1280
+ if (this.phoneInputValue.trim()) {
1281
+ const internationalPhone = this.getFormattedPhoneWithCountryCode();
1282
+ // For Japan methods, convert +81 to local 0 format
1283
+ if (this.isJapanOnlyPhoneMethod(methodId)) {
1284
+ return this.formatPhoneForJapan(internationalPhone);
1285
+ }
1286
+ // For other methods, keep international format as-is
1287
+ return internationalPhone;
1288
+ }
1289
+ // Otherwise use prop (already formatted)
1290
+ return this.phoneNumber || '';
1291
+ }
1292
+ /**
1293
+ * Format phone number for Japan (convert +81 to 0)
1294
+ * The Airwallex API for Japanese payment methods expects local format:
1295
+ * - Input: "+81 90 1234 5678" (international format)
1296
+ * - Output: "09012345678" (local format, no spaces)
1297
+ */
1298
+ formatPhoneForJapan(phoneNumber) {
1299
+ if (!phoneNumber)
1300
+ return '';
1301
+ if (phoneNumber.startsWith('+81')) {
1302
+ // Remove +81 prefix and replace with 0
1303
+ // Also remove all spaces for clean format
1304
+ return '0' + phoneNumber.slice(3).replace(/\s/g, '');
1305
+ }
1306
+ // If already local format or other format, just remove spaces
1307
+ return phoneNumber.replace(/\s/g, '');
1308
+ }
1309
+ /**
1310
+ * Format names for Pay-Easy (max 9 chars combined)
1311
+ * Pay-easy has a unique requirement: combined first name + last name must be maximum 9 characters
1312
+ */
1313
+ formatNamesForPayEasy(firstName, lastName) {
1314
+ const MAX_LENGTH = 9;
1315
+ const firstUpper = firstName.toUpperCase();
1316
+ const lastUpper = lastName.toUpperCase();
1317
+ const total = firstUpper.length + lastUpper.length;
1318
+ // Within limit - return as-is (uppercase)
1319
+ if (total <= MAX_LENGTH) {
1320
+ return { firstName: firstUpper, lastName: lastUpper };
1321
+ }
1322
+ // Need to truncate - distribute evenly
1323
+ const half = Math.floor(MAX_LENGTH / 2); // = 4
1324
+ let truncFirst = firstUpper;
1325
+ let truncLast = lastUpper;
1326
+ if (firstUpper.length <= half) {
1327
+ // First name fits in half, give remaining to last name
1328
+ truncLast = lastUpper.slice(0, MAX_LENGTH - firstUpper.length);
1329
+ }
1330
+ else if (lastUpper.length <= half) {
1331
+ // Last name fits in half, give remaining to first name
1332
+ truncFirst = firstUpper.slice(0, MAX_LENGTH - lastUpper.length);
1333
+ }
1334
+ else {
1335
+ // Both longer than half - split evenly
1336
+ truncFirst = firstUpper.slice(0, half); // 4 chars
1337
+ truncLast = lastUpper.slice(0, MAX_LENGTH - half); // 5 chars
1338
+ }
1339
+ return { firstName: truncFirst, lastName: truncLast };
1340
+ }
1341
+ /**
1342
+ * Format names for API submission (default: uppercase conversion)
1343
+ * All payment methods require names to be UPPERCASE
1344
+ */
1345
+ formatNamesUppercase() {
1346
+ return {
1347
+ firstName: this.firstName.toUpperCase(),
1348
+ lastName: this.lastName.toUpperCase(),
1349
+ };
1350
+ }
1351
+ /**
1352
+ * Validate phone number input
1353
+ * Rules:
1354
+ * 1. Required - phone number cannot be empty
1355
+ * 2. Min 8 digits - must have at least 8 digits
1356
+ * 3. Japan prefix - for konbini/payeasy, must validate as Japan number
1357
+ * 4. Format - must be valid phone format
1358
+ */
1359
+ validatePhoneInput() {
1360
+ const phone = this.phoneInputValue.trim();
1361
+ // Rule 1: Required
1362
+ if (!phone) {
1363
+ this.phoneInputError = 'Phone number is required';
1364
+ return false;
1365
+ }
1366
+ // Rule 2: Minimum 8 digits
1367
+ const digitsOnly = phone.replace(/\D/g, '');
1368
+ if (digitsOnly.length < this.MIN_PHONE_LENGTH) {
1369
+ this.phoneInputError = `Phone number must be at least ${this.MIN_PHONE_LENGTH} digits`;
1370
+ return false;
1371
+ }
1372
+ // Rule 3 & 4: Use libphonenumber-js for proper validation
1373
+ try {
1374
+ const isValid = isValidPhoneNumber(phone, this.selectedCountry);
1375
+ if (!isValid) {
1376
+ this.phoneInputError = 'Please enter a valid phone number';
1377
+ return false;
1378
+ }
1379
+ }
1380
+ catch {
1381
+ this.phoneInputError = 'Please enter a valid phone number';
1382
+ return false;
1383
+ }
1384
+ this.phoneInputError = '';
1385
+ return true;
1386
+ }
1387
+ /**
1388
+ * Handle phone input change with auto-formatting
1389
+ * Uses libphonenumber-js AsYouType formatter to format as user types
1390
+ */
1391
+ handlePhoneInputChange(e) {
1392
+ const input = e.target;
1393
+ const rawValue = input.value;
1394
+ // Extract only digits from input (reject all non-digits)
1395
+ let digits = rawValue.replace(/\D/g, '');
1396
+ // Country-specific max digits for national numbers
1397
+ const maxDigitsMap = {
1398
+ JP: 11, // 0X0-XXXX-XXXX (11 digits with leading 0)
1399
+ CN: 11, // 1XX-XXXX-XXXX
1400
+ SG: 8, // XXXX XXXX
1401
+ MY: 10, // 1X-XXX XXXX
1402
+ ID: 12, // 8XX-XXXX-XXXX
1403
+ TH: 9, // X XXXX XXXX
1404
+ PH: 10, // 9XX XXX XXXX
1405
+ HK: 8, // XXXX XXXX
1406
+ };
1407
+ const maxDigits = maxDigitsMap[this.selectedCountry] || 15;
1408
+ // Limit digits to max
1409
+ if (digits.length > maxDigits) {
1410
+ digits = digits.slice(0, maxDigits);
1411
+ }
1412
+ // For Japan, auto-prepend 0 if user starts with 9 (common mobile prefix)
1413
+ // Japanese mobile numbers start with 090, 080, 070
1414
+ if (this.selectedCountry === 'JP' && digits.length > 0 && !digits.startsWith('0')) {
1415
+ digits = '0' + digits;
1416
+ if (digits.length > maxDigits) {
1417
+ digits = digits.slice(0, maxDigits);
1418
+ }
1419
+ }
1420
+ // Use AsYouType formatter from libphonenumber-js
1421
+ const formatter = new AsYouType(this.selectedCountry);
1422
+ const formatted = formatter.input(digits);
1423
+ // Update state
1424
+ this.phoneInputValue = formatted;
1425
+ // IMPORTANT: Directly set input value to enforce formatting
1426
+ // This ensures the input field shows the formatted value, not the raw input
1427
+ input.value = formatted;
1428
+ // Clear error while typing
1429
+ if (this.phoneInputError) {
1430
+ this.phoneInputError = '';
1431
+ }
1432
+ }
1433
+ /**
1434
+ * Handle country change
1435
+ */
1436
+ handleCountryChange(e) {
1437
+ const select = e.target;
1438
+ this.selectedCountry = select.value;
1439
+ // Reset phone input when country changes
1440
+ this.phoneInputValue = '';
1441
+ this.phoneInputError = '';
1442
+ }
1200
1443
  // --- Render Methods ---
1201
1444
  /**
1202
1445
  * Get the formatted pay amount for buttons (net amount including fees)
@@ -1685,12 +1928,74 @@ let OnePayment = class OnePayment extends LitElement {
1685
1928
  </div>
1686
1929
  `;
1687
1930
  }
1931
+ /**
1932
+ * Render phone number input for payment methods that require it
1933
+ * Matches react-phone-input-2 behavior:
1934
+ * - Country dropdown with flags and dial codes
1935
+ * - Auto-formatting based on country
1936
+ * - Japan-only lock for konbini/payeasy
1937
+ */
1938
+ renderPhoneInput(methodId) {
1939
+ const isJapanOnly = this.isJapanOnlyPhoneMethod(methodId);
1940
+ // For Japan-only methods, lock country to Japan
1941
+ if (isJapanOnly && this.selectedCountry !== 'JP') {
1942
+ this.selectedCountry = 'JP';
1943
+ }
1944
+ return html `
1945
+ <div class="phone-input-section">
1946
+ <label class="phone-input-label">
1947
+ ${isJapanOnly ? 'Phone number (Japan)' : 'Phone number'}
1948
+ <span style="color: var(--op-color-danger);">*</span>
1949
+ </label>
1950
+ <div class="phone-input-wrapper">
1951
+ <select
1952
+ class="country-code-select"
1953
+ .value=${this.selectedCountry}
1954
+ @change=${this.handleCountryChange}
1955
+ ?disabled=${isJapanOnly}
1956
+ >
1957
+ ${isJapanOnly
1958
+ ? html `<option value="JP">🇯🇵 +81</option>`
1959
+ : this.COUNTRY_OPTIONS.map((country) => html `
1960
+ <option value=${country.code} ?selected=${this.selectedCountry === country.code}>
1961
+ ${country.flag} ${country.dialCode}
1962
+ </option>
1963
+ `)}
1964
+ </select>
1965
+ <input
1966
+ type="tel"
1967
+ class="phone-input-field ${this.phoneInputError ? 'error' : ''}"
1968
+ placeholder=${isJapanOnly
1969
+ ? 'Enter your Japanese phone number'
1970
+ : 'Enter your phone number'}
1971
+ .value=${this.phoneInputValue}
1972
+ @input=${this.handlePhoneInputChange}
1973
+ @blur=${() => this.validatePhoneInput()}
1974
+ />
1975
+ </div>
1976
+ ${this.phoneInputError
1977
+ ? html `<div class="phone-input-error">${this.phoneInputError}</div>`
1978
+ : ''}
1979
+ ${isJapanOnly
1980
+ ? html `<div class="phone-japan-note">
1981
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1982
+ <circle cx="12" cy="12" r="10"/>
1983
+ <path d="M12 16v-4M12 8h.01"/>
1984
+ </svg>
1985
+ Phone number must be a valid Japanese number (+81)
1986
+ </div>`
1987
+ : ''}
1988
+ </div>
1989
+ `;
1990
+ }
1688
1991
  renderBoostContent() {
1689
1992
  return html `
1690
1993
  <div class="paynow-container">
1691
1994
  <div class="paynow-instructions">
1692
1995
  <h3 class="instructions-title">How to Pay with Boost:</h3>
1693
1996
 
1997
+ ${this.renderPhoneInput(PAYMENT_METHODS.BOOST)}
1998
+
1694
1999
  <div class="instruction-steps">
1695
2000
  <div class="instruction-step">
1696
2001
  <div class="step-number">1</div>
@@ -1734,6 +2039,8 @@ let OnePayment = class OnePayment extends LitElement {
1734
2039
  <div class="paynow-instructions">
1735
2040
  <h3 class="instructions-title">How to Pay with ShopeePay:</h3>
1736
2041
 
2042
+ ${this.renderPhoneInput(PAYMENT_METHODS.SHOPEEPAY)}
2043
+
1737
2044
  <div class="instruction-steps">
1738
2045
  <div class="instruction-step">
1739
2046
  <div class="step-number">1</div>
@@ -1777,6 +2084,8 @@ let OnePayment = class OnePayment extends LitElement {
1777
2084
  <div class="paynow-instructions">
1778
2085
  <h3 class="instructions-title">How to Pay with Atome:</h3>
1779
2086
 
2087
+ ${this.renderPhoneInput(PAYMENT_METHODS.ATOM)}
2088
+
1780
2089
  <div class="instruction-steps">
1781
2090
  <div class="instruction-step">
1782
2091
  <div class="step-number">1</div>
@@ -1820,6 +2129,8 @@ let OnePayment = class OnePayment extends LitElement {
1820
2129
  <div class="paynow-instructions">
1821
2130
  <h3 class="instructions-title">How to Pay with Dana:</h3>
1822
2131
 
2132
+ ${this.renderPhoneInput(PAYMENT_METHODS.DANA)}
2133
+
1823
2134
  <div class="instruction-steps">
1824
2135
  <div class="instruction-step">
1825
2136
  <div class="step-number">1</div>
@@ -1863,6 +2174,8 @@ let OnePayment = class OnePayment extends LitElement {
1863
2174
  <div class="paynow-instructions">
1864
2175
  <h3 class="instructions-title">How to Pay with Touch 'n Go:</h3>
1865
2176
 
2177
+ ${this.renderPhoneInput(PAYMENT_METHODS.TNG)}
2178
+
1866
2179
  <div class="instruction-steps">
1867
2180
  <div class="instruction-step">
1868
2181
  <div class="step-number">1</div>
@@ -1906,6 +2219,8 @@ let OnePayment = class OnePayment extends LitElement {
1906
2219
  <div class="paynow-instructions">
1907
2220
  <h3 class="instructions-title">How to Pay with Alipay:</h3>
1908
2221
 
2222
+ ${this.renderPhoneInput(PAYMENT_METHODS.ALIPAYCN)}
2223
+
1909
2224
  <div class="instruction-steps">
1910
2225
  <div class="instruction-step">
1911
2226
  <div class="step-number">1</div>
@@ -1949,6 +2264,8 @@ let OnePayment = class OnePayment extends LitElement {
1949
2264
  <div class="paynow-instructions">
1950
2265
  <h3 class="instructions-title">How to Pay with AlipayHK:</h3>
1951
2266
 
2267
+ ${this.renderPhoneInput(PAYMENT_METHODS.ALIPAYHK)}
2268
+
1952
2269
  <div class="instruction-steps">
1953
2270
  <div class="instruction-step">
1954
2271
  <div class="step-number">1</div>
@@ -1992,6 +2309,8 @@ let OnePayment = class OnePayment extends LitElement {
1992
2309
  <div class="paynow-instructions">
1993
2310
  <h3 class="instructions-title">How to Pay with GCash:</h3>
1994
2311
 
2312
+ ${this.renderPhoneInput(PAYMENT_METHODS.GCASH)}
2313
+
1995
2314
  <div class="instruction-steps">
1996
2315
  <div class="instruction-step">
1997
2316
  <div class="step-number">1</div>
@@ -2035,6 +2354,8 @@ let OnePayment = class OnePayment extends LitElement {
2035
2354
  <div class="paynow-instructions">
2036
2355
  <h3 class="instructions-title">How to Pay with Konbini:</h3>
2037
2356
 
2357
+ ${this.renderPhoneInput(PAYMENT_METHODS.KONBINI)}
2358
+
2038
2359
  <div class="instruction-steps">
2039
2360
  <div class="instruction-step">
2040
2361
  <div class="step-number">1</div>
@@ -2078,6 +2399,8 @@ let OnePayment = class OnePayment extends LitElement {
2078
2399
  <div class="paynow-instructions">
2079
2400
  <h3 class="instructions-title">How to Pay with Pay-Easy:</h3>
2080
2401
 
2402
+ ${this.renderPhoneInput(PAYMENT_METHODS.PAYEASY)}
2403
+
2081
2404
  <div class="instruction-steps">
2082
2405
  <div class="instruction-step">
2083
2406
  <div class="step-number">1</div>
@@ -2086,7 +2409,9 @@ let OnePayment = class OnePayment extends LitElement {
2086
2409
 
2087
2410
  <div class="instruction-step">
2088
2411
  <div class="step-number">2</div>
2089
- <p class="step-text">Use your bank's ATM or online banking to complete the transfer.</p>
2412
+ <p class="step-text">
2413
+ Use your bank's ATM or online banking to complete the transfer.
2414
+ </p>
2090
2415
  </div>
2091
2416
 
2092
2417
  <div class="instruction-step">
@@ -2115,6 +2440,194 @@ let OnePayment = class OnePayment extends LitElement {
2115
2440
  </div>
2116
2441
  `;
2117
2442
  }
2443
+ renderGrabPaySGContent() {
2444
+ return html `
2445
+ <div class="paynow-container">
2446
+ <div class="paynow-instructions">
2447
+ <h3 class="instructions-title">How to Pay with GrabPay:</h3>
2448
+
2449
+ <div class="instruction-steps">
2450
+ <div class="instruction-step">
2451
+ <div class="step-number">1</div>
2452
+ <p class="step-text">Click <strong>"Pay"</strong> to open GrabPay.</p>
2453
+ </div>
2454
+
2455
+ <div class="instruction-step">
2456
+ <div class="step-number">2</div>
2457
+ <p class="step-text">Log in to your GrabPay account.</p>
2458
+ </div>
2459
+
2460
+ <div class="instruction-step">
2461
+ <div class="step-number">3</div>
2462
+ <p class="step-text">Confirm and complete your payment.</p>
2463
+ </div>
2464
+ </div>
2465
+
2466
+ <div class="submit-section">
2467
+ <button
2468
+ class="pay-button ${this.currentState.status === 'processing' ||
2469
+ this.currentState.status === 'requires_action'
2470
+ ? 'loading'
2471
+ : ''}"
2472
+ @click=${this.handleGrabPaySGPayment}
2473
+ ?disabled=${this.currentState.status === 'processing' ||
2474
+ this.currentState.status === 'requires_action'}
2475
+ >
2476
+ ${this.currentState.status === 'processing' ||
2477
+ this.currentState.status === 'requires_action'
2478
+ ? 'Redirecting...'
2479
+ : `Pay ${this.getPayButtonAmount()}`}
2480
+ </button>
2481
+ </div>
2482
+ </div>
2483
+ </div>
2484
+ `;
2485
+ }
2486
+ renderFPXContent() {
2487
+ // Fetch banks when component is first rendered for FPX
2488
+ if (this.availableBanks.length === 0 && !this.banksLoading) {
2489
+ this.fetchBanks('fpx');
2490
+ }
2491
+ const filteredBanks = this.getFilteredBanks();
2492
+ const isProcessing = this.currentState.status === 'processing' || this.currentState.status === 'requires_action';
2493
+ return html `
2494
+ <div class="fpx-container">
2495
+ <div class="fpx-instructions">
2496
+ <h3 class="instructions-title">Pay with FPX Online Banking</h3>
2497
+ <p class="fpx-description">
2498
+ Select your bank below and you'll be redirected to complete the payment securely.
2499
+ </p>
2500
+ </div>
2501
+
2502
+ <div class="bank-selection-wrapper">
2503
+ <label class="form-label">Select Your Bank</label>
2504
+ <div class="bank-search-container">
2505
+ <input
2506
+ type="text"
2507
+ class="input bank-search-input"
2508
+ placeholder="Search for your bank..."
2509
+ .value=${this.bankSearchQuery}
2510
+ @input=${(e) => {
2511
+ const target = e.target;
2512
+ this.bankSearchQuery = target.value;
2513
+ // Clear selection if user is typing
2514
+ if (this.selectedBank && target.value !== this.selectedBank.displayName) {
2515
+ this.selectedBank = null;
2516
+ }
2517
+ }}
2518
+ @focus=${() => {
2519
+ // Show all banks when focused
2520
+ if (this.selectedBank) {
2521
+ this.bankSearchQuery = '';
2522
+ this.selectedBank = null;
2523
+ }
2524
+ }}
2525
+ />
2526
+ <div class="bank-search-icon">
2527
+ <svg
2528
+ width="20"
2529
+ height="20"
2530
+ viewBox="0 0 24 24"
2531
+ fill="none"
2532
+ xmlns="http://www.w3.org/2000/svg"
2533
+ >
2534
+ <path
2535
+ d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z"
2536
+ stroke="currentColor"
2537
+ stroke-width="2"
2538
+ stroke-linecap="round"
2539
+ stroke-linejoin="round"
2540
+ />
2541
+ </svg>
2542
+ </div>
2543
+ </div>
2544
+
2545
+ ${this.banksLoading
2546
+ ? html `
2547
+ <div class="bank-loading">
2548
+ <div class="bank-loading-spinner"></div>
2549
+ <span>Loading banks...</span>
2550
+ </div>
2551
+ `
2552
+ : html `
2553
+ <div class="bank-grid">
2554
+ ${filteredBanks.map((bank) => html `
2555
+ <div
2556
+ class="bank-card ${this.selectedBank?.bankName === bank.bankName
2557
+ ? 'selected'
2558
+ : ''}"
2559
+ @click=${() => this.handleBankSelect(bank)}
2560
+ >
2561
+ <div class="bank-logo-wrapper">
2562
+ ${bank.logoUrl
2563
+ ? html `<img
2564
+ class="bank-logo"
2565
+ src="${bank.logoUrl}"
2566
+ alt="${bank.displayName}"
2567
+ @error=${(e) => {
2568
+ // Hide image and show fallback on error
2569
+ const img = e.target;
2570
+ img.style.display = 'none';
2571
+ const parent = img.parentElement;
2572
+ if (parent) {
2573
+ parent.innerHTML = `<div class="bank-logo-fallback">${bank.displayName.charAt(0)}</div>`;
2574
+ }
2575
+ }}
2576
+ />`
2577
+ : html `<div class="bank-logo-fallback">
2578
+ ${bank.displayName.charAt(0)}
2579
+ </div>`}
2580
+ </div>
2581
+ <span class="bank-name">${bank.displayName}</span>
2582
+ ${this.selectedBank?.bankName === bank.bankName
2583
+ ? html `<div class="bank-check">
2584
+ <svg
2585
+ width="16"
2586
+ height="16"
2587
+ viewBox="0 0 24 24"
2588
+ fill="none"
2589
+ xmlns="http://www.w3.org/2000/svg"
2590
+ >
2591
+ <path
2592
+ d="M20 6L9 17L4 12"
2593
+ stroke="currentColor"
2594
+ stroke-width="2"
2595
+ stroke-linecap="round"
2596
+ stroke-linejoin="round"
2597
+ />
2598
+ </svg>
2599
+ </div>`
2600
+ : ''}
2601
+ </div>
2602
+ `)}
2603
+ </div>
2604
+
2605
+ ${filteredBanks.length === 0 && this.bankSearchQuery
2606
+ ? html `
2607
+ <div class="bank-no-results">
2608
+ <p>No banks found matching "${this.bankSearchQuery}"</p>
2609
+ </div>
2610
+ `
2611
+ : ''}
2612
+ `}
2613
+ </div>
2614
+
2615
+ <div class="submit-section">
2616
+ <button
2617
+ class="pay-button ${isProcessing ? 'loading' : ''}"
2618
+ @click=${this.handleFPXPayment}
2619
+ ?disabled=${isProcessing || !this.selectedBank}
2620
+ >
2621
+ ${isProcessing
2622
+ ? 'Redirecting to bank...'
2623
+ : !this.selectedBank
2624
+ ? 'Select a bank to continue'
2625
+ : `Pay ${this.getPayButtonAmount()}`}
2626
+ </button>
2627
+ </div>
2628
+ </div>
2629
+ `;
2630
+ }
2118
2631
  render3DSModal() {
2119
2632
  if (!this.show3DSModal || !this.nextActionUrl) {
2120
2633
  return null;
@@ -2222,6 +2735,10 @@ let OnePayment = class OnePayment extends LitElement {
2222
2735
  return 'Konbini';
2223
2736
  case PAYMENT_METHODS.PAYEASY:
2224
2737
  return 'Pay-Easy';
2738
+ case PAYMENT_METHODS.GRABPAY_SG:
2739
+ return 'GrabPay';
2740
+ case PAYMENT_METHODS.FPX:
2741
+ return 'FPX Online Banking';
2225
2742
  default:
2226
2743
  return id;
2227
2744
  }
@@ -2255,7 +2772,11 @@ let OnePayment = class OnePayment extends LitElement {
2255
2772
  else if (id === PAYMENT_METHODS.PROMPTPAY) {
2256
2773
  return html `
2257
2774
  <div class="method-icon-right">
2258
- <img src="${PROMPTPAY_ICON_DATA_URL}" alt="PromptPay" style="width: 45px; height: auto;" />
2775
+ <img
2776
+ src="${PROMPTPAY_ICON_DATA_URL}"
2777
+ alt="PromptPay"
2778
+ style="width: 45px; height: auto;"
2779
+ />
2259
2780
  </div>
2260
2781
  `;
2261
2782
  }
@@ -2283,7 +2804,11 @@ let OnePayment = class OnePayment extends LitElement {
2283
2804
  else if (id === PAYMENT_METHODS.SHOPEEPAY) {
2284
2805
  return html `
2285
2806
  <div class="method-icon-right">
2286
- <img src="${SHOPEEPAY_ICON_DATA_URL}" alt="ShopeePay" style="width: 45px; height: auto;" />
2807
+ <img
2808
+ src="${SHOPEEPAY_ICON_DATA_URL}"
2809
+ alt="ShopeePay"
2810
+ style="width: 45px; height: auto;"
2811
+ />
2287
2812
  </div>
2288
2813
  `;
2289
2814
  }
@@ -2318,7 +2843,11 @@ let OnePayment = class OnePayment extends LitElement {
2318
2843
  else if (id === PAYMENT_METHODS.ALIPAYHK) {
2319
2844
  return html `
2320
2845
  <div class="method-icon-right">
2321
- <img src="${ALIPAYHK_ICON_DATA_URL}" alt="AlipayHK" style="width: 45px; height: auto;" />
2846
+ <img
2847
+ src="${ALIPAYHK_ICON_DATA_URL}"
2848
+ alt="AlipayHK"
2849
+ style="width: 45px; height: auto;"
2850
+ />
2322
2851
  </div>
2323
2852
  `;
2324
2853
  }
@@ -2341,6 +2870,24 @@ let OnePayment = class OnePayment extends LitElement {
2341
2870
  <div class="method-icon-right">
2342
2871
  <img src="${PAYEASY_ICON_DATA_URL}" alt="Pay-Easy" style="width: 45px; height: auto;" />
2343
2872
  </div>
2873
+ `;
2874
+ }
2875
+ else if (id === PAYMENT_METHODS.GRABPAY_SG) {
2876
+ return html `
2877
+ <div class="method-icon-right">
2878
+ <img src="${GRABPAY_ICON_DATA_URL}" alt="GrabPay" style="width: 45px; height: auto;" />
2879
+ </div>
2880
+ `;
2881
+ }
2882
+ else if (id === PAYMENT_METHODS.FPX) {
2883
+ return html `
2884
+ <div class="method-icon-right">
2885
+ <img
2886
+ src="${FPX_ICON_DATA_URL}"
2887
+ alt="FPX Online Banking"
2888
+ style="width: 45px; height: auto;"
2889
+ />
2890
+ </div>
2344
2891
  `;
2345
2892
  }
2346
2893
  return null;
@@ -2421,7 +2968,11 @@ let OnePayment = class OnePayment extends LitElement {
2421
2968
  ? this.renderKonbiniContent()
2422
2969
  : method.id === PAYMENT_METHODS.PAYEASY
2423
2970
  ? this.renderPayEasyContent()
2424
- : null}
2971
+ : method.id === PAYMENT_METHODS.GRABPAY_SG
2972
+ ? this.renderGrabPaySGContent()
2973
+ : method.id === PAYMENT_METHODS.FPX
2974
+ ? this.renderFPXContent()
2975
+ : null}
2425
2976
  </div>`
2426
2977
  : ''}
2427
2978
  </div>
@@ -3760,6 +4311,332 @@ OnePayment.styles = css `
3760
4311
  transform: translateX(-50%) translateY(0);
3761
4312
  }
3762
4313
  }
4314
+
4315
+ /* ═══════════════════════════════════════════════════════════════
4316
+ FPX BANK SELECTION STYLES
4317
+ ═══════════════════════════════════════════════════════════════ */
4318
+
4319
+ .fpx-container {
4320
+ display: flex;
4321
+ flex-direction: column;
4322
+ gap: 1.5rem;
4323
+ }
4324
+
4325
+ .fpx-instructions {
4326
+ text-align: left;
4327
+ }
4328
+
4329
+ .fpx-description {
4330
+ color: var(--op-color-text-secondary);
4331
+ font-size: var(--op-font-size-sm);
4332
+ margin: 0.5rem 0 0 0;
4333
+ line-height: 1.5;
4334
+ }
4335
+
4336
+ .bank-selection-wrapper {
4337
+ display: flex;
4338
+ flex-direction: column;
4339
+ gap: 0.75rem;
4340
+ }
4341
+
4342
+ .bank-search-container {
4343
+ position: relative;
4344
+ width: 100%;
4345
+ }
4346
+
4347
+ .bank-search-input {
4348
+ padding-right: 2.75rem;
4349
+ }
4350
+
4351
+ .bank-search-icon {
4352
+ position: absolute;
4353
+ right: 0.75rem;
4354
+ top: 50%;
4355
+ transform: translateY(-50%);
4356
+ color: var(--op-color-text-secondary);
4357
+ pointer-events: none;
4358
+ display: flex;
4359
+ align-items: center;
4360
+ justify-content: center;
4361
+ }
4362
+
4363
+ .bank-loading {
4364
+ display: flex;
4365
+ flex-direction: column;
4366
+ align-items: center;
4367
+ justify-content: center;
4368
+ padding: 2rem;
4369
+ gap: 1rem;
4370
+ color: var(--op-color-text-secondary);
4371
+ font-size: var(--op-font-size-sm);
4372
+ }
4373
+
4374
+ .bank-loading-spinner {
4375
+ width: 32px;
4376
+ height: 32px;
4377
+ border: 3px solid var(--op-color-border);
4378
+ border-top-color: var(--op-color-primary);
4379
+ border-radius: 50%;
4380
+ animation: spin 1s linear infinite;
4381
+ }
4382
+
4383
+ .bank-grid {
4384
+ display: grid;
4385
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
4386
+ gap: 0.75rem;
4387
+ max-height: 320px;
4388
+ overflow-y: auto;
4389
+ padding: 0.25rem;
4390
+ margin: -0.25rem;
4391
+ }
4392
+
4393
+ .bank-grid::-webkit-scrollbar {
4394
+ width: 6px;
4395
+ }
4396
+
4397
+ .bank-grid::-webkit-scrollbar-track {
4398
+ background: var(--op-color-surface);
4399
+ border-radius: 3px;
4400
+ }
4401
+
4402
+ .bank-grid::-webkit-scrollbar-thumb {
4403
+ background: var(--op-color-border);
4404
+ border-radius: 3px;
4405
+ }
4406
+
4407
+ .bank-grid::-webkit-scrollbar-thumb:hover {
4408
+ background: var(--op-color-text-secondary);
4409
+ }
4410
+
4411
+ .bank-card {
4412
+ position: relative;
4413
+ display: flex;
4414
+ flex-direction: column;
4415
+ align-items: center;
4416
+ padding: 1rem 0.75rem;
4417
+ background: var(--op-color-background);
4418
+ border: 2px solid var(--op-color-border);
4419
+ border-radius: var(--op-border-radius);
4420
+ cursor: pointer;
4421
+ transition: all 0.2s ease;
4422
+ gap: 0.625rem;
4423
+ min-height: 100px;
4424
+ }
4425
+
4426
+ .bank-card:hover {
4427
+ border-color: var(--op-color-text-secondary);
4428
+ background: var(--op-color-surface);
4429
+ transform: translateY(-2px);
4430
+ box-shadow: var(--op-box-shadow);
4431
+ }
4432
+
4433
+ .bank-card.selected {
4434
+ border-color: var(--op-color-primary);
4435
+ background: var(--op-color-surface);
4436
+ box-shadow: 0 0 0 3px rgba(42, 35, 39, 0.1);
4437
+ }
4438
+
4439
+ :host([data-theme='dark']) .bank-card.selected {
4440
+ box-shadow: 0 0 0 3px rgba(255, 190, 50, 0.15);
4441
+ }
4442
+
4443
+ .bank-logo-wrapper {
4444
+ width: 48px;
4445
+ height: 48px;
4446
+ display: flex;
4447
+ align-items: center;
4448
+ justify-content: center;
4449
+ flex-shrink: 0;
4450
+ }
4451
+
4452
+ .bank-logo {
4453
+ max-width: 100%;
4454
+ max-height: 100%;
4455
+ object-fit: contain;
4456
+ border-radius: 4px;
4457
+ }
4458
+
4459
+ .bank-logo-fallback {
4460
+ width: 48px;
4461
+ height: 48px;
4462
+ display: flex;
4463
+ align-items: center;
4464
+ justify-content: center;
4465
+ background: linear-gradient(135deg, var(--op-color-primary) 0%, var(--op-color-primary-hover) 100%);
4466
+ color: var(--op-color-primary-text);
4467
+ font-size: 1.25rem;
4468
+ font-weight: 700;
4469
+ border-radius: var(--op-border-radius);
4470
+ }
4471
+
4472
+ .bank-name {
4473
+ font-size: 0.75rem;
4474
+ font-weight: 500;
4475
+ color: var(--op-color-text);
4476
+ text-align: center;
4477
+ line-height: 1.3;
4478
+ overflow: hidden;
4479
+ text-overflow: ellipsis;
4480
+ display: -webkit-box;
4481
+ -webkit-line-clamp: 2;
4482
+ -webkit-box-orient: vertical;
4483
+ word-break: break-word;
4484
+ }
4485
+
4486
+ .bank-check {
4487
+ position: absolute;
4488
+ top: 0.5rem;
4489
+ right: 0.5rem;
4490
+ width: 20px;
4491
+ height: 20px;
4492
+ background: var(--op-color-primary);
4493
+ color: var(--op-color-primary-text);
4494
+ border-radius: 50%;
4495
+ display: flex;
4496
+ align-items: center;
4497
+ justify-content: center;
4498
+ animation: scaleIn 0.2s ease;
4499
+ }
4500
+
4501
+ .bank-no-results {
4502
+ text-align: center;
4503
+ padding: 2rem;
4504
+ color: var(--op-color-text-secondary);
4505
+ font-size: var(--op-font-size-sm);
4506
+ }
4507
+
4508
+ .bank-no-results p {
4509
+ margin: 0;
4510
+ }
4511
+
4512
+ /* Mobile responsiveness for bank grid */
4513
+ @media (max-width: 480px) {
4514
+ .bank-grid {
4515
+ grid-template-columns: repeat(2, 1fr);
4516
+ max-height: 280px;
4517
+ }
4518
+
4519
+ .bank-card {
4520
+ padding: 0.75rem 0.5rem;
4521
+ min-height: 90px;
4522
+ }
4523
+
4524
+ .bank-logo-wrapper {
4525
+ width: 40px;
4526
+ height: 40px;
4527
+ }
4528
+
4529
+ .bank-logo-fallback {
4530
+ width: 40px;
4531
+ height: 40px;
4532
+ font-size: 1rem;
4533
+ }
4534
+
4535
+ .bank-name {
4536
+ font-size: 0.6875rem;
4537
+ }
4538
+ }
4539
+
4540
+ /* ═══════════════════════════════════════════════════════════════
4541
+ PHONE NUMBER INPUT STYLES
4542
+ ═══════════════════════════════════════════════════════════════ */
4543
+
4544
+ .phone-input-section {
4545
+ margin-bottom: 1rem;
4546
+ }
4547
+
4548
+ .phone-input-label {
4549
+ display: block;
4550
+ font-size: var(--op-font-size-sm);
4551
+ font-weight: 500;
4552
+ color: var(--op-color-text);
4553
+ margin-bottom: 0.5rem;
4554
+ }
4555
+
4556
+ .phone-input-wrapper {
4557
+ display: flex;
4558
+ gap: 0.5rem;
4559
+ }
4560
+
4561
+ .country-code-select {
4562
+ flex-shrink: 0;
4563
+ width: 100px;
4564
+ padding: 0.75rem;
4565
+ font-size: var(--op-font-size-base);
4566
+ border: 1px solid var(--op-color-border);
4567
+ border-radius: var(--op-border-radius);
4568
+ background: var(--op-color-background);
4569
+ color: var(--op-color-text);
4570
+ cursor: pointer;
4571
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
4572
+ }
4573
+
4574
+ .country-code-select:focus {
4575
+ outline: none;
4576
+ border-color: var(--op-color-border-focus);
4577
+ box-shadow: 0 0 0 3px rgba(42, 35, 39, 0.1);
4578
+ }
4579
+
4580
+ .country-code-select:disabled {
4581
+ background: var(--op-color-surface);
4582
+ cursor: not-allowed;
4583
+ opacity: 0.7;
4584
+ }
4585
+
4586
+ .phone-input-field {
4587
+ flex: 1;
4588
+ padding: 0.75rem;
4589
+ font-size: var(--op-font-size-base);
4590
+ border: 1px solid var(--op-color-border);
4591
+ border-radius: var(--op-border-radius);
4592
+ background: var(--op-color-background);
4593
+ color: var(--op-color-text);
4594
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
4595
+ }
4596
+
4597
+ .phone-input-field:focus {
4598
+ outline: none;
4599
+ border-color: var(--op-color-border-focus);
4600
+ box-shadow: 0 0 0 3px rgba(42, 35, 39, 0.1);
4601
+ }
4602
+
4603
+ .phone-input-field::placeholder {
4604
+ color: var(--op-color-text-secondary);
4605
+ }
4606
+
4607
+ .phone-input-field.error {
4608
+ border-color: var(--op-color-danger);
4609
+ }
4610
+
4611
+ .phone-input-field.error:focus {
4612
+ box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.1);
4613
+ }
4614
+
4615
+ .phone-input-error {
4616
+ color: var(--op-color-danger);
4617
+ font-size: 0.75rem;
4618
+ margin-top: 0.375rem;
4619
+ }
4620
+
4621
+ .phone-japan-note {
4622
+ font-size: 0.75rem;
4623
+ color: var(--op-color-text-secondary);
4624
+ margin-top: 0.375rem;
4625
+ display: flex;
4626
+ align-items: center;
4627
+ gap: 0.25rem;
4628
+ }
4629
+
4630
+ /* Mobile responsiveness for phone input */
4631
+ @media (max-width: 480px) {
4632
+ .phone-input-wrapper {
4633
+ flex-direction: column;
4634
+ }
4635
+
4636
+ .country-code-select {
4637
+ width: 100%;
4638
+ }
4639
+ }
3763
4640
  `;
3764
4641
  __decorate([
3765
4642
  property({ type: Object })
@@ -3848,6 +4725,27 @@ __decorate([
3848
4725
  __decorate([
3849
4726
  state()
3850
4727
  ], OnePayment.prototype, "qrPollingInProgress", void 0);
4728
+ __decorate([
4729
+ state()
4730
+ ], OnePayment.prototype, "availableBanks", void 0);
4731
+ __decorate([
4732
+ state()
4733
+ ], OnePayment.prototype, "selectedBank", void 0);
4734
+ __decorate([
4735
+ state()
4736
+ ], OnePayment.prototype, "banksLoading", void 0);
4737
+ __decorate([
4738
+ state()
4739
+ ], OnePayment.prototype, "bankSearchQuery", void 0);
4740
+ __decorate([
4741
+ state()
4742
+ ], OnePayment.prototype, "phoneInputValue", void 0);
4743
+ __decorate([
4744
+ state()
4745
+ ], OnePayment.prototype, "phoneInputError", void 0);
4746
+ __decorate([
4747
+ state()
4748
+ ], OnePayment.prototype, "selectedCountry", void 0);
3851
4749
  OnePayment = __decorate([
3852
4750
  customElement('one-payment')
3853
4751
  ], OnePayment);