recur-tw 0.9.8 → 0.9.9
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/dist/index.cjs +640 -709
- package/dist/index.d.cts +49 -38
- package/dist/index.d.ts +49 -38
- package/dist/index.js +640 -709
- package/dist/recur.umd.js +1027 -636
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { html, render, nothing } from 'lit-html';
|
|
1
2
|
import React, { createContext, useState, useCallback, useMemo, useContext, useEffect } from 'react';
|
|
2
3
|
import { jsx } from 'react/jsx-runtime';
|
|
3
4
|
|
|
@@ -604,14 +605,28 @@ var init_skeleton_loader = __esm({
|
|
|
604
605
|
}
|
|
605
606
|
});
|
|
606
607
|
|
|
608
|
+
// src/components/styles/payment-form-skeleton.css
|
|
609
|
+
var payment_form_skeleton_default;
|
|
610
|
+
var init_payment_form_skeleton = __esm({
|
|
611
|
+
"src/components/styles/payment-form-skeleton.css"() {
|
|
612
|
+
payment_form_skeleton_default = "/**\n * Payment Form Skeleton - Shadow DOM Styles\n * Loading skeleton that matches the structure of RecurPaymentForm\n */\n\n:host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n.payment-form-wrapper {\n max-width: 500px;\n margin: 0 auto;\n padding: 24px;\n background: white;\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n\n/* Header */\n.form-header {\n margin-bottom: 24px;\n}\n\n.form-title {\n font-size: 20px;\n font-weight: 600;\n color: #1a202c;\n margin: 0 0 4px 0;\n}\n\n.form-subtitle {\n font-size: 14px;\n color: #718096;\n margin: 0;\n}\n\n/* Sections */\n.form-section {\n margin-bottom: 24px;\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: #2d3748;\n margin: 0 0 12px 0;\n}\n\n.label {\n display: block;\n font-size: 14px;\n font-weight: 500;\n color: #2d3748;\n margin-bottom: 6px;\n}\n\n/* Skeleton base styles */\n.skeleton {\n background: linear-gradient(\n 90deg,\n #f0f0f0 0%,\n #e8e8e8 50%,\n #f0f0f0 100%\n );\n background-size: 200% 100%;\n animation: skeleton-loading 1.5s ease-in-out infinite;\n border-radius: 4px;\n}\n\n@keyframes skeleton-loading {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n\n/* Order Summary */\n.order-summary {\n background: #f7fafc;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 16px;\n}\n\n.order-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.order-row:last-child {\n margin-bottom: 0;\n}\n\n.order-row--total {\n padding-top: 12px;\n border-top: 1px solid #e2e8f0;\n}\n\n.order-label {\n font-size: 14px;\n color: #4a5568;\n}\n\n.skeleton-order-value {\n height: 20px;\n width: 80px;\n}\n\n.skeleton-order-total {\n height: 26px;\n width: 100px;\n}\n\n/* Coupon Skeleton */\n.coupon-section {\n padding: 12px 0;\n border-top: 1px solid #e2e8f0;\n margin-top: 12px;\n min-height: 36px;\n display: flex;\n align-items: center;\n}\n\n.skeleton-coupon-trigger {\n height: 20px;\n width: 88px;\n}\n\n/* Customer Info Display */\n.customer-info-display {\n background: #f7fafc;\n border: 1px solid #e2e8f0;\n border-radius: 6px;\n padding: 12px;\n margin-bottom: 8px;\n}\n\n.info-row {\n margin-bottom: 12px;\n}\n\n.info-row:last-child {\n margin-bottom: 0;\n}\n\n.info-label {\n display: block;\n font-size: 12px;\n line-height: 16px;\n color: #718096;\n margin-bottom: 2px;\n}\n\n.skeleton-info-value {\n height: 20px;\n width: 120px;\n}\n\n/* Card Fields */\n.card-field {\n margin-bottom: 16px;\n}\n\n.card-field:last-child {\n margin-bottom: 0;\n}\n\n.skeleton-input {\n height: 36px;\n width: 100%;\n border-radius: 6px;\n}\n\n.card-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n column-gap: 16px;\n}\n\n/* Submit Button - matches .recur-submit-button: padding 14px*2 + font 16px * line-height ~1.5 */\n.skeleton-button {\n height: 52px;\n width: 100%;\n border-radius: 6px;\n}\n\n/* Security Note */\n.security-note {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: #f7fafc;\n border-radius: 6px;\n margin-top: 16px;\n}\n\n.security-icon {\n color: #48bb78;\n}\n\n.security-text {\n font-size: 13px;\n color: #4a5568;\n margin: 0;\n}\n";
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
|
|
607
616
|
// src/components/base/payment-form-skeleton.ts
|
|
608
617
|
var payment_form_skeleton_exports = {};
|
|
609
618
|
__export(payment_form_skeleton_exports, {
|
|
610
619
|
RecurPaymentFormSkeleton: () => RecurPaymentFormSkeleton
|
|
611
620
|
});
|
|
612
|
-
var RecurPaymentFormSkeleton;
|
|
613
|
-
var
|
|
621
|
+
var lockIcon, RecurPaymentFormSkeleton;
|
|
622
|
+
var init_payment_form_skeleton2 = __esm({
|
|
614
623
|
"src/components/base/payment-form-skeleton.ts"() {
|
|
624
|
+
init_payment_form_skeleton();
|
|
625
|
+
lockIcon = html`
|
|
626
|
+
<svg class="security-icon" width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
627
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
628
|
+
</svg>
|
|
629
|
+
`;
|
|
615
630
|
RecurPaymentFormSkeleton = class extends HTMLElement {
|
|
616
631
|
constructor() {
|
|
617
632
|
super();
|
|
@@ -620,275 +635,106 @@ var init_payment_form_skeleton = __esm({
|
|
|
620
635
|
connectedCallback() {
|
|
621
636
|
this.render();
|
|
622
637
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
.form-header {
|
|
641
|
-
margin-bottom: 24px;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
.form-title {
|
|
645
|
-
font-size: 20px;
|
|
646
|
-
font-weight: 600;
|
|
647
|
-
color: #1a202c;
|
|
648
|
-
margin: 0 0 4px 0;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
.form-subtitle {
|
|
652
|
-
font-size: 14px;
|
|
653
|
-
color: #718096;
|
|
654
|
-
margin: 0;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
.form-section {
|
|
658
|
-
margin-bottom: 24px;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
.section-title {
|
|
662
|
-
font-size: 14px;
|
|
663
|
-
font-weight: 600;
|
|
664
|
-
color: #2d3748;
|
|
665
|
-
margin: 0 0 12px 0;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
.label {
|
|
669
|
-
display: block;
|
|
670
|
-
font-size: 14px;
|
|
671
|
-
font-weight: 500;
|
|
672
|
-
color: #2d3748;
|
|
673
|
-
margin-bottom: 6px;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/* Skeleton base styles */
|
|
677
|
-
.skeleton {
|
|
678
|
-
background: linear-gradient(
|
|
679
|
-
90deg,
|
|
680
|
-
#f0f0f0 0%,
|
|
681
|
-
#e8e8e8 50%,
|
|
682
|
-
#f0f0f0 100%
|
|
683
|
-
);
|
|
684
|
-
background-size: 200% 100%;
|
|
685
|
-
animation: skeleton-loading 1.5s ease-in-out infinite;
|
|
686
|
-
border-radius: 4px;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
@keyframes skeleton-loading {
|
|
690
|
-
0% {
|
|
691
|
-
background-position: 200% 0;
|
|
692
|
-
}
|
|
693
|
-
100% {
|
|
694
|
-
background-position: -200% 0;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/* Order Summary */
|
|
699
|
-
.order-summary {
|
|
700
|
-
background: #f7fafc;
|
|
701
|
-
border: 1px solid #e2e8f0;
|
|
702
|
-
border-radius: 8px;
|
|
703
|
-
padding: 16px;
|
|
704
|
-
margin-bottom: 24px;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
.order-summary-title {
|
|
708
|
-
font-size: 14px;
|
|
709
|
-
font-weight: 600;
|
|
710
|
-
color: #2d3748;
|
|
711
|
-
margin: 0 0 12px 0;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
.order-row {
|
|
715
|
-
display: flex;
|
|
716
|
-
justify-content: space-between;
|
|
717
|
-
align-items: center;
|
|
718
|
-
margin-bottom: 8px;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
.order-row:last-child {
|
|
722
|
-
margin-bottom: 0;
|
|
723
|
-
padding-top: 12px;
|
|
724
|
-
border-top: 1px solid #e2e8f0;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
.order-label {
|
|
728
|
-
font-size: 14px;
|
|
729
|
-
color: #4a5568;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
.skeleton-order-value {
|
|
733
|
-
height: 20px;
|
|
734
|
-
width: 80px;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
.skeleton-order-total {
|
|
738
|
-
height: 26px;
|
|
739
|
-
width: 100px;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/* Customer Info Display */
|
|
743
|
-
.customer-info-display {
|
|
744
|
-
background: #f7fafc;
|
|
745
|
-
border: 1px solid #e2e8f0;
|
|
746
|
-
border-radius: 6px;
|
|
747
|
-
padding: 12px;
|
|
748
|
-
margin-bottom: 8px;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
.info-row {
|
|
752
|
-
margin-bottom: 12px;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
.info-row:last-child {
|
|
756
|
-
margin-bottom: 0;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.info-label {
|
|
760
|
-
display: block;
|
|
761
|
-
font-size: 12px;
|
|
762
|
-
line-height: 16px;
|
|
763
|
-
color: #718096;
|
|
764
|
-
margin-bottom: 2px;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
.skeleton-info-value {
|
|
768
|
-
height: 20px;
|
|
769
|
-
width: 120px;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/* Card Fields */
|
|
773
|
-
.card-field {
|
|
774
|
-
margin-bottom: 16px;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
.card-field:last-child {
|
|
778
|
-
margin-bottom: 0;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
.skeleton-input {
|
|
782
|
-
height: 36px;
|
|
783
|
-
width: 100%;
|
|
784
|
-
border-radius: 6px;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
.card-row {
|
|
788
|
-
display: grid;
|
|
789
|
-
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
790
|
-
column-gap: 16px;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
/* Submit Button - matches .recur-submit-button: padding 14px*2 + font 16px * line-height ~1.5 */
|
|
794
|
-
.skeleton-button {
|
|
795
|
-
height: 52px;
|
|
796
|
-
width: 100%;
|
|
797
|
-
border-radius: 6px;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
/* Security Note */
|
|
801
|
-
.security-note {
|
|
802
|
-
display: flex;
|
|
803
|
-
align-items: center;
|
|
804
|
-
gap: 8px;
|
|
805
|
-
padding: 12px;
|
|
806
|
-
background: #f7fafc;
|
|
807
|
-
border-radius: 6px;
|
|
808
|
-
margin-top: 16px;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
.security-icon {
|
|
812
|
-
color: #48bb78;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
.security-text {
|
|
816
|
-
font-size: 13px;
|
|
817
|
-
color: #4a5568;
|
|
818
|
-
margin: 0;
|
|
819
|
-
}
|
|
820
|
-
</style>
|
|
821
|
-
|
|
822
|
-
<div class="payment-form-wrapper">
|
|
823
|
-
<!-- Header (matches payment-form.ts) -->
|
|
824
|
-
<div class="form-header">
|
|
825
|
-
<h2 class="form-title">\u8A02\u95B1\u4ED8\u6B3E</h2>
|
|
826
|
-
<p class="form-subtitle">\u8ACB\u586B\u5BEB\u4FE1\u7528\u5361\u8CC7\u8A0A\u5B8C\u6210\u8A02\u95B1</p>
|
|
827
|
-
</div>
|
|
828
|
-
|
|
829
|
-
<!-- Order Summary (matches createOrderSummarySection) -->
|
|
638
|
+
// ============================================
|
|
639
|
+
// Section Renderers
|
|
640
|
+
// ============================================
|
|
641
|
+
renderHeader() {
|
|
642
|
+
return html`
|
|
643
|
+
<div class="form-header">
|
|
644
|
+
<h2 class="form-title">訂閱付款</h2>
|
|
645
|
+
</div>
|
|
646
|
+
`;
|
|
647
|
+
}
|
|
648
|
+
renderOrderSummary() {
|
|
649
|
+
return html`
|
|
650
|
+
<div class="form-section">
|
|
651
|
+
<h3 class="section-title">訂單摘要</h3>
|
|
830
652
|
<div class="order-summary">
|
|
831
|
-
<h3 class="order-summary-title">\u8A02\u55AE\u6458\u8981</h3>
|
|
832
653
|
<div class="order-row">
|
|
833
|
-
<span class="order-label"
|
|
654
|
+
<span class="order-label">訂閱方案</span>
|
|
834
655
|
<div class="skeleton skeleton-order-value"></div>
|
|
835
656
|
</div>
|
|
836
657
|
<div class="order-row">
|
|
837
|
-
<span class="order-label"
|
|
658
|
+
<span class="order-label">計費週期</span>
|
|
838
659
|
<div class="skeleton skeleton-order-value"></div>
|
|
839
660
|
</div>
|
|
840
|
-
<div class="
|
|
841
|
-
<
|
|
661
|
+
<div class="coupon-section">
|
|
662
|
+
<div class="skeleton skeleton-coupon-trigger"></div>
|
|
663
|
+
</div>
|
|
664
|
+
<div class="order-row order-row--total">
|
|
665
|
+
<span class="order-label">總計</span>
|
|
842
666
|
<div class="skeleton skeleton-order-total"></div>
|
|
843
667
|
</div>
|
|
844
668
|
</div>
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
<
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
669
|
+
</div>
|
|
670
|
+
`;
|
|
671
|
+
}
|
|
672
|
+
renderCustomerInfo() {
|
|
673
|
+
return html`
|
|
674
|
+
<div class="form-section">
|
|
675
|
+
<h3 class="section-title">聯絡資訊</h3>
|
|
676
|
+
<div class="customer-info-display">
|
|
677
|
+
<div class="info-row">
|
|
678
|
+
<span class="info-label">姓名</span>
|
|
679
|
+
<div class="skeleton skeleton-info-value"></div>
|
|
680
|
+
</div>
|
|
681
|
+
<div class="info-row">
|
|
682
|
+
<span class="info-label">電子郵件</span>
|
|
683
|
+
<div class="skeleton skeleton-info-value" style="width: 160px;"></div>
|
|
858
684
|
</div>
|
|
859
685
|
</div>
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
686
|
+
</div>
|
|
687
|
+
`;
|
|
688
|
+
}
|
|
689
|
+
renderCardFields() {
|
|
690
|
+
return html`
|
|
691
|
+
<div class="form-section">
|
|
692
|
+
<h3 class="section-title">信用卡資訊</h3>
|
|
693
|
+
<div class="card-field">
|
|
694
|
+
<label class="label">卡號</label>
|
|
695
|
+
<div class="skeleton skeleton-input"></div>
|
|
696
|
+
</div>
|
|
697
|
+
<div class="card-row">
|
|
864
698
|
<div class="card-field">
|
|
865
|
-
<label class="label"
|
|
699
|
+
<label class="label">有效期限</label>
|
|
866
700
|
<div class="skeleton skeleton-input"></div>
|
|
867
701
|
</div>
|
|
868
|
-
<div class="card-
|
|
869
|
-
<
|
|
870
|
-
|
|
871
|
-
<div class="skeleton skeleton-input"></div>
|
|
872
|
-
</div>
|
|
873
|
-
<div class="card-field">
|
|
874
|
-
<label class="label">\u5B89\u5168\u78BC</label>
|
|
875
|
-
<div class="skeleton skeleton-input"></div>
|
|
876
|
-
</div>
|
|
702
|
+
<div class="card-field">
|
|
703
|
+
<label class="label">安全碼</label>
|
|
704
|
+
<div class="skeleton skeleton-input"></div>
|
|
877
705
|
</div>
|
|
878
706
|
</div>
|
|
707
|
+
</div>
|
|
708
|
+
`;
|
|
709
|
+
}
|
|
710
|
+
renderSubmitButton() {
|
|
711
|
+
return html`<div class="skeleton skeleton-button"></div>`;
|
|
712
|
+
}
|
|
713
|
+
renderSecurityNote() {
|
|
714
|
+
return html`
|
|
715
|
+
<div class="security-note">
|
|
716
|
+
${lockIcon}
|
|
717
|
+
<p class="security-text">您的付款資訊經過加密保護</p>
|
|
718
|
+
</div>
|
|
719
|
+
`;
|
|
720
|
+
}
|
|
721
|
+
// ============================================
|
|
722
|
+
// Main Render
|
|
723
|
+
// ============================================
|
|
724
|
+
render() {
|
|
725
|
+
const template = html`
|
|
726
|
+
<style>${payment_form_skeleton_default}</style>
|
|
879
727
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
</svg>
|
|
888
|
-
<p class="security-text">\u60A8\u7684\u4ED8\u6B3E\u8CC7\u8A0A\u7D93\u904E\u52A0\u5BC6\u4FDD\u8B77</p>
|
|
889
|
-
</div>
|
|
728
|
+
<div class="payment-form-wrapper">
|
|
729
|
+
${this.renderHeader()}
|
|
730
|
+
${this.renderOrderSummary()}
|
|
731
|
+
${this.renderCustomerInfo()}
|
|
732
|
+
${this.renderCardFields()}
|
|
733
|
+
${this.renderSubmitButton()}
|
|
734
|
+
${this.renderSecurityNote()}
|
|
890
735
|
</div>
|
|
891
736
|
`;
|
|
737
|
+
render(template, this.shadowRoot);
|
|
892
738
|
}
|
|
893
739
|
};
|
|
894
740
|
if (typeof window !== "undefined" && !customElements.get("recur-payment-form-skeleton")) {
|
|
@@ -1171,14 +1017,55 @@ var init_toast = __esm({
|
|
|
1171
1017
|
}
|
|
1172
1018
|
});
|
|
1173
1019
|
|
|
1020
|
+
// src/components/styles/payment-form-shadow.css
|
|
1021
|
+
var payment_form_shadow_default;
|
|
1022
|
+
var init_payment_form_shadow = __esm({
|
|
1023
|
+
"src/components/styles/payment-form-shadow.css"() {
|
|
1024
|
+
payment_form_shadow_default = "/**\n * Payment Form - Shadow DOM Styles\n * These styles are encapsulated within the Shadow DOM\n */\n\n:host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n.payment-form-wrapper {\n max-width: 500px;\n margin: 0 auto;\n padding: 24px;\n background: white;\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n touch-action: manipulation;\n container-type: inline-size;\n}\n\n.form-header {\n margin-bottom: 24px;\n}\n\n.form-title {\n font-size: 20px;\n font-weight: 600;\n color: #1a202c;\n margin: 0 0 4px 0;\n}\n\n.form-subtitle {\n font-size: 14px;\n color: #718096;\n margin: 0;\n}\n\n.form-section {\n margin-bottom: 24px;\n}\n\n.section-title {\n font-size: 14px;\n font-weight: 600;\n color: #2d3748;\n margin: 0 0 12px 0;\n}\n\n::slotted(*) {\n display: block;\n}\n\n.security-note {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: #f7fafc;\n border-radius: 6px;\n margin-top: 16px;\n}\n\n.security-icon {\n color: #48bb78;\n}\n\n.security-text {\n font-size: 13px;\n color: #4a5568;\n margin: 0;\n}\n";
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
// src/components/styles/payment-form-light.css
|
|
1029
|
+
var payment_form_light_default;
|
|
1030
|
+
var init_payment_form_light = __esm({
|
|
1031
|
+
"src/components/styles/payment-form-light.css"() {
|
|
1032
|
+
payment_form_light_default = "/**\n * Payment Form - Light DOM Styles\n * These styles are applied to slotted content in the Light DOM\n * Using BEM naming convention with 'recur-' prefix for isolation\n */\n\n/* ============================================\n * Order Summary Styles\n * ============================================ */\n.recur-order-summary {\n background: #f7fafc;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n padding: 16px;\n}\n\n.recur-order-summary__row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.recur-order-summary__row--total {\n margin-bottom: 0;\n padding-top: 12px;\n border-top: 1px solid #e2e8f0;\n}\n\n.recur-order-summary__label {\n font-size: 14px;\n color: #4a5568;\n}\n\n.recur-order-summary__value {\n font-size: 14px;\n line-height: 20px;\n font-weight: 500;\n color: #2d3748;\n}\n\n.recur-order-summary__total {\n font-size: 18px;\n line-height: 26px;\n font-weight: 700;\n color: #1a202c;\n}\n\n/* ============================================\n * Coupon Section Styles\n * ============================================ */\n.recur-coupon {\n padding: 12px 0;\n border-top: 1px solid #e2e8f0;\n margin-top: 12px;\n min-height: 36px;\n display: flex;\n align-items: center;\n}\n\n.recur-coupon__trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 36px;\n background: none;\n border: none;\n padding: 0;\n font-size: 14px;\n line-height: 1;\n color: #718096;\n cursor: pointer;\n transition: color 0.2s;\n}\n\n.recur-coupon__trigger:hover {\n color: #4a5568;\n}\n\n.recur-coupon__trigger-icon {\n width: 16px;\n height: 16px;\n}\n\n.recur-coupon__input-row {\n display: flex;\n gap: 8px;\n width: 100%;\n}\n\n.recur-coupon__input {\n flex: 1;\n height: 36px;\n padding: 0 12px;\n font-size: 14px;\n text-transform: uppercase;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n outline: none;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.recur-coupon__input::placeholder {\n text-transform: none;\n}\n\n.recur-coupon__input:focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.recur-coupon__btn {\n height: 36px;\n padding: 0 12px;\n font-size: 14px;\n font-weight: 500;\n border-radius: 6px;\n cursor: pointer;\n transition: background-color 0.2s, opacity 0.2s;\n white-space: nowrap;\n}\n\n.recur-coupon__btn--apply {\n background: #18181b;\n color: white;\n border: none;\n}\n\n.recur-coupon__btn--apply:hover:not(:disabled) {\n background: #27272a;\n}\n\n.recur-coupon__btn--apply:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.recur-coupon__btn--cancel {\n background: transparent;\n color: #718096;\n border: 1px solid #d1d5db;\n}\n\n.recur-coupon__btn--cancel:hover {\n background: #f7fafc;\n}\n\n.recur-coupon__error {\n width: 100%;\n margin-top: 8px;\n font-size: 13px;\n color: #dc2626;\n}\n\n.recur-coupon__applied {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n}\n\n.recur-coupon__applied-info {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n}\n\n.recur-coupon__applied-icon {\n width: 16px;\n height: 16px;\n color: #16a34a;\n flex-shrink: 0;\n margin-top: 2px;\n}\n\n.recur-coupon__applied-details {\n min-width: 0;\n}\n\n.recur-coupon__applied-name {\n font-size: 14px;\n font-weight: 500;\n color: #16a34a;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.recur-coupon__applied-code {\n font-size: 12px;\n color: #718096;\n}\n\n.recur-coupon__applied-amount {\n font-size: 14px;\n font-weight: 500;\n color: #16a34a;\n white-space: nowrap;\n}\n\n.recur-coupon__remove {\n background: none;\n border: none;\n padding: 4px;\n cursor: pointer;\n color: #9ca3af;\n border-radius: 4px;\n transition: color 0.2s, background-color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.recur-coupon__remove:hover {\n color: #6b7280;\n background: rgba(0, 0, 0, 0.05);\n}\n\n.recur-coupon__remove-icon {\n width: 14px;\n height: 14px;\n}\n\n.recur-coupon__spinner {\n width: 14px;\n height: 14px;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n animation: recur-spin 0.6s linear infinite;\n}\n\n/* Discount Row */\n.recur-order-summary__row--discount {\n color: #16a34a;\n}\n\n.recur-order-summary__row--discount .recur-order-summary__value {\n color: #16a34a;\n font-weight: 500;\n}\n\n/* ============================================\n * Customer Info Styles\n * ============================================ */\n.recur-info-display {\n background: #f7fafc;\n border: 1px solid #e2e8f0;\n border-radius: 6px;\n padding: 12px;\n margin-bottom: 8px;\n}\n\n.recur-info-row {\n margin-bottom: 12px;\n}\n\n.recur-info-row:last-child {\n margin-bottom: 0;\n}\n\n.recur-info-label {\n display: block;\n font-size: 12px;\n line-height: 16px;\n color: #718096;\n margin-bottom: 2px;\n}\n\n.recur-info-value {\n display: block;\n font-size: 14px;\n line-height: 20px;\n min-height: 20px;\n color: #2d3748;\n font-weight: 500;\n word-break: break-all;\n}\n\n.recur-form-group {\n margin-bottom: 16px;\n}\n\n.recur-form-label {\n display: block;\n font-size: 14px;\n font-weight: 500;\n color: #2d3748;\n margin-bottom: 6px;\n}\n\n.recur-form-input {\n width: 100%;\n padding: 10px 12px;\n font-size: 16px;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n transition: border-color 0.2s;\n box-sizing: border-box;\n}\n\n.recur-form-input:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.recur-form-input:hover:not(:focus) {\n border-color: #9ca3af;\n}\n\n/* ============================================\n * Card Fields Styles\n * ============================================ */\n.recur-card-field {\n margin-bottom: 16px;\n}\n\n.recur-card-field-label {\n display: block;\n font-size: 14px;\n font-weight: 500;\n color: #2d3748;\n margin-bottom: 6px;\n}\n\n.recur-payuni-iframe {\n height: 36px;\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n touch-action: manipulation;\n}\n\n.recur-card-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n column-gap: 16px;\n max-width: 100%;\n box-sizing: border-box;\n}\n\n.recur-card-field-container {\n position: relative;\n touch-action: manipulation;\n}\n\n.recur-card-skeleton {\n position: absolute;\n top: 0;\n left: 0;\n height: 36px;\n width: 100%;\n background: linear-gradient(90deg, #f0f0f0 0%, #e8e8e8 50%, #f0f0f0 100%);\n background-size: 200% 100%;\n animation: recur-skeleton-loading 1.5s ease-in-out infinite;\n border-radius: 4px;\n z-index: 1;\n pointer-events: none;\n}\n\n.recur-card-skeleton.hidden {\n display: none;\n}\n\n@keyframes recur-skeleton-loading {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n/* ============================================\n * Submit Button Styles\n * ============================================ */\n.recur-submit-button {\n width: 100%;\n padding: 14px 20px;\n font-size: 16px;\n font-weight: 500;\n color: #ffffff;\n background: #18181b;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n transition: background-color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n box-sizing: border-box;\n}\n\n.recur-submit-button:hover:not(:disabled) {\n background: #27272a;\n}\n\n.recur-submit-button:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.recur-submit-button.loading {\n cursor: wait;\n}\n\n.recur-loading-spinner {\n width: 16px;\n height: 16px;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n animation: recur-spin 0.6s linear infinite;\n}\n\n@keyframes recur-spin {\n to { transform: rotate(360deg); }\n}\n";
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1174
1036
|
// src/components/payment-form.ts
|
|
1175
1037
|
var payment_form_exports = {};
|
|
1176
1038
|
__export(payment_form_exports, {
|
|
1177
1039
|
RecurPaymentForm: () => RecurPaymentForm
|
|
1178
1040
|
});
|
|
1179
|
-
var RecurPaymentForm;
|
|
1041
|
+
var icons, RecurPaymentForm;
|
|
1180
1042
|
var init_payment_form = __esm({
|
|
1181
1043
|
"src/components/payment-form.ts"() {
|
|
1044
|
+
init_payment_form_shadow();
|
|
1045
|
+
init_payment_form_light();
|
|
1046
|
+
icons = {
|
|
1047
|
+
lock: html`
|
|
1048
|
+
<svg class="security-icon" width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1049
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
1050
|
+
</svg>
|
|
1051
|
+
`,
|
|
1052
|
+
tag: html`
|
|
1053
|
+
<svg class="recur-coupon__trigger-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1054
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
|
1055
|
+
</svg>
|
|
1056
|
+
`,
|
|
1057
|
+
tagApplied: html`
|
|
1058
|
+
<svg class="recur-coupon__applied-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1059
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
|
1060
|
+
</svg>
|
|
1061
|
+
`,
|
|
1062
|
+
close: html`
|
|
1063
|
+
<svg class="recur-coupon__remove-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1064
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
1065
|
+
</svg>
|
|
1066
|
+
`,
|
|
1067
|
+
spinner: html`<span class="recur-coupon__spinner"></span>`
|
|
1068
|
+
};
|
|
1182
1069
|
RecurPaymentForm = class extends HTMLElement {
|
|
1183
1070
|
constructor() {
|
|
1184
1071
|
super();
|
|
@@ -1186,6 +1073,12 @@ var init_payment_form = __esm({
|
|
|
1186
1073
|
__publicField(this, "customStyles");
|
|
1187
1074
|
__publicField(this, "_isInitializing", false);
|
|
1188
1075
|
__publicField(this, "_initializationAborted", false);
|
|
1076
|
+
// Coupon state
|
|
1077
|
+
__publicField(this, "_showCouponInput", false);
|
|
1078
|
+
__publicField(this, "_couponCode", "");
|
|
1079
|
+
__publicField(this, "_couponLoading", false);
|
|
1080
|
+
__publicField(this, "_couponError", null);
|
|
1081
|
+
__publicField(this, "_appliedCoupon", null);
|
|
1189
1082
|
this.containerId = this.getAttribute("container-id") || `recur-${Date.now()}`;
|
|
1190
1083
|
this.customStyles = this.getAttribute("custom-styles") || "";
|
|
1191
1084
|
this.attachShadow({ mode: "open" });
|
|
@@ -1203,183 +1096,129 @@ var init_payment_form = __esm({
|
|
|
1203
1096
|
submitBtn.parentNode?.replaceChild(newBtn, submitBtn);
|
|
1204
1097
|
}
|
|
1205
1098
|
}
|
|
1206
|
-
// 監聽 attribute 變化
|
|
1207
1099
|
static get observedAttributes() {
|
|
1208
|
-
return [
|
|
1100
|
+
return [
|
|
1101
|
+
"custom-styles",
|
|
1102
|
+
"customer-name",
|
|
1103
|
+
"customer-email",
|
|
1104
|
+
"product-name",
|
|
1105
|
+
"amount",
|
|
1106
|
+
"interval",
|
|
1107
|
+
"checkout-id",
|
|
1108
|
+
"publishable-key",
|
|
1109
|
+
"api-base-url"
|
|
1110
|
+
];
|
|
1209
1111
|
}
|
|
1210
1112
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
1211
|
-
if (
|
|
1113
|
+
if (oldValue === newValue) return;
|
|
1114
|
+
if (name === "custom-styles") {
|
|
1212
1115
|
this.customStyles = newValue || "";
|
|
1213
1116
|
this.updateCustomStyles();
|
|
1214
|
-
} else
|
|
1215
|
-
this.
|
|
1117
|
+
} else {
|
|
1118
|
+
this.updateOrderSummaryDOM();
|
|
1216
1119
|
}
|
|
1217
1120
|
}
|
|
1121
|
+
// ============================================
|
|
1122
|
+
// Styles
|
|
1123
|
+
// ============================================
|
|
1124
|
+
getShadowDOMStyles() {
|
|
1125
|
+
return payment_form_shadow_default;
|
|
1126
|
+
}
|
|
1127
|
+
getLightDOMStyles() {
|
|
1128
|
+
return payment_form_light_default;
|
|
1129
|
+
}
|
|
1130
|
+
// ============================================
|
|
1131
|
+
// Render Methods
|
|
1132
|
+
// ============================================
|
|
1218
1133
|
render() {
|
|
1219
|
-
this.
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
.payment-form-wrapper {
|
|
1227
|
-
max-width: 500px;
|
|
1228
|
-
margin: 0 auto;
|
|
1229
|
-
padding: 24px;
|
|
1230
|
-
background: white;
|
|
1231
|
-
border-radius: 12px;
|
|
1232
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
1233
|
-
/* Prevent double-tap zoom on iOS */
|
|
1234
|
-
touch-action: manipulation;
|
|
1235
|
-
/* Enable container queries for responsive layout */
|
|
1236
|
-
container-type: inline-size;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
.form-header {
|
|
1240
|
-
margin-bottom: 24px;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
.form-title {
|
|
1244
|
-
font-size: 20px;
|
|
1245
|
-
font-weight: 600;
|
|
1246
|
-
color: #1a202c;
|
|
1247
|
-
margin: 0 0 4px 0;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
.form-subtitle {
|
|
1251
|
-
font-size: 14px;
|
|
1252
|
-
color: #718096;
|
|
1253
|
-
margin: 0;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
.form-section {
|
|
1257
|
-
margin-bottom: 24px;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
.section-title {
|
|
1261
|
-
font-size: 14px;
|
|
1262
|
-
font-weight: 600;
|
|
1263
|
-
color: #2d3748;
|
|
1264
|
-
margin: 0 0 12px 0;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
::slotted(*) {
|
|
1268
|
-
display: block;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
.security-note {
|
|
1272
|
-
display: flex;
|
|
1273
|
-
align-items: center;
|
|
1274
|
-
gap: 8px;
|
|
1275
|
-
padding: 12px;
|
|
1276
|
-
background: #f7fafc;
|
|
1277
|
-
border-radius: 6px;
|
|
1278
|
-
margin-top: 16px;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
.security-icon {
|
|
1282
|
-
color: #48bb78;
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
.security-text {
|
|
1286
|
-
font-size: 13px;
|
|
1287
|
-
color: #4a5568;
|
|
1288
|
-
margin: 0;
|
|
1289
|
-
}
|
|
1290
|
-
</style>
|
|
1134
|
+
this.renderShadowDOM();
|
|
1135
|
+
this.renderLightDOM();
|
|
1136
|
+
}
|
|
1137
|
+
renderShadowDOM() {
|
|
1138
|
+
const template = html`
|
|
1139
|
+
<style>${this.getShadowDOMStyles()}</style>
|
|
1291
1140
|
|
|
1292
1141
|
<div class="payment-form-wrapper">
|
|
1293
1142
|
<div class="form-header">
|
|
1294
|
-
<h2 class="form-title"
|
|
1295
|
-
<p class="form-subtitle">\u8ACB\u586B\u5BEB\u4FE1\u7528\u5361\u8CC7\u8A0A\u5B8C\u6210\u8A02\u95B1</p>
|
|
1143
|
+
<h2 class="form-title">訂閱付款</h2>
|
|
1296
1144
|
</div>
|
|
1297
1145
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1146
|
+
<div class="form-section">
|
|
1147
|
+
<h3 class="section-title">訂單摘要</h3>
|
|
1148
|
+
<slot name="order-summary"></slot>
|
|
1149
|
+
</div>
|
|
1300
1150
|
|
|
1301
1151
|
<div class="form-section">
|
|
1302
|
-
<h3 class="section-title"
|
|
1303
|
-
<!-- Slot for customer info fields (Light DOM) -->
|
|
1152
|
+
<h3 class="section-title">聯絡資訊</h3>
|
|
1304
1153
|
<slot name="customer-info"></slot>
|
|
1305
1154
|
</div>
|
|
1306
1155
|
|
|
1307
1156
|
<div class="form-section">
|
|
1308
|
-
<h3 class="section-title"
|
|
1309
|
-
<!-- Slot for PAYUNi card fields (Light DOM) -->
|
|
1157
|
+
<h3 class="section-title">信用卡資訊</h3>
|
|
1310
1158
|
<slot name="card-fields"></slot>
|
|
1311
1159
|
</div>
|
|
1312
1160
|
|
|
1313
|
-
<!-- Slot for submit button (Light DOM) -->
|
|
1314
1161
|
<slot name="actions"></slot>
|
|
1315
1162
|
|
|
1316
1163
|
<div class="security-note">
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
</svg>
|
|
1320
|
-
<p class="security-text">\u60A8\u7684\u4ED8\u6B3E\u8CC7\u8A0A\u7D93\u904E\u52A0\u5BC6\u4FDD\u8B77</p>
|
|
1164
|
+
${icons.lock}
|
|
1165
|
+
<p class="security-text">您的付款資訊經過加密保護</p>
|
|
1321
1166
|
</div>
|
|
1322
1167
|
</div>
|
|
1323
1168
|
`;
|
|
1324
|
-
this.
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1169
|
+
render(template, this.shadowRoot);
|
|
1170
|
+
}
|
|
1171
|
+
renderLightDOM() {
|
|
1172
|
+
const template = html`
|
|
1173
|
+
<style id="${this.containerId}-styles">
|
|
1174
|
+
${this.getLightDOMStyles()}
|
|
1175
|
+
${this.getCustomStyles()}
|
|
1176
|
+
</style>
|
|
1177
|
+
|
|
1178
|
+
<div slot="order-summary">
|
|
1179
|
+
${this.renderOrderSummary()}
|
|
1180
|
+
</div>
|
|
1181
|
+
|
|
1182
|
+
<div slot="customer-info">
|
|
1183
|
+
${this.renderCustomerInfo()}
|
|
1184
|
+
</div>
|
|
1185
|
+
|
|
1186
|
+
<div slot="card-fields">
|
|
1187
|
+
${this.renderCardFields()}
|
|
1188
|
+
</div>
|
|
1189
|
+
|
|
1190
|
+
<div slot="actions">
|
|
1191
|
+
${this.renderActions()}
|
|
1192
|
+
</div>
|
|
1193
|
+
`;
|
|
1194
|
+
render(template, this);
|
|
1195
|
+
}
|
|
1196
|
+
getCustomStyles() {
|
|
1197
|
+
return `
|
|
1198
|
+
/* PAYUNi iframe container height */
|
|
1332
1199
|
#${this.containerId}-card-no,
|
|
1333
1200
|
#${this.containerId}-card-exp,
|
|
1334
1201
|
#${this.containerId}-card-cvc {
|
|
1335
1202
|
height: 36px !important;
|
|
1336
1203
|
}
|
|
1204
|
+
${this.customStyles}
|
|
1337
1205
|
`;
|
|
1338
|
-
styleElement.textContent = this.customStyles + "\n" + idSpecificStyles;
|
|
1339
|
-
this.appendChild(styleElement);
|
|
1340
|
-
const orderSummary = this.createOrderSummarySection();
|
|
1341
|
-
orderSummary.slot = "order-summary";
|
|
1342
|
-
this.appendChild(orderSummary);
|
|
1343
|
-
const customerInfo = this.createCustomerInfoSection();
|
|
1344
|
-
customerInfo.slot = "customer-info";
|
|
1345
|
-
this.appendChild(customerInfo);
|
|
1346
|
-
const cardFields = this.createCardFieldsSection();
|
|
1347
|
-
cardFields.slot = "card-fields";
|
|
1348
|
-
this.appendChild(cardFields);
|
|
1349
|
-
const actions = this.createActionsSection();
|
|
1350
|
-
actions.slot = "actions";
|
|
1351
|
-
this.appendChild(actions);
|
|
1352
|
-
}
|
|
1353
|
-
updateCustomerInfoSection() {
|
|
1354
|
-
const existingSection = this.querySelector('[slot="customer-info"]');
|
|
1355
|
-
if (existingSection) {
|
|
1356
|
-
const newSection = this.createCustomerInfoSection();
|
|
1357
|
-
newSection.slot = "customer-info";
|
|
1358
|
-
existingSection.replaceWith(newSection);
|
|
1359
|
-
}
|
|
1360
1206
|
}
|
|
1361
1207
|
updateCustomStyles() {
|
|
1362
|
-
const styleElement = this.querySelector(`#${this.containerId}-
|
|
1208
|
+
const styleElement = this.querySelector(`#${this.containerId}-styles`);
|
|
1363
1209
|
if (styleElement) {
|
|
1364
|
-
|
|
1365
|
-
/* PAYUNi iframe \u5BB9\u5668\u9AD8\u5EA6\uFF08\u4F7F\u7528\u5BE6\u969B\u7684 container ID\uFF09 */
|
|
1366
|
-
#${this.containerId}-card-no,
|
|
1367
|
-
#${this.containerId}-card-exp,
|
|
1368
|
-
#${this.containerId}-card-cvc {
|
|
1369
|
-
height: 36px !important;
|
|
1370
|
-
}
|
|
1371
|
-
`;
|
|
1372
|
-
styleElement.textContent = this.customStyles + "\n" + idSpecificStyles;
|
|
1210
|
+
styleElement.textContent = this.getLightDOMStyles() + this.getCustomStyles();
|
|
1373
1211
|
}
|
|
1374
1212
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1213
|
+
// ============================================
|
|
1214
|
+
// Section Renderers (lit-html)
|
|
1215
|
+
// ============================================
|
|
1216
|
+
renderOrderSummary() {
|
|
1378
1217
|
const productName = this.getAttribute("product-name");
|
|
1379
1218
|
const amount = this.getAttribute("amount");
|
|
1380
1219
|
const interval = this.getAttribute("interval");
|
|
1381
1220
|
if (!productName || !amount) {
|
|
1382
|
-
return
|
|
1221
|
+
return html`<div class="recur-order-summary" style="display: none;"></div>`;
|
|
1383
1222
|
}
|
|
1384
1223
|
const intervalMap = {
|
|
1385
1224
|
"day": "\u65E5",
|
|
@@ -1388,275 +1227,444 @@ var init_payment_form = __esm({
|
|
|
1388
1227
|
"year": "\u5E74"
|
|
1389
1228
|
};
|
|
1390
1229
|
const periodText = interval ? intervalMap[interval] || interval : "";
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
.order-summary-title {
|
|
1402
|
-
font-size: 14px;
|
|
1403
|
-
font-weight: 600;
|
|
1404
|
-
color: #2d3748;
|
|
1405
|
-
margin: 0 0 12px 0;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
.order-summary-item {
|
|
1409
|
-
display: flex;
|
|
1410
|
-
justify-content: space-between;
|
|
1411
|
-
align-items: center;
|
|
1412
|
-
margin-bottom: 8px;
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
.order-summary-item:last-child {
|
|
1416
|
-
margin-bottom: 0;
|
|
1417
|
-
padding-top: 12px;
|
|
1418
|
-
border-top: 1px solid #e2e8f0;
|
|
1419
|
-
}
|
|
1230
|
+
const originalAmount = Number(amount);
|
|
1231
|
+
const discountAmount = this._appliedCoupon?.discountAmount || 0;
|
|
1232
|
+
const finalAmount = originalAmount - discountAmount;
|
|
1233
|
+
return html`
|
|
1234
|
+
<div class="recur-order-summary" id="${this.containerId}-order-summary">
|
|
1235
|
+
<div class="recur-order-summary__row">
|
|
1236
|
+
<span class="recur-order-summary__label">訂閱方案</span>
|
|
1237
|
+
<span class="recur-order-summary__value">${productName}</span>
|
|
1238
|
+
</div>
|
|
1420
1239
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1240
|
+
${periodText ? html`
|
|
1241
|
+
<div class="recur-order-summary__row">
|
|
1242
|
+
<span class="recur-order-summary__label">計費週期</span>
|
|
1243
|
+
<span class="recur-order-summary__value">每${periodText}</span>
|
|
1244
|
+
</div>
|
|
1245
|
+
` : nothing}
|
|
1425
1246
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
font-weight: 500;
|
|
1430
|
-
color: #2d3748;
|
|
1431
|
-
}
|
|
1247
|
+
<div class="recur-coupon" id="${this.containerId}-coupon">
|
|
1248
|
+
${this.renderCoupon()}
|
|
1249
|
+
</div>
|
|
1432
1250
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1251
|
+
${discountAmount > 0 ? html`
|
|
1252
|
+
<div class="recur-order-summary__row">
|
|
1253
|
+
<span class="recur-order-summary__label">小計</span>
|
|
1254
|
+
<span class="recur-order-summary__value">NT$ ${originalAmount.toLocaleString()}</span>
|
|
1255
|
+
</div>
|
|
1256
|
+
<div class="recur-order-summary__row recur-order-summary__row--discount">
|
|
1257
|
+
<span class="recur-order-summary__label">折扣</span>
|
|
1258
|
+
<span class="recur-order-summary__value">-NT$ ${discountAmount.toLocaleString()}</span>
|
|
1259
|
+
</div>
|
|
1260
|
+
` : nothing}
|
|
1440
1261
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
<span class="order-summary-value">${productName}</span>
|
|
1445
|
-
</div>
|
|
1446
|
-
${periodText ? `
|
|
1447
|
-
<div class="order-summary-item">
|
|
1448
|
-
<span class="order-summary-label">\u8A08\u8CBB\u9031\u671F</span>
|
|
1449
|
-
<span class="order-summary-value">\u6BCF${periodText}</span>
|
|
1262
|
+
<div class="recur-order-summary__row recur-order-summary__row--total">
|
|
1263
|
+
<span class="recur-order-summary__label">總計</span>
|
|
1264
|
+
<span class="recur-order-summary__total">NT$ ${finalAmount.toLocaleString()}</span>
|
|
1450
1265
|
</div>
|
|
1451
|
-
` : ""}
|
|
1452
|
-
<div class="order-summary-item">
|
|
1453
|
-
<span class="order-summary-label">\u7E3D\u8A08</span>
|
|
1454
|
-
<span class="order-summary-total">NT$ ${Number(amount).toLocaleString()}</span>
|
|
1455
1266
|
</div>
|
|
1456
1267
|
`;
|
|
1457
|
-
return section;
|
|
1458
1268
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
margin-bottom: 8px;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
.recur-info-row {
|
|
1476
|
-
margin-bottom: 12px;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
.recur-info-row:last-child {
|
|
1480
|
-
margin-bottom: 0;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
.recur-info-label {
|
|
1484
|
-
display: block;
|
|
1485
|
-
font-size: 12px;
|
|
1486
|
-
line-height: 16px;
|
|
1487
|
-
color: #718096;
|
|
1488
|
-
margin-bottom: 2px;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
.recur-info-value {
|
|
1492
|
-
display: block;
|
|
1493
|
-
font-size: 14px;
|
|
1494
|
-
line-height: 20px;
|
|
1495
|
-
min-height: 20px;
|
|
1496
|
-
color: #2d3748;
|
|
1497
|
-
font-weight: 500;
|
|
1498
|
-
word-break: break-all;
|
|
1499
|
-
}
|
|
1500
|
-
</style>
|
|
1501
|
-
|
|
1502
|
-
<div class="recur-info-display">
|
|
1503
|
-
${customerName ? `
|
|
1504
|
-
<div class="recur-info-row">
|
|
1505
|
-
<span class="recur-info-label">\u59D3\u540D</span>
|
|
1506
|
-
<span class="recur-info-value">${customerName}</span>
|
|
1269
|
+
renderCoupon() {
|
|
1270
|
+
if (this._appliedCoupon) {
|
|
1271
|
+
return html`
|
|
1272
|
+
<div class="recur-coupon__applied">
|
|
1273
|
+
<div class="recur-coupon__applied-info">
|
|
1274
|
+
${icons.tagApplied}
|
|
1275
|
+
<div class="recur-coupon__applied-details">
|
|
1276
|
+
<div class="recur-coupon__applied-name">
|
|
1277
|
+
${this._appliedCoupon.couponName || this._appliedCoupon.code}
|
|
1278
|
+
</div>
|
|
1279
|
+
${this._appliedCoupon.couponName ? html`
|
|
1280
|
+
<div class="recur-coupon__applied-code">代碼:${this._appliedCoupon.code}</div>
|
|
1281
|
+
` : nothing}
|
|
1507
1282
|
</div>
|
|
1508
|
-
` : ""}
|
|
1509
|
-
<div class="recur-info-row">
|
|
1510
|
-
<span class="recur-info-label">\u96FB\u5B50\u90F5\u4EF6</span>
|
|
1511
|
-
<span class="recur-info-value">${customerEmail}</span>
|
|
1512
1283
|
</div>
|
|
1284
|
+
<span class="recur-coupon__applied-amount">
|
|
1285
|
+
-NT$ ${this._appliedCoupon.discountAmount.toLocaleString()}
|
|
1286
|
+
</span>
|
|
1287
|
+
<button
|
|
1288
|
+
type="button"
|
|
1289
|
+
class="recur-coupon__remove"
|
|
1290
|
+
?disabled=${this._couponLoading}
|
|
1291
|
+
@click=${() => this.removeCoupon()}
|
|
1292
|
+
>
|
|
1293
|
+
${this._couponLoading ? icons.spinner : icons.close}
|
|
1294
|
+
</button>
|
|
1513
1295
|
</div>
|
|
1514
1296
|
`;
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
}
|
|
1551
|
-
</style>
|
|
1552
|
-
|
|
1553
|
-
<div class="recur-form-group">
|
|
1554
|
-
<label class="recur-form-label" for="${this.containerId}-email">Email</label>
|
|
1555
|
-
<input
|
|
1556
|
-
type="email"
|
|
1557
|
-
id="${this.containerId}-email"
|
|
1558
|
-
class="recur-form-input"
|
|
1559
|
-
placeholder="your@email.com"
|
|
1560
|
-
required
|
|
1561
|
-
/>
|
|
1297
|
+
}
|
|
1298
|
+
if (this._showCouponInput) {
|
|
1299
|
+
return html`
|
|
1300
|
+
<div style="width: 100%;">
|
|
1301
|
+
<div class="recur-coupon__input-row">
|
|
1302
|
+
<input
|
|
1303
|
+
type="text"
|
|
1304
|
+
class="recur-coupon__input"
|
|
1305
|
+
id="${this.containerId}-coupon-input"
|
|
1306
|
+
placeholder="輸入優惠碼"
|
|
1307
|
+
.value=${this._couponCode}
|
|
1308
|
+
?disabled=${this._couponLoading}
|
|
1309
|
+
@input=${(e) => this.handleCouponInputChange(e)}
|
|
1310
|
+
@keydown=${(e) => this.handleCouponKeydown(e)}
|
|
1311
|
+
/>
|
|
1312
|
+
<button
|
|
1313
|
+
type="button"
|
|
1314
|
+
class="recur-coupon__btn recur-coupon__btn--apply"
|
|
1315
|
+
?disabled=${this._couponLoading || !this._couponCode.trim()}
|
|
1316
|
+
@click=${() => this.applyCoupon()}
|
|
1317
|
+
>
|
|
1318
|
+
${this._couponLoading ? icons.spinner : "\u5957\u7528"}
|
|
1319
|
+
</button>
|
|
1320
|
+
<button
|
|
1321
|
+
type="button"
|
|
1322
|
+
class="recur-coupon__btn recur-coupon__btn--cancel"
|
|
1323
|
+
?disabled=${this._couponLoading}
|
|
1324
|
+
@click=${() => this.cancelCouponInput()}
|
|
1325
|
+
>
|
|
1326
|
+
取消
|
|
1327
|
+
</button>
|
|
1328
|
+
</div>
|
|
1329
|
+
${this._couponError ? html`
|
|
1330
|
+
<div class="recur-coupon__error">${this._couponError}</div>
|
|
1331
|
+
` : nothing}
|
|
1562
1332
|
</div>
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1333
|
+
`;
|
|
1334
|
+
}
|
|
1335
|
+
return html`
|
|
1336
|
+
<button
|
|
1337
|
+
type="button"
|
|
1338
|
+
class="recur-coupon__trigger"
|
|
1339
|
+
@click=${() => this.showCouponInput()}
|
|
1340
|
+
>
|
|
1341
|
+
${icons.tag}
|
|
1342
|
+
有優惠碼?
|
|
1343
|
+
</button>
|
|
1344
|
+
`;
|
|
1345
|
+
}
|
|
1346
|
+
// Coupon event handlers
|
|
1347
|
+
showCouponInput() {
|
|
1348
|
+
this._showCouponInput = true;
|
|
1349
|
+
this._couponError = null;
|
|
1350
|
+
this.updateCouponSectionDOM();
|
|
1351
|
+
requestAnimationFrame(() => {
|
|
1352
|
+
const input = document.getElementById(`${this.containerId}-coupon-input`);
|
|
1353
|
+
input?.focus();
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
cancelCouponInput() {
|
|
1357
|
+
this._showCouponInput = false;
|
|
1358
|
+
this._couponCode = "";
|
|
1359
|
+
this._couponError = null;
|
|
1360
|
+
this.updateCouponSectionDOM();
|
|
1361
|
+
}
|
|
1362
|
+
handleCouponInputChange(e) {
|
|
1363
|
+
this._couponCode = e.target.value.toUpperCase();
|
|
1364
|
+
this._couponError = null;
|
|
1365
|
+
this.updateApplyButtonState();
|
|
1366
|
+
}
|
|
1367
|
+
handleCouponKeydown(e) {
|
|
1368
|
+
if (e.key === "Enter") {
|
|
1369
|
+
e.preventDefault();
|
|
1370
|
+
this.applyCoupon();
|
|
1371
|
+
} else if (e.key === "Escape") {
|
|
1372
|
+
this.cancelCouponInput();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
updateApplyButtonState() {
|
|
1376
|
+
const btn = this.querySelector(".recur-coupon__btn--apply");
|
|
1377
|
+
if (btn) {
|
|
1378
|
+
btn.disabled = this._couponLoading || !this._couponCode.trim();
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
// Direct DOM manipulation for coupon section updates (avoids lit-html tracking issues)
|
|
1382
|
+
updateCouponSectionDOM() {
|
|
1383
|
+
const container = document.getElementById(`${this.containerId}-coupon`);
|
|
1384
|
+
if (!container) return;
|
|
1385
|
+
if (this._appliedCoupon) {
|
|
1386
|
+
container.innerHTML = this.getCouponAppliedHTML();
|
|
1387
|
+
this.bindCouponAppliedEvents(container);
|
|
1388
|
+
} else if (this._showCouponInput) {
|
|
1389
|
+
container.innerHTML = this.getCouponInputHTML();
|
|
1390
|
+
this.bindCouponInputEvents(container);
|
|
1391
|
+
} else {
|
|
1392
|
+
container.innerHTML = this.getCouponTriggerHTML();
|
|
1393
|
+
this.bindCouponTriggerEvents(container);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
getCouponTriggerHTML() {
|
|
1397
|
+
return `
|
|
1398
|
+
<button type="button" class="recur-coupon__trigger">
|
|
1399
|
+
<svg class="recur-coupon__trigger-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1400
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
|
1401
|
+
</svg>
|
|
1402
|
+
\u6709\u512A\u60E0\u78BC\uFF1F
|
|
1403
|
+
</button>
|
|
1404
|
+
`;
|
|
1405
|
+
}
|
|
1406
|
+
getCouponInputHTML() {
|
|
1407
|
+
const errorHTML = this._couponError ? `<div class="recur-coupon__error">${this._couponError}</div>` : "";
|
|
1408
|
+
return `
|
|
1409
|
+
<div style="width: 100%;">
|
|
1410
|
+
<div class="recur-coupon__input-row">
|
|
1566
1411
|
<input
|
|
1567
1412
|
type="text"
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
placeholder="\
|
|
1571
|
-
value="${
|
|
1572
|
-
|
|
1413
|
+
class="recur-coupon__input"
|
|
1414
|
+
id="${this.containerId}-coupon-input"
|
|
1415
|
+
placeholder="\u8F38\u5165\u512A\u60E0\u78BC"
|
|
1416
|
+
value="${this._couponCode}"
|
|
1417
|
+
${this._couponLoading ? "disabled" : ""}
|
|
1573
1418
|
/>
|
|
1419
|
+
<button
|
|
1420
|
+
type="button"
|
|
1421
|
+
class="recur-coupon__btn recur-coupon__btn--apply"
|
|
1422
|
+
${this._couponLoading || !this._couponCode.trim() ? "disabled" : ""}
|
|
1423
|
+
>
|
|
1424
|
+
${this._couponLoading ? '<span class="recur-coupon__spinner"></span>' : "\u5957\u7528"}
|
|
1425
|
+
</button>
|
|
1426
|
+
<button
|
|
1427
|
+
type="button"
|
|
1428
|
+
class="recur-coupon__btn recur-coupon__btn--cancel"
|
|
1429
|
+
${this._couponLoading ? "disabled" : ""}
|
|
1430
|
+
>
|
|
1431
|
+
\u53D6\u6D88
|
|
1432
|
+
</button>
|
|
1574
1433
|
</div>
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1434
|
+
${errorHTML}
|
|
1435
|
+
</div>
|
|
1436
|
+
`;
|
|
1578
1437
|
}
|
|
1579
|
-
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
<
|
|
1584
|
-
|
|
1585
|
-
|
|
1438
|
+
getCouponAppliedHTML() {
|
|
1439
|
+
const coupon = this._appliedCoupon;
|
|
1440
|
+
const codeHTML = coupon.couponName ? `<div class="recur-coupon__applied-code">\u4EE3\u78BC\uFF1A${coupon.code}</div>` : "";
|
|
1441
|
+
return `
|
|
1442
|
+
<div class="recur-coupon__applied">
|
|
1443
|
+
<div class="recur-coupon__applied-info">
|
|
1444
|
+
<svg class="recur-coupon__applied-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1445
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
|
1446
|
+
</svg>
|
|
1447
|
+
<div class="recur-coupon__applied-details">
|
|
1448
|
+
<div class="recur-coupon__applied-name">${coupon.couponName || coupon.code}</div>
|
|
1449
|
+
${codeHTML}
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
<span class="recur-coupon__applied-amount">-NT$ ${coupon.discountAmount.toLocaleString()}</span>
|
|
1453
|
+
<button type="button" class="recur-coupon__remove" ${this._couponLoading ? "disabled" : ""}>
|
|
1454
|
+
${this._couponLoading ? '<span class="recur-coupon__spinner"></span>' : '<svg class="recur-coupon__remove-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>'}
|
|
1455
|
+
</button>
|
|
1456
|
+
</div>
|
|
1457
|
+
`;
|
|
1458
|
+
}
|
|
1459
|
+
bindCouponTriggerEvents(container) {
|
|
1460
|
+
const btn = container.querySelector(".recur-coupon__trigger");
|
|
1461
|
+
btn?.addEventListener("click", () => this.showCouponInput());
|
|
1462
|
+
}
|
|
1463
|
+
bindCouponInputEvents(container) {
|
|
1464
|
+
const input = container.querySelector(".recur-coupon__input");
|
|
1465
|
+
const applyBtn = container.querySelector(".recur-coupon__btn--apply");
|
|
1466
|
+
const cancelBtn = container.querySelector(".recur-coupon__btn--cancel");
|
|
1467
|
+
input?.addEventListener("input", (e) => this.handleCouponInputChange(e));
|
|
1468
|
+
input?.addEventListener("keydown", (e) => this.handleCouponKeydown(e));
|
|
1469
|
+
applyBtn?.addEventListener("click", () => this.applyCoupon());
|
|
1470
|
+
cancelBtn?.addEventListener("click", () => this.cancelCouponInput());
|
|
1471
|
+
}
|
|
1472
|
+
bindCouponAppliedEvents(container) {
|
|
1473
|
+
const removeBtn = container.querySelector(".recur-coupon__remove");
|
|
1474
|
+
removeBtn?.addEventListener("click", () => this.removeCoupon());
|
|
1475
|
+
}
|
|
1476
|
+
updateOrderSummaryDOM() {
|
|
1477
|
+
const summaryContainer = document.getElementById(`${this.containerId}-order-summary`);
|
|
1478
|
+
if (!summaryContainer) return;
|
|
1479
|
+
const amount = Number(this.getAttribute("amount") || 0);
|
|
1480
|
+
const discountAmount = this._appliedCoupon?.discountAmount || 0;
|
|
1481
|
+
const finalAmount = amount - discountAmount;
|
|
1482
|
+
let discountRow = summaryContainer.querySelector(".recur-order-summary__row--discount");
|
|
1483
|
+
const totalRow = summaryContainer.querySelector(".recur-order-summary__row--total");
|
|
1484
|
+
if (discountAmount > 0) {
|
|
1485
|
+
if (!discountRow && totalRow) {
|
|
1486
|
+
const subtotalHTML = `
|
|
1487
|
+
<div class="recur-order-summary__row">
|
|
1488
|
+
<span class="recur-order-summary__label">\u5C0F\u8A08</span>
|
|
1489
|
+
<span class="recur-order-summary__value">NT$ ${amount.toLocaleString()}</span>
|
|
1490
|
+
</div>
|
|
1491
|
+
`;
|
|
1492
|
+
const discountHTML = `
|
|
1493
|
+
<div class="recur-order-summary__row recur-order-summary__row--discount">
|
|
1494
|
+
<span class="recur-order-summary__label">\u6298\u6263</span>
|
|
1495
|
+
<span class="recur-order-summary__value">-NT$ ${discountAmount.toLocaleString()}</span>
|
|
1496
|
+
</div>
|
|
1497
|
+
`;
|
|
1498
|
+
totalRow.insertAdjacentHTML("beforebegin", subtotalHTML + discountHTML);
|
|
1499
|
+
}
|
|
1500
|
+
} else {
|
|
1501
|
+
const subtotalRow = summaryContainer.querySelector(".recur-order-summary__row:not(.recur-order-summary__row--total):last-of-type");
|
|
1502
|
+
discountRow?.remove();
|
|
1503
|
+
if (subtotalRow?.querySelector(".recur-order-summary__label")?.textContent === "\u5C0F\u8A08") {
|
|
1504
|
+
subtotalRow.remove();
|
|
1505
|
+
}
|
|
1586
1506
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
font-size: 14px;
|
|
1591
|
-
font-weight: 500;
|
|
1592
|
-
color: #2d3748;
|
|
1593
|
-
margin-bottom: 6px;
|
|
1507
|
+
const totalValue = totalRow?.querySelector(".recur-order-summary__total");
|
|
1508
|
+
if (totalValue) {
|
|
1509
|
+
totalValue.textContent = `NT$ ${finalAmount.toLocaleString()}`;
|
|
1594
1510
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1511
|
+
this.updateCouponSectionDOM();
|
|
1512
|
+
}
|
|
1513
|
+
// ============================================
|
|
1514
|
+
// Coupon API Methods
|
|
1515
|
+
// ============================================
|
|
1516
|
+
async applyCoupon() {
|
|
1517
|
+
const code = this._couponCode.trim();
|
|
1518
|
+
if (!code) {
|
|
1519
|
+
this._couponError = "\u8ACB\u8F38\u5165\u512A\u60E0\u78BC";
|
|
1520
|
+
this.updateCouponSectionDOM();
|
|
1521
|
+
return;
|
|
1604
1522
|
}
|
|
1605
|
-
|
|
1606
|
-
.
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
box-sizing: border-box;
|
|
1523
|
+
const checkoutId = this.getAttribute("checkout-id");
|
|
1524
|
+
const apiBaseUrl = this.getAttribute("api-base-url") || "";
|
|
1525
|
+
if (!checkoutId) {
|
|
1526
|
+
this._couponError = "\u7121\u6CD5\u5957\u7528\u512A\u60E0\u78BC";
|
|
1527
|
+
this.updateCouponSectionDOM();
|
|
1528
|
+
return;
|
|
1612
1529
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
.
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1530
|
+
this._couponLoading = true;
|
|
1531
|
+
this._couponError = null;
|
|
1532
|
+
this.updateCouponSectionDOM();
|
|
1533
|
+
try {
|
|
1534
|
+
const response = await fetch(`${apiBaseUrl}/api/checkout/${checkoutId}/coupon`, {
|
|
1535
|
+
method: "POST",
|
|
1536
|
+
headers: { "Content-Type": "application/json" },
|
|
1537
|
+
body: JSON.stringify({ code })
|
|
1538
|
+
});
|
|
1539
|
+
const data = await response.json();
|
|
1540
|
+
if (!response.ok) {
|
|
1541
|
+
this._couponError = data.error?.message || "\u512A\u60E0\u78BC\u7121\u6548";
|
|
1542
|
+
this._couponLoading = false;
|
|
1543
|
+
this.updateCouponSectionDOM();
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
this._appliedCoupon = {
|
|
1547
|
+
code: data.discount.code,
|
|
1548
|
+
couponName: data.discount.couponName,
|
|
1549
|
+
discountType: data.discount.discountType,
|
|
1550
|
+
discountAmount: data.discount.discountAmount,
|
|
1551
|
+
duration: data.discount.duration,
|
|
1552
|
+
durationMonths: data.discount.durationMonths,
|
|
1553
|
+
bonusTrialDays: data.discount.bonusTrialDays,
|
|
1554
|
+
bonusMonths: data.discount.bonusMonths
|
|
1555
|
+
};
|
|
1556
|
+
this._showCouponInput = false;
|
|
1557
|
+
this._couponCode = "";
|
|
1558
|
+
this._couponLoading = false;
|
|
1559
|
+
this.updateOrderSummaryDOM();
|
|
1560
|
+
this.dispatchEvent(new CustomEvent("coupon-applied", {
|
|
1561
|
+
detail: { coupon: this._appliedCoupon },
|
|
1562
|
+
bubbles: true,
|
|
1563
|
+
composed: true
|
|
1564
|
+
}));
|
|
1565
|
+
} catch (err) {
|
|
1566
|
+
this._couponError = "\u7DB2\u8DEF\u932F\u8AA4\uFF0C\u8ACB\u7A0D\u5F8C\u518D\u8A66";
|
|
1567
|
+
this._couponLoading = false;
|
|
1568
|
+
this.updateCouponSectionDOM();
|
|
1619
1569
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
.
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
height: 36px;
|
|
1627
|
-
width: 100%;
|
|
1628
|
-
background: linear-gradient(90deg, #f0f0f0 0%, #e8e8e8 50%, #f0f0f0 100%);
|
|
1629
|
-
background-size: 200% 100%;
|
|
1630
|
-
animation: recur-skeleton-loading 1.5s ease-in-out infinite;
|
|
1631
|
-
border-radius: 4px;
|
|
1632
|
-
z-index: 1;
|
|
1633
|
-
pointer-events: none;
|
|
1570
|
+
}
|
|
1571
|
+
async removeCoupon() {
|
|
1572
|
+
const checkoutId = this.getAttribute("checkout-id");
|
|
1573
|
+
const apiBaseUrl = this.getAttribute("api-base-url") || "";
|
|
1574
|
+
if (!checkoutId) {
|
|
1575
|
+
return;
|
|
1634
1576
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1577
|
+
this._couponLoading = true;
|
|
1578
|
+
this.updateCouponSectionDOM();
|
|
1579
|
+
try {
|
|
1580
|
+
const response = await fetch(`${apiBaseUrl}/api/checkout/${checkoutId}/coupon`, {
|
|
1581
|
+
method: "DELETE"
|
|
1582
|
+
});
|
|
1583
|
+
if (!response.ok) {
|
|
1584
|
+
const data = await response.json();
|
|
1585
|
+
this._couponError = data.error?.message || "\u79FB\u9664\u5931\u6557";
|
|
1586
|
+
this._couponLoading = false;
|
|
1587
|
+
this.updateCouponSectionDOM();
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const removedCoupon = this._appliedCoupon;
|
|
1591
|
+
this._appliedCoupon = null;
|
|
1592
|
+
this._couponLoading = false;
|
|
1593
|
+
this.updateOrderSummaryDOM();
|
|
1594
|
+
this.dispatchEvent(new CustomEvent("coupon-removed", {
|
|
1595
|
+
detail: { coupon: removedCoupon },
|
|
1596
|
+
bubbles: true,
|
|
1597
|
+
composed: true
|
|
1598
|
+
}));
|
|
1599
|
+
} catch (err) {
|
|
1600
|
+
this._couponError = "\u7DB2\u8DEF\u932F\u8AA4\uFF0C\u8ACB\u7A0D\u5F8C\u518D\u8A66";
|
|
1601
|
+
this._couponLoading = false;
|
|
1602
|
+
this.updateCouponSectionDOM();
|
|
1639
1603
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Get applied coupon (for external use)
|
|
1607
|
+
*/
|
|
1608
|
+
getAppliedCoupon() {
|
|
1609
|
+
return this._appliedCoupon;
|
|
1610
|
+
}
|
|
1611
|
+
renderCustomerInfo() {
|
|
1612
|
+
const customerName = this.getAttribute("customer-name");
|
|
1613
|
+
const customerEmail = this.getAttribute("customer-email");
|
|
1614
|
+
if (customerEmail) {
|
|
1615
|
+
return html`
|
|
1616
|
+
<div class="recur-info-display">
|
|
1617
|
+
${customerName ? html`
|
|
1618
|
+
<div class="recur-info-row">
|
|
1619
|
+
<span class="recur-info-label">姓名</span>
|
|
1620
|
+
<span class="recur-info-value">${customerName}</span>
|
|
1621
|
+
</div>
|
|
1622
|
+
` : nothing}
|
|
1623
|
+
<div class="recur-info-row">
|
|
1624
|
+
<span class="recur-info-label">電子郵件</span>
|
|
1625
|
+
<span class="recur-info-value">${customerEmail}</span>
|
|
1626
|
+
</div>
|
|
1627
|
+
</div>
|
|
1628
|
+
`;
|
|
1644
1629
|
}
|
|
1645
|
-
|
|
1630
|
+
return html`
|
|
1631
|
+
<div class="recur-form-group">
|
|
1632
|
+
<label class="recur-form-label" for="${this.containerId}-email">Email</label>
|
|
1633
|
+
<input
|
|
1634
|
+
type="email"
|
|
1635
|
+
id="${this.containerId}-email"
|
|
1636
|
+
class="recur-form-input"
|
|
1637
|
+
placeholder="your@email.com"
|
|
1638
|
+
required
|
|
1639
|
+
/>
|
|
1640
|
+
</div>
|
|
1646
1641
|
|
|
1642
|
+
<div class="recur-form-group">
|
|
1643
|
+
<label class="recur-form-label" for="${this.containerId}-name">姓名</label>
|
|
1644
|
+
<input
|
|
1645
|
+
type="text"
|
|
1646
|
+
id="${this.containerId}-name"
|
|
1647
|
+
class="recur-form-input"
|
|
1648
|
+
placeholder="王小明"
|
|
1649
|
+
.value=${customerName || ""}
|
|
1650
|
+
required
|
|
1651
|
+
/>
|
|
1652
|
+
</div>
|
|
1653
|
+
`;
|
|
1654
|
+
}
|
|
1655
|
+
renderCardFields() {
|
|
1656
|
+
return html`
|
|
1647
1657
|
<div class="recur-card-field">
|
|
1648
|
-
<label class="recur-card-field-label"
|
|
1658
|
+
<label class="recur-card-field-label">卡號</label>
|
|
1649
1659
|
<div class="recur-card-field-container" id="${this.containerId}-card-no-wrapper">
|
|
1650
|
-
<!-- Skeleton shown until PAYUNi iframe loads -->
|
|
1651
1660
|
<div class="recur-card-skeleton" id="${this.containerId}-card-no-skeleton"></div>
|
|
1652
|
-
<!-- PAYUNi \u6703\u5728\u9019\u88E1\u6CE8\u5165 iframe -->
|
|
1653
1661
|
<div id="${this.containerId}-card-no" class="recur-payuni-iframe"></div>
|
|
1654
1662
|
</div>
|
|
1655
1663
|
</div>
|
|
1656
1664
|
|
|
1657
1665
|
<div class="recur-card-row">
|
|
1658
1666
|
<div class="recur-card-field">
|
|
1659
|
-
<label class="recur-card-field-label"
|
|
1667
|
+
<label class="recur-card-field-label">有效期限</label>
|
|
1660
1668
|
<div class="recur-card-field-container" id="${this.containerId}-card-exp-wrapper">
|
|
1661
1669
|
<div class="recur-card-skeleton" id="${this.containerId}-card-exp-skeleton"></div>
|
|
1662
1670
|
<div id="${this.containerId}-card-exp" class="recur-payuni-iframe"></div>
|
|
@@ -1664,7 +1672,7 @@ var init_payment_form = __esm({
|
|
|
1664
1672
|
</div>
|
|
1665
1673
|
|
|
1666
1674
|
<div class="recur-card-field">
|
|
1667
|
-
<label class="recur-card-field-label"
|
|
1675
|
+
<label class="recur-card-field-label">安全碼</label>
|
|
1668
1676
|
<div class="recur-card-field-container" id="${this.containerId}-card-cvc-wrapper">
|
|
1669
1677
|
<div class="recur-card-skeleton" id="${this.containerId}-card-cvc-skeleton"></div>
|
|
1670
1678
|
<div id="${this.containerId}-card-cvc" class="recur-payuni-iframe"></div>
|
|
@@ -1672,11 +1680,7 @@ var init_payment_form = __esm({
|
|
|
1672
1680
|
</div>
|
|
1673
1681
|
</div>
|
|
1674
1682
|
`;
|
|
1675
|
-
return section;
|
|
1676
1683
|
}
|
|
1677
|
-
/**
|
|
1678
|
-
* Hide card field skeletons after PAYUNi iframes are loaded
|
|
1679
|
-
*/
|
|
1680
1684
|
hideCardSkeletons() {
|
|
1681
1685
|
const skeletonIds = [
|
|
1682
1686
|
`${this.containerId}-card-no-skeleton`,
|
|
@@ -1690,71 +1694,21 @@ var init_payment_form = __esm({
|
|
|
1690
1694
|
}
|
|
1691
1695
|
});
|
|
1692
1696
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
section.className = "actions-section";
|
|
1696
|
-
section.innerHTML = `
|
|
1697
|
-
<style>
|
|
1698
|
-
.recur-submit-button {
|
|
1699
|
-
width: 100%;
|
|
1700
|
-
padding: 14px 20px;
|
|
1701
|
-
font-size: 16px;
|
|
1702
|
-
font-weight: 500;
|
|
1703
|
-
color: #ffffff;
|
|
1704
|
-
background: #18181b;
|
|
1705
|
-
border: none;
|
|
1706
|
-
border-radius: 6px;
|
|
1707
|
-
cursor: pointer;
|
|
1708
|
-
transition: background-color 0.2s;
|
|
1709
|
-
display: flex;
|
|
1710
|
-
align-items: center;
|
|
1711
|
-
justify-content: center;
|
|
1712
|
-
gap: 8px;
|
|
1713
|
-
box-sizing: border-box;
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
.recur-submit-button:hover:not(:disabled) {
|
|
1717
|
-
background: #27272a;
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
.recur-submit-button:disabled {
|
|
1721
|
-
opacity: 0.6;
|
|
1722
|
-
cursor: not-allowed;
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
.recur-submit-button.loading {
|
|
1726
|
-
cursor: wait;
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
.recur-loading-spinner {
|
|
1730
|
-
width: 16px;
|
|
1731
|
-
height: 16px;
|
|
1732
|
-
border: 2px solid currentColor;
|
|
1733
|
-
border-top-color: transparent;
|
|
1734
|
-
border-radius: 50%;
|
|
1735
|
-
animation: recur-spin 0.6s linear infinite;
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
@keyframes recur-spin {
|
|
1739
|
-
to { transform: rotate(360deg); }
|
|
1740
|
-
}
|
|
1741
|
-
</style>
|
|
1742
|
-
|
|
1697
|
+
renderActions() {
|
|
1698
|
+
return html`
|
|
1743
1699
|
<button
|
|
1744
1700
|
type="submit"
|
|
1745
1701
|
id="${this.containerId}-submit-btn"
|
|
1746
1702
|
class="recur-submit-button"
|
|
1747
1703
|
disabled
|
|
1748
1704
|
>
|
|
1749
|
-
<span id="${this.containerId}-submit-text"
|
|
1705
|
+
<span id="${this.containerId}-submit-text">確認付款</span>
|
|
1750
1706
|
</button>
|
|
1751
1707
|
`;
|
|
1752
|
-
return section;
|
|
1753
1708
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
*/
|
|
1709
|
+
// ============================================
|
|
1710
|
+
// PAYUNi Integration
|
|
1711
|
+
// ============================================
|
|
1758
1712
|
async initializePayment(sdkToken, environment) {
|
|
1759
1713
|
if (this._initializationAborted) {
|
|
1760
1714
|
console.log("[PaymentForm] Initialization aborted before start");
|
|
@@ -1788,7 +1742,6 @@ var init_payment_form = __esm({
|
|
|
1788
1742
|
const paymentSession = window.UniPayment.createSession(sdkToken, {
|
|
1789
1743
|
env: environment === "SANDBOX" ? "S" : "P",
|
|
1790
1744
|
elements: {
|
|
1791
|
-
// 這些 ID 在 Light DOM 中,document.getElementById 可以找到
|
|
1792
1745
|
CardNo: `${this.containerId}-card-no`,
|
|
1793
1746
|
CardExp: `${this.containerId}-card-exp`,
|
|
1794
1747
|
CardCvc: `${this.containerId}-card-cvc`
|
|
@@ -1877,17 +1830,17 @@ var init_payment_form = __esm({
|
|
|
1877
1830
|
detail: {
|
|
1878
1831
|
customerEmail: email,
|
|
1879
1832
|
customerName: name,
|
|
1880
|
-
paymentSession: this._paymentSession
|
|
1833
|
+
paymentSession: this._paymentSession,
|
|
1834
|
+
appliedCoupon: this._appliedCoupon
|
|
1881
1835
|
},
|
|
1882
1836
|
bubbles: true,
|
|
1883
1837
|
composed: true
|
|
1884
|
-
// 讓事件可以穿透 Shadow DOM
|
|
1885
1838
|
}));
|
|
1886
1839
|
});
|
|
1887
1840
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1841
|
+
// ============================================
|
|
1842
|
+
// UI Helpers
|
|
1843
|
+
// ============================================
|
|
1891
1844
|
setButtonLoading(loading) {
|
|
1892
1845
|
const submitBtn = document.getElementById(`${this.containerId}-submit-btn`);
|
|
1893
1846
|
if (!submitBtn) return;
|
|
@@ -1904,16 +1857,9 @@ var init_payment_form = __esm({
|
|
|
1904
1857
|
submitBtn.innerHTML = '<span id="' + this.containerId + '-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>';
|
|
1905
1858
|
}
|
|
1906
1859
|
}
|
|
1907
|
-
/**
|
|
1908
|
-
* Reset button to normal state (call this when payment completes or fails)
|
|
1909
|
-
*/
|
|
1910
1860
|
resetButton() {
|
|
1911
1861
|
this.setButtonLoading(false);
|
|
1912
1862
|
}
|
|
1913
|
-
/**
|
|
1914
|
-
* Set verifying state (for 3D Secure verification)
|
|
1915
|
-
* Shows a "verifying" message while polling for 3D result
|
|
1916
|
-
*/
|
|
1917
1863
|
setVerifying(verifying) {
|
|
1918
1864
|
const submitBtn = document.getElementById(`${this.containerId}-submit-btn`);
|
|
1919
1865
|
if (!submitBtn) return;
|
|
@@ -1928,9 +1874,6 @@ var init_payment_form = __esm({
|
|
|
1928
1874
|
this.setButtonLoading(false);
|
|
1929
1875
|
}
|
|
1930
1876
|
}
|
|
1931
|
-
/**
|
|
1932
|
-
* Show error message using RecurErrorDisplay component
|
|
1933
|
-
*/
|
|
1934
1877
|
showError(message) {
|
|
1935
1878
|
let errorContainer = this.querySelector(".recur-sdk__error-container");
|
|
1936
1879
|
if (!errorContainer) {
|
|
@@ -1946,18 +1889,12 @@ var init_payment_form = __esm({
|
|
|
1946
1889
|
errorContainer.appendChild(errorDisplay);
|
|
1947
1890
|
errorDisplay.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1948
1891
|
}
|
|
1949
|
-
/**
|
|
1950
|
-
* Clear any displayed errors
|
|
1951
|
-
*/
|
|
1952
1892
|
clearError() {
|
|
1953
1893
|
const errorContainer = this.querySelector(".recur-sdk__error-container");
|
|
1954
1894
|
if (errorContainer) {
|
|
1955
1895
|
errorContainer.innerHTML = "";
|
|
1956
1896
|
}
|
|
1957
1897
|
}
|
|
1958
|
-
/**
|
|
1959
|
-
* 等待組件完全連接到 DOM
|
|
1960
|
-
*/
|
|
1961
1898
|
updateComplete() {
|
|
1962
1899
|
return new Promise((resolve) => {
|
|
1963
1900
|
if (this.isConnected) {
|
|
@@ -1973,18 +1910,12 @@ var init_payment_form = __esm({
|
|
|
1973
1910
|
}
|
|
1974
1911
|
});
|
|
1975
1912
|
}
|
|
1976
|
-
/**
|
|
1977
|
-
* 獲取表單數據
|
|
1978
|
-
*/
|
|
1979
1913
|
getFormData() {
|
|
1980
1914
|
return {
|
|
1981
1915
|
email: document.getElementById(`${this.containerId}-email`)?.value,
|
|
1982
1916
|
name: document.getElementById(`${this.containerId}-name`)?.value
|
|
1983
1917
|
};
|
|
1984
1918
|
}
|
|
1985
|
-
/**
|
|
1986
|
-
* 設置表單數據
|
|
1987
|
-
*/
|
|
1988
1919
|
setFormData(data) {
|
|
1989
1920
|
if (data.email) {
|
|
1990
1921
|
const emailInput = document.getElementById(`${this.containerId}-email`);
|
|
@@ -2538,7 +2469,7 @@ async function registerComponents() {
|
|
|
2538
2469
|
Promise.resolve().then(() => (init_success_message(), success_message_exports)),
|
|
2539
2470
|
Promise.resolve().then(() => (init_error_display(), error_display_exports)),
|
|
2540
2471
|
Promise.resolve().then(() => (init_skeleton_loader(), skeleton_loader_exports)),
|
|
2541
|
-
Promise.resolve().then(() => (
|
|
2472
|
+
Promise.resolve().then(() => (init_payment_form_skeleton2(), payment_form_skeleton_exports)),
|
|
2542
2473
|
Promise.resolve().then(() => (init_toast(), toast_exports)),
|
|
2543
2474
|
Promise.resolve().then(() => (init_payment_form(), payment_form_exports)),
|
|
2544
2475
|
Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports)),
|