recur-tw 0.17.0 → 0.18.0
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 +130 -16
- package/dist/index.d.cts +88 -5
- package/dist/index.d.ts +88 -5
- package/dist/index.js +124 -16
- package/dist/recur.umd.js +88 -65
- package/dist/server.cjs +5 -5
- package/dist/server.js +5 -5
- package/package.json +5 -3
package/dist/index.cjs
CHANGED
|
@@ -757,7 +757,7 @@ var init_payment_form_skeleton = __esmMin((() => {
|
|
|
757
757
|
}));
|
|
758
758
|
|
|
759
759
|
//#endregion
|
|
760
|
-
//#region \0@oxc-project+runtime@0.
|
|
760
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
|
|
761
761
|
function _typeof(o) {
|
|
762
762
|
"@babel/helpers - typeof";
|
|
763
763
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
@@ -769,7 +769,7 @@ function _typeof(o) {
|
|
|
769
769
|
var init_typeof = __esmMin((() => {}));
|
|
770
770
|
|
|
771
771
|
//#endregion
|
|
772
|
-
//#region \0@oxc-project+runtime@0.
|
|
772
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPrimitive.js
|
|
773
773
|
function toPrimitive(t, r) {
|
|
774
774
|
if ("object" != _typeof(t) || !t) return t;
|
|
775
775
|
var e = t[Symbol.toPrimitive];
|
|
@@ -785,7 +785,7 @@ var init_toPrimitive = __esmMin((() => {
|
|
|
785
785
|
}));
|
|
786
786
|
|
|
787
787
|
//#endregion
|
|
788
|
-
//#region \0@oxc-project+runtime@0.
|
|
788
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/toPropertyKey.js
|
|
789
789
|
function toPropertyKey(t) {
|
|
790
790
|
var i = toPrimitive(t, "string");
|
|
791
791
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
@@ -796,7 +796,7 @@ var init_toPropertyKey = __esmMin((() => {
|
|
|
796
796
|
}));
|
|
797
797
|
|
|
798
798
|
//#endregion
|
|
799
|
-
//#region \0@oxc-project+runtime@0.
|
|
799
|
+
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/defineProperty.js
|
|
800
800
|
function _defineProperty(e, r, t) {
|
|
801
801
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
802
802
|
value: t,
|
|
@@ -1078,7 +1078,7 @@ var init_payment_form_shadow = __esmMin((() => {
|
|
|
1078
1078
|
//#region src/components/styles/payment-form-light.css?css-text
|
|
1079
1079
|
var payment_form_light_default;
|
|
1080
1080
|
var init_payment_form_light = __esmMin((() => {
|
|
1081
|
-
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 text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n.recur-order-summary__total {\n font-size: 18px;\n line-height: 26px;\n font-weight: 700;\n color: #1a202c;\n text-align: right;\n font-variant-numeric: tabular-nums;\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: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\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 align-items: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n}\n\n.recur-coupon__input-wrapper {\n position: relative;\n flex: 1;\n min-width: 0;\n}\n\n.recur-coupon__input {\n width: 100%;\n height: 36px;\n padding: 0 32px 0 12px;\n font-size: 14px;\n text-transform: uppercase;\n background-color: #ffffff;\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:hover:not(:focus) {\n border-color: #9ca3af;\n}\n\n.recur-coupon__input:focus {\n border-color: var(--ring, hsl(215 16% 47%));\n outline: 0;\n box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.recur-coupon__clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n padding: 4px;\n cursor: pointer;\n color: #9ca3af;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: color 0.2s;\n}\n\n.recur-coupon__clear:hover {\n color: #6b7280;\n}\n\n.recur-coupon__clear-icon {\n width: 16px;\n height: 16px;\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 display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.recur-coupon__btn--apply {\n background: #18181b;\n color: white;\n border: none;\n min-width: 52px;\n}\n\n.recur-coupon__btn--apply .recur-coupon__spinner {\n width: 14px;\n height: 14px;\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__error {\n width: 100%;\n margin-top: 8px;\n font-size: 13px;\n color: #dc2626;\n}\n\n.recur-coupon__applied-wrapper {\n width: 100%;\n}\n\n.recur-coupon__description {\n font-size: 12px;\n color: #6b7280;\n margin: 4px 0 0 22px;\n}\n\n.recur-coupon__bonus {\n display: flex;\n align-items: center;\n gap: 6px;\n background: #dcfce7;\n border-radius: 6px;\n padding: 8px 12px;\n margin-top: 8px;\n font-size: 12px;\n color: #15803d;\n}\n\n.recur-coupon__applied {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n}\n\n.recur-coupon__applied-info {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n flex: 1;\n}\n\n.recur-coupon__applied-icon {\n width: 16px;\n height: 16px;\n color: #dc2626;\n flex-shrink: 0;\n}\n\n.recur-coupon__applied-name {\n font-size: 14px;\n font-weight: 500;\n color: #dc2626;\n white-space: nowrap;\n}\n\n.recur-coupon__applied-code {\n font-size: 14px;\n color: #9ca3af;\n white-space: nowrap;\n margin-right: 2px;\n}\n\n.recur-coupon__applied-amount {\n font-size: 14px;\n font-weight: 500;\n color: #dc2626;\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n flex-shrink: 0;\n margin-left: auto;\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 flex-shrink: 0;\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 height: 36px;\n padding: 0 12px;\n font-size: 14px;\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 border-color: var(--ring, hsl(215 16% 47%));\n outline: 0;\n box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\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\n/* ============================================\n * Skeleton Loading Styles\n * ============================================ */\n.recur-skeleton {\n display: inline-block;\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}\n\n/* ============================================\n * Test Mode Banner Styles\n * ============================================ */\n.recur-test-mode-banner {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #f59e0b;\n border-radius: 8px;\n margin-bottom: 16px;\n overflow: hidden;\n}\n\n.recur-test-mode-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 10px 14px;\n cursor: pointer;\n user-select: none;\n transition: background 0.2s;\n}\n\n.recur-test-mode-header:hover {\n background: rgba(245, 158, 11, 0.1);\n}\n\n.recur-test-mode-badge {\n font-size: 13px;\n font-weight: 600;\n color: #92400e;\n}\n\n.recur-test-mode-toggle {\n font-size: 12px;\n color: #b45309;\n}\n\n.recur-test-cards {\n background: rgba(255, 255, 255, 0.7);\n padding: 12px 14px;\n border-top: 1px solid rgba(245, 158, 11, 0.3);\n}\n\n.recur-test-cards-info {\n font-size: 12px;\n color: #78350f;\n margin: 0 0 10px 0;\n}\n\n.recur-test-card {\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 8px;\n}\n\n.recur-test-card:last-child {\n margin-bottom: 0;\n}\n\n.recur-test-card-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 4px;\n}\n\n.recur-test-card-number {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #1f2937;\n letter-spacing: 0.5px;\n}\n\n.recur-test-card-copy {\n font-size: 11px;\n font-weight: 500;\n color: #3b82f6;\n background: #eff6ff;\n border: 1px solid #bfdbfe;\n border-radius: 4px;\n padding: 3px 8px;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n.recur-test-card-copy:hover {\n background: #dbeafe;\n border-color: #93c5fd;\n}\n\n.recur-test-card-copy.copied {\n background: #dcfce7;\n border-color: #86efac;\n color: #16a34a;\n}\n\n.recur-test-card-details {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n font-size: 11px;\n color: #6b7280;\n}\n\n.recur-test-card-desc {\n color: #374151;\n font-weight: 500;\n}\n\n.recur-test-card-meta {\n color: #9ca3af;\n}\n";
|
|
1081
|
+
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 text-align: right;\n font-variant-numeric: tabular-nums;\n}\n\n.recur-order-summary__total {\n font-size: 18px;\n line-height: 26px;\n font-weight: 700;\n color: #1a202c;\n text-align: right;\n font-variant-numeric: tabular-nums;\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: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\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 align-items: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n}\n\n.recur-coupon__input-wrapper {\n position: relative;\n flex: 1;\n min-width: 0;\n}\n\n.recur-coupon__input {\n width: 100%;\n height: 36px;\n padding: 0 32px 0 12px;\n font-size: 14px;\n text-transform: uppercase;\n background-color: #ffffff;\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:hover:not(:focus) {\n border-color: #9ca3af;\n}\n\n.recur-coupon__input:focus {\n border-color: var(--ring, hsl(215 16% 47%));\n outline: 0;\n box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.recur-coupon__clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n padding: 4px;\n cursor: pointer;\n color: #9ca3af;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: color 0.2s;\n}\n\n.recur-coupon__clear:hover {\n color: #6b7280;\n}\n\n.recur-coupon__clear-icon {\n width: 16px;\n height: 16px;\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 display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.recur-coupon__btn--apply {\n background: #18181b;\n color: white;\n border: none;\n min-width: 52px;\n}\n\n.recur-coupon__btn--apply .recur-coupon__spinner {\n width: 14px;\n height: 14px;\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__error {\n width: 100%;\n margin-top: 8px;\n font-size: 13px;\n color: #dc2626;\n}\n\n.recur-coupon__applied-wrapper {\n width: 100%;\n}\n\n.recur-coupon__description {\n font-size: 12px;\n color: #6b7280;\n margin: 4px 0 0 22px;\n}\n\n.recur-coupon__bonus {\n display: flex;\n align-items: center;\n gap: 6px;\n background: #dcfce7;\n border-radius: 6px;\n padding: 8px 12px;\n margin-top: 8px;\n font-size: 12px;\n color: #15803d;\n}\n\n.recur-coupon__applied {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n}\n\n.recur-coupon__applied-info {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n flex: 1;\n}\n\n.recur-coupon__applied-icon {\n width: 16px;\n height: 16px;\n color: #dc2626;\n flex-shrink: 0;\n}\n\n.recur-coupon__applied-name {\n font-size: 14px;\n font-weight: 500;\n color: #dc2626;\n white-space: nowrap;\n}\n\n.recur-coupon__applied-code {\n font-size: 14px;\n color: #9ca3af;\n white-space: nowrap;\n margin-right: 2px;\n}\n\n.recur-coupon__applied-amount {\n font-size: 14px;\n font-weight: 500;\n color: #dc2626;\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n flex-shrink: 0;\n margin-left: auto;\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 flex-shrink: 0;\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 height: 36px;\n padding: 0 12px;\n font-size: 14px;\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 border-color: var(--ring, hsl(215 16% 47%));\n outline: 0;\n box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent);\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\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/* Card verification notice ($0 total, card still bound for future charges) */\n.recur-verification-notice {\n border: 1px solid #bfdbfe;\n background-color: #eff6ff;\n color: #1e40af;\n border-radius: 8px;\n padding: 12px 16px;\n font-size: 14px;\n font-weight: 500;\n margin-bottom: 16px;\n}\n\n.recur-verification-notice.hidden {\n display: none;\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\n/* ============================================\n * Skeleton Loading Styles\n * ============================================ */\n.recur-skeleton {\n display: inline-block;\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}\n\n/* ============================================\n * Test Mode Banner Styles\n * ============================================ */\n.recur-test-mode-banner {\n background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);\n border: 1px solid #f59e0b;\n border-radius: 8px;\n margin-bottom: 16px;\n overflow: hidden;\n}\n\n.recur-test-mode-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 10px 14px;\n cursor: pointer;\n user-select: none;\n transition: background 0.2s;\n}\n\n.recur-test-mode-header:hover {\n background: rgba(245, 158, 11, 0.1);\n}\n\n.recur-test-mode-badge {\n font-size: 13px;\n font-weight: 600;\n color: #92400e;\n}\n\n.recur-test-mode-toggle {\n font-size: 12px;\n color: #b45309;\n}\n\n.recur-test-cards {\n background: rgba(255, 255, 255, 0.7);\n padding: 12px 14px;\n border-top: 1px solid rgba(245, 158, 11, 0.3);\n}\n\n.recur-test-cards-info {\n font-size: 12px;\n color: #78350f;\n margin: 0 0 10px 0;\n}\n\n.recur-test-card {\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 6px;\n padding: 10px 12px;\n margin-bottom: 8px;\n}\n\n.recur-test-card:last-child {\n margin-bottom: 0;\n}\n\n.recur-test-card-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 4px;\n}\n\n.recur-test-card-number {\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n font-size: 14px;\n font-weight: 600;\n color: #1f2937;\n letter-spacing: 0.5px;\n}\n\n.recur-test-card-copy {\n font-size: 11px;\n font-weight: 500;\n color: #3b82f6;\n background: #eff6ff;\n border: 1px solid #bfdbfe;\n border-radius: 4px;\n padding: 3px 8px;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n.recur-test-card-copy:hover {\n background: #dbeafe;\n border-color: #93c5fd;\n}\n\n.recur-test-card-copy.copied {\n background: #dcfce7;\n border-color: #86efac;\n color: #16a34a;\n}\n\n.recur-test-card-details {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n font-size: 11px;\n color: #6b7280;\n}\n\n.recur-test-card-desc {\n color: #374151;\n font-weight: 500;\n}\n\n.recur-test-card-meta {\n color: #9ca3af;\n}\n";
|
|
1082
1082
|
}));
|
|
1083
1083
|
|
|
1084
1084
|
//#endregion
|
|
@@ -1107,6 +1107,7 @@ function formatPrice(amount) {
|
|
|
1107
1107
|
function getCheckoutDiscountDisplay(input) {
|
|
1108
1108
|
const { discountType, couponDiscountValue, discountAmount, duration, durationMonths, productPrice, productInterval, productIntervalCount, bonusMonths, bonusTrialDays } = input;
|
|
1109
1109
|
const intervalText = getIntervalText(productInterval, productIntervalCount);
|
|
1110
|
+
const isOneTime = !intervalText;
|
|
1110
1111
|
const originalPriceText = intervalText ? `${formatPrice(productPrice)}/${intervalText}` : formatPrice(productPrice);
|
|
1111
1112
|
const bonusSuffix = buildBonusSuffix(bonusMonths, bonusTrialDays);
|
|
1112
1113
|
if (discountAmount === 0 && (bonusMonths || bonusTrialDays)) return {
|
|
@@ -1121,9 +1122,10 @@ function getCheckoutDiscountDisplay(input) {
|
|
|
1121
1122
|
if (discountType === "FIRST_PERIOD_PRICE") {
|
|
1122
1123
|
const firstPeriodPrice = couponDiscountValue;
|
|
1123
1124
|
const priceText = firstPeriodPrice === 0 ? "免費" : `特價 ${formatPrice(firstPeriodPrice)}`;
|
|
1125
|
+
const base = isOneTime ? priceText : `首期${priceText}`;
|
|
1124
1126
|
return {
|
|
1125
|
-
line1: bonusSuffix ?
|
|
1126
|
-
line2: afterLine
|
|
1127
|
+
line1: bonusSuffix ? `${base},${bonusSuffix}` : base,
|
|
1128
|
+
line2: isOneTime ? null : afterLine
|
|
1127
1129
|
};
|
|
1128
1130
|
}
|
|
1129
1131
|
if (discountType === "FIXED_AMOUNT") return buildStandardDiscount({
|
|
@@ -1131,14 +1133,16 @@ function getCheckoutDiscountDisplay(input) {
|
|
|
1131
1133
|
duration,
|
|
1132
1134
|
durationMonths,
|
|
1133
1135
|
bonusSuffix,
|
|
1134
|
-
afterLine
|
|
1136
|
+
afterLine,
|
|
1137
|
+
isOneTime
|
|
1135
1138
|
});
|
|
1136
1139
|
if (discountType === "PERCENTAGE") return buildStandardDiscount({
|
|
1137
|
-
discountText: `享 ${formatZhe(couponDiscountValue)}`,
|
|
1140
|
+
discountText: couponDiscountValue >= 1e4 ? "免費" : `享 ${formatZhe(couponDiscountValue)}`,
|
|
1138
1141
|
duration,
|
|
1139
1142
|
durationMonths,
|
|
1140
1143
|
bonusSuffix,
|
|
1141
|
-
afterLine
|
|
1144
|
+
afterLine,
|
|
1145
|
+
isOneTime
|
|
1142
1146
|
});
|
|
1143
1147
|
return {
|
|
1144
1148
|
line1: "",
|
|
@@ -1146,7 +1150,11 @@ function getCheckoutDiscountDisplay(input) {
|
|
|
1146
1150
|
};
|
|
1147
1151
|
}
|
|
1148
1152
|
function buildStandardDiscount(opts) {
|
|
1149
|
-
const { discountText, duration, durationMonths, bonusSuffix, afterLine } = opts;
|
|
1153
|
+
const { discountText, duration, durationMonths, bonusSuffix, afterLine, isOneTime } = opts;
|
|
1154
|
+
if (isOneTime) return {
|
|
1155
|
+
line1: bonusSuffix ? `${discountText},${bonusSuffix}` : discountText,
|
|
1156
|
+
line2: null
|
|
1157
|
+
};
|
|
1150
1158
|
switch (duration) {
|
|
1151
1159
|
case "ONCE": {
|
|
1152
1160
|
const base = `首期${discountText}`;
|
|
@@ -1737,6 +1745,7 @@ var init_payment_form = __esmMin((() => {
|
|
|
1737
1745
|
const totalValue = summaryContainer.querySelector(".recur-order-summary__row--total")?.querySelector(".recur-order-summary__total");
|
|
1738
1746
|
if (totalValue) totalValue.textContent = `NT$ ${finalAmount.toLocaleString()}`;
|
|
1739
1747
|
this.updateCouponSectionDOM();
|
|
1748
|
+
this.updateVerificationUI();
|
|
1740
1749
|
}
|
|
1741
1750
|
async applyCoupon() {
|
|
1742
1751
|
const code = this._couponCode.trim();
|
|
@@ -1875,8 +1884,41 @@ var init_payment_form = __esmMin((() => {
|
|
|
1875
1884
|
</div>
|
|
1876
1885
|
`;
|
|
1877
1886
|
}
|
|
1887
|
+
/** Final payable amount after any applied coupon (never negative). */
|
|
1888
|
+
getFinalAmount() {
|
|
1889
|
+
const amount = Number(this.getAttribute("amount") || 0);
|
|
1890
|
+
const discountAmount = this._appliedCoupon?.discountAmount || 0;
|
|
1891
|
+
return Math.max(0, amount - discountAmount);
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* $0 total but the card form is still shown: the backend runs a NT$2
|
|
1895
|
+
* verification charge (voided afterwards) to bind the card for future
|
|
1896
|
+
* charges. The UI must say 驗證 instead of 付款.
|
|
1897
|
+
*/
|
|
1898
|
+
isCardVerificationMode() {
|
|
1899
|
+
const amountAttr = this.getAttribute("amount");
|
|
1900
|
+
if (amountAttr === null || amountAttr === "") return false;
|
|
1901
|
+
return this.getFinalAmount() === 0;
|
|
1902
|
+
}
|
|
1903
|
+
getSubmitLabel() {
|
|
1904
|
+
return this.isCardVerificationMode() ? "驗證卡片" : "確認付款";
|
|
1905
|
+
}
|
|
1906
|
+
/** Sync the verification notice + submit label with the current amount. */
|
|
1907
|
+
updateVerificationUI() {
|
|
1908
|
+
const notice = document.getElementById(`${this.containerId}-verification-notice`);
|
|
1909
|
+
if (notice) notice.classList.toggle("hidden", !this.isCardVerificationMode());
|
|
1910
|
+
const submitText = document.getElementById(`${this.containerId}-submit-text`);
|
|
1911
|
+
if (submitText) submitText.textContent = this.getSubmitLabel();
|
|
1912
|
+
}
|
|
1878
1913
|
renderCardFields() {
|
|
1879
1914
|
return lit_html.html`
|
|
1915
|
+
<div
|
|
1916
|
+
id="${this.containerId}-verification-notice"
|
|
1917
|
+
class="recur-verification-notice${this.isCardVerificationMode() ? "" : " hidden"}"
|
|
1918
|
+
>
|
|
1919
|
+
您的優惠碼已抵扣全額,仍需驗證信用卡以供日後扣款使用
|
|
1920
|
+
</div>
|
|
1921
|
+
|
|
1880
1922
|
<div class="recur-card-field">
|
|
1881
1923
|
<label class="recur-card-field-label">卡號</label>
|
|
1882
1924
|
<div class="recur-card-field-container" id="${this.containerId}-card-no-wrapper">
|
|
@@ -1922,7 +1964,7 @@ var init_payment_form = __esmMin((() => {
|
|
|
1922
1964
|
class="recur-submit-button"
|
|
1923
1965
|
disabled
|
|
1924
1966
|
>
|
|
1925
|
-
<span id="${this.containerId}-submit-text"
|
|
1967
|
+
<span id="${this.containerId}-submit-text">${this.getSubmitLabel()}</span>
|
|
1926
1968
|
</button>
|
|
1927
1969
|
`;
|
|
1928
1970
|
}
|
|
@@ -2067,7 +2109,7 @@ var init_payment_form = __esmMin((() => {
|
|
|
2067
2109
|
} else {
|
|
2068
2110
|
submitBtn.classList.remove("loading");
|
|
2069
2111
|
submitBtn.disabled = false;
|
|
2070
|
-
submitBtn.innerHTML = "<span id=\"" + this.containerId + "-submit-text\"
|
|
2112
|
+
submitBtn.innerHTML = "<span id=\"" + this.containerId + "-submit-text\">" + this.getSubmitLabel() + "</span>";
|
|
2071
2113
|
}
|
|
2072
2114
|
}
|
|
2073
2115
|
resetButton() {
|
|
@@ -2763,9 +2805,66 @@ function toCamelCase(obj) {
|
|
|
2763
2805
|
return obj;
|
|
2764
2806
|
}
|
|
2765
2807
|
|
|
2808
|
+
//#endregion
|
|
2809
|
+
//#region src/einvoice.ts
|
|
2810
|
+
/** 手機條碼: `/` + 7 chars from the MOF alphabet. */
|
|
2811
|
+
const MOBILE_BARCODE_RE = /^\/[0-9A-Z.+-]{7}$/;
|
|
2812
|
+
/** 統一編號: 8 digits. */
|
|
2813
|
+
const UBN_RE = /^\d{8}$/;
|
|
2814
|
+
/** 愛心碼: 3–7 digits. */
|
|
2815
|
+
const DONATION_CODE_RE = /^\d{3,7}$/;
|
|
2816
|
+
/**
|
|
2817
|
+
* 統一編號 checksum (post-2023 rule: weighted digit-sum divisible by 5, with
|
|
2818
|
+
* the 7-in-7th-digit alternate). Providers reject checksum-invalid UBNs at
|
|
2819
|
+
* issue time as a non-retryable failure, so the SDK catches this at entry.
|
|
2820
|
+
*/
|
|
2821
|
+
function isValidTaiwanUbn(ubn) {
|
|
2822
|
+
if (!UBN_RE.test(ubn)) return false;
|
|
2823
|
+
const weights = [
|
|
2824
|
+
1,
|
|
2825
|
+
2,
|
|
2826
|
+
1,
|
|
2827
|
+
2,
|
|
2828
|
+
1,
|
|
2829
|
+
2,
|
|
2830
|
+
4,
|
|
2831
|
+
1
|
|
2832
|
+
];
|
|
2833
|
+
let sum = 0;
|
|
2834
|
+
for (let i = 0; i < 8; i++) {
|
|
2835
|
+
const product = Number(ubn.charAt(i)) * (weights[i] ?? 0);
|
|
2836
|
+
sum += Math.floor(product / 10) + product % 10;
|
|
2837
|
+
}
|
|
2838
|
+
if (sum % 5 === 0) return true;
|
|
2839
|
+
return ubn.charAt(6) === "7" && (sum + 1) % 5 === 0;
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Validate buyer invoice preferences.
|
|
2843
|
+
*
|
|
2844
|
+
* @returns an error message, or `null` when the prefs are valid
|
|
2845
|
+
*/
|
|
2846
|
+
function validateEinvoicePrefs(prefs) {
|
|
2847
|
+
if (typeof prefs !== "object" || prefs === null) return "Invalid einvoice prefs: expected an object like { type: \"personal\" }";
|
|
2848
|
+
switch (prefs.type) {
|
|
2849
|
+
case "personal": return null;
|
|
2850
|
+
case "mobile_barcode": return MOBILE_BARCODE_RE.test(prefs.carrierCode) ? null : "Invalid einvoice mobile barcode: must be \"/\" followed by 7 characters (0-9, A-Z, ., +, -)";
|
|
2851
|
+
case "ubn":
|
|
2852
|
+
if (!isValidTaiwanUbn(prefs.ubn)) return "Invalid einvoice UBN (統一編號): must be 8 digits with a valid checksum";
|
|
2853
|
+
if (!prefs.buyerName || !prefs.buyerName.trim() || prefs.buyerName.trim().length > 60) return "Invalid einvoice buyerName (公司抬頭): required, at most 60 characters";
|
|
2854
|
+
return null;
|
|
2855
|
+
case "donation": return DONATION_CODE_RE.test(prefs.donationCode) ? null : "Invalid einvoice donation code (愛心碼): must be 3-7 digits";
|
|
2856
|
+
default: return `Invalid einvoice type: ${String(prefs.type)}`;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
/** Throw when buyer invoice preferences are invalid (fail fast, before the API call). */
|
|
2860
|
+
function assertValidEinvoicePrefs(prefs) {
|
|
2861
|
+
const error = validateEinvoicePrefs(prefs);
|
|
2862
|
+
if (error) throw new Error(error);
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2766
2865
|
//#endregion
|
|
2767
2866
|
//#region package.json
|
|
2768
|
-
var version = "0.
|
|
2867
|
+
var version = "0.18.0";
|
|
2769
2868
|
|
|
2770
2869
|
//#endregion
|
|
2771
2870
|
//#region src/context.tsx
|
|
@@ -3108,6 +3207,10 @@ function RecurProvider({ children, config: initialConfig = {}, customer: custome
|
|
|
3108
3207
|
if (options.externalCustomerId) checkoutRequestBody.external_customer_id = options.externalCustomerId;
|
|
3109
3208
|
if (options.successUrl) checkoutRequestBody.success_url = options.successUrl;
|
|
3110
3209
|
if (options.cancelUrl) checkoutRequestBody.cancel_url = options.cancelUrl;
|
|
3210
|
+
if (options.einvoice) {
|
|
3211
|
+
assertValidEinvoicePrefs(options.einvoice);
|
|
3212
|
+
checkoutRequestBody.einvoice = options.einvoice;
|
|
3213
|
+
}
|
|
3111
3214
|
const checkoutResponse = await fetch(`${baseUrl}/v1/checkouts`, {
|
|
3112
3215
|
method: "POST",
|
|
3113
3216
|
headers,
|
|
@@ -3559,6 +3662,10 @@ function RecurProvider({ children, config: initialConfig = {}, customer: custome
|
|
|
3559
3662
|
if (options.customerEmail) requestBody.customerEmail = options.customerEmail;
|
|
3560
3663
|
if (options.customerName) requestBody.customerName = options.customerName;
|
|
3561
3664
|
if (options.externalCustomerId) requestBody.externalCustomerId = options.externalCustomerId;
|
|
3665
|
+
if (options.einvoice) {
|
|
3666
|
+
assertValidEinvoicePrefs(options.einvoice);
|
|
3667
|
+
requestBody.einvoice = options.einvoice;
|
|
3668
|
+
}
|
|
3562
3669
|
const response = await fetch(`${baseUrl}/v1/checkout/sessions`, {
|
|
3563
3670
|
method: "POST",
|
|
3564
3671
|
headers: {
|
|
@@ -3579,7 +3686,8 @@ function RecurProvider({ children, config: initialConfig = {}, customer: custome
|
|
|
3579
3686
|
id: result.id,
|
|
3580
3687
|
url: result.url,
|
|
3581
3688
|
expiresAt: result.expiresAt,
|
|
3582
|
-
clientSecret: result.clientSecret
|
|
3689
|
+
clientSecret: result.clientSecret,
|
|
3690
|
+
einvoiceEnabled: result.einvoiceEnabled
|
|
3583
3691
|
};
|
|
3584
3692
|
}, [config]);
|
|
3585
3693
|
const redirectToCheckout = (0, react.useCallback)(async (options) => {
|
|
@@ -4331,11 +4439,17 @@ function useCustomer() {
|
|
|
4331
4439
|
}
|
|
4332
4440
|
|
|
4333
4441
|
//#endregion
|
|
4442
|
+
exports.DONATION_CODE_RE = DONATION_CODE_RE;
|
|
4443
|
+
exports.MOBILE_BARCODE_RE = MOBILE_BARCODE_RE;
|
|
4334
4444
|
exports.PromoCodeInput = PromoCodeInput;
|
|
4335
4445
|
exports.RecurProvider = RecurProvider;
|
|
4446
|
+
exports.UBN_RE = UBN_RE;
|
|
4447
|
+
exports.assertValidEinvoicePrefs = assertValidEinvoicePrefs;
|
|
4448
|
+
exports.isValidTaiwanUbn = isValidTaiwanUbn;
|
|
4336
4449
|
exports.useCustomer = useCustomer;
|
|
4337
4450
|
exports.usePlans = useProducts;
|
|
4338
4451
|
exports.useProducts = useProducts;
|
|
4339
4452
|
exports.usePromoCode = usePromoCode;
|
|
4340
4453
|
exports.useRecur = useRecur;
|
|
4341
|
-
exports.useSubscribe = useSubscribe;
|
|
4454
|
+
exports.useSubscribe = useSubscribe;
|
|
4455
|
+
exports.validateEinvoicePrefs = validateEinvoicePrefs;
|
package/dist/index.d.cts
CHANGED
|
@@ -159,12 +159,58 @@ declare global {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
//#endregion
|
|
162
|
-
//#region src/
|
|
162
|
+
//#region src/einvoice.d.ts
|
|
163
|
+
/**
|
|
164
|
+
* Taiwan e-invoice (電子發票) buyer preferences
|
|
165
|
+
*
|
|
166
|
+
* Mirrors the server's einvoicePrefsSchema (@workspace/einvoice). Validation is
|
|
167
|
+
* kept inline — no zod, no dependency on the einvoice packages — so the browser
|
|
168
|
+
* bundles stay lean. A contract test (__tests__/einvoice.test.ts) pins this
|
|
169
|
+
* implementation against the server schema and @paid-tw/einvoice's validators.
|
|
170
|
+
*/
|
|
171
|
+
/**
|
|
172
|
+
* Buyer invoice choice, collected at checkout when the merchant has an active
|
|
173
|
+
* e-invoice integration:
|
|
174
|
+
* - `personal`: 個人雲端發票, delivered via the buyer's email
|
|
175
|
+
* - `mobile_barcode`: 手機條碼載具, e.g. `/ABC1234`
|
|
176
|
+
* - `ubn`: 公司統編三聯式 (B2B), 8-digit 統一編號 + company title
|
|
177
|
+
* - `donation`: 捐贈發票 via 愛心碼
|
|
178
|
+
*/
|
|
179
|
+
type EinvoicePrefs = {
|
|
180
|
+
type: 'personal';
|
|
181
|
+
} | {
|
|
182
|
+
type: 'mobile_barcode';
|
|
183
|
+
carrierCode: string;
|
|
184
|
+
} | {
|
|
185
|
+
type: 'ubn';
|
|
186
|
+
ubn: string;
|
|
187
|
+
buyerName: string;
|
|
188
|
+
} | {
|
|
189
|
+
type: 'donation';
|
|
190
|
+
donationCode: string;
|
|
191
|
+
};
|
|
192
|
+
/** 手機條碼: `/` + 7 chars from the MOF alphabet. */
|
|
193
|
+
declare const MOBILE_BARCODE_RE: RegExp;
|
|
194
|
+
/** 統一編號: 8 digits. */
|
|
195
|
+
declare const UBN_RE: RegExp;
|
|
196
|
+
/** 愛心碼: 3–7 digits. */
|
|
197
|
+
declare const DONATION_CODE_RE: RegExp;
|
|
198
|
+
/**
|
|
199
|
+
* 統一編號 checksum (post-2023 rule: weighted digit-sum divisible by 5, with
|
|
200
|
+
* the 7-in-7th-digit alternate). Providers reject checksum-invalid UBNs at
|
|
201
|
+
* issue time as a non-retryable failure, so the SDK catches this at entry.
|
|
202
|
+
*/
|
|
203
|
+
declare function isValidTaiwanUbn(ubn: string): boolean;
|
|
163
204
|
/**
|
|
164
|
-
*
|
|
205
|
+
* Validate buyer invoice preferences.
|
|
165
206
|
*
|
|
166
|
-
*
|
|
207
|
+
* @returns an error message, or `null` when the prefs are valid
|
|
167
208
|
*/
|
|
209
|
+
declare function validateEinvoicePrefs(prefs: EinvoicePrefs): string | null;
|
|
210
|
+
/** Throw when buyer invoice preferences are invalid (fail fast, before the API call). */
|
|
211
|
+
declare function assertValidEinvoicePrefs(prefs: EinvoicePrefs): void;
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/types.d.ts
|
|
168
214
|
interface RecurConfig {
|
|
169
215
|
/**
|
|
170
216
|
* Publishable API key for authentication
|
|
@@ -251,11 +297,23 @@ interface CheckoutOptions {
|
|
|
251
297
|
/**
|
|
252
298
|
* External customer ID from your system (optional)
|
|
253
299
|
* Use this to link Recur subscriptions to your existing users.
|
|
254
|
-
*
|
|
300
|
+
* Applied when the customer is created or first linked during checkout;
|
|
301
|
+
* can later be updated via the Secret Key API (PATCH /v1/customers/:id).
|
|
255
302
|
*
|
|
256
303
|
* @example 'user_123', 'cus_abc456'
|
|
257
304
|
*/
|
|
258
305
|
externalCustomerId?: string;
|
|
306
|
+
/**
|
|
307
|
+
* Taiwan e-invoice buyer preferences (optional)
|
|
308
|
+
* Used when the organization has an active e-invoice integration
|
|
309
|
+
* (`einvoiceEnabled` in the checkout response). Collect the buyer's choice
|
|
310
|
+
* in your own UI and pass it here; it is validated locally and stored with
|
|
311
|
+
* the checkout so the invoice is issued accordingly after payment.
|
|
312
|
+
*
|
|
313
|
+
* @example { type: 'mobile_barcode', carrierCode: '/ABC1234' }
|
|
314
|
+
* @example { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' }
|
|
315
|
+
*/
|
|
316
|
+
einvoice?: EinvoicePrefs;
|
|
259
317
|
/**
|
|
260
318
|
* URL to redirect after successful payment (recommended for mobile)
|
|
261
319
|
* Enables redirect after 3D verification in mobile environments.
|
|
@@ -367,6 +425,8 @@ interface CheckoutResult {
|
|
|
367
425
|
* Used to determine PAYUNi SDK environment
|
|
368
426
|
*/
|
|
369
427
|
livemode?: boolean;
|
|
428
|
+
/** Whether the organization issues e-invoices for paid checkouts */
|
|
429
|
+
einvoiceEnabled?: boolean;
|
|
370
430
|
}
|
|
371
431
|
/**
|
|
372
432
|
* Payment failure codes from payment provider
|
|
@@ -568,6 +628,16 @@ interface RedirectToCheckoutOptions {
|
|
|
568
628
|
* @example 'user_123', 'cus_abc456'
|
|
569
629
|
*/
|
|
570
630
|
externalCustomerId?: string;
|
|
631
|
+
/**
|
|
632
|
+
* Taiwan e-invoice buyer preferences (optional)
|
|
633
|
+
* Prefills the 發票資訊 section on the hosted checkout page (the buyer can
|
|
634
|
+
* still change it there). Only used when the organization has an active
|
|
635
|
+
* e-invoice integration.
|
|
636
|
+
*
|
|
637
|
+
* @example { type: 'mobile_barcode', carrierCode: '/ABC1234' }
|
|
638
|
+
* @example { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' }
|
|
639
|
+
*/
|
|
640
|
+
einvoice?: EinvoicePrefs;
|
|
571
641
|
}
|
|
572
642
|
/**
|
|
573
643
|
* Result from creating a checkout session
|
|
@@ -581,6 +651,8 @@ interface CheckoutSessionResult {
|
|
|
581
651
|
expiresAt: string;
|
|
582
652
|
/** Stripe-like client secret for secure status polling */
|
|
583
653
|
clientSecret?: string;
|
|
654
|
+
/** Whether the organization issues e-invoices for paid checkouts */
|
|
655
|
+
einvoiceEnabled?: boolean;
|
|
584
656
|
}
|
|
585
657
|
interface RecurContextValue {
|
|
586
658
|
/**
|
|
@@ -1064,6 +1136,17 @@ declare class RecurPaymentForm extends HTMLElement {
|
|
|
1064
1136
|
productPrice?: number;
|
|
1065
1137
|
} | null;
|
|
1066
1138
|
private renderCustomerInfo;
|
|
1139
|
+
/** Final payable amount after any applied coupon (never negative). */
|
|
1140
|
+
private getFinalAmount;
|
|
1141
|
+
/**
|
|
1142
|
+
* $0 total but the card form is still shown: the backend runs a NT$2
|
|
1143
|
+
* verification charge (voided afterwards) to bind the card for future
|
|
1144
|
+
* charges. The UI must say 驗證 instead of 付款.
|
|
1145
|
+
*/
|
|
1146
|
+
private isCardVerificationMode;
|
|
1147
|
+
private getSubmitLabel;
|
|
1148
|
+
/** Sync the verification notice + submit label with the current amount. */
|
|
1149
|
+
private updateVerificationUI;
|
|
1067
1150
|
private renderCardFields;
|
|
1068
1151
|
private hideCardSkeletons;
|
|
1069
1152
|
private renderActions;
|
|
@@ -1775,4 +1858,4 @@ declare function PromoCodeInput({ promo, placeholder, applyText, clearText, disa
|
|
|
1775
1858
|
*/
|
|
1776
1859
|
declare function useCustomer(): UseCustomerResult;
|
|
1777
1860
|
//#endregion
|
|
1778
|
-
export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CheckoutSessionResult, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type RedirectToCheckoutOptions, type SubscriptionResult, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsOptions, type UseProductsResult as UsePlansResult, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe };
|
|
1861
|
+
export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CheckoutSessionResult, type CustomerIdentifier, DONATION_CODE_RE, type DuplicateSubscriptionDetails, type EinvoicePrefs, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, MOBILE_BARCODE_RE, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type RedirectToCheckoutOptions, type SubscriptionResult, UBN_RE, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsOptions, type UseProductsResult as UsePlansResult, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, assertValidEinvoicePrefs, isValidTaiwanUbn, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe, validateEinvoicePrefs };
|
package/dist/index.d.ts
CHANGED
|
@@ -159,12 +159,58 @@ declare global {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
//#endregion
|
|
162
|
-
//#region src/
|
|
162
|
+
//#region src/einvoice.d.ts
|
|
163
|
+
/**
|
|
164
|
+
* Taiwan e-invoice (電子發票) buyer preferences
|
|
165
|
+
*
|
|
166
|
+
* Mirrors the server's einvoicePrefsSchema (@workspace/einvoice). Validation is
|
|
167
|
+
* kept inline — no zod, no dependency on the einvoice packages — so the browser
|
|
168
|
+
* bundles stay lean. A contract test (__tests__/einvoice.test.ts) pins this
|
|
169
|
+
* implementation against the server schema and @paid-tw/einvoice's validators.
|
|
170
|
+
*/
|
|
171
|
+
/**
|
|
172
|
+
* Buyer invoice choice, collected at checkout when the merchant has an active
|
|
173
|
+
* e-invoice integration:
|
|
174
|
+
* - `personal`: 個人雲端發票, delivered via the buyer's email
|
|
175
|
+
* - `mobile_barcode`: 手機條碼載具, e.g. `/ABC1234`
|
|
176
|
+
* - `ubn`: 公司統編三聯式 (B2B), 8-digit 統一編號 + company title
|
|
177
|
+
* - `donation`: 捐贈發票 via 愛心碼
|
|
178
|
+
*/
|
|
179
|
+
type EinvoicePrefs = {
|
|
180
|
+
type: 'personal';
|
|
181
|
+
} | {
|
|
182
|
+
type: 'mobile_barcode';
|
|
183
|
+
carrierCode: string;
|
|
184
|
+
} | {
|
|
185
|
+
type: 'ubn';
|
|
186
|
+
ubn: string;
|
|
187
|
+
buyerName: string;
|
|
188
|
+
} | {
|
|
189
|
+
type: 'donation';
|
|
190
|
+
donationCode: string;
|
|
191
|
+
};
|
|
192
|
+
/** 手機條碼: `/` + 7 chars from the MOF alphabet. */
|
|
193
|
+
declare const MOBILE_BARCODE_RE: RegExp;
|
|
194
|
+
/** 統一編號: 8 digits. */
|
|
195
|
+
declare const UBN_RE: RegExp;
|
|
196
|
+
/** 愛心碼: 3–7 digits. */
|
|
197
|
+
declare const DONATION_CODE_RE: RegExp;
|
|
198
|
+
/**
|
|
199
|
+
* 統一編號 checksum (post-2023 rule: weighted digit-sum divisible by 5, with
|
|
200
|
+
* the 7-in-7th-digit alternate). Providers reject checksum-invalid UBNs at
|
|
201
|
+
* issue time as a non-retryable failure, so the SDK catches this at entry.
|
|
202
|
+
*/
|
|
203
|
+
declare function isValidTaiwanUbn(ubn: string): boolean;
|
|
163
204
|
/**
|
|
164
|
-
*
|
|
205
|
+
* Validate buyer invoice preferences.
|
|
165
206
|
*
|
|
166
|
-
*
|
|
207
|
+
* @returns an error message, or `null` when the prefs are valid
|
|
167
208
|
*/
|
|
209
|
+
declare function validateEinvoicePrefs(prefs: EinvoicePrefs): string | null;
|
|
210
|
+
/** Throw when buyer invoice preferences are invalid (fail fast, before the API call). */
|
|
211
|
+
declare function assertValidEinvoicePrefs(prefs: EinvoicePrefs): void;
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/types.d.ts
|
|
168
214
|
interface RecurConfig {
|
|
169
215
|
/**
|
|
170
216
|
* Publishable API key for authentication
|
|
@@ -251,11 +297,23 @@ interface CheckoutOptions {
|
|
|
251
297
|
/**
|
|
252
298
|
* External customer ID from your system (optional)
|
|
253
299
|
* Use this to link Recur subscriptions to your existing users.
|
|
254
|
-
*
|
|
300
|
+
* Applied when the customer is created or first linked during checkout;
|
|
301
|
+
* can later be updated via the Secret Key API (PATCH /v1/customers/:id).
|
|
255
302
|
*
|
|
256
303
|
* @example 'user_123', 'cus_abc456'
|
|
257
304
|
*/
|
|
258
305
|
externalCustomerId?: string;
|
|
306
|
+
/**
|
|
307
|
+
* Taiwan e-invoice buyer preferences (optional)
|
|
308
|
+
* Used when the organization has an active e-invoice integration
|
|
309
|
+
* (`einvoiceEnabled` in the checkout response). Collect the buyer's choice
|
|
310
|
+
* in your own UI and pass it here; it is validated locally and stored with
|
|
311
|
+
* the checkout so the invoice is issued accordingly after payment.
|
|
312
|
+
*
|
|
313
|
+
* @example { type: 'mobile_barcode', carrierCode: '/ABC1234' }
|
|
314
|
+
* @example { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' }
|
|
315
|
+
*/
|
|
316
|
+
einvoice?: EinvoicePrefs;
|
|
259
317
|
/**
|
|
260
318
|
* URL to redirect after successful payment (recommended for mobile)
|
|
261
319
|
* Enables redirect after 3D verification in mobile environments.
|
|
@@ -367,6 +425,8 @@ interface CheckoutResult {
|
|
|
367
425
|
* Used to determine PAYUNi SDK environment
|
|
368
426
|
*/
|
|
369
427
|
livemode?: boolean;
|
|
428
|
+
/** Whether the organization issues e-invoices for paid checkouts */
|
|
429
|
+
einvoiceEnabled?: boolean;
|
|
370
430
|
}
|
|
371
431
|
/**
|
|
372
432
|
* Payment failure codes from payment provider
|
|
@@ -568,6 +628,16 @@ interface RedirectToCheckoutOptions {
|
|
|
568
628
|
* @example 'user_123', 'cus_abc456'
|
|
569
629
|
*/
|
|
570
630
|
externalCustomerId?: string;
|
|
631
|
+
/**
|
|
632
|
+
* Taiwan e-invoice buyer preferences (optional)
|
|
633
|
+
* Prefills the 發票資訊 section on the hosted checkout page (the buyer can
|
|
634
|
+
* still change it there). Only used when the organization has an active
|
|
635
|
+
* e-invoice integration.
|
|
636
|
+
*
|
|
637
|
+
* @example { type: 'mobile_barcode', carrierCode: '/ABC1234' }
|
|
638
|
+
* @example { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' }
|
|
639
|
+
*/
|
|
640
|
+
einvoice?: EinvoicePrefs;
|
|
571
641
|
}
|
|
572
642
|
/**
|
|
573
643
|
* Result from creating a checkout session
|
|
@@ -581,6 +651,8 @@ interface CheckoutSessionResult {
|
|
|
581
651
|
expiresAt: string;
|
|
582
652
|
/** Stripe-like client secret for secure status polling */
|
|
583
653
|
clientSecret?: string;
|
|
654
|
+
/** Whether the organization issues e-invoices for paid checkouts */
|
|
655
|
+
einvoiceEnabled?: boolean;
|
|
584
656
|
}
|
|
585
657
|
interface RecurContextValue {
|
|
586
658
|
/**
|
|
@@ -1064,6 +1136,17 @@ declare class RecurPaymentForm extends HTMLElement {
|
|
|
1064
1136
|
productPrice?: number;
|
|
1065
1137
|
} | null;
|
|
1066
1138
|
private renderCustomerInfo;
|
|
1139
|
+
/** Final payable amount after any applied coupon (never negative). */
|
|
1140
|
+
private getFinalAmount;
|
|
1141
|
+
/**
|
|
1142
|
+
* $0 total but the card form is still shown: the backend runs a NT$2
|
|
1143
|
+
* verification charge (voided afterwards) to bind the card for future
|
|
1144
|
+
* charges. The UI must say 驗證 instead of 付款.
|
|
1145
|
+
*/
|
|
1146
|
+
private isCardVerificationMode;
|
|
1147
|
+
private getSubmitLabel;
|
|
1148
|
+
/** Sync the verification notice + submit label with the current amount. */
|
|
1149
|
+
private updateVerificationUI;
|
|
1067
1150
|
private renderCardFields;
|
|
1068
1151
|
private hideCardSkeletons;
|
|
1069
1152
|
private renderActions;
|
|
@@ -1775,4 +1858,4 @@ declare function PromoCodeInput({ promo, placeholder, applyText, clearText, disa
|
|
|
1775
1858
|
*/
|
|
1776
1859
|
declare function useCustomer(): UseCustomerResult;
|
|
1777
1860
|
//#endregion
|
|
1778
|
-
export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CheckoutSessionResult, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type RedirectToCheckoutOptions, type SubscriptionResult, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsOptions, type UseProductsResult as UsePlansResult, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe };
|
|
1861
|
+
export { type ApiErrorCode, type ApplicableProduct, type CheckDeniedReason, type CheckOptions, type CheckResult, type CheckoutError, type CheckoutErrorDetails, type CheckoutOptions, type CheckoutResult, type CheckoutSessionResult, type CustomerIdentifier, DONATION_CODE_RE, type DuplicateSubscriptionDetails, type EinvoicePrefs, type Entitlement, type EntitlementCustomer, type EntitlementStatus, type EntitlementSubscription, MOBILE_BARCODE_RE, type PaymentFailedAction, type PaymentFailureCode, type PaymentFailureDetails, type Plan, type PlansResult, type Product, type ProductMetadata, type ProductsResult, type PromoCodeDiscount, PromoCodeInput, type PromoCodeInputProps, type PromoCodeStatus, type RecurConfig, type RecurContextValue, RecurProvider, type RecurProviderProps, type RedirectToCheckoutOptions, type SubscriptionResult, UBN_RE, type UseCustomerResult, type UseProductsOptions as UsePlansOptions, type UseProductsOptions, type UseProductsResult as UsePlansResult, type UseProductsResult, type UsePromoCodeOptions, type UsePromoCodeReturn, type UseSubscribeOptions, type UseSubscribeResult, assertValidEinvoicePrefs, isValidTaiwanUbn, useCustomer, useProducts as usePlans, useProducts, usePromoCode, useRecur, useSubscribe, validateEinvoicePrefs };
|