recur-tw 0.0.3-beta.5 → 0.0.4-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -606,6 +606,523 @@ var init_skeleton_loader = __esm({
606
606
  }
607
607
  });
608
608
 
609
+ // src/components/base/payment-form-skeleton.ts
610
+ var payment_form_skeleton_exports = {};
611
+ __export(payment_form_skeleton_exports, {
612
+ RecurPaymentFormSkeleton: () => RecurPaymentFormSkeleton
613
+ });
614
+ var RecurPaymentFormSkeleton;
615
+ var init_payment_form_skeleton = __esm({
616
+ "src/components/base/payment-form-skeleton.ts"() {
617
+ RecurPaymentFormSkeleton = class extends HTMLElement {
618
+ constructor() {
619
+ super();
620
+ this.attachShadow({ mode: "open" });
621
+ }
622
+ connectedCallback() {
623
+ this.render();
624
+ }
625
+ render() {
626
+ this.shadowRoot.innerHTML = `
627
+ <style>
628
+ :host {
629
+ display: block;
630
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
631
+ }
632
+
633
+ .payment-form-wrapper {
634
+ max-width: 500px;
635
+ margin: 0 auto;
636
+ padding: 24px;
637
+ background: white;
638
+ border-radius: 12px;
639
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
640
+ }
641
+
642
+ .form-header {
643
+ margin-bottom: 24px;
644
+ }
645
+
646
+ .form-section {
647
+ margin-bottom: 24px;
648
+ }
649
+
650
+ .section-title {
651
+ margin: 0 0 12px 0;
652
+ font-size: 16px;
653
+ font-weight: 600;
654
+ color: #2d3748;
655
+ }
656
+
657
+ .label {
658
+ font-size: 14px;
659
+ color: #4a5568;
660
+ margin-bottom: 4px;
661
+ }
662
+
663
+ /* Skeleton base styles */
664
+ .skeleton {
665
+ background: linear-gradient(
666
+ 90deg,
667
+ #f0f0f0 0%,
668
+ #f8f8f8 50%,
669
+ #f0f0f0 100%
670
+ );
671
+ background-size: 200% 100%;
672
+ animation: skeleton-loading 1.5s ease-in-out infinite;
673
+ border-radius: 4px;
674
+ }
675
+
676
+ @keyframes skeleton-loading {
677
+ 0% {
678
+ background-position: 200% 0;
679
+ }
680
+ 100% {
681
+ background-position: -200% 0;
682
+ }
683
+ }
684
+
685
+ .skeleton-title {
686
+ height: 28px;
687
+ width: 200px;
688
+ margin-bottom: 8px;
689
+ }
690
+
691
+ .skeleton-subtitle {
692
+ height: 18px;
693
+ width: 320px;
694
+ }
695
+
696
+ .skeleton-order-summary {
697
+ padding: 16px;
698
+ background: #f7fafc;
699
+ border-radius: 6px;
700
+ margin-bottom: 24px;
701
+ }
702
+
703
+ .skeleton-order-row {
704
+ display: flex;
705
+ justify-content: space-between;
706
+ align-items: center;
707
+ margin-bottom: 12px;
708
+ }
709
+
710
+ .skeleton-order-row:last-child {
711
+ margin-bottom: 0;
712
+ padding-top: 12px;
713
+ border-top: 1px solid #e2e8f0;
714
+ font-weight: 600;
715
+ }
716
+
717
+ .skeleton-order-value {
718
+ height: 20px;
719
+ width: 120px;
720
+ }
721
+
722
+ .input-group {
723
+ margin-bottom: 16px;
724
+ }
725
+
726
+ .input-group:last-child {
727
+ margin-bottom: 0;
728
+ }
729
+
730
+ .skeleton-input {
731
+ height: 44px;
732
+ width: 100%;
733
+ }
734
+
735
+ .card-fields-grid {
736
+ display: grid;
737
+ grid-template-columns: 1fr 1fr;
738
+ gap: 12px;
739
+ }
740
+
741
+ .skeleton-button {
742
+ height: 44px;
743
+ width: 100%;
744
+ border-radius: 6px;
745
+ }
746
+
747
+ .security-note {
748
+ display: flex;
749
+ align-items: center;
750
+ gap: 8px;
751
+ padding: 12px;
752
+ background: #f7fafc;
753
+ border-radius: 6px;
754
+ margin-top: 16px;
755
+ }
756
+
757
+ .security-icon {
758
+ color: #48bb78;
759
+ }
760
+
761
+ .security-text {
762
+ font-size: 13px;
763
+ color: #4a5568;
764
+ margin: 0;
765
+ }
766
+ </style>
767
+
768
+ <div class="payment-form-wrapper">
769
+ <!-- Header Skeleton -->
770
+ <div class="form-header">
771
+ <div class="skeleton skeleton-title"></div>
772
+ <div class="skeleton skeleton-subtitle"></div>
773
+ </div>
774
+
775
+ <!-- Order Summary - Static labels, skeleton values -->
776
+ <div class="skeleton-order-summary">
777
+ <h3 class="section-title">\u8A02\u55AE\u6458\u8981</h3>
778
+ <div class="skeleton-order-row">
779
+ <span class="label">\u65B9\u6848</span>
780
+ <div class="skeleton skeleton-order-value"></div>
781
+ </div>
782
+ <div class="skeleton-order-row">
783
+ <span class="label">\u91D1\u984D</span>
784
+ <div class="skeleton skeleton-order-value"></div>
785
+ </div>
786
+ <div class="skeleton-order-row">
787
+ <span class="label">\u9031\u671F</span>
788
+ <div class="skeleton skeleton-order-value"></div>
789
+ </div>
790
+ <div class="skeleton-order-row">
791
+ <span class="label">\u7E3D\u8A08</span>
792
+ <div class="skeleton skeleton-order-value"></div>
793
+ </div>
794
+ </div>
795
+
796
+ <!-- Customer Info Section - Static labels, skeleton inputs -->
797
+ <div class="form-section">
798
+ <h3 class="section-title">\u5BA2\u6236\u8CC7\u8A0A</h3>
799
+ <div class="input-group">
800
+ <div class="label">\u59D3\u540D</div>
801
+ <div class="skeleton skeleton-input"></div>
802
+ </div>
803
+ <div class="input-group">
804
+ <div class="label">Email</div>
805
+ <div class="skeleton skeleton-input"></div>
806
+ </div>
807
+ <div class="input-group">
808
+ <div class="label">\u96FB\u8A71\uFF08\u9078\u586B\uFF09</div>
809
+ <div class="skeleton skeleton-input"></div>
810
+ </div>
811
+ </div>
812
+
813
+ <!-- Card Fields Section - Static labels, skeleton inputs -->
814
+ <div class="form-section">
815
+ <h3 class="section-title">\u4FE1\u7528\u5361\u8CC7\u8A0A</h3>
816
+ <div class="input-group">
817
+ <div class="label">\u4FE1\u7528\u5361\u865F</div>
818
+ <div class="skeleton skeleton-input"></div>
819
+ </div>
820
+ <div class="card-fields-grid">
821
+ <div class="input-group">
822
+ <div class="label">\u6709\u6548\u671F\u9650</div>
823
+ <div class="skeleton skeleton-input"></div>
824
+ </div>
825
+ <div class="input-group">
826
+ <div class="label">\u5B89\u5168\u78BC</div>
827
+ <div class="skeleton skeleton-input"></div>
828
+ </div>
829
+ </div>
830
+ </div>
831
+
832
+ <!-- Submit Button Skeleton -->
833
+ <div class="skeleton skeleton-button"></div>
834
+
835
+ <!-- Security Note -->
836
+ <div class="security-note">
837
+ <svg class="security-icon" width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
838
+ <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" />
839
+ </svg>
840
+ <p class="security-text">\u60A8\u7684\u4ED8\u6B3E\u8CC7\u8A0A\u7D93\u904E\u52A0\u5BC6\u4FDD\u8B77</p>
841
+ </div>
842
+ </div>
843
+ `;
844
+ }
845
+ };
846
+ if (typeof window !== "undefined" && !customElements.get("recur-payment-form-skeleton")) {
847
+ customElements.define("recur-payment-form-skeleton", RecurPaymentFormSkeleton);
848
+ }
849
+ }
850
+ });
851
+
852
+ // src/components/base/toast.ts
853
+ var toast_exports = {};
854
+ __export(toast_exports, {
855
+ RecurToast: () => RecurToast,
856
+ RecurToastContainer: () => RecurToastContainer
857
+ });
858
+ var RecurToast, _RecurToastContainer, RecurToastContainer;
859
+ var init_toast = __esm({
860
+ "src/components/base/toast.ts"() {
861
+ RecurToast = class extends HTMLElement {
862
+ static get observedAttributes() {
863
+ return ["message", "type", "duration"];
864
+ }
865
+ constructor() {
866
+ super();
867
+ this.attachShadow({ mode: "open" });
868
+ }
869
+ connectedCallback() {
870
+ this.render();
871
+ this.setupAutoDismiss();
872
+ }
873
+ get message() {
874
+ return this.getAttribute("message") || "Notification";
875
+ }
876
+ get type() {
877
+ const typeAttr = this.getAttribute("type");
878
+ if (typeAttr === "success" || typeAttr === "error") return typeAttr;
879
+ return "info";
880
+ }
881
+ get duration() {
882
+ const durationAttr = this.getAttribute("duration");
883
+ return durationAttr ? parseInt(durationAttr, 10) : 5e3;
884
+ }
885
+ setupAutoDismiss() {
886
+ const duration = this.duration;
887
+ if (duration > 0) {
888
+ setTimeout(() => this.dismiss(), duration);
889
+ }
890
+ }
891
+ dismiss() {
892
+ this.style.animation = "recur-toast-slide-out 0.3s ease-in-out";
893
+ setTimeout(() => this.remove(), 300);
894
+ }
895
+ getTypeIcon() {
896
+ switch (this.type) {
897
+ case "success":
898
+ return `<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
899
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
900
+ </svg>`;
901
+ case "error":
902
+ return `<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
903
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
904
+ </svg>`;
905
+ case "info":
906
+ default:
907
+ return `<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
908
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
909
+ </svg>`;
910
+ }
911
+ }
912
+ getTypeColor() {
913
+ switch (this.type) {
914
+ case "success":
915
+ return "#10b981";
916
+ // green-500
917
+ case "error":
918
+ return "#ef4444";
919
+ // red-500
920
+ case "info":
921
+ default:
922
+ return "#3b82f6";
923
+ }
924
+ }
925
+ render() {
926
+ const color = this.getTypeColor();
927
+ this.shadowRoot.innerHTML = `
928
+ <style>
929
+ :host {
930
+ display: block;
931
+ margin-bottom: 12px;
932
+ animation: recur-toast-slide-in 0.3s ease-out;
933
+ }
934
+
935
+ @keyframes recur-toast-slide-in {
936
+ from {
937
+ transform: translateX(100%);
938
+ opacity: 0;
939
+ }
940
+ to {
941
+ transform: translateX(0);
942
+ opacity: 1;
943
+ }
944
+ }
945
+
946
+ @keyframes recur-toast-slide-out {
947
+ from {
948
+ transform: translateX(0);
949
+ opacity: 1;
950
+ }
951
+ to {
952
+ transform: translateX(100%);
953
+ opacity: 0;
954
+ }
955
+ }
956
+
957
+ .toast {
958
+ display: flex;
959
+ align-items: center;
960
+ gap: 12px;
961
+ padding: 12px 16px;
962
+ background: white;
963
+ border-left: 4px solid ${color};
964
+ border-radius: 8px;
965
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
966
+ min-width: 280px;
967
+ max-width: 420px;
968
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
969
+ cursor: pointer;
970
+ transition: transform 0.2s ease;
971
+ }
972
+
973
+ .toast:hover {
974
+ transform: translateY(-2px);
975
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
976
+ }
977
+
978
+ .toast-icon {
979
+ flex-shrink: 0;
980
+ color: ${color};
981
+ display: flex;
982
+ align-items: center;
983
+ justify-content: center;
984
+ }
985
+
986
+ .toast-message {
987
+ flex: 1;
988
+ color: #374151;
989
+ font-size: 14px;
990
+ line-height: 1.5;
991
+ margin: 0;
992
+ }
993
+
994
+ .toast-close {
995
+ flex-shrink: 0;
996
+ color: #9ca3af;
997
+ cursor: pointer;
998
+ padding: 4px;
999
+ display: flex;
1000
+ align-items: center;
1001
+ justify-content: center;
1002
+ border-radius: 4px;
1003
+ transition: all 0.2s;
1004
+ }
1005
+
1006
+ .toast-close:hover {
1007
+ color: #6b7280;
1008
+ background: #f3f4f6;
1009
+ }
1010
+
1011
+ /* Dark mode support */
1012
+ :host([theme="dark"]) .toast {
1013
+ background: #1f2937;
1014
+ }
1015
+
1016
+ :host([theme="dark"]) .toast-message {
1017
+ color: #e5e7eb;
1018
+ }
1019
+
1020
+ :host([theme="dark"]) .toast-close {
1021
+ color: #6b7280;
1022
+ }
1023
+
1024
+ :host([theme="dark"]) .toast-close:hover {
1025
+ color: #9ca3af;
1026
+ background: #374151;
1027
+ }
1028
+ </style>
1029
+
1030
+ <div class="toast" role="alert">
1031
+ <div class="toast-icon">
1032
+ ${this.getTypeIcon()}
1033
+ </div>
1034
+ <p class="toast-message">${this.message}</p>
1035
+ <button class="toast-close" aria-label="Close">
1036
+ <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1037
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
1038
+ </svg>
1039
+ </button>
1040
+ </div>
1041
+ `;
1042
+ const closeBtn = this.shadowRoot.querySelector(".toast-close");
1043
+ closeBtn?.addEventListener("click", (e) => {
1044
+ e.stopPropagation();
1045
+ this.dismiss();
1046
+ });
1047
+ const toast = this.shadowRoot.querySelector(".toast");
1048
+ toast?.addEventListener("click", () => this.dismiss());
1049
+ }
1050
+ /**
1051
+ * Static helper method to show toast
1052
+ */
1053
+ static show(message, type = "info", duration = 5e3) {
1054
+ const container = RecurToastContainer.getInstance();
1055
+ const toast = document.createElement("recur-toast");
1056
+ toast.setAttribute("message", message);
1057
+ toast.setAttribute("type", type);
1058
+ toast.setAttribute("duration", duration.toString());
1059
+ container.appendChild(toast);
1060
+ return toast;
1061
+ }
1062
+ };
1063
+ _RecurToastContainer = class _RecurToastContainer extends HTMLElement {
1064
+ constructor() {
1065
+ super();
1066
+ this.attachShadow({ mode: "open" });
1067
+ }
1068
+ connectedCallback() {
1069
+ this.render();
1070
+ }
1071
+ render() {
1072
+ this.shadowRoot.innerHTML = `
1073
+ <style>
1074
+ :host {
1075
+ position: fixed;
1076
+ top: 24px;
1077
+ right: 24px;
1078
+ z-index: 9999;
1079
+ display: flex;
1080
+ flex-direction: column;
1081
+ pointer-events: none;
1082
+ }
1083
+
1084
+ ::slotted(*) {
1085
+ pointer-events: auto;
1086
+ }
1087
+
1088
+ /* Mobile responsive */
1089
+ @media (max-width: 640px) {
1090
+ :host {
1091
+ top: 16px;
1092
+ right: 16px;
1093
+ left: 16px;
1094
+ }
1095
+ }
1096
+ </style>
1097
+
1098
+ <slot></slot>
1099
+ `;
1100
+ }
1101
+ /**
1102
+ * Get or create singleton instance
1103
+ */
1104
+ static getInstance() {
1105
+ if (!_RecurToastContainer.instance) {
1106
+ _RecurToastContainer.instance = document.querySelector("recur-toast-container");
1107
+ if (!_RecurToastContainer.instance) {
1108
+ _RecurToastContainer.instance = document.createElement("recur-toast-container");
1109
+ document.body.appendChild(_RecurToastContainer.instance);
1110
+ }
1111
+ }
1112
+ return _RecurToastContainer.instance;
1113
+ }
1114
+ };
1115
+ __publicField(_RecurToastContainer, "instance", null);
1116
+ RecurToastContainer = _RecurToastContainer;
1117
+ if (typeof window !== "undefined" && !customElements.get("recur-toast")) {
1118
+ customElements.define("recur-toast", RecurToast);
1119
+ }
1120
+ if (typeof window !== "undefined" && !customElements.get("recur-toast-container")) {
1121
+ customElements.define("recur-toast-container", RecurToastContainer);
1122
+ }
1123
+ }
1124
+ });
1125
+
609
1126
  // src/components/payment-form.ts
