@gait-financial/react 0.1.12 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -84
- package/dist/index.cjs +70 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -23
- package/dist/index.d.ts +0 -23
- package/dist/index.js +71 -198
- package/dist/index.js.map +1 -1
- package/dist/register.cjs +17 -34
- package/dist/register.cjs.map +1 -1
- package/dist/register.d.cts +2 -16
- package/dist/register.d.ts +2 -16
- package/dist/register.js +17 -34
- package/dist/register.js.map +1 -1
- package/dist/wc/button.js +102 -1769
- package/package.json +1 -1
- package/wc/button.js +102 -1769
package/dist/wc/button.js
CHANGED
|
@@ -1,128 +1,25 @@
|
|
|
1
|
-
(function(
|
|
2
|
-
"use strict";
|
|
3
|
-
function darkenColor(hex, percent) {
|
|
4
|
-
const color = hex.replace("#", "");
|
|
5
|
-
const num = parseInt(color, 16);
|
|
6
|
-
const r = num >> 16 & 255;
|
|
7
|
-
const g = num >> 8 & 255;
|
|
8
|
-
const b = num & 255;
|
|
9
|
-
const newR = Math.max(0, Math.floor(r * (1 - percent)));
|
|
10
|
-
const newG = Math.max(0, Math.floor(g * (1 - percent)));
|
|
11
|
-
const newB = Math.max(0, Math.floor(b * (1 - percent)));
|
|
12
|
-
return `#${(newR << 16 | newG << 8 | newB).toString(16).padStart(6, "0")}`;
|
|
13
|
-
}
|
|
14
|
-
function isColorDark(hex) {
|
|
15
|
-
const color = hex.replace("#", "");
|
|
16
|
-
const num = parseInt(color, 16);
|
|
17
|
-
const r = num >> 16 & 255;
|
|
18
|
-
const g = num >> 8 & 255;
|
|
19
|
-
const b = num & 255;
|
|
20
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
21
|
-
return luminance < 0.5;
|
|
22
|
-
}
|
|
23
|
-
const colors = {
|
|
24
|
-
// Primary colors (CTA button - dark)
|
|
25
|
-
primary: {
|
|
26
|
-
main: "rgb(47, 44, 37)",
|
|
27
|
-
dark: "rgb(35, 33, 28)",
|
|
28
|
-
contrastText: "#ffffff"
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
const spacing = {
|
|
32
|
-
sm: "8px",
|
|
33
|
-
md: "16px",
|
|
34
|
-
lg: "24px"
|
|
35
|
-
};
|
|
36
|
-
const borderRadius = {
|
|
37
|
-
md: "8px"
|
|
38
|
-
};
|
|
39
|
-
const transitions = {
|
|
40
|
-
normal: "250ms ease-in-out"
|
|
41
|
-
};
|
|
42
|
-
const zIndex = {
|
|
43
|
-
modalBackdrop: 1040
|
|
44
|
-
};
|
|
45
|
-
const typography = {
|
|
46
|
-
fontFamily: {
|
|
47
|
-
primary: '"Inter", "Helvetica", "Arial", sans-serif'
|
|
48
|
-
},
|
|
49
|
-
fontSize: {
|
|
50
|
-
// 12px
|
|
51
|
-
sm: "0.875rem",
|
|
52
|
-
// 14px
|
|
53
|
-
md: "1rem",
|
|
54
|
-
// 16px
|
|
55
|
-
lg: "1.125rem"
|
|
56
|
-
},
|
|
57
|
-
fontWeight: {
|
|
58
|
-
medium: 500
|
|
59
|
-
},
|
|
60
|
-
lineHeight: {
|
|
61
|
-
normal: 1.5
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
const GAIT_BRAND_COLOR = "#4147BF";
|
|
65
|
-
const GAIT_SECONDARY_COLOR = "#F3F2FF";
|
|
66
|
-
const splitPaymentConfig = {
|
|
67
|
-
/**
|
|
68
|
-
* Split payment fee amount
|
|
69
|
-
* This fee is added to the total cost when using split payment
|
|
70
|
-
*/
|
|
71
|
-
fee: 10
|
|
72
|
-
};
|
|
73
|
-
function getThemeStyles(darkenColor2, isColorDark2) {
|
|
74
|
-
return {
|
|
75
|
-
bg: GAIT_BRAND_COLOR,
|
|
76
|
-
bgHover: darkenColor2(GAIT_BRAND_COLOR, 0.15),
|
|
77
|
-
bgActive: darkenColor2(GAIT_BRAND_COLOR, 0.25),
|
|
78
|
-
color: isColorDark2(GAIT_BRAND_COLOR) ? "#ffffff" : "#000000"
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function getSizeStyles(size) {
|
|
82
|
-
const sizeStyles = {
|
|
83
|
-
small: {
|
|
84
|
-
padding: "8px 20px",
|
|
85
|
-
fontSize: typography.fontSize.sm,
|
|
86
|
-
minHeight: "36px"
|
|
87
|
-
},
|
|
88
|
-
medium: {
|
|
89
|
-
padding: "12px 28px",
|
|
90
|
-
fontSize: typography.fontSize.md,
|
|
91
|
-
minHeight: "44px"
|
|
92
|
-
},
|
|
93
|
-
large: {
|
|
94
|
-
padding: "16px 36px",
|
|
95
|
-
fontSize: typography.fontSize.lg,
|
|
96
|
-
minHeight: "52px"
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
return sizeStyles[size];
|
|
100
|
-
}
|
|
101
|
-
function generateButtonStyles(size, darkenColorFn, isColorDarkFn) {
|
|
102
|
-
const themeStyles = getThemeStyles(darkenColorFn, isColorDarkFn);
|
|
103
|
-
const currentSize = getSizeStyles(size);
|
|
104
|
-
return `
|
|
1
|
+
(function(I){"use strict";function F(r,t){const e=r.replace("#",""),i=parseInt(e,16),a=i>>16&255,n=i>>8&255,o=i&255,d=Math.max(0,Math.floor(a*(1-t))),l=Math.max(0,Math.floor(n*(1-t))),s=Math.max(0,Math.floor(o*(1-t)));return`#${(d<<16|l<<8|s).toString(16).padStart(6,"0")}`}function q(r){const t=r.replace("#",""),e=parseInt(t,16),i=e>>16&255,a=e>>8&255,n=e&255;return(.299*i+.587*a+.114*n)/255<.5}const h={primary:{main:"rgb(47, 44, 37)",dark:"rgb(35, 33, 28)",contrastText:"#ffffff"}},E={sm:"8px",md:"16px",lg:"24px"},P={md:"8px"},f={normal:"250ms ease-in-out"},O={modalBackdrop:1040},y={fontFamily:{primary:'"Inter", "Helvetica", "Arial", sans-serif'},fontSize:{sm:"0.875rem",md:"1rem",lg:"1.125rem"},fontWeight:{medium:500},lineHeight:{normal:1.5}},g="#4147BF",C="#F3F2FF",B={fee:10};function N(r,t){return{bg:g,bgHover:r(g,.15),bgActive:r(g,.25),color:t(g)?"#ffffff":"#000000"}}function G(r){return{small:{padding:"8px 20px",fontSize:y.fontSize.sm,minHeight:"36px"},medium:{padding:"12px 28px",fontSize:y.fontSize.md,minHeight:"44px"},large:{padding:"16px 36px",fontSize:y.fontSize.lg,minHeight:"52px"}}[r]}function D(r,t,e){const i=N(t,e),a=G(r);return`
|
|
105
2
|
<style>
|
|
106
3
|
:host {
|
|
107
4
|
display: inline-block;
|
|
108
|
-
--gait-button-bg: ${
|
|
109
|
-
--gait-button-bg-hover: ${
|
|
110
|
-
--gait-button-bg-active: ${
|
|
111
|
-
--gait-button-color: ${
|
|
112
|
-
--gait-button-padding: ${
|
|
113
|
-
--gait-button-font-size: ${
|
|
114
|
-
--gait-button-min-height: ${
|
|
5
|
+
--gait-button-bg: ${i.bg};
|
|
6
|
+
--gait-button-bg-hover: ${i.bgHover};
|
|
7
|
+
--gait-button-bg-active: ${i.bgActive};
|
|
8
|
+
--gait-button-color: ${i.color};
|
|
9
|
+
--gait-button-padding: ${a.padding};
|
|
10
|
+
--gait-button-font-size: ${a.fontSize};
|
|
11
|
+
--gait-button-min-height: ${a.minHeight};
|
|
115
12
|
}
|
|
116
13
|
|
|
117
14
|
button {
|
|
118
15
|
display: inline-flex;
|
|
119
16
|
align-items: center;
|
|
120
17
|
justify-content: center;
|
|
121
|
-
gap: ${
|
|
18
|
+
gap: ${E.sm};
|
|
122
19
|
|
|
123
|
-
font-family: ${
|
|
124
|
-
font-weight: ${
|
|
125
|
-
line-height: ${
|
|
20
|
+
font-family: ${y.fontFamily.primary};
|
|
21
|
+
font-weight: ${y.fontWeight.medium};
|
|
22
|
+
line-height: ${y.lineHeight.normal};
|
|
126
23
|
font-size: var(--gait-button-font-size);
|
|
127
24
|
|
|
128
25
|
padding: var(--gait-button-padding);
|
|
@@ -132,10 +29,10 @@
|
|
|
132
29
|
color: var(--gait-button-color);
|
|
133
30
|
|
|
134
31
|
border: none;
|
|
135
|
-
border-radius: ${
|
|
32
|
+
border-radius: ${P.md};
|
|
136
33
|
cursor: pointer;
|
|
137
34
|
|
|
138
|
-
transition: all ${
|
|
35
|
+
transition: all ${f.normal};
|
|
139
36
|
|
|
140
37
|
/* Remove default button styles */
|
|
141
38
|
outline: none;
|
|
@@ -184,15 +81,7 @@
|
|
|
184
81
|
width: 100%;
|
|
185
82
|
}
|
|
186
83
|
</style>
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
function injectModalStyles() {
|
|
190
|
-
if (document.getElementById("gait-modal-styles")) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const style = document.createElement("style");
|
|
194
|
-
style.id = "gait-modal-styles";
|
|
195
|
-
style.textContent = `
|
|
84
|
+
`}function V(){if(document.getElementById("gait-modal-styles"))return;const r=document.createElement("style");r.id="gait-modal-styles",r.textContent=`
|
|
196
85
|
/* Modal Backdrop - The overlay behind the modal */
|
|
197
86
|
.gait-modal-backdrop {
|
|
198
87
|
position: fixed;
|
|
@@ -204,9 +93,9 @@
|
|
|
204
93
|
display: flex;
|
|
205
94
|
align-items: center;
|
|
206
95
|
justify-content: center;
|
|
207
|
-
z-index: ${
|
|
96
|
+
z-index: ${O.modalBackdrop};
|
|
208
97
|
opacity: 0;
|
|
209
|
-
transition: opacity ${
|
|
98
|
+
transition: opacity ${f.normal};
|
|
210
99
|
overflow: hidden;
|
|
211
100
|
}
|
|
212
101
|
|
|
@@ -507,7 +396,7 @@
|
|
|
507
396
|
border: 1px solid #e5e7eb;
|
|
508
397
|
border-radius: 8px;
|
|
509
398
|
cursor: pointer;
|
|
510
|
-
transition: all ${
|
|
399
|
+
transition: all ${f.normal};
|
|
511
400
|
color: #6b7280;
|
|
512
401
|
}
|
|
513
402
|
|
|
@@ -518,9 +407,9 @@
|
|
|
518
407
|
}
|
|
519
408
|
|
|
520
409
|
.gait-modal-split-option.selected {
|
|
521
|
-
background: ${
|
|
522
|
-
border: 1px solid ${
|
|
523
|
-
color: ${
|
|
410
|
+
background: ${C};
|
|
411
|
+
border: 1px solid ${g};
|
|
412
|
+
color: ${g};
|
|
524
413
|
}
|
|
525
414
|
|
|
526
415
|
.gait-modal-email-inputs {
|
|
@@ -539,11 +428,11 @@
|
|
|
539
428
|
background: #fff;
|
|
540
429
|
color: #111827;
|
|
541
430
|
outline: none;
|
|
542
|
-
transition: all ${
|
|
431
|
+
transition: all ${f.normal};
|
|
543
432
|
}
|
|
544
433
|
|
|
545
434
|
.gait-modal-email-input:focus {
|
|
546
|
-
border-color: ${
|
|
435
|
+
border-color: ${h.primary.main};
|
|
547
436
|
box-shadow: 0 0 0 2px rgba(47, 44, 37, 0.1);
|
|
548
437
|
}
|
|
549
438
|
|
|
@@ -606,7 +495,7 @@
|
|
|
606
495
|
|
|
607
496
|
.gait-modal-email-item-amount {
|
|
608
497
|
font-weight: 500;
|
|
609
|
-
color: ${
|
|
498
|
+
color: ${h.primary.main};
|
|
610
499
|
font-size: 14px;
|
|
611
500
|
}
|
|
612
501
|
|
|
@@ -625,12 +514,12 @@
|
|
|
625
514
|
border: none;
|
|
626
515
|
cursor: pointer;
|
|
627
516
|
padding: 2px 4px;
|
|
628
|
-
transition: color ${
|
|
517
|
+
transition: color ${f.normal};
|
|
629
518
|
}
|
|
630
519
|
|
|
631
520
|
.gait-modal-email-item-edit:hover,
|
|
632
521
|
.gait-modal-email-item-remove:hover {
|
|
633
|
-
color: ${
|
|
522
|
+
color: ${h.primary.main};
|
|
634
523
|
}
|
|
635
524
|
|
|
636
525
|
/* For "By amount" mode - two input fields side by side */
|
|
@@ -653,18 +542,18 @@
|
|
|
653
542
|
padding: 8px 16px;
|
|
654
543
|
font-size: 13px;
|
|
655
544
|
font-weight: 500;
|
|
656
|
-
border: 1px solid ${
|
|
545
|
+
border: 1px solid ${h.primary.main};
|
|
657
546
|
border-radius: 6px;
|
|
658
|
-
background: ${
|
|
659
|
-
color: ${
|
|
547
|
+
background: ${h.primary.main};
|
|
548
|
+
color: ${h.primary.contrastText};
|
|
660
549
|
cursor: pointer;
|
|
661
|
-
transition: all ${
|
|
550
|
+
transition: all ${f.normal};
|
|
662
551
|
white-space: nowrap;
|
|
663
552
|
}
|
|
664
553
|
|
|
665
554
|
.gait-modal-done-button:hover {
|
|
666
|
-
background: ${
|
|
667
|
-
border-color: ${
|
|
555
|
+
background: ${h.primary.dark};
|
|
556
|
+
border-color: ${h.primary.dark};
|
|
668
557
|
}
|
|
669
558
|
|
|
670
559
|
.gait-modal-email-amount-input {
|
|
@@ -680,7 +569,7 @@
|
|
|
680
569
|
}
|
|
681
570
|
|
|
682
571
|
.gait-modal-email-amount-input:focus {
|
|
683
|
-
border-color: ${
|
|
572
|
+
border-color: ${h.primary.main};
|
|
684
573
|
box-shadow: 0 0 0 2px rgba(47, 44, 37, 0.1);
|
|
685
574
|
}
|
|
686
575
|
|
|
@@ -692,11 +581,11 @@
|
|
|
692
581
|
border: none;
|
|
693
582
|
cursor: pointer;
|
|
694
583
|
text-align: left;
|
|
695
|
-
transition: color ${
|
|
584
|
+
transition: color ${f.normal};
|
|
696
585
|
}
|
|
697
586
|
|
|
698
587
|
.gait-modal-add-email-button:hover {
|
|
699
|
-
color: ${
|
|
588
|
+
color: ${h.primary.main};
|
|
700
589
|
text-decoration: underline;
|
|
701
590
|
}
|
|
702
591
|
|
|
@@ -716,7 +605,7 @@
|
|
|
716
605
|
.gait-modal-split-amount {
|
|
717
606
|
font-size: 18px;
|
|
718
607
|
font-weight: 600;
|
|
719
|
-
color: ${
|
|
608
|
+
color: ${g};
|
|
720
609
|
}
|
|
721
610
|
|
|
722
611
|
.gait-modal-send-split-button {
|
|
@@ -725,16 +614,16 @@
|
|
|
725
614
|
font-size: 14px;
|
|
726
615
|
font-weight: 500;
|
|
727
616
|
border-radius: 8px;
|
|
728
|
-
border: 1px solid ${
|
|
729
|
-
background: ${
|
|
730
|
-
color: ${
|
|
617
|
+
border: 1px solid ${g};
|
|
618
|
+
background: ${C};
|
|
619
|
+
color: ${g};
|
|
731
620
|
cursor: pointer;
|
|
732
|
-
transition: all ${
|
|
621
|
+
transition: all ${f.normal};
|
|
733
622
|
margin-top: 16px;
|
|
734
623
|
}
|
|
735
624
|
|
|
736
625
|
.gait-modal-send-split-button:hover {
|
|
737
|
-
background: ${
|
|
626
|
+
background: ${C};
|
|
738
627
|
filter: brightness(0.98);
|
|
739
628
|
}
|
|
740
629
|
|
|
@@ -754,10 +643,10 @@
|
|
|
754
643
|
font-weight: 600;
|
|
755
644
|
border-radius: 10px;
|
|
756
645
|
border: none;
|
|
757
|
-
background: var(--gait-modal-button-bg, ${
|
|
758
|
-
color: var(--gait-modal-button-color, ${
|
|
646
|
+
background: var(--gait-modal-button-bg, ${h.primary.main});
|
|
647
|
+
color: var(--gait-modal-button-color, ${h.primary.contrastText});
|
|
759
648
|
cursor: pointer;
|
|
760
|
-
transition: all ${
|
|
649
|
+
transition: all ${f.normal};
|
|
761
650
|
margin-top: 12px;
|
|
762
651
|
display: flex;
|
|
763
652
|
flex-direction: row;
|
|
@@ -768,7 +657,7 @@
|
|
|
768
657
|
}
|
|
769
658
|
|
|
770
659
|
.gait-modal-confirm-button-with-amount:hover {
|
|
771
|
-
background-color: var(--gait-modal-button-bg-hover, ${
|
|
660
|
+
background-color: var(--gait-modal-button-bg-hover, ${h.primary.dark});
|
|
772
661
|
}
|
|
773
662
|
|
|
774
663
|
.gait-modal-confirm-button-with-amount:active {
|
|
@@ -815,7 +704,7 @@
|
|
|
815
704
|
background: var(--gait-modal-button-bg, #e91e63);
|
|
816
705
|
color: var(--gait-modal-button-color, #fff);
|
|
817
706
|
cursor: pointer;
|
|
818
|
-
transition: all ${
|
|
707
|
+
transition: all ${f.normal};
|
|
819
708
|
}
|
|
820
709
|
|
|
821
710
|
.gait-modal-confirm-button:hover {
|
|
@@ -828,29 +717,29 @@
|
|
|
828
717
|
|
|
829
718
|
/* Modal Footer - The button section */
|
|
830
719
|
.gait-modal-footer {
|
|
831
|
-
padding: ${
|
|
720
|
+
padding: ${E.lg};
|
|
832
721
|
border-top: 1px solid #e5e7eb;
|
|
833
722
|
display: flex;
|
|
834
723
|
justify-content: flex-end;
|
|
835
|
-
gap: ${
|
|
724
|
+
gap: ${E.md};
|
|
836
725
|
}
|
|
837
726
|
|
|
838
727
|
/* Modal Button - The close button */
|
|
839
728
|
.gait-modal-button {
|
|
840
|
-
padding: ${
|
|
841
|
-
font-family: ${
|
|
842
|
-
font-size: ${
|
|
843
|
-
font-weight: ${
|
|
729
|
+
padding: ${E.sm} ${E.lg};
|
|
730
|
+
font-family: ${y.fontFamily.primary};
|
|
731
|
+
font-size: ${y.fontSize.md};
|
|
732
|
+
font-weight: ${y.fontWeight.medium};
|
|
844
733
|
border: none;
|
|
845
|
-
border-radius: ${
|
|
734
|
+
border-radius: ${P.md};
|
|
846
735
|
cursor: pointer;
|
|
847
|
-
transition: all ${
|
|
848
|
-
background-color: ${
|
|
849
|
-
color: ${
|
|
736
|
+
transition: all ${f.normal};
|
|
737
|
+
background-color: ${h.primary.main};
|
|
738
|
+
color: ${h.primary.contrastText};
|
|
850
739
|
}
|
|
851
740
|
|
|
852
741
|
.gait-modal-button:hover {
|
|
853
|
-
background-color: ${
|
|
742
|
+
background-color: ${h.primary.dark};
|
|
854
743
|
}
|
|
855
744
|
|
|
856
745
|
.gait-modal-button:active {
|
|
@@ -945,73 +834,55 @@
|
|
|
945
834
|
background-color: #ef4444;
|
|
946
835
|
color: #fff;
|
|
947
836
|
}
|
|
948
|
-
|
|
949
|
-
document.head.appendChild(style);
|
|
950
|
-
}
|
|
951
|
-
const GAIT_LOGO_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 311.96 313.95" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M168.07,139.33c6.81-15.69,22.45-26.66,40.65-26.66s33.83,10.97,40.66,26.66h62.58C303.19,60.94,236.71,0,155.98,0S8.77,60.95,0,139.33h168.07Z"/><path fill="currentColor" d="M249.37,174.62c-6.81,15.68-22.46,26.65-40.65,26.65s-33.82-10.97-40.64-26.65H0c8.77,78.39,75.25,139.33,155.98,139.33s147.21-60.94,155.98-139.33h-62.59Z"/></svg>`;
|
|
952
|
-
function generateButtonTemplate(disabled, customStyle) {
|
|
953
|
-
return `
|
|
837
|
+
`,document.head.appendChild(r)}const j='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 311.96 313.95" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M168.07,139.33c6.81-15.69,22.45-26.66,40.65-26.66s33.83,10.97,40.66,26.66h62.58C303.19,60.94,236.71,0,155.98,0S8.77,60.95,0,139.33h168.07Z"/><path fill="currentColor" d="M249.37,174.62c-6.81,15.68-22.46,26.65-40.65,26.65s-33.82-10.97-40.64-26.65H0c8.77,78.39,75.25,139.33,155.98,139.33s147.21-60.94,155.98-139.33h-62.59Z"/></svg>';function H(r,t){return`
|
|
954
838
|
<button
|
|
955
839
|
type="button"
|
|
956
|
-
${
|
|
957
|
-
style="${
|
|
840
|
+
${r?"disabled":""}
|
|
841
|
+
style="${t}"
|
|
958
842
|
>
|
|
959
|
-
<span class="gait-button-icon">${
|
|
843
|
+
<span class="gait-button-icon">${j}</span>
|
|
960
844
|
<span class="gait-button-label">Gait</span>
|
|
961
845
|
</button>
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return items.map((item) => `
|
|
846
|
+
`}function A(r){return`R ${r.toFixed(2)}`}const _=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
847
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
848
|
+
</svg>
|
|
849
|
+
`,$=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
850
|
+
<path d="M20 6L9 17l-5-5" />
|
|
851
|
+
</svg>
|
|
852
|
+
`,U=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
853
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
854
|
+
</svg>
|
|
855
|
+
`,W=`<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
856
|
+
<path d="M21 12a9 9 0 11-6.22-8.56" />
|
|
857
|
+
</svg>
|
|
858
|
+
`;function J(r){return!r||r.length===0?"":r.map(t=>`
|
|
976
859
|
<div class="gait-modal-item">
|
|
977
860
|
<img
|
|
978
|
-
src="${
|
|
979
|
-
alt="${
|
|
861
|
+
src="${t.image||""}"
|
|
862
|
+
alt="${t.name||"Product"}"
|
|
980
863
|
class="gait-modal-item-image"
|
|
981
864
|
/>
|
|
982
865
|
<div class="gait-modal-item-meta">
|
|
983
866
|
<h3 class="gait-modal-item-title">
|
|
984
|
-
${
|
|
867
|
+
${t.name||""}
|
|
985
868
|
</h3>
|
|
986
869
|
<div class="gait-modal-item-quantity">
|
|
987
|
-
Qty: ${
|
|
988
|
-
<span class="gait-modal-item-price">${
|
|
870
|
+
Qty: ${t.quantity||0}
|
|
871
|
+
<span class="gait-modal-item-price">${A(t.price||0)}</span>
|
|
989
872
|
</div>
|
|
990
873
|
</div>
|
|
991
874
|
</div>
|
|
992
|
-
`).join("");
|
|
993
|
-
}
|
|
994
|
-
function renderPriceBreakdown(breakdown) {
|
|
995
|
-
if (!breakdown || breakdown.length === 0) {
|
|
996
|
-
return "";
|
|
997
|
-
}
|
|
998
|
-
return breakdown.map((item) => {
|
|
999
|
-
const price = typeof item.price === "number" ? item.price : parseFloat(String(item.price)) || 0;
|
|
1000
|
-
return `
|
|
875
|
+
`).join("")}function Y(r){return!r||r.length===0?"":r.map(t=>{const e=typeof t.price=="number"?t.price:parseFloat(String(t.price))||0;return`
|
|
1001
876
|
<div class="gait-modal-price-item">
|
|
1002
|
-
<span>${
|
|
1003
|
-
<span>${
|
|
877
|
+
<span>${t.name||""}</span>
|
|
878
|
+
<span>${A(e)}</span>
|
|
1004
879
|
</div>
|
|
1005
|
-
|
|
1006
|
-
}).join("");
|
|
1007
|
-
}
|
|
1008
|
-
function generateModalTemplate(items, priceBreakdown, totalCost) {
|
|
1009
|
-
return `
|
|
880
|
+
`}).join("")}function K(r,t,e){return`
|
|
1010
881
|
<div class="gait-modal-container">
|
|
1011
882
|
<div class="gait-modal-header">
|
|
1012
883
|
<h2 class="gait-modal-header-title">Split Payment</h2>
|
|
1013
884
|
<button class="gait-modal-close-icon" data-gait-modal-close aria-label="Close">
|
|
1014
|
-
${
|
|
885
|
+
${_}
|
|
1015
886
|
</button>
|
|
1016
887
|
</div>
|
|
1017
888
|
|
|
@@ -1019,20 +890,20 @@
|
|
|
1019
890
|
<!-- LEFT -->
|
|
1020
891
|
<div class="gait-modal-left-panel">
|
|
1021
892
|
<div class="gait-modal-items-container">
|
|
1022
|
-
${
|
|
893
|
+
${J(r)}
|
|
1023
894
|
</div>
|
|
1024
895
|
|
|
1025
896
|
<div class="gait-modal-price-section">
|
|
1026
897
|
<h4 class="gait-modal-price-title">Price details</h4>
|
|
1027
898
|
|
|
1028
|
-
${
|
|
899
|
+
${Y(t)}
|
|
1029
900
|
|
|
1030
|
-
${
|
|
901
|
+
${e>0?`
|
|
1031
902
|
<div class="gait-modal-price-total">
|
|
1032
903
|
<span>Total</span>
|
|
1033
|
-
<span>${
|
|
904
|
+
<span>${A(e)}</span>
|
|
1034
905
|
</div>
|
|
1035
|
-
|
|
906
|
+
`:""}
|
|
1036
907
|
</div>
|
|
1037
908
|
</div>
|
|
1038
909
|
|
|
@@ -1066,1560 +937,22 @@
|
|
|
1066
937
|
</div>
|
|
1067
938
|
</div>
|
|
1068
939
|
</div>
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
function getDataAttributes(element) {
|
|
1072
|
-
const dataAttrs = {};
|
|
1073
|
-
Array.from(element.attributes).forEach((attr) => {
|
|
1074
|
-
if (attr.name.startsWith("data-")) {
|
|
1075
|
-
dataAttrs[attr.name] = attr.value;
|
|
1076
|
-
}
|
|
1077
|
-
});
|
|
1078
|
-
return dataAttrs;
|
|
1079
|
-
}
|
|
1080
|
-
function getAllAttributes(element) {
|
|
1081
|
-
const attrs = {};
|
|
1082
|
-
Array.from(element.attributes).forEach((attr) => {
|
|
1083
|
-
attrs[attr.name] = attr.value;
|
|
1084
|
-
});
|
|
1085
|
-
return attrs;
|
|
1086
|
-
}
|
|
1087
|
-
function createAttributeGetters(element) {
|
|
1088
|
-
return {
|
|
1089
|
-
get dataId() {
|
|
1090
|
-
return element.getAttribute("data-id") || null;
|
|
1091
|
-
},
|
|
1092
|
-
get disabled() {
|
|
1093
|
-
return element.hasAttribute("disabled") && element.getAttribute("disabled") !== "false";
|
|
1094
|
-
},
|
|
1095
|
-
get size() {
|
|
1096
|
-
return element.getAttribute("size") || "medium";
|
|
1097
|
-
},
|
|
1098
|
-
get items() {
|
|
1099
|
-
const itemsAttr = element.getAttribute("items");
|
|
1100
|
-
if (!itemsAttr) {
|
|
1101
|
-
return [];
|
|
1102
|
-
}
|
|
1103
|
-
try {
|
|
1104
|
-
return JSON.parse(itemsAttr);
|
|
1105
|
-
} catch (e) {
|
|
1106
|
-
console.warn("[GaitButton] Failed to parse items attribute:", e);
|
|
1107
|
-
return [];
|
|
1108
|
-
}
|
|
1109
|
-
},
|
|
1110
|
-
get totalCost() {
|
|
1111
|
-
const totalCostAttr = element.getAttribute("total-cost");
|
|
1112
|
-
if (!totalCostAttr) {
|
|
1113
|
-
return 0;
|
|
1114
|
-
}
|
|
1115
|
-
const parsed = parseFloat(totalCostAttr);
|
|
1116
|
-
const baseTotal = isNaN(parsed) ? 0 : parsed;
|
|
1117
|
-
return baseTotal + splitPaymentConfig.fee;
|
|
1118
|
-
},
|
|
1119
|
-
get priceBreakdown() {
|
|
1120
|
-
const totalCostAttr = element.getAttribute("total-cost");
|
|
1121
|
-
const parsed = parseFloat(totalCostAttr || "0");
|
|
1122
|
-
const baseTotal = isNaN(parsed) ? 0 : parsed;
|
|
1123
|
-
const allPriceBreakdown = [
|
|
1124
|
-
{
|
|
1125
|
-
name: "Subtotal",
|
|
1126
|
-
price: baseTotal
|
|
1127
|
-
},
|
|
1128
|
-
{
|
|
1129
|
-
name: "Split Payment Fee",
|
|
1130
|
-
price: splitPaymentConfig.fee
|
|
1131
|
-
}
|
|
1132
|
-
];
|
|
1133
|
-
return allPriceBreakdown;
|
|
1134
|
-
},
|
|
1135
|
-
get customer() {
|
|
1136
|
-
const customerAttr = element.getAttribute("customer");
|
|
1137
|
-
if (!customerAttr) {
|
|
1138
|
-
return null;
|
|
1139
|
-
}
|
|
1140
|
-
try {
|
|
1141
|
-
return JSON.parse(customerAttr);
|
|
1142
|
-
} catch (e) {
|
|
1143
|
-
console.warn("[GaitButton] Failed to parse customer attribute:", e);
|
|
1144
|
-
return null;
|
|
1145
|
-
}
|
|
1146
|
-
},
|
|
1147
|
-
get webhook() {
|
|
1148
|
-
const webhookAttr = element.getAttribute("webhook");
|
|
1149
|
-
if (!webhookAttr) {
|
|
1150
|
-
return null;
|
|
1151
|
-
}
|
|
1152
|
-
try {
|
|
1153
|
-
return JSON.parse(webhookAttr);
|
|
1154
|
-
} catch (e) {
|
|
1155
|
-
console.warn("[GaitButton] Failed to parse webhook attribute:", e);
|
|
1156
|
-
return null;
|
|
1157
|
-
}
|
|
1158
|
-
},
|
|
1159
|
-
get merchantStoreUrl() {
|
|
1160
|
-
const url = element.getAttribute("merchant-store-url");
|
|
1161
|
-
if (url && url.trim() !== "") {
|
|
1162
|
-
return url.trim();
|
|
1163
|
-
}
|
|
1164
|
-
if (typeof window !== "undefined" && window.location) {
|
|
1165
|
-
return window.location.origin;
|
|
1166
|
-
}
|
|
1167
|
-
return "";
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
function isValidEmail(email) {
|
|
1172
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1173
|
-
return emailRegex.test(email);
|
|
1174
|
-
}
|
|
1175
|
-
function calculateSplit(totalCost, splitMethod, emailData) {
|
|
1176
|
-
if (emailData.length === 0) {
|
|
1177
|
-
return totalCost;
|
|
1178
|
-
}
|
|
1179
|
-
if (splitMethod === "equally") {
|
|
1180
|
-
const participantCount = emailData.length + 1;
|
|
1181
|
-
const perPerson = totalCost / participantCount;
|
|
1182
|
-
return Math.max(0, totalCost - perPerson * emailData.length);
|
|
1183
|
-
} else if (splitMethod === "by-amount") {
|
|
1184
|
-
let totalSplit = 0;
|
|
1185
|
-
emailData.forEach((item) => {
|
|
1186
|
-
const amount = item.amount || 0;
|
|
1187
|
-
totalSplit += amount;
|
|
1188
|
-
});
|
|
1189
|
-
return Math.max(0, totalCost - totalSplit);
|
|
1190
|
-
}
|
|
1191
|
-
return totalCost;
|
|
1192
|
-
}
|
|
1193
|
-
function calculateEqualSplit(totalCost, participantCount) {
|
|
1194
|
-
return totalCost / participantCount;
|
|
1195
|
-
}
|
|
1196
|
-
function getEmailData(container, splitMethod, totalCost) {
|
|
1197
|
-
const data = [];
|
|
1198
|
-
const items = container.querySelectorAll("[data-email-item]");
|
|
1199
|
-
items.forEach((item) => {
|
|
1200
|
-
const email = item.getAttribute("data-email-item") || "";
|
|
1201
|
-
if (email) {
|
|
1202
|
-
if (splitMethod === "by-amount") {
|
|
1203
|
-
const amountSpan = item.querySelector(".gait-modal-email-item-amount");
|
|
1204
|
-
const amount = amountSpan ? parseFloat(amountSpan.textContent?.replace(/[^\d.]/g, "") || "0") || 0 : 0;
|
|
1205
|
-
data.push({ email, amount });
|
|
1206
|
-
} else {
|
|
1207
|
-
const participantCount = items.length + 1;
|
|
1208
|
-
const amount = calculateEqualSplit(totalCost, participantCount);
|
|
1209
|
-
data.push({ email, amount });
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
});
|
|
1213
|
-
return data;
|
|
1214
|
-
}
|
|
1215
|
-
function getEmailDataCount(container) {
|
|
1216
|
-
const items = container.querySelectorAll("[data-email-item]");
|
|
1217
|
-
const inputs = container.querySelectorAll("[data-email-input]");
|
|
1218
|
-
return items.length + inputs.length;
|
|
1219
|
-
}
|
|
1220
|
-
class SplitPaymentManager {
|
|
1221
|
-
modalElement;
|
|
1222
|
-
totalCost;
|
|
1223
|
-
currentSplitMethod = "equally";
|
|
1224
|
-
dispatchEventFn;
|
|
1225
|
-
isSplitSent = false;
|
|
1226
|
-
isSplitLoading = false;
|
|
1227
|
-
splitFeedbackElement = null;
|
|
1228
|
-
feedbackHandler = null;
|
|
1229
|
-
constructor(modalElement, totalCost, dispatchEventFn) {
|
|
1230
|
-
this.modalElement = modalElement;
|
|
1231
|
-
this.totalCost = totalCost;
|
|
1232
|
-
this.dispatchEventFn = dispatchEventFn;
|
|
1233
|
-
this.setup();
|
|
1234
|
-
this.setupFeedbackListener();
|
|
1235
|
-
}
|
|
1236
|
-
setup() {
|
|
1237
|
-
const splitOptions = this.modalElement.querySelectorAll("[data-split-method]");
|
|
1238
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1239
|
-
const addEmailButton = this.modalElement.querySelector("[data-add-email]");
|
|
1240
|
-
const sendSplitButton = this.modalElement.querySelector("[data-send-split]");
|
|
1241
|
-
if (!emailInputsContainer || !addEmailButton || !sendSplitButton) {
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
splitOptions.forEach((option) => {
|
|
1245
|
-
option.addEventListener("click", () => {
|
|
1246
|
-
if (this.isSplitSent) {
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1249
|
-
splitOptions.forEach((opt) => opt.classList.remove("selected"));
|
|
1250
|
-
option.classList.add("selected");
|
|
1251
|
-
const newSplitMethod = option.getAttribute("data-split-method") || "equally";
|
|
1252
|
-
const oldSplitMethod = this.currentSplitMethod;
|
|
1253
|
-
this.currentSplitMethod = newSplitMethod;
|
|
1254
|
-
if (oldSplitMethod !== newSplitMethod) {
|
|
1255
|
-
this.updateActiveInputsForSplitMethod(oldSplitMethod, newSplitMethod);
|
|
1256
|
-
}
|
|
1257
|
-
this.updateEmailItemsForSplitMethod();
|
|
1258
|
-
this.updateSplitCalculation();
|
|
1259
|
-
});
|
|
1260
|
-
});
|
|
1261
|
-
addEmailButton.addEventListener("click", () => this.addEmailField());
|
|
1262
|
-
emailInputsContainer.addEventListener("input", (e) => {
|
|
1263
|
-
const target = e.target;
|
|
1264
|
-
if (target.hasAttribute("data-email-input")) {
|
|
1265
|
-
this.updateSplitCalculation();
|
|
1266
|
-
}
|
|
1267
|
-
});
|
|
1268
|
-
sendSplitButton.addEventListener("click", () => this.handleSendSplit());
|
|
1269
|
-
this.updateSplitCalculation();
|
|
1270
|
-
}
|
|
1271
|
-
addEmailField() {
|
|
1272
|
-
if (this.isSplitSent) {
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1276
|
-
if (!emailInputsContainer) return;
|
|
1277
|
-
const existingEmptyInput = this.findEmptyInput(emailInputsContainer);
|
|
1278
|
-
if (existingEmptyInput) {
|
|
1279
|
-
if (existingEmptyInput instanceof HTMLInputElement) {
|
|
1280
|
-
existingEmptyInput.focus();
|
|
1281
|
-
} else {
|
|
1282
|
-
const emailInput = existingEmptyInput.querySelector("[data-email-input]");
|
|
1283
|
-
if (emailInput) {
|
|
1284
|
-
emailInput.focus();
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
if (this.currentSplitMethod === "by-amount") {
|
|
1290
|
-
this.addByAmountInput(emailInputsContainer);
|
|
1291
|
-
} else {
|
|
1292
|
-
this.addEquallyInput(emailInputsContainer);
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
findEmptyInput(container) {
|
|
1296
|
-
if (this.currentSplitMethod === "by-amount") {
|
|
1297
|
-
const rows = container.querySelectorAll(".gait-modal-email-amount-row");
|
|
1298
|
-
for (const row of Array.from(rows)) {
|
|
1299
|
-
const emailInput = row.querySelector("[data-email-input]");
|
|
1300
|
-
if (emailInput && !emailInput.value.trim()) {
|
|
1301
|
-
return row;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
} else {
|
|
1305
|
-
const inputs = container.querySelectorAll("[data-email-input]");
|
|
1306
|
-
for (const input of Array.from(inputs)) {
|
|
1307
|
-
if (!input.parentElement?.classList.contains("gait-modal-email-amount-row")) {
|
|
1308
|
-
if (!input.value.trim()) {
|
|
1309
|
-
return input;
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
return null;
|
|
1315
|
-
}
|
|
1316
|
-
removeEmptyInputs(container) {
|
|
1317
|
-
const rows = container.querySelectorAll(".gait-modal-email-amount-row");
|
|
1318
|
-
rows.forEach((row) => {
|
|
1319
|
-
const emailInput = row.querySelector("[data-email-input]");
|
|
1320
|
-
if (emailInput && !emailInput.value.trim()) {
|
|
1321
|
-
container.removeChild(row);
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
const inputs = container.querySelectorAll("[data-email-input]");
|
|
1325
|
-
inputs.forEach((input) => {
|
|
1326
|
-
if (!input.parentElement?.classList.contains("gait-modal-email-amount-row")) {
|
|
1327
|
-
if (!input.value.trim()) {
|
|
1328
|
-
container.removeChild(input);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1332
|
-
}
|
|
1333
|
-
/**
|
|
1334
|
-
* Calculate the remaining amount available for a new split
|
|
1335
|
-
* This is totalCost minus all amounts already assigned to other people
|
|
1336
|
-
*/
|
|
1337
|
-
calculateRemainingAmount(excludeRow) {
|
|
1338
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1339
|
-
if (!emailInputsContainer) return this.totalCost;
|
|
1340
|
-
let totalAssigned = 0;
|
|
1341
|
-
const items = emailInputsContainer.querySelectorAll("[data-email-item]");
|
|
1342
|
-
items.forEach((item) => {
|
|
1343
|
-
const amountSpan = item.querySelector(".gait-modal-email-item-amount");
|
|
1344
|
-
if (amountSpan) {
|
|
1345
|
-
const amountText = amountSpan.textContent || "0.00";
|
|
1346
|
-
const amount = parseFloat(amountText.replace(/[^\d.]/g, "")) || 0;
|
|
1347
|
-
totalAssigned += amount;
|
|
1348
|
-
}
|
|
1349
|
-
});
|
|
1350
|
-
const rows = emailInputsContainer.querySelectorAll(".gait-modal-email-amount-row");
|
|
1351
|
-
rows.forEach((row) => {
|
|
1352
|
-
if (row === excludeRow) return;
|
|
1353
|
-
const amountInput = row.querySelector("[data-email-amount-input]");
|
|
1354
|
-
if (amountInput && amountInput.value) {
|
|
1355
|
-
const amount = parseFloat(amountInput.value) || 0;
|
|
1356
|
-
totalAssigned += amount;
|
|
1357
|
-
}
|
|
1358
|
-
});
|
|
1359
|
-
const remaining = Math.max(0, this.totalCost - totalAssigned);
|
|
1360
|
-
return remaining;
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Update max attribute on all amount inputs
|
|
1364
|
-
*/
|
|
1365
|
-
updateAmountInputMaxValues(excludeRow) {
|
|
1366
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1367
|
-
if (!emailInputsContainer) return;
|
|
1368
|
-
const remaining = this.calculateRemainingAmount(excludeRow);
|
|
1369
|
-
const maxValue = remaining.toFixed(2);
|
|
1370
|
-
const amountInputs = emailInputsContainer.querySelectorAll("[data-email-amount-input]");
|
|
1371
|
-
amountInputs.forEach((input) => {
|
|
1372
|
-
const row = input.closest(".gait-modal-email-amount-row");
|
|
1373
|
-
if (row !== excludeRow) {
|
|
1374
|
-
input.max = maxValue;
|
|
1375
|
-
const currentValue = parseFloat(input.value) || 0;
|
|
1376
|
-
if (currentValue > remaining) {
|
|
1377
|
-
input.value = maxValue;
|
|
1378
|
-
input.style.borderColor = "#ef4444";
|
|
1379
|
-
setTimeout(() => {
|
|
1380
|
-
input.style.borderColor = "";
|
|
1381
|
-
}, 2e3);
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
addByAmountInput(container) {
|
|
1387
|
-
const row = document.createElement("div");
|
|
1388
|
-
row.className = "gait-modal-email-amount-row";
|
|
1389
|
-
const emailInput = document.createElement("input");
|
|
1390
|
-
emailInput.type = "email";
|
|
1391
|
-
emailInput.className = "gait-modal-email-input";
|
|
1392
|
-
emailInput.placeholder = "Email address";
|
|
1393
|
-
emailInput.setAttribute("data-email-input", "");
|
|
1394
|
-
const amountInput = document.createElement("input");
|
|
1395
|
-
amountInput.type = "number";
|
|
1396
|
-
amountInput.className = "gait-modal-email-amount-input";
|
|
1397
|
-
amountInput.placeholder = "0.00";
|
|
1398
|
-
amountInput.step = "0.01";
|
|
1399
|
-
amountInput.min = "0";
|
|
1400
|
-
amountInput.setAttribute("data-email-amount-input", "");
|
|
1401
|
-
const remaining = this.calculateRemainingAmount();
|
|
1402
|
-
amountInput.max = remaining.toFixed(2);
|
|
1403
|
-
const doneButton = document.createElement("button");
|
|
1404
|
-
doneButton.type = "button";
|
|
1405
|
-
doneButton.className = "gait-modal-done-button";
|
|
1406
|
-
doneButton.textContent = "Done";
|
|
1407
|
-
doneButton.setAttribute("data-done-email", "");
|
|
1408
|
-
const handleDone = () => {
|
|
1409
|
-
const email = emailInput.value.trim();
|
|
1410
|
-
let amount = parseFloat(amountInput.value) || 0;
|
|
1411
|
-
const remaining2 = this.calculateRemainingAmount(row);
|
|
1412
|
-
if (amount > remaining2) {
|
|
1413
|
-
amount = remaining2;
|
|
1414
|
-
amountInput.value = remaining2.toFixed(2);
|
|
1415
|
-
amountInput.style.borderColor = "#ef4444";
|
|
1416
|
-
setTimeout(() => {
|
|
1417
|
-
amountInput.style.borderColor = "";
|
|
1418
|
-
}, 2e3);
|
|
1419
|
-
}
|
|
1420
|
-
if (email && isValidEmail(email)) {
|
|
1421
|
-
const item = this.createEmailItem(email, amount);
|
|
1422
|
-
container.replaceChild(item, row);
|
|
1423
|
-
this.updateSplitCalculation();
|
|
1424
|
-
this.updateAmountInputMaxValues();
|
|
1425
|
-
} else if (!email) {
|
|
1426
|
-
container.removeChild(row);
|
|
1427
|
-
this.updateAmountInputMaxValues();
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
amountInput.addEventListener("input", () => {
|
|
1431
|
-
const remaining2 = this.calculateRemainingAmount(row);
|
|
1432
|
-
const currentValue = parseFloat(amountInput.value) || 0;
|
|
1433
|
-
if (currentValue > remaining2) {
|
|
1434
|
-
amountInput.value = remaining2.toFixed(2);
|
|
1435
|
-
amountInput.style.borderColor = "#ef4444";
|
|
1436
|
-
setTimeout(() => {
|
|
1437
|
-
amountInput.style.borderColor = "";
|
|
1438
|
-
}, 2e3);
|
|
1439
|
-
} else {
|
|
1440
|
-
amountInput.style.borderColor = "";
|
|
1441
|
-
}
|
|
1442
|
-
this.updateAmountInputMaxValues(row);
|
|
1443
|
-
});
|
|
1444
|
-
doneButton.addEventListener("click", handleDone);
|
|
1445
|
-
emailInput.addEventListener("blur", () => {
|
|
1446
|
-
if (!emailInput.value.trim() && !amountInput.value) {
|
|
1447
|
-
setTimeout(() => {
|
|
1448
|
-
if (row.parentElement && !emailInput.value.trim()) {
|
|
1449
|
-
container.removeChild(row);
|
|
1450
|
-
}
|
|
1451
|
-
}, 100);
|
|
1452
|
-
}
|
|
1453
|
-
});
|
|
1454
|
-
row.appendChild(emailInput);
|
|
1455
|
-
row.appendChild(amountInput);
|
|
1456
|
-
row.appendChild(doneButton);
|
|
1457
|
-
container.appendChild(row);
|
|
1458
|
-
emailInput.focus();
|
|
1459
|
-
}
|
|
1460
|
-
addEquallyInput(container) {
|
|
1461
|
-
const input = document.createElement("input");
|
|
1462
|
-
input.type = "email";
|
|
1463
|
-
input.className = "gait-modal-email-input";
|
|
1464
|
-
input.placeholder = "Email address";
|
|
1465
|
-
input.setAttribute("data-email-input", "");
|
|
1466
|
-
const handleEmailDone = () => {
|
|
1467
|
-
const email = input.value.trim();
|
|
1468
|
-
if (email && isValidEmail(email)) {
|
|
1469
|
-
this.convertInputToEmailItem(input);
|
|
1470
|
-
this.updateSplitCalculation();
|
|
1471
|
-
}
|
|
1472
|
-
};
|
|
1473
|
-
input.addEventListener("blur", () => {
|
|
1474
|
-
const email = input.value.trim();
|
|
1475
|
-
if (email && isValidEmail(email)) {
|
|
1476
|
-
handleEmailDone();
|
|
1477
|
-
} else if (!email) {
|
|
1478
|
-
setTimeout(() => {
|
|
1479
|
-
if (input.parentElement && !input.value.trim()) {
|
|
1480
|
-
container.removeChild(input);
|
|
1481
|
-
}
|
|
1482
|
-
}, 100);
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
1485
|
-
input.addEventListener("keydown", (e) => {
|
|
1486
|
-
if (e.key === "Enter") {
|
|
1487
|
-
e.preventDefault();
|
|
1488
|
-
handleEmailDone();
|
|
1489
|
-
}
|
|
1490
|
-
});
|
|
1491
|
-
container.appendChild(input);
|
|
1492
|
-
input.focus();
|
|
1493
|
-
}
|
|
1494
|
-
createEmailItem(email, amount = 0, isSent = false) {
|
|
1495
|
-
const item = document.createElement("div");
|
|
1496
|
-
item.className = `gait-modal-email-item${isSent ? " sent" : ""}`;
|
|
1497
|
-
item.setAttribute("data-email-item", email);
|
|
1498
|
-
let displayAmount = amount;
|
|
1499
|
-
if (this.currentSplitMethod === "equally") {
|
|
1500
|
-
const container = this.modalElement.querySelector("[data-email-inputs]");
|
|
1501
|
-
const participantCount = container ? getEmailDataCount(container) + 1 : 2;
|
|
1502
|
-
displayAmount = calculateEqualSplit(this.totalCost, participantCount);
|
|
1503
|
-
}
|
|
1504
|
-
const checkmark = isSent ? `<div class="gait-modal-email-item-sent-check">${checkIcon}</div>` : "";
|
|
1505
|
-
item.innerHTML = `
|
|
1506
|
-
<span class="gait-modal-email-item-email">${email}</span>
|
|
940
|
+
`}function Z(r){const t={};return Array.from(r.attributes).forEach(e=>{e.name.startsWith("data-")&&(t[e.name]=e.value)}),t}function Q(r){const t={};return Array.from(r.attributes).forEach(e=>{t[e.name]=e.value}),t}function X(r){return{get dataId(){return r.getAttribute("data-id")||null},get disabled(){return r.hasAttribute("disabled")&&r.getAttribute("disabled")!=="false"},get size(){return r.getAttribute("size")||"medium"},get items(){const t=r.getAttribute("items");if(!t)return[];try{return JSON.parse(t)}catch(e){return console.warn("[GaitButton] Failed to parse items attribute:",e),[]}},get totalCost(){const t=r.getAttribute("total-cost");if(!t)return 0;const e=parseFloat(t);return(isNaN(e)?0:e)+B.fee},get priceBreakdown(){const t=r.getAttribute("total-cost"),e=parseFloat(t||"0");return[{name:"Subtotal",price:isNaN(e)?0:e},{name:"Split Payment Fee",price:B.fee}]},get customer(){const t=r.getAttribute("customer");if(!t)return null;try{return JSON.parse(t)}catch(e){return console.warn("[GaitButton] Failed to parse customer attribute:",e),null}},get webhook(){const t=r.getAttribute("webhook");if(!t)return null;try{return JSON.parse(t)}catch(e){return console.warn("[GaitButton] Failed to parse webhook attribute:",e),null}},get merchantStoreUrl(){const t=r.getAttribute("merchant-store-url");return t&&t.trim()!==""?t.trim():typeof window<"u"&&window.location?window.location.origin:""}}}function v(r){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(r)}function L(r,t,e){if(e.length===0)return r;if(t==="equally"){const i=e.length+1,a=r/i;return Math.max(0,r-a*e.length)}else if(t==="by-amount"){let i=0;return e.forEach(a=>{const n=a.amount||0;i+=n}),Math.max(0,r-i)}return r}function w(r,t){return r/t}function R(r,t,e){const i=[],a=r.querySelectorAll("[data-email-item]");return a.forEach(n=>{const o=n.getAttribute("data-email-item")||"";if(o)if(t==="by-amount"){const d=n.querySelector(".gait-modal-email-item-amount"),l=d&&parseFloat(d.textContent?.replace(/[^\d.]/g,"")||"0")||0;i.push({email:o,amount:l})}else{const d=a.length+1,l=w(e,d);i.push({email:o,amount:l})}}),i}function z(r){const t=r.querySelectorAll("[data-email-item]"),e=r.querySelectorAll("[data-email-input]");return t.length+e.length}class tt{modalElement;totalCost;currentSplitMethod="equally";dispatchEventFn;isSplitSent=!1;isSplitLoading=!1;splitFeedbackElement=null;feedbackHandler=null;constructor(t,e,i){this.modalElement=t,this.totalCost=e,this.dispatchEventFn=i,this.setup(),this.setupFeedbackListener()}setup(){const t=this.modalElement.querySelectorAll("[data-split-method]"),e=this.modalElement.querySelector("[data-email-inputs]"),i=this.modalElement.querySelector("[data-add-email]"),a=this.modalElement.querySelector("[data-send-split]");!e||!i||!a||(t.forEach(n=>{n.addEventListener("click",()=>{if(this.isSplitSent)return;t.forEach(l=>l.classList.remove("selected")),n.classList.add("selected");const o=n.getAttribute("data-split-method")||"equally",d=this.currentSplitMethod;this.currentSplitMethod=o,d!==o&&this.updateActiveInputsForSplitMethod(d,o),this.updateEmailItemsForSplitMethod(),this.updateSplitCalculation()})}),i.addEventListener("click",()=>this.addEmailField()),e.addEventListener("input",n=>{n.target.hasAttribute("data-email-input")&&this.updateSplitCalculation()}),a.addEventListener("click",()=>this.handleSendSplit()),this.updateSplitCalculation())}addEmailField(){if(this.isSplitSent)return;const t=this.modalElement.querySelector("[data-email-inputs]");if(!t)return;const e=this.findEmptyInput(t);if(e){if(e instanceof HTMLInputElement)e.focus();else{const i=e.querySelector("[data-email-input]");i&&i.focus()}return}this.currentSplitMethod==="by-amount"?this.addByAmountInput(t):this.addEquallyInput(t)}findEmptyInput(t){if(this.currentSplitMethod==="by-amount"){const e=t.querySelectorAll(".gait-modal-email-amount-row");for(const i of Array.from(e)){const a=i.querySelector("[data-email-input]");if(a&&!a.value.trim())return i}}else{const e=t.querySelectorAll("[data-email-input]");for(const i of Array.from(e))if(!i.parentElement?.classList.contains("gait-modal-email-amount-row")&&!i.value.trim())return i}return null}removeEmptyInputs(t){t.querySelectorAll(".gait-modal-email-amount-row").forEach(a=>{const n=a.querySelector("[data-email-input]");n&&!n.value.trim()&&t.removeChild(a)}),t.querySelectorAll("[data-email-input]").forEach(a=>{a.parentElement?.classList.contains("gait-modal-email-amount-row")||a.value.trim()||t.removeChild(a)})}calculateRemainingAmount(t){const e=this.modalElement.querySelector("[data-email-inputs]");if(!e)return this.totalCost;let i=0;return e.querySelectorAll("[data-email-item]").forEach(d=>{const l=d.querySelector(".gait-modal-email-item-amount");if(l){const s=l.textContent||"0.00",m=parseFloat(s.replace(/[^\d.]/g,""))||0;i+=m}}),e.querySelectorAll(".gait-modal-email-amount-row").forEach(d=>{if(d===t)return;const l=d.querySelector("[data-email-amount-input]");if(l&&l.value){const s=parseFloat(l.value)||0;i+=s}}),Math.max(0,this.totalCost-i)}updateAmountInputMaxValues(t){const e=this.modalElement.querySelector("[data-email-inputs]");if(!e)return;const i=this.calculateRemainingAmount(t),a=i.toFixed(2);e.querySelectorAll("[data-email-amount-input]").forEach(o=>{o.closest(".gait-modal-email-amount-row")!==t&&(o.max=a,(parseFloat(o.value)||0)>i&&(o.value=a,o.style.borderColor="#ef4444",setTimeout(()=>{o.style.borderColor=""},2e3)))})}addByAmountInput(t){const e=document.createElement("div");e.className="gait-modal-email-amount-row";const i=document.createElement("input");i.type="email",i.className="gait-modal-email-input",i.placeholder="Email address",i.setAttribute("data-email-input","");const a=document.createElement("input");a.type="number",a.className="gait-modal-email-amount-input",a.placeholder="0.00",a.step="0.01",a.min="0",a.setAttribute("data-email-amount-input","");const n=this.calculateRemainingAmount();a.max=n.toFixed(2);const o=document.createElement("button");o.type="button",o.className="gait-modal-done-button",o.textContent="Done",o.setAttribute("data-done-email","");const d=()=>{const l=i.value.trim();let s=parseFloat(a.value)||0;const m=this.calculateRemainingAmount(e);if(s>m&&(s=m,a.value=m.toFixed(2),a.style.borderColor="#ef4444",setTimeout(()=>{a.style.borderColor=""},2e3)),l&&v(l)){const u=this.createEmailItem(l,s);t.replaceChild(u,e),this.updateSplitCalculation(),this.updateAmountInputMaxValues()}else l||(t.removeChild(e),this.updateAmountInputMaxValues())};a.addEventListener("input",()=>{const l=this.calculateRemainingAmount(e);(parseFloat(a.value)||0)>l?(a.value=l.toFixed(2),a.style.borderColor="#ef4444",setTimeout(()=>{a.style.borderColor=""},2e3)):a.style.borderColor="",this.updateAmountInputMaxValues(e)}),o.addEventListener("click",d),i.addEventListener("blur",()=>{!i.value.trim()&&!a.value&&setTimeout(()=>{e.parentElement&&!i.value.trim()&&t.removeChild(e)},100)}),e.appendChild(i),e.appendChild(a),e.appendChild(o),t.appendChild(e),i.focus()}addEquallyInput(t){const e=document.createElement("input");e.type="email",e.className="gait-modal-email-input",e.placeholder="Email address",e.setAttribute("data-email-input","");const i=()=>{const a=e.value.trim();a&&v(a)&&(this.convertInputToEmailItem(e),this.updateSplitCalculation())};e.addEventListener("blur",()=>{const a=e.value.trim();a&&v(a)?i():a||setTimeout(()=>{e.parentElement&&!e.value.trim()&&t.removeChild(e)},100)}),e.addEventListener("keydown",a=>{a.key==="Enter"&&(a.preventDefault(),i())}),t.appendChild(e),e.focus()}createEmailItem(t,e=0,i=!1){const a=document.createElement("div");a.className=`gait-modal-email-item${i?" sent":""}`,a.setAttribute("data-email-item",t);let n=e;if(this.currentSplitMethod==="equally"){const s=this.modalElement.querySelector("[data-email-inputs]"),m=s?z(s)+1:2;n=w(this.totalCost,m)}const o=i?`<div class="gait-modal-email-item-sent-check">${$}</div>`:"";a.innerHTML=`
|
|
941
|
+
<span class="gait-modal-email-item-email">${t}</span>
|
|
1507
942
|
<div class="gait-modal-email-item-right">
|
|
1508
943
|
<div class="gait-modal-email-item-amount-container">
|
|
1509
|
-
<span class="gait-modal-email-item-amount">R ${
|
|
1510
|
-
${
|
|
944
|
+
<span class="gait-modal-email-item-amount">R ${n.toFixed(2)}</span>
|
|
945
|
+
${o}
|
|
1511
946
|
</div>
|
|
1512
947
|
<div class="gait-modal-email-item-actions">
|
|
1513
|
-
<button class="gait-modal-email-item-edit" data-edit-email="${
|
|
1514
|
-
<button class="gait-modal-email-item-remove" data-remove-email="${
|
|
948
|
+
<button class="gait-modal-email-item-edit" data-edit-email="${t}">Edit</button>
|
|
949
|
+
<button class="gait-modal-email-item-remove" data-remove-email="${t}">Remove</button>
|
|
1515
950
|
</div>
|
|
1516
951
|
</div>
|
|
1517
|
-
`;
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
editButton.addEventListener("click", (e) => {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
|
-
const removeButton = item.querySelector(`[data-remove-email="${email}"]`);
|
|
1527
|
-
if (removeButton) {
|
|
1528
|
-
removeButton.addEventListener("click", (e) => {
|
|
1529
|
-
e.preventDefault();
|
|
1530
|
-
e.stopPropagation();
|
|
1531
|
-
this.removeEmailItem(item);
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
|
-
return item;
|
|
1535
|
-
}
|
|
1536
|
-
convertEmailItemToInput(item) {
|
|
1537
|
-
const container = item.parentElement;
|
|
1538
|
-
if (!container) return;
|
|
1539
|
-
const email = item.getAttribute("data-email-item") || "";
|
|
1540
|
-
if (this.currentSplitMethod === "by-amount") {
|
|
1541
|
-
const row = document.createElement("div");
|
|
1542
|
-
row.className = "gait-modal-email-amount-row";
|
|
1543
|
-
const emailInput = document.createElement("input");
|
|
1544
|
-
emailInput.type = "email";
|
|
1545
|
-
emailInput.className = "gait-modal-email-input";
|
|
1546
|
-
emailInput.placeholder = "Email address";
|
|
1547
|
-
emailInput.setAttribute("data-email-input", "");
|
|
1548
|
-
emailInput.value = email;
|
|
1549
|
-
const amountInput = document.createElement("input");
|
|
1550
|
-
amountInput.type = "number";
|
|
1551
|
-
amountInput.className = "gait-modal-email-amount-input";
|
|
1552
|
-
amountInput.placeholder = "0.00";
|
|
1553
|
-
amountInput.step = "0.01";
|
|
1554
|
-
amountInput.min = "0";
|
|
1555
|
-
amountInput.setAttribute("data-email-amount-input", "");
|
|
1556
|
-
const amountSpan = item.querySelector(".gait-modal-email-item-amount");
|
|
1557
|
-
let initialAmount = 0;
|
|
1558
|
-
if (amountSpan) {
|
|
1559
|
-
const amountText = amountSpan.textContent || "0.00";
|
|
1560
|
-
initialAmount = parseFloat(amountText.replace(/[^\d.]/g, "")) || 0;
|
|
1561
|
-
amountInput.value = initialAmount.toFixed(2);
|
|
1562
|
-
}
|
|
1563
|
-
const remaining = this.calculateRemainingAmount(row) + initialAmount;
|
|
1564
|
-
amountInput.max = remaining.toFixed(2);
|
|
1565
|
-
const doneButton = document.createElement("button");
|
|
1566
|
-
doneButton.type = "button";
|
|
1567
|
-
doneButton.className = "gait-modal-done-button";
|
|
1568
|
-
doneButton.textContent = "Done";
|
|
1569
|
-
doneButton.setAttribute("data-done-email", "");
|
|
1570
|
-
doneButton.addEventListener("click", () => {
|
|
1571
|
-
const emailVal = emailInput.value.trim();
|
|
1572
|
-
let amountVal = parseFloat(amountInput.value) || 0;
|
|
1573
|
-
const remaining2 = this.calculateRemainingAmount(row) + initialAmount;
|
|
1574
|
-
if (amountVal > remaining2) {
|
|
1575
|
-
amountVal = remaining2;
|
|
1576
|
-
amountInput.value = remaining2.toFixed(2);
|
|
1577
|
-
amountInput.style.borderColor = "#ef4444";
|
|
1578
|
-
setTimeout(() => {
|
|
1579
|
-
amountInput.style.borderColor = "";
|
|
1580
|
-
}, 2e3);
|
|
1581
|
-
}
|
|
1582
|
-
if (emailVal && isValidEmail(emailVal)) {
|
|
1583
|
-
const newItem = this.createEmailItem(emailVal, amountVal);
|
|
1584
|
-
if (row.parentElement) {
|
|
1585
|
-
row.parentElement.replaceChild(newItem, row);
|
|
1586
|
-
}
|
|
1587
|
-
this.updateSplitCalculation();
|
|
1588
|
-
this.updateAmountInputMaxValues();
|
|
1589
|
-
} else if (!emailVal) {
|
|
1590
|
-
if (row.parentElement) {
|
|
1591
|
-
row.parentElement.removeChild(row);
|
|
1592
|
-
}
|
|
1593
|
-
this.updateAmountInputMaxValues();
|
|
1594
|
-
}
|
|
1595
|
-
});
|
|
1596
|
-
amountInput.addEventListener("input", () => {
|
|
1597
|
-
const remaining2 = this.calculateRemainingAmount(row) + initialAmount;
|
|
1598
|
-
const currentValue = parseFloat(amountInput.value) || 0;
|
|
1599
|
-
if (currentValue > remaining2) {
|
|
1600
|
-
amountInput.value = remaining2.toFixed(2);
|
|
1601
|
-
amountInput.style.borderColor = "#ef4444";
|
|
1602
|
-
setTimeout(() => {
|
|
1603
|
-
amountInput.style.borderColor = "";
|
|
1604
|
-
}, 2e3);
|
|
1605
|
-
} else {
|
|
1606
|
-
amountInput.style.borderColor = "";
|
|
1607
|
-
}
|
|
1608
|
-
this.updateAmountInputMaxValues(row);
|
|
1609
|
-
});
|
|
1610
|
-
row.appendChild(emailInput);
|
|
1611
|
-
row.appendChild(amountInput);
|
|
1612
|
-
row.appendChild(doneButton);
|
|
1613
|
-
if (item.parentElement) {
|
|
1614
|
-
item.parentElement.replaceChild(row, item);
|
|
1615
|
-
}
|
|
1616
|
-
emailInput.focus();
|
|
1617
|
-
emailInput.select();
|
|
1618
|
-
} else {
|
|
1619
|
-
const input = document.createElement("input");
|
|
1620
|
-
input.type = "email";
|
|
1621
|
-
input.className = "gait-modal-email-input";
|
|
1622
|
-
input.placeholder = "Email address";
|
|
1623
|
-
input.setAttribute("data-email-input", "");
|
|
1624
|
-
input.value = email;
|
|
1625
|
-
const handleEmailDone = () => {
|
|
1626
|
-
const emailVal = input.value.trim();
|
|
1627
|
-
if (emailVal && isValidEmail(emailVal)) {
|
|
1628
|
-
this.convertInputToEmailItem(input);
|
|
1629
|
-
this.updateSplitCalculation();
|
|
1630
|
-
} else if (!emailVal) {
|
|
1631
|
-
setTimeout(() => {
|
|
1632
|
-
if (input.parentElement && !input.value.trim()) {
|
|
1633
|
-
input.parentElement.removeChild(input);
|
|
1634
|
-
}
|
|
1635
|
-
}, 100);
|
|
1636
|
-
}
|
|
1637
|
-
};
|
|
1638
|
-
input.addEventListener("blur", handleEmailDone);
|
|
1639
|
-
input.addEventListener("keydown", (e) => {
|
|
1640
|
-
if (e.key === "Enter") {
|
|
1641
|
-
e.preventDefault();
|
|
1642
|
-
handleEmailDone();
|
|
1643
|
-
}
|
|
1644
|
-
});
|
|
1645
|
-
if (item.parentElement) {
|
|
1646
|
-
item.parentElement.replaceChild(input, item);
|
|
1647
|
-
}
|
|
1648
|
-
input.focus();
|
|
1649
|
-
input.select();
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
convertInputToEmailItem(input) {
|
|
1653
|
-
const email = input.value.trim();
|
|
1654
|
-
if (!email || !isValidEmail(email)) {
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
const container = input.parentElement;
|
|
1658
|
-
if (!container) return;
|
|
1659
|
-
const participantCount = getEmailDataCount(container) + 1;
|
|
1660
|
-
const perPersonAmount = this.currentSplitMethod === "equally" ? calculateEqualSplit(this.totalCost, participantCount) : 0;
|
|
1661
|
-
const item = this.createEmailItem(email, perPersonAmount);
|
|
1662
|
-
container.replaceChild(item, input);
|
|
1663
|
-
}
|
|
1664
|
-
removeEmailItem(item) {
|
|
1665
|
-
const container = item.parentElement;
|
|
1666
|
-
if (container) {
|
|
1667
|
-
container.removeChild(item);
|
|
1668
|
-
this.updateSplitCalculation();
|
|
1669
|
-
this.updateAmountInputMaxValues();
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
updateActiveInputsForSplitMethod(oldMethod, newMethod) {
|
|
1673
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1674
|
-
if (!emailInputsContainer) return;
|
|
1675
|
-
this.removeEmptyInputs(emailInputsContainer);
|
|
1676
|
-
if (oldMethod === "equally" && newMethod === "by-amount") {
|
|
1677
|
-
const emailInputs = Array.from(emailInputsContainer.querySelectorAll("[data-email-input]")).filter((input) => !input.parentElement?.classList.contains("gait-modal-email-amount-row"));
|
|
1678
|
-
emailInputs.forEach((input) => {
|
|
1679
|
-
const email = input.value.trim();
|
|
1680
|
-
const container = input.parentElement;
|
|
1681
|
-
if (!container) return;
|
|
1682
|
-
const row = document.createElement("div");
|
|
1683
|
-
row.className = "gait-modal-email-amount-row";
|
|
1684
|
-
const emailInput = document.createElement("input");
|
|
1685
|
-
emailInput.type = "email";
|
|
1686
|
-
emailInput.className = "gait-modal-email-input";
|
|
1687
|
-
emailInput.placeholder = "Email address";
|
|
1688
|
-
emailInput.setAttribute("data-email-input", "");
|
|
1689
|
-
emailInput.value = email;
|
|
1690
|
-
const amountInput = document.createElement("input");
|
|
1691
|
-
amountInput.type = "number";
|
|
1692
|
-
amountInput.className = "gait-modal-email-amount-input";
|
|
1693
|
-
amountInput.placeholder = "0.00";
|
|
1694
|
-
amountInput.step = "0.01";
|
|
1695
|
-
amountInput.min = "0";
|
|
1696
|
-
amountInput.setAttribute("data-email-amount-input", "");
|
|
1697
|
-
const remaining = this.calculateRemainingAmount(row);
|
|
1698
|
-
amountInput.max = remaining.toFixed(2);
|
|
1699
|
-
const doneButton = document.createElement("button");
|
|
1700
|
-
doneButton.type = "button";
|
|
1701
|
-
doneButton.className = "gait-modal-done-button";
|
|
1702
|
-
doneButton.textContent = "Done";
|
|
1703
|
-
doneButton.setAttribute("data-done-email", "");
|
|
1704
|
-
doneButton.addEventListener("click", () => {
|
|
1705
|
-
const emailVal = emailInput.value.trim();
|
|
1706
|
-
let amountVal = parseFloat(amountInput.value) || 0;
|
|
1707
|
-
const remaining2 = this.calculateRemainingAmount(row);
|
|
1708
|
-
if (amountVal > remaining2) {
|
|
1709
|
-
amountVal = remaining2;
|
|
1710
|
-
amountInput.value = remaining2.toFixed(2);
|
|
1711
|
-
amountInput.style.borderColor = "#ef4444";
|
|
1712
|
-
setTimeout(() => {
|
|
1713
|
-
amountInput.style.borderColor = "";
|
|
1714
|
-
}, 2e3);
|
|
1715
|
-
}
|
|
1716
|
-
if (emailVal && isValidEmail(emailVal)) {
|
|
1717
|
-
const item = this.createEmailItem(emailVal, amountVal);
|
|
1718
|
-
container.replaceChild(item, row);
|
|
1719
|
-
this.updateSplitCalculation();
|
|
1720
|
-
this.updateAmountInputMaxValues();
|
|
1721
|
-
}
|
|
1722
|
-
});
|
|
1723
|
-
amountInput.addEventListener("input", () => {
|
|
1724
|
-
const remaining2 = this.calculateRemainingAmount(row);
|
|
1725
|
-
const currentValue = parseFloat(amountInput.value) || 0;
|
|
1726
|
-
if (currentValue > remaining2) {
|
|
1727
|
-
amountInput.value = remaining2.toFixed(2);
|
|
1728
|
-
amountInput.style.borderColor = "#ef4444";
|
|
1729
|
-
setTimeout(() => {
|
|
1730
|
-
amountInput.style.borderColor = "";
|
|
1731
|
-
}, 2e3);
|
|
1732
|
-
} else {
|
|
1733
|
-
amountInput.style.borderColor = "";
|
|
1734
|
-
}
|
|
1735
|
-
this.updateAmountInputMaxValues(row);
|
|
1736
|
-
});
|
|
1737
|
-
row.appendChild(emailInput);
|
|
1738
|
-
row.appendChild(amountInput);
|
|
1739
|
-
row.appendChild(doneButton);
|
|
1740
|
-
container.replaceChild(row, input);
|
|
1741
|
-
emailInput.focus();
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
if (oldMethod === "by-amount" && newMethod === "equally") {
|
|
1745
|
-
const rows = emailInputsContainer.querySelectorAll(".gait-modal-email-amount-row");
|
|
1746
|
-
rows.forEach((row) => {
|
|
1747
|
-
const emailInput = row.querySelector("[data-email-input]");
|
|
1748
|
-
if (!emailInput) return;
|
|
1749
|
-
const email = emailInput.value.trim();
|
|
1750
|
-
const container = row.parentElement;
|
|
1751
|
-
if (!container) return;
|
|
1752
|
-
const input = document.createElement("input");
|
|
1753
|
-
input.type = "email";
|
|
1754
|
-
input.className = "gait-modal-email-input";
|
|
1755
|
-
input.placeholder = "Email address";
|
|
1756
|
-
input.setAttribute("data-email-input", "");
|
|
1757
|
-
input.value = email;
|
|
1758
|
-
const handleEmailDone = () => {
|
|
1759
|
-
const emailVal = input.value.trim();
|
|
1760
|
-
if (emailVal && isValidEmail(emailVal)) {
|
|
1761
|
-
this.convertInputToEmailItem(input);
|
|
1762
|
-
this.updateSplitCalculation();
|
|
1763
|
-
}
|
|
1764
|
-
};
|
|
1765
|
-
input.addEventListener("blur", handleEmailDone);
|
|
1766
|
-
input.addEventListener("keydown", (e) => {
|
|
1767
|
-
if (e.key === "Enter") {
|
|
1768
|
-
e.preventDefault();
|
|
1769
|
-
handleEmailDone();
|
|
1770
|
-
}
|
|
1771
|
-
});
|
|
1772
|
-
container.replaceChild(input, row);
|
|
1773
|
-
input.focus();
|
|
1774
|
-
});
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
updateEmailItemsForSplitMethod() {
|
|
1778
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1779
|
-
if (!emailInputsContainer) return;
|
|
1780
|
-
const items = emailInputsContainer.querySelectorAll("[data-email-item]");
|
|
1781
|
-
const participantCount = items.length + 1;
|
|
1782
|
-
items.forEach((item) => {
|
|
1783
|
-
const rightColumn = item.querySelector(".gait-modal-email-item-right");
|
|
1784
|
-
if (!rightColumn) return;
|
|
1785
|
-
const amountSpan = rightColumn.querySelector(".gait-modal-email-item-amount");
|
|
1786
|
-
if (!amountSpan) return;
|
|
1787
|
-
if (this.currentSplitMethod === "equally") {
|
|
1788
|
-
const perPerson = calculateEqualSplit(this.totalCost, participantCount);
|
|
1789
|
-
amountSpan.textContent = `R ${perPerson.toFixed(2)}`;
|
|
1790
|
-
} else if (this.currentSplitMethod === "by-amount") {
|
|
1791
|
-
const currentAmountText = amountSpan.textContent || "R 0.00";
|
|
1792
|
-
const currentAmount = parseFloat(currentAmountText.replace(/[^\d.]/g, "")) || 0;
|
|
1793
|
-
amountSpan.textContent = `R ${currentAmount.toFixed(2)}`;
|
|
1794
|
-
}
|
|
1795
|
-
});
|
|
1796
|
-
}
|
|
1797
|
-
updateSplitCalculation() {
|
|
1798
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1799
|
-
const confirmAmountDisplay = this.modalElement.querySelector("[data-confirm-amount]");
|
|
1800
|
-
if (!emailInputsContainer || !confirmAmountDisplay) {
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
const emailData = getEmailData(emailInputsContainer, this.currentSplitMethod, this.totalCost);
|
|
1804
|
-
const emailInputs = emailInputsContainer.querySelectorAll("[data-email-input]");
|
|
1805
|
-
emailInputs.forEach((input) => {
|
|
1806
|
-
const email = input.value.trim();
|
|
1807
|
-
if (email && isValidEmail(email)) {
|
|
1808
|
-
const participantCount = emailData.length + emailInputs.length + 1;
|
|
1809
|
-
const amount = this.currentSplitMethod === "equally" ? calculateEqualSplit(this.totalCost, participantCount) : 0;
|
|
1810
|
-
emailData.push({ email, amount });
|
|
1811
|
-
}
|
|
1812
|
-
});
|
|
1813
|
-
const yourAmount = emailData.length > 0 ? calculateSplit(this.totalCost, this.currentSplitMethod, emailData) : this.totalCost;
|
|
1814
|
-
confirmAmountDisplay.textContent = `R ${yourAmount.toFixed(2)}`;
|
|
1815
|
-
if (this.currentSplitMethod === "by-amount") {
|
|
1816
|
-
this.updateAmountInputMaxValues();
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
handleSendSplit() {
|
|
1820
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1821
|
-
const sendSplitButton = this.modalElement.querySelector("[data-send-split]");
|
|
1822
|
-
if (!emailInputsContainer || !sendSplitButton) return;
|
|
1823
|
-
const emailData = getEmailData(emailInputsContainer, this.currentSplitMethod, this.totalCost);
|
|
1824
|
-
if (emailData.length === 0) {
|
|
1825
|
-
this.showFeedback("Please add at least one email address", "error");
|
|
1826
|
-
return;
|
|
1827
|
-
}
|
|
1828
|
-
if (this.isSplitSent || this.isSplitLoading) {
|
|
1829
|
-
return;
|
|
1830
|
-
}
|
|
1831
|
-
this.isSplitLoading = true;
|
|
1832
|
-
sendSplitButton.disabled = true;
|
|
1833
|
-
sendSplitButton.textContent = "Sending...";
|
|
1834
|
-
this.showFeedback("Sending split payment...", "loading");
|
|
1835
|
-
const yourAmount = calculateSplit(this.totalCost, this.currentSplitMethod, emailData);
|
|
1836
|
-
this.dispatchEventFn(new CustomEvent("gait-split-sent", {
|
|
1837
|
-
detail: {
|
|
1838
|
-
splitMethod: this.currentSplitMethod,
|
|
1839
|
-
emails: emailData.map((d) => d.email),
|
|
1840
|
-
emailData,
|
|
1841
|
-
totalCost: this.totalCost,
|
|
1842
|
-
yourAmount,
|
|
1843
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1844
|
-
},
|
|
1845
|
-
bubbles: true,
|
|
1846
|
-
composed: true
|
|
1847
|
-
}));
|
|
1848
|
-
}
|
|
1849
|
-
setupFeedbackListener() {
|
|
1850
|
-
this.feedbackHandler = (event) => {
|
|
1851
|
-
const customEvent = event;
|
|
1852
|
-
if (customEvent.detail) {
|
|
1853
|
-
this.handleSplitFeedback(customEvent.detail);
|
|
1854
|
-
}
|
|
1855
|
-
};
|
|
1856
|
-
document.addEventListener("gait-split-feedback", this.feedbackHandler);
|
|
1857
|
-
}
|
|
1858
|
-
// Cleanup method (can be called when modal is closed)
|
|
1859
|
-
cleanup() {
|
|
1860
|
-
if (this.feedbackHandler) {
|
|
1861
|
-
document.removeEventListener("gait-split-feedback", this.feedbackHandler);
|
|
1862
|
-
this.feedbackHandler = null;
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
handleSplitFeedback(feedback) {
|
|
1866
|
-
this.isSplitLoading = false;
|
|
1867
|
-
const sendSplitButton = this.modalElement.querySelector("[data-send-split]");
|
|
1868
|
-
if (feedback.success) {
|
|
1869
|
-
this.isSplitSent = true;
|
|
1870
|
-
this.markEmailsAsSent();
|
|
1871
|
-
this.disableSplitOptions();
|
|
1872
|
-
if (sendSplitButton) {
|
|
1873
|
-
sendSplitButton.disabled = true;
|
|
1874
|
-
sendSplitButton.textContent = "Split sent!";
|
|
1875
|
-
}
|
|
1876
|
-
this.showFeedback(feedback.message || "Split payment sent successfully", "success");
|
|
1877
|
-
} else {
|
|
1878
|
-
if (sendSplitButton) {
|
|
1879
|
-
sendSplitButton.disabled = false;
|
|
1880
|
-
sendSplitButton.textContent = "Send split";
|
|
1881
|
-
}
|
|
1882
|
-
this.showFeedback(feedback.message || feedback.error || "Failed to send split payment", "error");
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
markEmailsAsSent() {
|
|
1886
|
-
const emailInputsContainer = this.modalElement.querySelector("[data-email-inputs]");
|
|
1887
|
-
if (!emailInputsContainer) return;
|
|
1888
|
-
const emailItems = emailInputsContainer.querySelectorAll("[data-email-item]");
|
|
1889
|
-
emailItems.forEach((item) => {
|
|
1890
|
-
if (!item.classList.contains("sent")) {
|
|
1891
|
-
item.classList.add("sent");
|
|
1892
|
-
const rightColumn = item.querySelector(".gait-modal-email-item-right");
|
|
1893
|
-
if (rightColumn) {
|
|
1894
|
-
let amountContainer = rightColumn.querySelector(".gait-modal-email-item-amount-container");
|
|
1895
|
-
const amountSpan = rightColumn.querySelector(".gait-modal-email-item-amount");
|
|
1896
|
-
if (!amountContainer && amountSpan) {
|
|
1897
|
-
amountContainer = document.createElement("div");
|
|
1898
|
-
amountContainer.className = "gait-modal-email-item-amount-container";
|
|
1899
|
-
amountSpan.parentElement?.replaceChild(amountContainer, amountSpan);
|
|
1900
|
-
amountContainer.appendChild(amountSpan);
|
|
1901
|
-
}
|
|
1902
|
-
if (amountContainer && !amountContainer.querySelector(".gait-modal-email-item-sent-check")) {
|
|
1903
|
-
const checkmark = document.createElement("div");
|
|
1904
|
-
checkmark.className = "gait-modal-email-item-sent-check";
|
|
1905
|
-
checkmark.innerHTML = checkIcon;
|
|
1906
|
-
amountContainer.appendChild(checkmark);
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
const actions = item.querySelector(".gait-modal-email-item-actions");
|
|
1910
|
-
if (actions) {
|
|
1911
|
-
actions.style.display = "none";
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
});
|
|
1915
|
-
}
|
|
1916
|
-
showFeedback(message, type) {
|
|
1917
|
-
if (this.splitFeedbackElement) {
|
|
1918
|
-
this.splitFeedbackElement.remove();
|
|
1919
|
-
this.splitFeedbackElement = null;
|
|
1920
|
-
}
|
|
1921
|
-
const feedback = document.createElement("div");
|
|
1922
|
-
feedback.className = `gait-modal-feedback gait-modal-feedback-${type}`;
|
|
1923
|
-
feedback.setAttribute("data-split-feedback", "");
|
|
1924
|
-
const icon = type === "success" ? checkIcon : type === "error" ? crossIcon : spinnerIcon;
|
|
1925
|
-
feedback.innerHTML = `
|
|
1926
|
-
<span class="gait-modal-feedback-icon">${icon}</span>
|
|
1927
|
-
<span class="gait-modal-feedback-message">${message}</span>
|
|
1928
|
-
`;
|
|
1929
|
-
const splitSection = this.modalElement.querySelector(".gait-modal-split-section");
|
|
1930
|
-
if (splitSection) {
|
|
1931
|
-
splitSection.insertBefore(feedback, splitSection.firstChild);
|
|
1932
|
-
this.splitFeedbackElement = feedback;
|
|
1933
|
-
if (type !== "loading") {
|
|
1934
|
-
setTimeout(() => {
|
|
1935
|
-
if (this.splitFeedbackElement === feedback) {
|
|
1936
|
-
feedback.style.opacity = "0";
|
|
1937
|
-
setTimeout(() => {
|
|
1938
|
-
if (feedback.parentElement) {
|
|
1939
|
-
feedback.remove();
|
|
1940
|
-
}
|
|
1941
|
-
if (this.splitFeedbackElement === feedback) {
|
|
1942
|
-
this.splitFeedbackElement = null;
|
|
1943
|
-
}
|
|
1944
|
-
}, 300);
|
|
1945
|
-
}
|
|
1946
|
-
}, 5e3);
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
disableSplitOptions() {
|
|
1951
|
-
const splitOptions = this.modalElement.querySelectorAll("[data-split-method]");
|
|
1952
|
-
splitOptions.forEach((option) => {
|
|
1953
|
-
const optionElement = option;
|
|
1954
|
-
optionElement.style.pointerEvents = "none";
|
|
1955
|
-
optionElement.style.opacity = "0.5";
|
|
1956
|
-
optionElement.style.cursor = "not-allowed";
|
|
1957
|
-
optionElement.setAttribute("aria-disabled", "true");
|
|
1958
|
-
});
|
|
1959
|
-
const addEmailButton = this.modalElement.querySelector("[data-add-email]");
|
|
1960
|
-
if (addEmailButton) {
|
|
1961
|
-
addEmailButton.style.pointerEvents = "none";
|
|
1962
|
-
addEmailButton.style.opacity = "0.5";
|
|
1963
|
-
addEmailButton.style.cursor = "not-allowed";
|
|
1964
|
-
addEmailButton.setAttribute("aria-disabled", "true");
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
async function sendSplitPayment(request, timeout = 2e3) {
|
|
1969
|
-
console.log("[SplitPaymentAPI] Sending split payment request:", request);
|
|
1970
|
-
const apiUrl = "https://api.usegait.cloud/checkout";
|
|
1971
|
-
try {
|
|
1972
|
-
const response = await fetch(`${apiUrl}/v1/payments`, {
|
|
1973
|
-
method: "POST",
|
|
1974
|
-
headers: {
|
|
1975
|
-
"Content-Type": "application/json"
|
|
1976
|
-
},
|
|
1977
|
-
body: JSON.stringify(request),
|
|
1978
|
-
signal: AbortSignal.timeout(timeout)
|
|
1979
|
-
});
|
|
1980
|
-
const apiResponse = await response.json().catch(() => ({
|
|
1981
|
-
success: false,
|
|
1982
|
-
error: "Failed to parse API response"
|
|
1983
|
-
}));
|
|
1984
|
-
if (apiResponse.success) {
|
|
1985
|
-
const successResponse = {
|
|
1986
|
-
success: true,
|
|
1987
|
-
message: apiResponse.message,
|
|
1988
|
-
data: apiResponse.data
|
|
1989
|
-
};
|
|
1990
|
-
console.log("[SplitPaymentAPI] Split payment sent successfully:", successResponse);
|
|
1991
|
-
return successResponse;
|
|
1992
|
-
} else {
|
|
1993
|
-
const errorResponse = {
|
|
1994
|
-
success: false,
|
|
1995
|
-
message: apiResponse.message,
|
|
1996
|
-
error: apiResponse.error
|
|
1997
|
-
};
|
|
1998
|
-
console.error("[SplitPaymentAPI] Split payment failed:", errorResponse);
|
|
1999
|
-
return errorResponse;
|
|
2000
|
-
}
|
|
2001
|
-
} catch (error) {
|
|
2002
|
-
const errorResponse = {
|
|
2003
|
-
success: false,
|
|
2004
|
-
message: "Failed to send split payment",
|
|
2005
|
-
error: error instanceof Error ? error.message : "Network error or server unavailable"
|
|
2006
|
-
};
|
|
2007
|
-
console.error("[SplitPaymentAPI] Split payment failed:", errorResponse, error);
|
|
2008
|
-
return errorResponse;
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
let random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
|
|
2012
|
-
let customRandom = (alphabet, defaultSize, getRandom) => {
|
|
2013
|
-
let mask = (2 << Math.log2(alphabet.length - 1)) - 1;
|
|
2014
|
-
let step = -~(1.6 * mask * defaultSize / alphabet.length);
|
|
2015
|
-
return (size = defaultSize) => {
|
|
2016
|
-
let id = "";
|
|
2017
|
-
while (true) {
|
|
2018
|
-
let bytes = getRandom(step);
|
|
2019
|
-
let j = step | 0;
|
|
2020
|
-
while (j--) {
|
|
2021
|
-
id += alphabet[bytes[j] & mask] || "";
|
|
2022
|
-
if (id.length >= size) return id;
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
};
|
|
2026
|
-
};
|
|
2027
|
-
let customAlphabet = (alphabet, size = 21) => customRandom(alphabet, size | 0, random);
|
|
2028
|
-
const config = {
|
|
2029
|
-
apiGateway: {
|
|
2030
|
-
MERCHANT_SERVICE_API: "https://api.usegait.cloud/merchant",
|
|
2031
|
-
CHECKOUT_SERVICE_API: "https://api.usegait.cloud/checkout"
|
|
2032
|
-
},
|
|
2033
|
-
paylink: {
|
|
2034
|
-
DOMAIN: "https://checkout.usegait.cloud"
|
|
2035
|
-
}
|
|
2036
|
-
};
|
|
2037
|
-
const domain$1 = config.apiGateway.MERCHANT_SERVICE_API;
|
|
2038
|
-
class MerchantService {
|
|
2039
|
-
static async getMerchant(checkoutId) {
|
|
2040
|
-
try {
|
|
2041
|
-
const resp = await fetch(`${domain$1}/v1/merchant/identity/${checkoutId}`, {
|
|
2042
|
-
"method": "GET",
|
|
2043
|
-
headers: {
|
|
2044
|
-
"Content-Type": "application/json"
|
|
2045
|
-
}
|
|
2046
|
-
});
|
|
2047
|
-
return await resp.json();
|
|
2048
|
-
} catch (error) {
|
|
2049
|
-
return error;
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
const domain = config.apiGateway.CHECKOUT_SERVICE_API;
|
|
2054
|
-
class PaymentsService {
|
|
2055
|
-
static async getPayment(paymentId) {
|
|
2056
|
-
try {
|
|
2057
|
-
const resp = await fetch(`${domain}/v1/payments/${paymentId}`, {
|
|
2058
|
-
"method": "GET",
|
|
2059
|
-
headers: {
|
|
2060
|
-
"Content-Type": "application/json"
|
|
2061
|
-
}
|
|
2062
|
-
});
|
|
2063
|
-
return await resp.json();
|
|
2064
|
-
} catch (error) {
|
|
2065
|
-
return error;
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
static async updatePaymentStatus(merchantId, splitId, status) {
|
|
2069
|
-
try {
|
|
2070
|
-
const resp = await fetch(`${domain}/v1/payments/merchants/${merchantId}/${splitId}/status`, {
|
|
2071
|
-
"method": "PUT",
|
|
2072
|
-
headers: {
|
|
2073
|
-
"Content-Type": "application/json"
|
|
2074
|
-
},
|
|
2075
|
-
body: JSON.stringify({
|
|
2076
|
-
status
|
|
2077
|
-
})
|
|
2078
|
-
});
|
|
2079
|
-
return await resp.json();
|
|
2080
|
-
} catch (error) {
|
|
2081
|
-
return error;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
static async createWebhook(merchantId, splitId, webhook) {
|
|
2085
|
-
try {
|
|
2086
|
-
const resp = await fetch(`${domain}/v1/webhooks/${merchantId}/${splitId}`, {
|
|
2087
|
-
"method": "POST",
|
|
2088
|
-
headers: {
|
|
2089
|
-
"Content-Type": "application/json"
|
|
2090
|
-
},
|
|
2091
|
-
body: JSON.stringify(webhook)
|
|
2092
|
-
});
|
|
2093
|
-
return await resp.json();
|
|
2094
|
-
} catch (error) {
|
|
2095
|
-
return error;
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
static async updatePayment(merchantId, paymentId, splitId) {
|
|
2099
|
-
try {
|
|
2100
|
-
const resp = await fetch(`${domain}/v1/payments/merchants/${merchantId}/${splitId}`, {
|
|
2101
|
-
"method": "PUT",
|
|
2102
|
-
headers: {
|
|
2103
|
-
"Content-Type": "application/json"
|
|
2104
|
-
},
|
|
2105
|
-
body: JSON.stringify({
|
|
2106
|
-
paymentId
|
|
2107
|
-
})
|
|
2108
|
-
});
|
|
2109
|
-
return await resp.json();
|
|
2110
|
-
} catch (error) {
|
|
2111
|
-
return error;
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
function devLog(...args) {
|
|
2116
|
-
{
|
|
2117
|
-
console.log(...args);
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
const _GAIT_BUTTON_VERSION_ENV = typeof process !== "undefined" && process.env ? "0.1.0" : void 0;
|
|
2121
|
-
const GAIT_BUTTON_VERSION = _GAIT_BUTTON_VERSION_ENV || "0.1.4";
|
|
2122
|
-
class GaitButton extends HTMLElement {
|
|
2123
|
-
buttonElement = null;
|
|
2124
|
-
hasRendered = false;
|
|
2125
|
-
mutationObserver = null;
|
|
2126
|
-
modalElement = null;
|
|
2127
|
-
modalId;
|
|
2128
|
-
splitPaymentManager = null;
|
|
2129
|
-
attributeGetters;
|
|
2130
|
-
storedSplitPaymentRequest = null;
|
|
2131
|
-
merchantId = null;
|
|
2132
|
-
paymentProvider = "stripe";
|
|
2133
|
-
// Expose version as static and instance properties for easy access
|
|
2134
|
-
static version = GAIT_BUTTON_VERSION;
|
|
2135
|
-
version = GAIT_BUTTON_VERSION;
|
|
2136
|
-
// Observed attributes for reactivity (label, theme, brand-color removed - fixed to Gait / #4147BF)
|
|
2137
|
-
static get observedAttributes() {
|
|
2138
|
-
return ["data-id", "disabled", "size", "style", "items", "price-breakdown", "total-cost", "webhook", "merchant-store-url"];
|
|
2139
|
-
}
|
|
2140
|
-
constructor() {
|
|
2141
|
-
super();
|
|
2142
|
-
this.attachShadow({ mode: "open" });
|
|
2143
|
-
this.modalId = `gait-modal-${Math.random().toString(36).substr(2, 9)}`;
|
|
2144
|
-
this.attributeGetters = createAttributeGetters(this);
|
|
2145
|
-
}
|
|
2146
|
-
// Lifecycle: Called when element is connected to DOM
|
|
2147
|
-
connectedCallback() {
|
|
2148
|
-
if (!this.hasRendered && this.shadowRoot) {
|
|
2149
|
-
this.render();
|
|
2150
|
-
}
|
|
2151
|
-
this.setupEventListeners();
|
|
2152
|
-
this.setupMutationObserver();
|
|
2153
|
-
this.setupApiListeners();
|
|
2154
|
-
setTimeout(() => this.processData(), 0);
|
|
2155
|
-
}
|
|
2156
|
-
// Lifecycle: Called when element is disconnected from DOM
|
|
2157
|
-
disconnectedCallback() {
|
|
2158
|
-
if (this.buttonElement) {
|
|
2159
|
-
this.buttonElement.removeEventListener("click", this.handleClick);
|
|
2160
|
-
}
|
|
2161
|
-
if (this.mutationObserver) {
|
|
2162
|
-
this.mutationObserver.disconnect();
|
|
2163
|
-
this.mutationObserver = null;
|
|
2164
|
-
}
|
|
2165
|
-
this.closeModal();
|
|
2166
|
-
if (this.splitPaymentManager) {
|
|
2167
|
-
this.splitPaymentManager = null;
|
|
2168
|
-
}
|
|
2169
|
-
this.removeEventListener("gait-split-sent", this.handleSplitSent);
|
|
2170
|
-
}
|
|
2171
|
-
// Lifecycle: Called when observed attributes change
|
|
2172
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
2173
|
-
if (oldValue === newValue) {
|
|
2174
|
-
return;
|
|
2175
|
-
}
|
|
2176
|
-
if (!this.isConnected || !this.shadowRoot) {
|
|
2177
|
-
return;
|
|
2178
|
-
}
|
|
2179
|
-
if (name === "style" && this.buttonElement && this.hasRendered) {
|
|
2180
|
-
const sanitizedStyle = (newValue || "").replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
|
|
2181
|
-
this.buttonElement.setAttribute("style", sanitizedStyle);
|
|
2182
|
-
return;
|
|
2183
|
-
}
|
|
2184
|
-
if (this.hasRendered) {
|
|
2185
|
-
this.render();
|
|
2186
|
-
this.setupEventListeners();
|
|
2187
|
-
}
|
|
2188
|
-
if (name === "data-id" || name.startsWith("data-")) {
|
|
2189
|
-
if (name === "data-id") {
|
|
2190
|
-
this.merchantId = null;
|
|
2191
|
-
}
|
|
2192
|
-
setTimeout(() => this.processData(), 0);
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
// Getters for attributes with defaults (using attribute getters)
|
|
2196
|
-
get dataId() {
|
|
2197
|
-
return this.attributeGetters.dataId;
|
|
2198
|
-
}
|
|
2199
|
-
get disabled() {
|
|
2200
|
-
return this.attributeGetters.disabled;
|
|
2201
|
-
}
|
|
2202
|
-
get size() {
|
|
2203
|
-
return this.attributeGetters.size;
|
|
2204
|
-
}
|
|
2205
|
-
get items() {
|
|
2206
|
-
return this.attributeGetters.items;
|
|
2207
|
-
}
|
|
2208
|
-
get priceBreakdown() {
|
|
2209
|
-
return this.attributeGetters.priceBreakdown;
|
|
2210
|
-
}
|
|
2211
|
-
get totalCost() {
|
|
2212
|
-
return this.attributeGetters.totalCost;
|
|
2213
|
-
}
|
|
2214
|
-
get customer() {
|
|
2215
|
-
return this.attributeGetters.customer;
|
|
2216
|
-
}
|
|
2217
|
-
get webhook() {
|
|
2218
|
-
return this.attributeGetters.webhook;
|
|
2219
|
-
}
|
|
2220
|
-
get merchantStoreUrl() {
|
|
2221
|
-
return this.attributeGetters.merchantStoreUrl;
|
|
2222
|
-
}
|
|
2223
|
-
// Get all data-* attributes
|
|
2224
|
-
getDataAttributes() {
|
|
2225
|
-
return getDataAttributes(this);
|
|
2226
|
-
}
|
|
2227
|
-
// Fetch merchantId from API using checkoutId (data-id)
|
|
2228
|
-
async fetchMerchantId() {
|
|
2229
|
-
const checkoutId = this.dataId;
|
|
2230
|
-
if (!checkoutId) {
|
|
2231
|
-
console.warn("[GaitButton] No checkoutId (data-id) provided, cannot fetch merchantId");
|
|
2232
|
-
this.showMerchantIdError();
|
|
2233
|
-
return false;
|
|
2234
|
-
}
|
|
2235
|
-
try {
|
|
2236
|
-
const response = await MerchantService.getMerchant(checkoutId);
|
|
2237
|
-
if (response.success && response.data?.merchantId) {
|
|
2238
|
-
this.merchantId = response.data.merchantId;
|
|
2239
|
-
this.paymentProvider = response.data.paymentProvider;
|
|
2240
|
-
return true;
|
|
2241
|
-
} else {
|
|
2242
|
-
console.error("[GaitButton] Failed to fetch merchantId:", response);
|
|
2243
|
-
this.showMerchantIdError();
|
|
2244
|
-
return false;
|
|
2245
|
-
}
|
|
2246
|
-
} catch (error) {
|
|
2247
|
-
console.error("[GaitButton] Error fetching merchantId:", error);
|
|
2248
|
-
this.showMerchantIdError();
|
|
2249
|
-
return false;
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
// Show error message when merchantId cannot be identified
|
|
2253
|
-
showMerchantIdError() {
|
|
2254
|
-
const errorMessage = "Unable to identify your account. Please go to your dashboard and copy your checkout identity.";
|
|
2255
|
-
alert(errorMessage);
|
|
2256
|
-
this.dispatchEvent(new CustomEvent("gait-merchant-id-error", {
|
|
2257
|
-
detail: {
|
|
2258
|
-
message: errorMessage,
|
|
2259
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2260
|
-
},
|
|
2261
|
-
bubbles: true,
|
|
2262
|
-
composed: true
|
|
2263
|
-
}));
|
|
2264
|
-
}
|
|
2265
|
-
// Process data from attributes and do something (like console.log)
|
|
2266
|
-
async processData() {
|
|
2267
|
-
const dataId = this.dataId;
|
|
2268
|
-
const allDataAttrs = this.getDataAttributes();
|
|
2269
|
-
if (dataId && !this.merchantId) {
|
|
2270
|
-
await this.fetchMerchantId();
|
|
2271
|
-
}
|
|
2272
|
-
if (dataId || Object.keys(allDataAttrs).length > 0) {
|
|
2273
|
-
const data = {
|
|
2274
|
-
id: dataId,
|
|
2275
|
-
...allDataAttrs,
|
|
2276
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2277
|
-
};
|
|
2278
|
-
devLog("[GaitButton] Processing data v0.0.16:", data);
|
|
2279
|
-
this.dispatchEvent(new CustomEvent("gait-data-processed", {
|
|
2280
|
-
detail: data,
|
|
2281
|
-
bubbles: true,
|
|
2282
|
-
composed: true
|
|
2283
|
-
}));
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
// Handle click events
|
|
2287
|
-
handleClick = async (event) => {
|
|
2288
|
-
if (this.disabled) {
|
|
2289
|
-
event.preventDefault();
|
|
2290
|
-
event.stopPropagation();
|
|
2291
|
-
return;
|
|
2292
|
-
}
|
|
2293
|
-
const clickData = {
|
|
2294
|
-
label: "Gait",
|
|
2295
|
-
dataId: this.dataId,
|
|
2296
|
-
dataAttributes: this.getDataAttributes(),
|
|
2297
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2298
|
-
};
|
|
2299
|
-
this.dispatchEvent(new CustomEvent("gait-click", {
|
|
2300
|
-
detail: clickData,
|
|
2301
|
-
bubbles: true,
|
|
2302
|
-
composed: true
|
|
2303
|
-
}));
|
|
2304
|
-
this.dispatchEvent(new CustomEvent("click", {
|
|
2305
|
-
detail: clickData,
|
|
2306
|
-
bubbles: true,
|
|
2307
|
-
composed: true
|
|
2308
|
-
}));
|
|
2309
|
-
if (!this.merchantId) {
|
|
2310
|
-
const fetched = await this.fetchMerchantId();
|
|
2311
|
-
if (!fetched || !this.merchantId) {
|
|
2312
|
-
return;
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
this.openModal();
|
|
2316
|
-
};
|
|
2317
|
-
// Setup event listeners
|
|
2318
|
-
setupEventListeners() {
|
|
2319
|
-
if (!this.shadowRoot) {
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
if (this.buttonElement) {
|
|
2323
|
-
this.buttonElement.removeEventListener("click", this.handleClick);
|
|
2324
|
-
}
|
|
2325
|
-
this.buttonElement = this.shadowRoot.querySelector("button");
|
|
2326
|
-
if (this.buttonElement) {
|
|
2327
|
-
this.buttonElement.addEventListener("click", this.handleClick);
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
// Setup MutationObserver to watch for data-* attribute changes
|
|
2331
|
-
setupMutationObserver() {
|
|
2332
|
-
if (this.mutationObserver) {
|
|
2333
|
-
this.mutationObserver.disconnect();
|
|
2334
|
-
}
|
|
2335
|
-
this.mutationObserver = new MutationObserver((mutations) => {
|
|
2336
|
-
let shouldProcessData = false;
|
|
2337
|
-
for (const mutation of mutations) {
|
|
2338
|
-
if (mutation.type === "attributes") {
|
|
2339
|
-
const attrName = mutation.attributeName;
|
|
2340
|
-
if (attrName && attrName.startsWith("data-") && !GaitButton.observedAttributes.includes(attrName)) {
|
|
2341
|
-
shouldProcessData = true;
|
|
2342
|
-
break;
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
if (shouldProcessData && this.isConnected) {
|
|
2347
|
-
this.processData();
|
|
2348
|
-
}
|
|
2349
|
-
});
|
|
2350
|
-
this.mutationObserver.observe(this, {
|
|
2351
|
-
attributes: true,
|
|
2352
|
-
attributeFilter: void 0
|
|
2353
|
-
// Observe all attributes
|
|
2354
|
-
});
|
|
2355
|
-
}
|
|
2356
|
-
// Setup API event listeners
|
|
2357
|
-
setupApiListeners() {
|
|
2358
|
-
this.addEventListener("gait-split-sent", this.handleSplitSent);
|
|
2359
|
-
}
|
|
2360
|
-
// Handle confirm payment (Pay remaining button)
|
|
2361
|
-
handleConfirmPayment = async () => {
|
|
2362
|
-
console.log("[GaitButton] Pay remaining button clicked");
|
|
2363
|
-
if (!this.storedSplitPaymentRequest) {
|
|
2364
|
-
console.error("[GaitButton] Split payment must be sent first before paying remaining amount");
|
|
2365
|
-
return;
|
|
2366
|
-
}
|
|
2367
|
-
if (!this.merchantId) {
|
|
2368
|
-
const fetched = await this.fetchMerchantId();
|
|
2369
|
-
if (!fetched || !this.merchantId) {
|
|
2370
|
-
return;
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
const initiatorObject = {
|
|
2374
|
-
merchantId: this.merchantId,
|
|
2375
|
-
splitId: this.storedSplitPaymentRequest.splitId,
|
|
2376
|
-
paymentId: this.storedSplitPaymentRequest.paymentSplits.find((payment) => payment.initiator)?.paymentId
|
|
2377
|
-
};
|
|
2378
|
-
const paylinkDomain = config.paylink.DOMAIN;
|
|
2379
|
-
const paylink = `${paylinkDomain}/payment/${initiatorObject.paymentId}`;
|
|
2380
|
-
window.open(paylink, "_blank");
|
|
2381
|
-
this.dispatchEvent(new CustomEvent("gait-confirm", {
|
|
2382
|
-
detail: initiatorObject,
|
|
2383
|
-
bubbles: true,
|
|
2384
|
-
composed: true
|
|
2385
|
-
}));
|
|
2386
|
-
this.closeModal();
|
|
2387
|
-
};
|
|
2388
|
-
// Handle split payment sent event and make API call
|
|
2389
|
-
handleSplitSent = async (event) => {
|
|
2390
|
-
const customEvent = event;
|
|
2391
|
-
const splitData = customEvent.detail;
|
|
2392
|
-
if (!this.merchantId) {
|
|
2393
|
-
const fetched = await this.fetchMerchantId();
|
|
2394
|
-
if (!fetched || !this.merchantId) {
|
|
2395
|
-
return;
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
const merchantId = this.merchantId;
|
|
2399
|
-
const customer = this.customer;
|
|
2400
|
-
if (!customer || !customer.email) {
|
|
2401
|
-
console.error("[GaitButton] Customer email is required for split payment");
|
|
2402
|
-
return;
|
|
2403
|
-
}
|
|
2404
|
-
const webhook = this.webhook;
|
|
2405
|
-
if (!webhook || !webhook.url || webhook.body == null) {
|
|
2406
|
-
console.error("[GaitButton] Webhook with url and body is required");
|
|
2407
|
-
return;
|
|
2408
|
-
}
|
|
2409
|
-
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
2410
|
-
const generateId = customAlphabet(alphabet, 6);
|
|
2411
|
-
const splitId = generateId();
|
|
2412
|
-
const paymentSplits = [];
|
|
2413
|
-
paymentSplits.push({
|
|
2414
|
-
paymentId: generateId(),
|
|
2415
|
-
email: customer.email,
|
|
2416
|
-
amount: splitData.yourAmount,
|
|
2417
|
-
isPaid: false,
|
|
2418
|
-
datePaid: null,
|
|
2419
|
-
initiator: true
|
|
2420
|
-
});
|
|
2421
|
-
splitData.emailData.forEach((emailData) => {
|
|
2422
|
-
paymentSplits.push({
|
|
2423
|
-
paymentId: generateId(),
|
|
2424
|
-
email: emailData.email,
|
|
2425
|
-
amount: emailData.amount || 0,
|
|
2426
|
-
isPaid: false,
|
|
2427
|
-
datePaid: null,
|
|
2428
|
-
initiator: false
|
|
2429
|
-
});
|
|
2430
|
-
});
|
|
2431
|
-
const id = {
|
|
2432
|
-
merchantId,
|
|
2433
|
-
splitId,
|
|
2434
|
-
paymentSplits,
|
|
2435
|
-
splitMethod: splitData.splitMethod,
|
|
2436
|
-
timestamp: splitData.timestamp,
|
|
2437
|
-
totalCost: splitData.totalCost,
|
|
2438
|
-
items: this.items,
|
|
2439
|
-
priceBreakdown: this.priceBreakdown,
|
|
2440
|
-
brandColor: GAIT_BRAND_COLOR,
|
|
2441
|
-
merchantStoreUrl: this.merchantStoreUrl,
|
|
2442
|
-
subTotal: this.priceBreakdown.find((item) => item.name === "Subtotal")?.price || 0,
|
|
2443
|
-
paymentProvider: this.paymentProvider
|
|
2444
|
-
};
|
|
2445
|
-
console.log("[GaitButton] Split payment request body:", JSON.stringify(id, null, 2));
|
|
2446
|
-
try {
|
|
2447
|
-
const response = await sendSplitPayment(id, 2e3);
|
|
2448
|
-
if (response.success) {
|
|
2449
|
-
const finalSplitId = response.data?.splitId || splitId;
|
|
2450
|
-
this.storedSplitPaymentRequest = {
|
|
2451
|
-
...id,
|
|
2452
|
-
splitId: finalSplitId
|
|
2453
|
-
};
|
|
2454
|
-
try {
|
|
2455
|
-
const webhookResponse = await PaymentsService.createWebhook(
|
|
2456
|
-
merchantId,
|
|
2457
|
-
finalSplitId,
|
|
2458
|
-
{
|
|
2459
|
-
url: webhook.url,
|
|
2460
|
-
body: webhook.body
|
|
2461
|
-
}
|
|
2462
|
-
);
|
|
2463
|
-
console.log("[GaitButton] Webhook created:", webhookResponse);
|
|
2464
|
-
} catch (webhookError) {
|
|
2465
|
-
console.error("[GaitButton] Failed to create webhook:", webhookError);
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
if (this.modalElement) {
|
|
2469
|
-
const confirmButton = this.modalElement.querySelector("[data-gait-modal-confirm]");
|
|
2470
|
-
if (confirmButton) {
|
|
2471
|
-
confirmButton.disabled = false;
|
|
2472
|
-
confirmButton.style.opacity = "1";
|
|
2473
|
-
confirmButton.style.cursor = "pointer";
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
this.dispatchEvent(new CustomEvent("gait-split-feedback", {
|
|
2477
|
-
detail: {
|
|
2478
|
-
success: response.success,
|
|
2479
|
-
message: response.message,
|
|
2480
|
-
data: response.success ? this.storedSplitPaymentRequest : response.data,
|
|
2481
|
-
error: response.error,
|
|
2482
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2483
|
-
},
|
|
2484
|
-
bubbles: true,
|
|
2485
|
-
composed: true
|
|
2486
|
-
}));
|
|
2487
|
-
console.log("[GaitButton] Split payment API response:", response);
|
|
2488
|
-
} catch (error) {
|
|
2489
|
-
console.error("[GaitButton] Split payment API error:", error);
|
|
2490
|
-
this.dispatchEvent(new CustomEvent("gait-split-feedback", {
|
|
2491
|
-
detail: {
|
|
2492
|
-
success: false,
|
|
2493
|
-
message: "Failed to send split payment",
|
|
2494
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
2495
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2496
|
-
},
|
|
2497
|
-
bubbles: true,
|
|
2498
|
-
composed: true
|
|
2499
|
-
}));
|
|
2500
|
-
}
|
|
2501
|
-
};
|
|
2502
|
-
// Open modal
|
|
2503
|
-
openModal() {
|
|
2504
|
-
injectModalStyles();
|
|
2505
|
-
this.closeModal();
|
|
2506
|
-
this.modalElement = document.createElement("div");
|
|
2507
|
-
this.modalElement.className = "gait-modal-backdrop";
|
|
2508
|
-
this.modalElement.id = this.modalId;
|
|
2509
|
-
if (this.modalElement) {
|
|
2510
|
-
this.modalElement.style.setProperty("--gait-modal-brand-color", GAIT_BRAND_COLOR);
|
|
2511
|
-
this.modalElement.style.setProperty("--gait-modal-button-bg", GAIT_BRAND_COLOR);
|
|
2512
|
-
this.modalElement.style.setProperty("--gait-modal-button-bg-hover", darkenColor(GAIT_BRAND_COLOR, 0.15));
|
|
2513
|
-
this.modalElement.style.setProperty("--gait-modal-button-color", isColorDark(GAIT_BRAND_COLOR) ? "#ffffff" : "#000000");
|
|
2514
|
-
}
|
|
2515
|
-
this.modalElement.innerHTML = generateModalTemplate(
|
|
2516
|
-
this.items,
|
|
2517
|
-
this.priceBreakdown,
|
|
2518
|
-
this.totalCost
|
|
2519
|
-
);
|
|
2520
|
-
const closeButton = this.modalElement.querySelector("[data-gait-modal-close]");
|
|
2521
|
-
if (closeButton) {
|
|
2522
|
-
closeButton.addEventListener("click", this.closeModal);
|
|
2523
|
-
}
|
|
2524
|
-
const confirmButton = this.modalElement.querySelector("[data-gait-modal-confirm]");
|
|
2525
|
-
if (confirmButton) {
|
|
2526
|
-
confirmButton.disabled = true;
|
|
2527
|
-
confirmButton.style.opacity = "0.5";
|
|
2528
|
-
confirmButton.style.cursor = "not-allowed";
|
|
2529
|
-
confirmButton.addEventListener("click", this.handleConfirmPayment);
|
|
2530
|
-
}
|
|
2531
|
-
this.splitPaymentManager = new SplitPaymentManager(
|
|
2532
|
-
this.modalElement,
|
|
2533
|
-
this.totalCost,
|
|
2534
|
-
(event) => this.dispatchEvent(event)
|
|
2535
|
-
);
|
|
2536
|
-
this.modalElement._splitPaymentManager = this.splitPaymentManager;
|
|
2537
|
-
this.modalElement.addEventListener("click", (e) => {
|
|
2538
|
-
if (e.target === this.modalElement) {
|
|
2539
|
-
this.closeModal();
|
|
2540
|
-
}
|
|
2541
|
-
});
|
|
2542
|
-
const handleEscape = (e) => {
|
|
2543
|
-
if (e.key === "Escape" && this.modalElement) {
|
|
2544
|
-
this.closeModal();
|
|
2545
|
-
document.removeEventListener("keydown", handleEscape);
|
|
2546
|
-
}
|
|
2547
|
-
};
|
|
2548
|
-
document.addEventListener("keydown", handleEscape);
|
|
2549
|
-
document.body.appendChild(this.modalElement);
|
|
2550
|
-
requestAnimationFrame(() => {
|
|
2551
|
-
if (this.modalElement) {
|
|
2552
|
-
this.modalElement.classList.add("gait-modal-open");
|
|
2553
|
-
}
|
|
2554
|
-
});
|
|
2555
|
-
}
|
|
2556
|
-
// Close modal
|
|
2557
|
-
closeModal = () => {
|
|
2558
|
-
if (this.modalElement) {
|
|
2559
|
-
if (this.splitPaymentManager) {
|
|
2560
|
-
this.splitPaymentManager.cleanup();
|
|
2561
|
-
}
|
|
2562
|
-
this.modalElement.classList.remove("gait-modal-open");
|
|
2563
|
-
setTimeout(() => {
|
|
2564
|
-
if (this.modalElement && this.modalElement.parentNode) {
|
|
2565
|
-
this.modalElement.parentNode.removeChild(this.modalElement);
|
|
2566
|
-
}
|
|
2567
|
-
this.modalElement = null;
|
|
2568
|
-
this.splitPaymentManager = null;
|
|
2569
|
-
}, 250);
|
|
2570
|
-
}
|
|
2571
|
-
};
|
|
2572
|
-
// Render the component
|
|
2573
|
-
render() {
|
|
2574
|
-
const shadow = this.shadowRoot;
|
|
2575
|
-
if (!shadow) {
|
|
2576
|
-
return;
|
|
2577
|
-
}
|
|
2578
|
-
const wasFirstRender = !this.hasRendered;
|
|
2579
|
-
const styles = generateButtonStyles(
|
|
2580
|
-
this.size,
|
|
2581
|
-
darkenColor,
|
|
2582
|
-
isColorDark
|
|
2583
|
-
);
|
|
2584
|
-
const hostStyle = this.getAttribute("style") || "";
|
|
2585
|
-
const sanitizedStyle = hostStyle.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
|
|
2586
|
-
shadow.innerHTML = `
|
|
2587
|
-
${styles}
|
|
2588
|
-
${generateButtonTemplate(this.disabled, sanitizedStyle)}
|
|
2589
|
-
`;
|
|
2590
|
-
this.hasRendered = true;
|
|
2591
|
-
this.setupEventListeners();
|
|
2592
|
-
if (wasFirstRender) {
|
|
2593
|
-
const allAttributes = getAllAttributes(this);
|
|
2594
|
-
console.log("[GaitButton] rendered", allAttributes);
|
|
2595
|
-
this.dispatchEvent(new CustomEvent("gait-loaded", {
|
|
2596
|
-
detail: {
|
|
2597
|
-
attributes: allAttributes,
|
|
2598
|
-
dataId: this.dataId,
|
|
2599
|
-
dataAttributes: this.getDataAttributes(),
|
|
2600
|
-
label: "Gait",
|
|
2601
|
-
size: this.size,
|
|
2602
|
-
disabled: this.disabled,
|
|
2603
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2604
|
-
},
|
|
2605
|
-
bubbles: true,
|
|
2606
|
-
composed: true
|
|
2607
|
-
}));
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
if (!customElements.get("gait-button")) {
|
|
2612
|
-
customElements.define("gait-button", GaitButton);
|
|
2613
|
-
}
|
|
2614
|
-
if (!customElements.get("gait-button")) {
|
|
2615
|
-
customElements.define("gait-button", GaitButton);
|
|
2616
|
-
}
|
|
2617
|
-
if (typeof window !== "undefined") {
|
|
2618
|
-
window.GaitButton = GaitButton;
|
|
2619
|
-
window.GaitButtonVersion = GAIT_BUTTON_VERSION;
|
|
2620
|
-
console.log(`[GaitButton] Version: ${GAIT_BUTTON_VERSION}`);
|
|
2621
|
-
}
|
|
2622
|
-
exports.GAIT_BUTTON_VERSION = GAIT_BUTTON_VERSION;
|
|
2623
|
-
exports.GaitButton = GaitButton;
|
|
2624
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2625
|
-
})(this.GaitButton = this.GaitButton || {});
|
|
952
|
+
`;const d=a.querySelector(`[data-edit-email="${t}"]`);d&&d.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),this.convertEmailItemToInput(a)});const l=a.querySelector(`[data-remove-email="${t}"]`);return l&&l.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation(),this.removeEmailItem(a)}),a}convertEmailItemToInput(t){if(!t.parentElement)return;const i=t.getAttribute("data-email-item")||"";if(this.currentSplitMethod==="by-amount"){const a=document.createElement("div");a.className="gait-modal-email-amount-row";const n=document.createElement("input");n.type="email",n.className="gait-modal-email-input",n.placeholder="Email address",n.setAttribute("data-email-input",""),n.value=i;const o=document.createElement("input");o.type="number",o.className="gait-modal-email-amount-input",o.placeholder="0.00",o.step="0.01",o.min="0",o.setAttribute("data-email-amount-input","");const d=t.querySelector(".gait-modal-email-item-amount");let l=0;if(d){const u=d.textContent||"0.00";l=parseFloat(u.replace(/[^\d.]/g,""))||0,o.value=l.toFixed(2)}const s=this.calculateRemainingAmount(a)+l;o.max=s.toFixed(2);const m=document.createElement("button");m.type="button",m.className="gait-modal-done-button",m.textContent="Done",m.setAttribute("data-done-email",""),m.addEventListener("click",()=>{const u=n.value.trim();let c=parseFloat(o.value)||0;const p=this.calculateRemainingAmount(a)+l;if(c>p&&(c=p,o.value=p.toFixed(2),o.style.borderColor="#ef4444",setTimeout(()=>{o.style.borderColor=""},2e3)),u&&v(u)){const b=this.createEmailItem(u,c);a.parentElement&&a.parentElement.replaceChild(b,a),this.updateSplitCalculation(),this.updateAmountInputMaxValues()}else u||(a.parentElement&&a.parentElement.removeChild(a),this.updateAmountInputMaxValues())}),o.addEventListener("input",()=>{const u=this.calculateRemainingAmount(a)+l;(parseFloat(o.value)||0)>u?(o.value=u.toFixed(2),o.style.borderColor="#ef4444",setTimeout(()=>{o.style.borderColor=""},2e3)):o.style.borderColor="",this.updateAmountInputMaxValues(a)}),a.appendChild(n),a.appendChild(o),a.appendChild(m),t.parentElement&&t.parentElement.replaceChild(a,t),n.focus(),n.select()}else{const a=document.createElement("input");a.type="email",a.className="gait-modal-email-input",a.placeholder="Email address",a.setAttribute("data-email-input",""),a.value=i;const n=()=>{const o=a.value.trim();o&&v(o)?(this.convertInputToEmailItem(a),this.updateSplitCalculation()):o||setTimeout(()=>{a.parentElement&&!a.value.trim()&&a.parentElement.removeChild(a)},100)};a.addEventListener("blur",n),a.addEventListener("keydown",o=>{o.key==="Enter"&&(o.preventDefault(),n())}),t.parentElement&&t.parentElement.replaceChild(a,t),a.focus(),a.select()}}convertInputToEmailItem(t){const e=t.value.trim();if(!e||!v(e))return;const i=t.parentElement;if(!i)return;const a=z(i)+1,n=this.currentSplitMethod==="equally"?w(this.totalCost,a):0,o=this.createEmailItem(e,n);i.replaceChild(o,t)}removeEmailItem(t){const e=t.parentElement;e&&(e.removeChild(t),this.updateSplitCalculation(),this.updateAmountInputMaxValues())}updateActiveInputsForSplitMethod(t,e){const i=this.modalElement.querySelector("[data-email-inputs]");i&&(this.removeEmptyInputs(i),t==="equally"&&e==="by-amount"&&Array.from(i.querySelectorAll("[data-email-input]")).filter(n=>!n.parentElement?.classList.contains("gait-modal-email-amount-row")).forEach(n=>{const o=n.value.trim(),d=n.parentElement;if(!d)return;const l=document.createElement("div");l.className="gait-modal-email-amount-row";const s=document.createElement("input");s.type="email",s.className="gait-modal-email-input",s.placeholder="Email address",s.setAttribute("data-email-input",""),s.value=o;const m=document.createElement("input");m.type="number",m.className="gait-modal-email-amount-input",m.placeholder="0.00",m.step="0.01",m.min="0",m.setAttribute("data-email-amount-input","");const u=this.calculateRemainingAmount(l);m.max=u.toFixed(2);const c=document.createElement("button");c.type="button",c.className="gait-modal-done-button",c.textContent="Done",c.setAttribute("data-done-email",""),c.addEventListener("click",()=>{const p=s.value.trim();let b=parseFloat(m.value)||0;const T=this.calculateRemainingAmount(l);if(b>T&&(b=T,m.value=T.toFixed(2),m.style.borderColor="#ef4444",setTimeout(()=>{m.style.borderColor=""},2e3)),p&&v(p)){const st=this.createEmailItem(p,b);d.replaceChild(st,l),this.updateSplitCalculation(),this.updateAmountInputMaxValues()}}),m.addEventListener("input",()=>{const p=this.calculateRemainingAmount(l);(parseFloat(m.value)||0)>p?(m.value=p.toFixed(2),m.style.borderColor="#ef4444",setTimeout(()=>{m.style.borderColor=""},2e3)):m.style.borderColor="",this.updateAmountInputMaxValues(l)}),l.appendChild(s),l.appendChild(m),l.appendChild(c),d.replaceChild(l,n),s.focus()}),t==="by-amount"&&e==="equally"&&i.querySelectorAll(".gait-modal-email-amount-row").forEach(n=>{const o=n.querySelector("[data-email-input]");if(!o)return;const d=o.value.trim(),l=n.parentElement;if(!l)return;const s=document.createElement("input");s.type="email",s.className="gait-modal-email-input",s.placeholder="Email address",s.setAttribute("data-email-input",""),s.value=d;const m=()=>{const u=s.value.trim();u&&v(u)&&(this.convertInputToEmailItem(s),this.updateSplitCalculation())};s.addEventListener("blur",m),s.addEventListener("keydown",u=>{u.key==="Enter"&&(u.preventDefault(),m())}),l.replaceChild(s,n),s.focus()}))}updateEmailItemsForSplitMethod(){const t=this.modalElement.querySelector("[data-email-inputs]");if(!t)return;const e=t.querySelectorAll("[data-email-item]"),i=e.length+1;e.forEach(a=>{const n=a.querySelector(".gait-modal-email-item-right");if(!n)return;const o=n.querySelector(".gait-modal-email-item-amount");if(o){if(this.currentSplitMethod==="equally"){const d=w(this.totalCost,i);o.textContent=`R ${d.toFixed(2)}`}else if(this.currentSplitMethod==="by-amount"){const d=o.textContent||"R 0.00",l=parseFloat(d.replace(/[^\d.]/g,""))||0;o.textContent=`R ${l.toFixed(2)}`}}})}updateSplitCalculation(){const t=this.modalElement.querySelector("[data-email-inputs]"),e=this.modalElement.querySelector("[data-confirm-amount]");if(!t||!e)return;const i=R(t,this.currentSplitMethod,this.totalCost),a=t.querySelectorAll("[data-email-input]");a.forEach(o=>{const d=o.value.trim();if(d&&v(d)){const l=i.length+a.length+1,s=this.currentSplitMethod==="equally"?w(this.totalCost,l):0;i.push({email:d,amount:s})}});const n=i.length>0?L(this.totalCost,this.currentSplitMethod,i):this.totalCost;e.textContent=`R ${n.toFixed(2)}`,this.currentSplitMethod==="by-amount"&&this.updateAmountInputMaxValues()}handleSendSplit(){const t=this.modalElement.querySelector("[data-email-inputs]"),e=this.modalElement.querySelector("[data-send-split]");if(!t||!e)return;const i=R(t,this.currentSplitMethod,this.totalCost);if(i.length===0){this.showFeedback("Please add at least one email address","error");return}if(this.isSplitSent||this.isSplitLoading)return;this.isSplitLoading=!0,e.disabled=!0,e.textContent="Sending...",this.showFeedback("Sending split payment...","loading");const a=L(this.totalCost,this.currentSplitMethod,i);this.dispatchEventFn(new CustomEvent("gait-split-sent",{detail:{splitMethod:this.currentSplitMethod,emails:i.map(n=>n.email),emailData:i,totalCost:this.totalCost,yourAmount:a,timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}setupFeedbackListener(){this.feedbackHandler=t=>{const e=t;e.detail&&this.handleSplitFeedback(e.detail)},document.addEventListener("gait-split-feedback",this.feedbackHandler)}cleanup(){this.feedbackHandler&&(document.removeEventListener("gait-split-feedback",this.feedbackHandler),this.feedbackHandler=null)}handleSplitFeedback(t){this.isSplitLoading=!1;const e=this.modalElement.querySelector("[data-send-split]");t.success?(this.isSplitSent=!0,this.markEmailsAsSent(),this.disableSplitOptions(),e&&(e.disabled=!0,e.textContent="Split sent!"),this.showFeedback(t.message||"Split payment sent successfully","success")):(e&&(e.disabled=!1,e.textContent="Send split"),this.showFeedback(t.message||t.error||"Failed to send split payment","error"))}markEmailsAsSent(){const t=this.modalElement.querySelector("[data-email-inputs]");if(!t)return;t.querySelectorAll("[data-email-item]").forEach(i=>{if(!i.classList.contains("sent")){i.classList.add("sent");const a=i.querySelector(".gait-modal-email-item-right");if(a){let o=a.querySelector(".gait-modal-email-item-amount-container");const d=a.querySelector(".gait-modal-email-item-amount");if(!o&&d&&(o=document.createElement("div"),o.className="gait-modal-email-item-amount-container",d.parentElement?.replaceChild(o,d),o.appendChild(d)),o&&!o.querySelector(".gait-modal-email-item-sent-check")){const l=document.createElement("div");l.className="gait-modal-email-item-sent-check",l.innerHTML=$,o.appendChild(l)}}const n=i.querySelector(".gait-modal-email-item-actions");n&&(n.style.display="none")}})}showFeedback(t,e){this.splitFeedbackElement&&(this.splitFeedbackElement.remove(),this.splitFeedbackElement=null);const i=document.createElement("div");i.className=`gait-modal-feedback gait-modal-feedback-${e}`,i.setAttribute("data-split-feedback","");const a=e==="success"?$:e==="error"?U:W;i.innerHTML=`
|
|
953
|
+
<span class="gait-modal-feedback-icon">${a}</span>
|
|
954
|
+
<span class="gait-modal-feedback-message">${t}</span>
|
|
955
|
+
`;const n=this.modalElement.querySelector(".gait-modal-split-section");n&&(n.insertBefore(i,n.firstChild),this.splitFeedbackElement=i,e!=="loading"&&setTimeout(()=>{this.splitFeedbackElement===i&&(i.style.opacity="0",setTimeout(()=>{i.parentElement&&i.remove(),this.splitFeedbackElement===i&&(this.splitFeedbackElement=null)},300))},5e3))}disableSplitOptions(){this.modalElement.querySelectorAll("[data-split-method]").forEach(i=>{const a=i;a.style.pointerEvents="none",a.style.opacity="0.5",a.style.cursor="not-allowed",a.setAttribute("aria-disabled","true")});const e=this.modalElement.querySelector("[data-add-email]");e&&(e.style.pointerEvents="none",e.style.opacity="0.5",e.style.cursor="not-allowed",e.setAttribute("aria-disabled","true"))}}async function et(r,t=2e3){console.log("[SplitPaymentAPI] Sending split payment request:",r);const e="https://api.usegait.com/checkout";try{const a=await(await fetch(`${e}/v1/payments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r),signal:AbortSignal.timeout(t)})).json().catch(()=>({success:!1,error:"Failed to parse API response"}));if(a.success){const n={success:!0,message:a.message,data:a.data};return console.log("[SplitPaymentAPI] Split payment sent successfully:",n),n}else{const n={success:!1,message:a.message,error:a.error};return console.error("[SplitPaymentAPI] Split payment failed:",n),n}}catch(i){const a={success:!1,message:"Failed to send split payment",error:i instanceof Error?i.message:"Network error or server unavailable"};return console.error("[SplitPaymentAPI] Split payment failed:",a,i),a}}let at=r=>crypto.getRandomValues(new Uint8Array(r)),it=(r,t,e)=>{let i=(2<<Math.log2(r.length-1))-1,a=-~(1.6*i*t/r.length);return(n=t)=>{let o="";for(;;){let d=e(a),l=a|0;for(;l--;)if(o+=r[d[l]&i]||"",o.length>=n)return o}}},ot=(r,t=21)=>it(r,t|0,at);const M={apiGateway:{MERCHANT_SERVICE_API:"https://api.usegait.com/merchant",CHECKOUT_SERVICE_API:"https://api.usegait.com/checkout"},paylink:{DOMAIN:"https://checkout.usegait.com"}},nt=M.apiGateway.MERCHANT_SERVICE_API;class rt{static async getMerchant(t){try{return await(await fetch(`${nt}/v1/merchant/identity/${t}`,{method:"GET",headers:{"Content-Type":"application/json"}})).json()}catch(e){return e}}}const k=M.apiGateway.CHECKOUT_SERVICE_API;class lt{static async getPayment(t){try{return await(await fetch(`${k}/v1/payments/${t}`,{method:"GET",headers:{"Content-Type":"application/json"}})).json()}catch(e){return e}}static async updatePaymentStatus(t,e,i){try{return await(await fetch(`${k}/v1/payments/merchants/${t}/${e}/status`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:i})})).json()}catch(a){return a}}static async createWebhook(t,e,i){try{return await(await fetch(`${k}/v1/webhooks/${t}/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)})).json()}catch(a){return a}}static async updatePayment(t,e,i){try{return await(await fetch(`${k}/v1/payments/merchants/${t}/${i}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({paymentId:e})})).json()}catch(a){return a}}}const S=(typeof process<"u"&&process.env?"0.1.0":void 0)||"0.1.4";class x extends HTMLElement{buttonElement=null;hasRendered=!1;mutationObserver=null;modalElement=null;modalId;splitPaymentManager=null;attributeGetters;storedSplitPaymentRequest=null;merchantId=null;paymentProvider="stripe";static version=S;version=S;static get observedAttributes(){return["data-id","disabled","size","style","items","price-breakdown","total-cost","webhook","merchant-store-url"]}constructor(){super(),this.attachShadow({mode:"open"}),this.modalId=`gait-modal-${Math.random().toString(36).substr(2,9)}`,this.attributeGetters=X(this)}connectedCallback(){!this.hasRendered&&this.shadowRoot&&this.render(),this.setupEventListeners(),this.setupMutationObserver(),this.setupApiListeners(),setTimeout(()=>this.processData(),0)}disconnectedCallback(){this.buttonElement&&this.buttonElement.removeEventListener("click",this.handleClick),this.mutationObserver&&(this.mutationObserver.disconnect(),this.mutationObserver=null),this.closeModal(),this.splitPaymentManager&&(this.splitPaymentManager=null),this.removeEventListener("gait-split-sent",this.handleSplitSent)}attributeChangedCallback(t,e,i){if(e!==i&&!(!this.isConnected||!this.shadowRoot)){if(t==="style"&&this.buttonElement&&this.hasRendered){const a=(i||"").replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"");this.buttonElement.setAttribute("style",a);return}this.hasRendered&&(this.render(),this.setupEventListeners()),(t==="data-id"||t.startsWith("data-"))&&(t==="data-id"&&(this.merchantId=null),setTimeout(()=>this.processData(),0))}}get dataId(){return this.attributeGetters.dataId}get disabled(){return this.attributeGetters.disabled}get size(){return this.attributeGetters.size}get items(){return this.attributeGetters.items}get priceBreakdown(){return this.attributeGetters.priceBreakdown}get totalCost(){return this.attributeGetters.totalCost}get customer(){return this.attributeGetters.customer}get webhook(){return this.attributeGetters.webhook}get merchantStoreUrl(){return this.attributeGetters.merchantStoreUrl}getDataAttributes(){return Z(this)}async fetchMerchantId(){const t=this.dataId;if(!t)return console.warn("[GaitButton] No checkoutId (data-id) provided, cannot fetch merchantId"),this.showMerchantIdError(),!1;try{const e=await rt.getMerchant(t);return e.success&&e.data?.merchantId?(this.merchantId=e.data.merchantId,this.paymentProvider=e.data.paymentProvider,!0):(console.error("[GaitButton] Failed to fetch merchantId:",e),this.showMerchantIdError(),!1)}catch(e){return console.error("[GaitButton] Error fetching merchantId:",e),this.showMerchantIdError(),!1}}showMerchantIdError(){const t="Unable to identify your account. Please go to your dashboard and copy your checkout identity.";alert(t),this.dispatchEvent(new CustomEvent("gait-merchant-id-error",{detail:{message:t,timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}async processData(){const t=this.dataId,e=this.getDataAttributes();if(t&&!this.merchantId&&await this.fetchMerchantId(),t||Object.keys(e).length>0){const i={id:t,...e,timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent("gait-data-processed",{detail:i,bubbles:!0,composed:!0}))}}handleClick=async t=>{if(this.disabled){t.preventDefault(),t.stopPropagation();return}const e={label:"Gait",dataId:this.dataId,dataAttributes:this.getDataAttributes(),timestamp:new Date().toISOString()};this.dispatchEvent(new CustomEvent("gait-click",{detail:e,bubbles:!0,composed:!0})),this.dispatchEvent(new CustomEvent("click",{detail:e,bubbles:!0,composed:!0})),!(!this.merchantId&&(!await this.fetchMerchantId()||!this.merchantId))&&this.openModal()};setupEventListeners(){this.shadowRoot&&(this.buttonElement&&this.buttonElement.removeEventListener("click",this.handleClick),this.buttonElement=this.shadowRoot.querySelector("button"),this.buttonElement&&this.buttonElement.addEventListener("click",this.handleClick))}setupMutationObserver(){this.mutationObserver&&this.mutationObserver.disconnect(),this.mutationObserver=new MutationObserver(t=>{let e=!1;for(const i of t)if(i.type==="attributes"){const a=i.attributeName;if(a&&a.startsWith("data-")&&!x.observedAttributes.includes(a)){e=!0;break}}e&&this.isConnected&&this.processData()}),this.mutationObserver.observe(this,{attributes:!0,attributeFilter:void 0})}setupApiListeners(){this.addEventListener("gait-split-sent",this.handleSplitSent)}handleConfirmPayment=async()=>{if(console.log("[GaitButton] Pay remaining button clicked"),!this.storedSplitPaymentRequest){console.error("[GaitButton] Split payment must be sent first before paying remaining amount");return}if(!this.merchantId&&(!await this.fetchMerchantId()||!this.merchantId))return;const t={merchantId:this.merchantId,splitId:this.storedSplitPaymentRequest.splitId,paymentId:this.storedSplitPaymentRequest.paymentSplits.find(a=>a.initiator)?.paymentId},i=`${M.paylink.DOMAIN}/payment/${t.paymentId}`;window.open(i,"_blank"),this.dispatchEvent(new CustomEvent("gait-confirm",{detail:t,bubbles:!0,composed:!0})),this.closeModal()};handleSplitSent=async t=>{const i=t.detail;if(!this.merchantId&&(!await this.fetchMerchantId()||!this.merchantId))return;const a=this.merchantId,n=this.customer;if(!n||!n.email){console.error("[GaitButton] Customer email is required for split payment");return}const o=this.webhook;if(!o||!o.url||o.body==null){console.error("[GaitButton] Webhook with url and body is required");return}const l=ot("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",6),s=l(),m=[];m.push({paymentId:l(),email:n.email,amount:i.yourAmount,isPaid:!1,datePaid:null,initiator:!0}),i.emailData.forEach(c=>{m.push({paymentId:l(),email:c.email,amount:c.amount||0,isPaid:!1,datePaid:null,initiator:!1})});const u={merchantId:a,splitId:s,paymentSplits:m,splitMethod:i.splitMethod,timestamp:i.timestamp,totalCost:i.totalCost,items:this.items,priceBreakdown:this.priceBreakdown,brandColor:g,merchantStoreUrl:this.merchantStoreUrl,subTotal:this.priceBreakdown.find(c=>c.name==="Subtotal")?.price||0,paymentProvider:this.paymentProvider};console.log("[GaitButton] Split payment request body:",JSON.stringify(u,null,2));try{const c=await et(u,2e3);if(c.success){const p=c.data?.splitId||s;this.storedSplitPaymentRequest={...u,splitId:p};try{const b=await lt.createWebhook(a,p,{url:o.url,body:o.body});console.log("[GaitButton] Webhook created:",b)}catch(b){console.error("[GaitButton] Failed to create webhook:",b)}}if(this.modalElement){const p=this.modalElement.querySelector("[data-gait-modal-confirm]");p&&(p.disabled=!1,p.style.opacity="1",p.style.cursor="pointer")}this.dispatchEvent(new CustomEvent("gait-split-feedback",{detail:{success:c.success,message:c.message,data:c.success?this.storedSplitPaymentRequest:c.data,error:c.error,timestamp:new Date().toISOString()},bubbles:!0,composed:!0})),console.log("[GaitButton] Split payment API response:",c)}catch(c){console.error("[GaitButton] Split payment API error:",c),this.dispatchEvent(new CustomEvent("gait-split-feedback",{detail:{success:!1,message:"Failed to send split payment",error:c instanceof Error?c.message:"Unknown error",timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}};openModal(){V(),this.closeModal(),this.modalElement=document.createElement("div"),this.modalElement.className="gait-modal-backdrop",this.modalElement.id=this.modalId,this.modalElement&&(this.modalElement.style.setProperty("--gait-modal-brand-color",g),this.modalElement.style.setProperty("--gait-modal-button-bg",g),this.modalElement.style.setProperty("--gait-modal-button-bg-hover",F(g,.15)),this.modalElement.style.setProperty("--gait-modal-button-color",q(g)?"#ffffff":"#000000")),this.modalElement.innerHTML=K(this.items,this.priceBreakdown,this.totalCost);const t=this.modalElement.querySelector("[data-gait-modal-close]");t&&t.addEventListener("click",this.closeModal);const e=this.modalElement.querySelector("[data-gait-modal-confirm]");e&&(e.disabled=!0,e.style.opacity="0.5",e.style.cursor="not-allowed",e.addEventListener("click",this.handleConfirmPayment)),this.splitPaymentManager=new tt(this.modalElement,this.totalCost,a=>this.dispatchEvent(a)),this.modalElement._splitPaymentManager=this.splitPaymentManager,this.modalElement.addEventListener("click",a=>{a.target===this.modalElement&&this.closeModal()});const i=a=>{a.key==="Escape"&&this.modalElement&&(this.closeModal(),document.removeEventListener("keydown",i))};document.addEventListener("keydown",i),document.body.appendChild(this.modalElement),requestAnimationFrame(()=>{this.modalElement&&this.modalElement.classList.add("gait-modal-open")})}closeModal=()=>{this.modalElement&&(this.splitPaymentManager&&this.splitPaymentManager.cleanup(),this.modalElement.classList.remove("gait-modal-open"),setTimeout(()=>{this.modalElement&&this.modalElement.parentNode&&this.modalElement.parentNode.removeChild(this.modalElement),this.modalElement=null,this.splitPaymentManager=null},250))};render(){const t=this.shadowRoot;if(!t)return;const e=!this.hasRendered,i=D(this.size,F,q),n=(this.getAttribute("style")||"").replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,"");if(t.innerHTML=`
|
|
956
|
+
${i}
|
|
957
|
+
${H(this.disabled,n)}
|
|
958
|
+
`,this.hasRendered=!0,this.setupEventListeners(),e){const o=Q(this);console.log("[GaitButton] rendered",o),this.dispatchEvent(new CustomEvent("gait-loaded",{detail:{attributes:o,dataId:this.dataId,dataAttributes:this.getDataAttributes(),label:"Gait",size:this.size,disabled:this.disabled,timestamp:new Date().toISOString()},bubbles:!0,composed:!0}))}}}customElements.get("gait-button")||customElements.define("gait-button",x),customElements.get("gait-button")||customElements.define("gait-button",x),typeof window<"u"&&(window.GaitButton=x,window.GaitButtonVersion=S,console.log(`[GaitButton] Version: ${S}`)),I.GAIT_BUTTON_VERSION=S,I.GaitButton=x,Object.defineProperty(I,Symbol.toStringTag,{value:"Module"})})(this.GaitButton=this.GaitButton||{});
|