610
1127
  var payment_form_exports = {};
611
1128
  __export(payment_form_exports, {
@@ -619,6 +1136,8 @@ var init_payment_form = __esm({
619
1136
  super();
620
1137
  __publicField(this, "containerId");
621
1138
  __publicField(this, "customStyles");
1139
+ __publicField(this, "_isInitializing", false);
1140
+ __publicField(this, "_initializationAborted", false);
622
1141
  this.containerId = this.getAttribute("container-id") || `recur-${Date.now()}`;
623
1142
  this.customStyles = this.getAttribute("custom-styles") || "";
624
1143
  this.attachShadow({ mode: "open" });
@@ -626,6 +1145,16 @@ var init_payment_form = __esm({
626
1145
  connectedCallback() {
627
1146
  this.render();
628
1147
  }
1148
+ disconnectedCallback() {
1149
+ console.log("[PaymentForm] Component disconnected, cleaning up...");
1150
+ this._initializationAborted = true;
1151
+ this._paymentSession = null;
1152
+ const submitBtn = document.getElementById(`${this.containerId}-submit-btn`);
1153
+ if (submitBtn) {
1154
+ const newBtn = submitBtn.cloneNode(true);
1155
+ submitBtn.parentNode?.replaceChild(newBtn, submitBtn);
1156
+ }
1157
+ }
629
1158
  // 監聽 attribute 變化
630
1159
  static get observedAttributes() {
631
1160
  return ["custom-styles", "customer-name", "customer-email", "plan-name", "amount", "billing-period"];
@@ -1120,11 +1649,35 @@ var init_payment_form = __esm({
1120
1649
  * 這個方法會在外部調用,傳入 SDK token
1121
1650
  */
1122
1651
  async initializePayment(sdkToken, environment) {
1652
+ if (this._initializationAborted) {
1653
+ console.log("[PaymentForm] Initialization aborted before start");
1654
+ return null;
1655
+ }
1656
+ this._isInitializing = true;
1123
1657
  await this.updateComplete();
1658
+ if (this._initializationAborted) {
1659
+ console.log("[PaymentForm] Initialization aborted after updateComplete");
1660
+ this._isInitializing = false;
1661
+ return null;
1662
+ }
1124
1663
  if (!window.UniPayment) {
1664
+ this._isInitializing = false;
1125
1665
  throw new Error("PAYUNi SDK not loaded");
1126
1666
  }
1127
1667
  try {
1668
+ const cardNoEl = document.getElementById(`${this.containerId}-card-no`);
1669
+ const cardExpEl = document.getElementById(`${this.containerId}-card-exp`);
1670
+ const cardCvcEl = document.getElementById(`${this.containerId}-card-cvc`);
1671
+ if (!cardNoEl || !cardExpEl || !cardCvcEl) {
1672
+ console.log("[PaymentForm] DOM elements not found, likely disconnected");
1673
+ this._isInitializing = false;
1674
+ return null;
1675
+ }
1676
+ if (this._initializationAborted) {
1677
+ console.log("[PaymentForm] Initialization aborted before createSession");
1678
+ this._isInitializing = false;
1679
+ return null;
1680
+ }
1128
1681
  const paymentSession = window.UniPayment.createSession(sdkToken, {
1129
1682
  env: environment === "SANDBOX" ? "S" : "P",
1130
1683
  elements: {
@@ -1134,8 +1687,32 @@ var init_payment_form = __esm({
1134
1687
  CardCvc: `${this.containerId}-card-cvc`
1135
1688
  }
1136
1689
  });
1137
- await paymentSession.start();
1690
+ if (this._initializationAborted) {
1691
+ console.log("[PaymentForm] Initialization aborted before paymentSession.start()");
1692
+ this._isInitializing = false;
1693
+ return null;
1694
+ }
1695
+ try {
1696
+ await paymentSession.start();
1697
+ } catch (startError) {
1698
+ const is1008Error = startError?.message?.includes("1008") || startError?.message?.includes("timeout") || startError?.code === 1008;
1699
+ if (is1008Error && this._initializationAborted) {
1700
+ console.log("[PaymentForm] Caught 1008 timeout error after component disconnect - ignoring");
1701
+ this._isInitializing = false;
1702
+ return null;
1703
+ }
1704
+ throw startError;
1705
+ }
1706
+ if (this._initializationAborted) {
1707
+ console.log("[PaymentForm] Initialization aborted after paymentSession.start()");
1708
+ this._isInitializing = false;
1709
+ return null;
1710
+ }
1138
1711
  paymentSession.onUpdate?.((data) => {
1712
+ if (this._initializationAborted) {
1713
+ console.log("[PaymentForm] Ignoring onUpdate event - component disconnected");
1714
+ return;
1715
+ }
1139
1716
  console.log("[PaymentForm] PAYUNi update:", data);
1140
1717
  const allFieldsComplete = data.status && data.status.CardNo === true && data.status.CardExp === true && data.status.CardCvc === true;
1141
1718
  const submitBtn = document.getElementById(`${this.containerId}-submit-btn`);
@@ -1144,10 +1721,17 @@ var init_payment_form = __esm({
1144
1721
  console.log("[PaymentForm] Submit button disabled:", !allFieldsComplete);
1145
1722
  }
1146
1723
  });
1724
+ if (this._initializationAborted) {
1725
+ console.log("[PaymentForm] Initialization aborted before saving session");
1726
+ this._isInitializing = false;
1727
+ return null;
1728
+ }
1147
1729
  this._paymentSession = paymentSession;
1148
1730
  this.setupFormSubmission();
1731
+ this._isInitializing = false;
1149
1732
  return paymentSession;
1150
1733
  } catch (error) {
1734
+ this._isInitializing = false;
1151
1735
  console.error("[PaymentForm] Failed to initialize payment:", error);
1152
1736
  throw error;
1153
1737
  }
@@ -1171,17 +1755,18 @@ var init_payment_form = __esm({
1171
1755
  name = nameInput.value;
1172
1756
  phone = phoneInput?.value;
1173
1757
  if (!email || !name) {
1174
- alert("\u8ACB\u586B\u5BEB\u59D3\u540D\u548C\u96FB\u5B50\u90F5\u4EF6");
1758
+ this.showError("\u8ACB\u586B\u5BEB\u59D3\u540D\u548C\u96FB\u5B50\u90F5\u4EF6");
1175
1759
  return;
1176
1760
  }
1177
1761
  } else {
1178
1762
  email = this.getAttribute("customer-email") || void 0;
1179
1763
  name = this.getAttribute("customer-name") || void 0;
1180
1764
  if (!email || !name) {
1181
- alert("\u5BA2\u6236\u8CC7\u8A0A\u4E0D\u5B8C\u6574");
1765
+ this.showError("\u5BA2\u6236\u8CC7\u8A0A\u4E0D\u5B8C\u6574");
1182
1766
  return;
1183
1767
  }
1184
1768
  }
1769
+ this.clearError();
1185
1770
  this.setButtonLoading(true);
1186
1771
  this.dispatchEvent(new CustomEvent("submit", {
1187
1772
  detail: {
@@ -1221,6 +1806,33 @@ var init_payment_form = __esm({
1221
1806
  resetButton() {
1222
1807
  this.setButtonLoading(false);
1223
1808
  }
1809
+ /**
1810
+ * Show error message using RecurErrorDisplay component
1811
+ */
1812
+ showError(message) {
1813
+ let errorContainer = this.querySelector(".recur-sdk__error-container");
1814
+ if (!errorContainer) {
1815
+ errorContainer = document.createElement("div");
1816
+ errorContainer.className = "recur-sdk__error-container";
1817
+ errorContainer.style.cssText = "margin-bottom: 16px;";
1818
+ this.insertBefore(errorContainer, this.firstChild);
1819
+ }
1820
+ const errorDisplay = document.createElement("recur-error-display");
1821
+ errorDisplay.setAttribute("error", message);
1822
+ errorDisplay.setAttribute("dismissible", "true");
1823
+ errorContainer.innerHTML = "";
1824
+ errorContainer.appendChild(errorDisplay);
1825
+ errorDisplay.scrollIntoView({ behavior: "smooth", block: "center" });
1826
+ }
1827
+ /**
1828
+ * Clear any displayed errors
1829
+ */
1830
+ clearError() {
1831
+ const errorContainer = this.querySelector(".recur-sdk__error-container");
1832
+ if (errorContainer) {
1833
+ errorContainer.innerHTML = "";
1834
+ }
1835
+ }
1224
1836
  /**
1225
1837
  * 等待組件完全連接到 DOM
1226
1838
  */
@@ -1284,6 +1896,8 @@ async function registerComponents() {
1284
1896
  Promise.resolve().then(() => (init_success_message(), success_message_exports)),
1285
1897
  Promise.resolve().then(() => (init_error_display(), error_display_exports)),
1286
1898
  Promise.resolve().then(() => (init_skeleton_loader(), skeleton_loader_exports)),
1899
+ Promise.resolve().then(() => (init_payment_form_skeleton(), payment_form_skeleton_exports)),
1900
+ Promise.resolve().then(() => (init_toast(), toast_exports)),
1287
1901
  Promise.resolve().then(() => (init_payment_form(), payment_form_exports))
1288
1902
  ]);
1289
1903
  const components = [
@@ -1291,6 +1905,9 @@ async function registerComponents() {
1291
1905
  "recur-success-message",
1292
1906
  "recur-error-display",
1293
1907
  "recur-skeleton-loader",
1908
+ "recur-payment-form-skeleton",
1909
+ "recur-toast",
1910
+ "recur-toast-container",
1294
1911
  "recur-payment-form"
1295
1912
  ];
1296
1913
  const unregistered = components.filter((name) => !customElements.get(name));
@@ -1444,10 +2061,9 @@ function RecurProvider({ children, config: initialConfig = {} }) {
1444
2061
  loadingContainer = document.createElement("div");
1445
2062
  loadingContainer.id = "recur-modal-loading";
1446
2063
  loadingContainer.className = "recur-sdk__loading-container";
1447
- loadingContainer.style.cssText = "text-align: center; padding: 40px 0;";
1448
- const spinner = document.createElement("recur-loading-spinner");
1449
- spinner.setAttribute("message", "\u6B63\u5728\u8655\u7406\u8A02\u95B1...");
1450
- loadingContainer.appendChild(spinner);
2064
+ loadingContainer.style.cssText = "padding: 0;";
2065
+ const skeleton = document.createElement("recur-payment-form-skeleton");
2066
+ loadingContainer.appendChild(skeleton);
1451
2067
  modalContent.appendChild(closeButton);
1452
2068
  modalContent.appendChild(title);
1453
2069
  modalContent.appendChild(loadingContainer);
@@ -1476,10 +2092,9 @@ function RecurProvider({ children, config: initialConfig = {} }) {
1476
2092
  loadingContainer = document.createElement("div");
1477
2093
  loadingContainer.id = "recur-embedded-loading";
1478
2094
  loadingContainer.className = "recur-sdk__loading-container";
1479
- loadingContainer.style.cssText = "text-align: center; padding: 40px 0;";
1480
- const embeddedSpinner = document.createElement("recur-loading-spinner");
1481
- embeddedSpinner.setAttribute("message", "\u6B63\u5728\u8655\u7406\u8A02\u95B1...");
1482
- loadingContainer.appendChild(embeddedSpinner);
2095
+ loadingContainer.style.cssText = "padding: 0;";
2096
+ const embeddedSkeleton = document.createElement("recur-payment-form-skeleton");
2097
+ loadingContainer.appendChild(embeddedSkeleton);
1483
2098
  embeddedContainer.appendChild(loadingContainer);
1484
2099
  } else {
1485
2100
  console.log("[Recur SDK] Container not found after polling, will continue without early loading state");
@@ -1607,10 +2222,14 @@ function RecurProvider({ children, config: initialConfig = {} }) {
1607
2222
  container.appendChild(paymentForm);
1608
2223
  await new Promise((resolve) => setTimeout(resolve, 100));
1609
2224
  console.log("[Recur SDK] Initializing PAYUNi SDK via Web Component...");
1610
- await paymentForm.initializePayment(
2225
+ const initResult = await paymentForm.initializePayment(
1611
2226
  tokenResult.sdkToken,
1612
2227
  tokenResult.environment
1613
2228
  );
2229
+ if (initResult === null) {
2230
+ console.log("[Recur SDK] Initialization aborted - component was unmounted");
2231
+ return;
2232
+ }
1614
2233
  console.log("[Recur SDK] PAYUNi SDK initialized successfully");
1615
2234
  console.log("[Recur SDK] Step 7: Setting up submit handler...");
1616
2235
  paymentForm.addEventListener("submit", (async (event) => {