recur-tw 0.8.1 → 0.8.3
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/AGENTS.md +124 -0
- package/dist/index.cjs +10 -8
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +10 -8
- package/dist/recur.umd.js +12 -12
- package/dist/server.cjs +6 -1
- package/dist/server.js +6 -1
- package/package.json +13 -12
package/AGENTS.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# AI Agent Quick Reference for recur-tw SDK
|
|
2
|
+
|
|
3
|
+
> This file helps AI coding assistants understand the SDK structure quickly.
|
|
4
|
+
|
|
5
|
+
## Module Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
recur-tw
|
|
9
|
+
├── index.js → React SDK (needs React 18+)
|
|
10
|
+
├── server.js → Server SDK (Node.js only, uses secret key)
|
|
11
|
+
└── recur.umd.js → Vanilla JS (browser, via CDN or script tag)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Which Module to Use?
|
|
15
|
+
|
|
16
|
+
| Project Type | Import | Entry Point |
|
|
17
|
+
|-------------|--------|-------------|
|
|
18
|
+
| React/Next.js | `import { RecurProvider, useRecur } from 'recur-tw'` | `dist/index.js` |
|
|
19
|
+
| Node.js Server | `import { Recur } from 'recur-tw/server'` | `dist/server.js` |
|
|
20
|
+
| Vanilla JS (CDN) | `<script src="https://unpkg.com/recur-tw/dist/recur.umd.js">` | UMD global `RecurCheckout` |
|
|
21
|
+
| Vanilla JS (bundler) | NOT SUPPORTED as ESM - use CDN script tag | - |
|
|
22
|
+
|
|
23
|
+
## IMPORTANT: Vanilla JS Usage
|
|
24
|
+
|
|
25
|
+
The vanilla module (`recur-tw/vanilla`) exports UMD format ONLY. It is NOT an ES module.
|
|
26
|
+
|
|
27
|
+
**CORRECT** - Use via script tag:
|
|
28
|
+
```html
|
|
29
|
+
<script src="https://unpkg.com/recur-tw/dist/recur.umd.js"></script>
|
|
30
|
+
<script>
|
|
31
|
+
const recur = RecurCheckout.init({ publishableKey: 'pk_xxx' });
|
|
32
|
+
recur.redirectToCheckout({ productId: 'prod_xxx', successUrl: '/success', cancelUrl: '/cancel' });
|
|
33
|
+
</script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**WRONG** - Do NOT try to import as ESM:
|
|
37
|
+
```javascript
|
|
38
|
+
// This will NOT work!
|
|
39
|
+
import { init } from 'recur-tw/vanilla';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## React Quick Start
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
'use client';
|
|
46
|
+
import { RecurProvider, useRecur, useProducts } from 'recur-tw';
|
|
47
|
+
|
|
48
|
+
// 1. Wrap app with provider
|
|
49
|
+
function App() {
|
|
50
|
+
return (
|
|
51
|
+
<RecurProvider config={{ publishableKey: 'pk_xxx' }}>
|
|
52
|
+
<CheckoutPage />
|
|
53
|
+
</RecurProvider>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 2. Use hooks
|
|
58
|
+
function CheckoutPage() {
|
|
59
|
+
const { data: products } = useProducts();
|
|
60
|
+
const { checkout } = useRecur();
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<button onClick={() => checkout({
|
|
64
|
+
planId: products[0].id,
|
|
65
|
+
customerEmail: 'user@example.com'
|
|
66
|
+
})}>
|
|
67
|
+
Subscribe
|
|
68
|
+
</button>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Server SDK Quick Start (Node.js)
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { Recur } from 'recur-tw/server';
|
|
77
|
+
|
|
78
|
+
const recur = new Recur({ secretKey: 'sk_xxx' });
|
|
79
|
+
|
|
80
|
+
// Create checkout session
|
|
81
|
+
const session = await recur.checkoutSessions.create({
|
|
82
|
+
productId: 'prod_xxx',
|
|
83
|
+
successUrl: 'https://example.com/success',
|
|
84
|
+
cancelUrl: 'https://example.com/cancel',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
console.log(session.url); // Redirect user here
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Available Exports
|
|
91
|
+
|
|
92
|
+
### Main Module (`recur-tw`)
|
|
93
|
+
- `RecurProvider` - React context provider
|
|
94
|
+
- `useRecur()` - Checkout hook (checkout, isCheckingOut)
|
|
95
|
+
- `useProducts()` - Fetch products hook
|
|
96
|
+
- Types: `RecurConfig`, `CheckoutOptions`, `Product`, etc.
|
|
97
|
+
|
|
98
|
+
### Server Module (`recur-tw/server`)
|
|
99
|
+
- `Recur` - Server SDK class
|
|
100
|
+
- `recur.checkoutSessions.create()` - Create checkout session
|
|
101
|
+
- `recur.checkoutSessions.retrieve()` - Get session details
|
|
102
|
+
- `recur.portal.createSession()` - Create customer portal session
|
|
103
|
+
|
|
104
|
+
### Vanilla Module (UMD global `RecurCheckout`)
|
|
105
|
+
- `RecurCheckout.init(config)` - Initialize SDK
|
|
106
|
+
- `recur.checkout(options)` - Embedded checkout
|
|
107
|
+
- `recur.redirectToCheckout(options)` - Hosted checkout redirect
|
|
108
|
+
- `recur.createCheckoutSession(options)` - Get session URL
|
|
109
|
+
|
|
110
|
+
## Key Differences
|
|
111
|
+
|
|
112
|
+
| Feature | React SDK | Server SDK | Vanilla JS |
|
|
113
|
+
|---------|-----------|------------|------------|
|
|
114
|
+
| Environment | Browser | Node.js | Browser |
|
|
115
|
+
| Auth Key | Publishable (`pk_*`) | Secret (`sk_*`) | Publishable (`pk_*`) |
|
|
116
|
+
| Import Style | ESM | ESM | Script tag (UMD) |
|
|
117
|
+
| React Required | Yes | No | No |
|
|
118
|
+
|
|
119
|
+
## Common Mistakes
|
|
120
|
+
|
|
121
|
+
1. **Trying to import vanilla as ESM** - Use script tag instead
|
|
122
|
+
2. **Using secret key in browser** - Only use `pk_*` keys client-side
|
|
123
|
+
3. **Missing RecurProvider** - React hooks need the provider wrapper
|
|
124
|
+
4. **Wrong import path** - Server SDK is `recur-tw/server`, not `recur-tw`
|
package/dist/index.cjs
CHANGED
|
@@ -2598,6 +2598,8 @@ function toCamelCase(obj) {
|
|
|
2598
2598
|
}
|
|
2599
2599
|
return obj;
|
|
2600
2600
|
}
|
|
2601
|
+
var SDK_VERSION = "0.8.1";
|
|
2602
|
+
var SDK_TYPE = "react";
|
|
2601
2603
|
var RecurContext = React.createContext(null);
|
|
2602
2604
|
function RecurProvider({ children, config: initialConfig = {} }) {
|
|
2603
2605
|
const [config, setConfig] = React.useState({
|
|
@@ -2635,17 +2637,14 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2635
2637
|
if (!config.publishableKey) {
|
|
2636
2638
|
throw new Error("publishableKey is required");
|
|
2637
2639
|
}
|
|
2638
|
-
if (!options.customerName) {
|
|
2639
|
-
throw new Error("customerName is required");
|
|
2640
|
-
}
|
|
2641
|
-
if (!options.customerEmail) {
|
|
2642
|
-
throw new Error("customerEmail is required");
|
|
2643
|
-
}
|
|
2644
2640
|
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
2645
2641
|
console.log("[Recur SDK] Base URL:", baseUrl);
|
|
2646
2642
|
const headers = {
|
|
2647
2643
|
"Content-Type": "application/json",
|
|
2648
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
2644
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
2645
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
2646
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
2647
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
2649
2648
|
};
|
|
2650
2649
|
let modalContent = null;
|
|
2651
2650
|
let loadingContainer = null;
|
|
@@ -3010,7 +3009,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3010
3009
|
method: "GET",
|
|
3011
3010
|
headers: {
|
|
3012
3011
|
"Content-Type": "application/json",
|
|
3013
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
3012
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
3013
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
3014
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
3015
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
3014
3016
|
}
|
|
3015
3017
|
});
|
|
3016
3018
|
if (!response.ok) {
|
package/dist/index.d.cts
CHANGED
|
@@ -222,14 +222,20 @@ interface CheckoutOptions {
|
|
|
222
222
|
*/
|
|
223
223
|
planId?: string;
|
|
224
224
|
/**
|
|
225
|
-
* Customer name
|
|
225
|
+
* Customer name (optional)
|
|
226
|
+
* Pre-fills the name field on the checkout page.
|
|
227
|
+
* If not provided, customer will enter it during checkout.
|
|
226
228
|
*/
|
|
227
229
|
customerName?: string;
|
|
228
230
|
/**
|
|
229
|
-
* Customer email address (
|
|
230
|
-
*
|
|
231
|
+
* Customer email address (optional)
|
|
232
|
+
* Pre-fills the email field on the checkout page.
|
|
233
|
+
* If not provided, customer will enter it during checkout.
|
|
234
|
+
*
|
|
235
|
+
* Note: Email is required at checkout time (for receipt),
|
|
236
|
+
* but optional when creating the session.
|
|
231
237
|
*/
|
|
232
|
-
customerEmail
|
|
238
|
+
customerEmail?: string;
|
|
233
239
|
/**
|
|
234
240
|
* External customer ID from your system (optional)
|
|
235
241
|
* Use this to link Recur subscriptions to your existing users.
|
package/dist/index.d.ts
CHANGED
|
@@ -222,14 +222,20 @@ interface CheckoutOptions {
|
|
|
222
222
|
*/
|
|
223
223
|
planId?: string;
|
|
224
224
|
/**
|
|
225
|
-
* Customer name
|
|
225
|
+
* Customer name (optional)
|
|
226
|
+
* Pre-fills the name field on the checkout page.
|
|
227
|
+
* If not provided, customer will enter it during checkout.
|
|
226
228
|
*/
|
|
227
229
|
customerName?: string;
|
|
228
230
|
/**
|
|
229
|
-
* Customer email address (
|
|
230
|
-
*
|
|
231
|
+
* Customer email address (optional)
|
|
232
|
+
* Pre-fills the email field on the checkout page.
|
|
233
|
+
* If not provided, customer will enter it during checkout.
|
|
234
|
+
*
|
|
235
|
+
* Note: Email is required at checkout time (for receipt),
|
|
236
|
+
* but optional when creating the session.
|
|
231
237
|
*/
|
|
232
|
-
customerEmail
|
|
238
|
+
customerEmail?: string;
|
|
233
239
|
/**
|
|
234
240
|
* External customer ID from your system (optional)
|
|
235
241
|
* Use this to link Recur subscriptions to your existing users.
|
package/dist/index.js
CHANGED
|
@@ -2592,6 +2592,8 @@ function toCamelCase(obj) {
|
|
|
2592
2592
|
}
|
|
2593
2593
|
return obj;
|
|
2594
2594
|
}
|
|
2595
|
+
var SDK_VERSION = "0.8.1";
|
|
2596
|
+
var SDK_TYPE = "react";
|
|
2595
2597
|
var RecurContext = createContext(null);
|
|
2596
2598
|
function RecurProvider({ children, config: initialConfig = {} }) {
|
|
2597
2599
|
const [config, setConfig] = useState({
|
|
@@ -2629,17 +2631,14 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2629
2631
|
if (!config.publishableKey) {
|
|
2630
2632
|
throw new Error("publishableKey is required");
|
|
2631
2633
|
}
|
|
2632
|
-
if (!options.customerName) {
|
|
2633
|
-
throw new Error("customerName is required");
|
|
2634
|
-
}
|
|
2635
|
-
if (!options.customerEmail) {
|
|
2636
|
-
throw new Error("customerEmail is required");
|
|
2637
|
-
}
|
|
2638
2634
|
const baseUrl = config.baseUrl || "https://api.recur.tw";
|
|
2639
2635
|
console.log("[Recur SDK] Base URL:", baseUrl);
|
|
2640
2636
|
const headers = {
|
|
2641
2637
|
"Content-Type": "application/json",
|
|
2642
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
2638
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
2639
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
2640
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
2641
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
2643
2642
|
};
|
|
2644
2643
|
let modalContent = null;
|
|
2645
2644
|
let loadingContainer = null;
|
|
@@ -3004,7 +3003,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3004
3003
|
method: "GET",
|
|
3005
3004
|
headers: {
|
|
3006
3005
|
"Content-Type": "application/json",
|
|
3007
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
3006
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
3007
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
3008
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
3009
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
3008
3010
|
}
|
|
3009
3011
|
});
|
|
3010
3012
|
if (!response.ok) {
|
package/dist/recur.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var RecurCheckout=(()=>{var I=Object.defineProperty;var
|
|
1
|
+
"use strict";var RecurCheckout=(()=>{var I=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var be=Object.getOwnPropertyNames;var ye=Object.prototype.hasOwnProperty;var ve=(o,e,t)=>e in o?I(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t;var b=(o,e)=>()=>(o&&(e=o(o=0)),e);var f=(o,e)=>{for(var t in e)I(o,t,{get:e[t],enumerable:!0})},ke=(o,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of be(e))!ye.call(o,i)&&i!==t&&I(o,i,{get:()=>e[i],enumerable:!(r=ge(e,i))||r.enumerable});return o};var xe=o=>ke(I({},"__esModule",{value:!0}),o);var c=(o,e,t)=>ve(o,typeof e!="symbol"?e+"":e,t);var V={};f(V,{RecurLoadingSpinner:()=>R});var R,X=b(()=>{"use strict";R=class extends HTMLElement{static get observedAttributes(){return["message","size"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get message(){return this.getAttribute("message")||"\u6B63\u5728\u8655\u7406\u8A02\u95B1..."}get size(){let e=this.getAttribute("size");return e==="small"||e==="large"?e:"medium"}getSizeValue(){return{small:24,medium:40,large:56}[this.size]}render(){let e=this.getSizeValue();this.shadowRoot.innerHTML=`
|
|
2
2
|
<style>
|
|
3
3
|
:host {
|
|
4
4
|
display: block;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
<div class="recur-sdk__spinner"></div>
|
|
42
42
|
${this.message?`<p class="recur-sdk__loading-text">${this.message}</p>`:""}
|
|
43
|
-
`}};typeof window<"u"&&!customElements.get("recur-loading-spinner")&&customElements.define("recur-loading-spinner",R)});var J={};f(J,{RecurSuccessMessage:()=>P});var P,W=
|
|
43
|
+
`}};typeof window<"u"&&!customElements.get("recur-loading-spinner")&&customElements.define("recur-loading-spinner",R)});var J={};f(J,{RecurSuccessMessage:()=>P});var P,W=b(()=>{"use strict";P=class extends HTMLElement{static get observedAttributes(){return["title","message","icon"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get successTitle(){return this.getAttribute("title")||"Subscription Complete!"}get successMessage(){return this.getAttribute("message")||"Thank you for subscribing. Your payment has been processed successfully."}get showIcon(){return this.getAttribute("icon")!=="false"}render(){this.shadowRoot.innerHTML=`
|
|
44
44
|
<style>
|
|
45
45
|
:host {
|
|
46
46
|
display: block;
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<p class="recur-sdk__success-message">${this.successMessage}</p>
|
|
122
122
|
<slot></slot>
|
|
123
123
|
</div>
|
|
124
|
-
`}};typeof window<"u"&&!customElements.get("recur-success-message")&&customElements.define("recur-success-message",P)});var G={};f(G,{RecurErrorDisplay:()=>L});var L,Z=
|
|
124
|
+
`}};typeof window<"u"&&!customElements.get("recur-success-message")&&customElements.define("recur-success-message",P)});var G={};f(G,{RecurErrorDisplay:()=>L});var L,Z=b(()=>{"use strict";L=class extends HTMLElement{static get observedAttributes(){return["error","dismissible"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get error(){return this.getAttribute("error")||""}get isDismissible(){return this.getAttribute("dismissible")==="true"}handleDismiss(){this.dispatchEvent(new CustomEvent("dismiss",{bubbles:!0,composed:!0})),this.remove()}render(){if(!this.error){this.shadowRoot.innerHTML="";return}this.shadowRoot.innerHTML=`
|
|
125
125
|
<style>
|
|
126
126
|
:host {
|
|
127
127
|
display: block;
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
</button>
|
|
212
212
|
`:""}
|
|
213
213
|
</div>
|
|
214
|
-
`,this.isDismissible&&this.shadowRoot.querySelector(".recur-sdk__error-dismiss")?.addEventListener("click",()=>this.handleDismiss())}};typeof window<"u"&&!customElements.get("recur-error-display")&&customElements.define("recur-error-display",L)});var Q={};f(Q,{RecurSkeletonLoader:()=>M});var M,ee=
|
|
214
|
+
`,this.isDismissible&&this.shadowRoot.querySelector(".recur-sdk__error-dismiss")?.addEventListener("click",()=>this.handleDismiss())}};typeof window<"u"&&!customElements.get("recur-error-display")&&customElements.define("recur-error-display",L)});var Q={};f(Q,{RecurSkeletonLoader:()=>M});var M,ee=b(()=>{"use strict";M=class extends HTMLElement{static get observedAttributes(){return["type"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}attributeChangedCallback(e,t,r){t!==r&&this.render()}get type(){let e=this.getAttribute("type");return e==="list"||e==="card"?e:"payment-form"}renderPaymentFormSkeleton(){return`
|
|
215
215
|
<div class="skeleton-field">
|
|
216
216
|
<div class="skeleton-label"></div>
|
|
217
217
|
<div class="skeleton-input"></div>
|
|
@@ -377,7 +377,7 @@
|
|
|
377
377
|
</style>
|
|
378
378
|
|
|
379
379
|
${e}
|
|
380
|
-
`}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",M)});var te={};f(te,{RecurPaymentFormSkeleton:()=>U});var U,re=
|
|
380
|
+
`}};typeof window<"u"&&!customElements.get("recur-skeleton-loader")&&customElements.define("recur-skeleton-loader",M)});var te={};f(te,{RecurPaymentFormSkeleton:()=>U});var U,re=b(()=>{"use strict";U=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render()}render(){this.shadowRoot.innerHTML=`
|
|
381
381
|
<style>
|
|
382
382
|
:host {
|
|
383
383
|
display: block;
|
|
@@ -644,7 +644,7 @@
|
|
|
644
644
|
<p class="security-text">\u60A8\u7684\u4ED8\u6B3E\u8CC7\u8A0A\u7D93\u904E\u52A0\u5BC6\u4FDD\u8B77</p>
|
|
645
645
|
</div>
|
|
646
646
|
</div>
|
|
647
|
-
`}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",U)});var ie={};f(ie,{RecurToast:()=>_,RecurToastContainer:()=>E});var _,y,E,se=
|
|
647
|
+
`}};typeof window<"u"&&!customElements.get("recur-payment-form-skeleton")&&customElements.define("recur-payment-form-skeleton",U)});var ie={};f(ie,{RecurToast:()=>_,RecurToastContainer:()=>E});var _,y,E,se=b(()=>{"use strict";_=class extends HTMLElement{static get observedAttributes(){return["message","type","duration"]}constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){this.render(),this.setupAutoDismiss()}get message(){return this.getAttribute("message")||"Notification"}get type(){let e=this.getAttribute("type");return e==="success"||e==="error"?e:"info"}get duration(){let e=this.getAttribute("duration");return e?parseInt(e,10):5e3}setupAutoDismiss(){let e=this.duration;e>0&&setTimeout(()=>this.dismiss(),e)}dismiss(){this.style.animation="recur-toast-slide-out 0.3s ease-in-out",setTimeout(()=>this.remove(),300)}getTypeIcon(){switch(this.type){case"success":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
648
648
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
649
649
|
</svg>`;case"error":return`<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
650
650
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
@@ -791,7 +791,7 @@
|
|
|
791
791
|
</style>
|
|
792
792
|
|
|
793
793
|
<slot></slot>
|
|
794
|
-
`}static getInstance(){return y.instance||(y.instance=document.querySelector("recur-toast-container"),y.instance||(y.instance=document.createElement("recur-toast-container"),document.body.appendChild(y.instance))),y.instance}};c(y,"instance",null);E=y;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",_);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",E)});var oe={};f(oe,{RecurPaymentForm:()=>A});var A,ne=
|
|
794
|
+
`}static getInstance(){return y.instance||(y.instance=document.querySelector("recur-toast-container"),y.instance||(y.instance=document.createElement("recur-toast-container"),document.body.appendChild(y.instance))),y.instance}};c(y,"instance",null);E=y;typeof window<"u"&&!customElements.get("recur-toast")&&customElements.define("recur-toast",_);typeof window<"u"&&!customElements.get("recur-toast-container")&&customElements.define("recur-toast-container",E)});var oe={};f(oe,{RecurPaymentForm:()=>A});var A,ne=b(()=>{"use strict";A=class extends HTMLElement{constructor(){super();c(this,"containerId");c(this,"customStyles");c(this,"_isInitializing",!1);c(this,"_initializationAborted",!1);this.containerId=this.getAttribute("container-id")||`recur-${Date.now()}`,this.customStyles=this.getAttribute("custom-styles")||"",this.attachShadow({mode:"open"})}connectedCallback(){this.render()}disconnectedCallback(){console.log("[PaymentForm] Component disconnected, cleaning up..."),this._initializationAborted=!0,this._paymentSession=null;let t=document.getElementById(`${this.containerId}-submit-btn`);if(t){let r=t.cloneNode(!0);t.parentNode?.replaceChild(r,t)}}static get observedAttributes(){return["custom-styles","customer-name","customer-email","plan-name","amount","billing-period"]}attributeChangedCallback(t,r,i){t==="custom-styles"&&r!==i?(this.customStyles=i||"",this.updateCustomStyles()):r!==i&&this.updateCustomerInfoSection()}render(){this.shadowRoot.innerHTML=`
|
|
795
795
|
<style>
|
|
796
796
|
:host {
|
|
797
797
|
display: block;
|
|
@@ -1223,7 +1223,7 @@
|
|
|
1223
1223
|
`,t}async initializePayment(t,r){if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before start"),null;if(this._isInitializing=!0,await this.updateComplete(),this._initializationAborted)return console.log("[PaymentForm] Initialization aborted after updateComplete"),this._isInitializing=!1,null;if(!window.UniPayment)throw this._isInitializing=!1,new Error("PAYUNi SDK not loaded");try{let i=document.getElementById(`${this.containerId}-card-no`),s=document.getElementById(`${this.containerId}-card-exp`),a=document.getElementById(`${this.containerId}-card-cvc`);if(!i||!s||!a)return console.log("[PaymentForm] DOM elements not found, likely disconnected"),this._isInitializing=!1,null;if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before createSession"),this._isInitializing=!1,null;let n=window.UniPayment.createSession(t,{env:r==="SANDBOX"?"S":"P",elements:{CardNo:`${this.containerId}-card-no`,CardExp:`${this.containerId}-card-exp`,CardCvc:`${this.containerId}-card-cvc`}});if(this._initializationAborted)return console.log("[PaymentForm] Initialization aborted before paymentSession.start()"),this._isInitializing=!1,null;try{await n.start()}catch(l){if((l?.message?.includes("1008")||l?.message?.includes("timeout")||l?.code===1008)&&this._initializationAborted)return console.log("[PaymentForm] Caught 1008 timeout error after component disconnect - ignoring"),this._isInitializing=!1,null;throw l}return this._initializationAborted?(console.log("[PaymentForm] Initialization aborted after paymentSession.start()"),this._isInitializing=!1,null):(this.hideCardSkeletons(),n.onUpdate?.(l=>{if(this._initializationAborted){console.log("[PaymentForm] Ignoring onUpdate event - component disconnected");return}console.log("[PaymentForm] PAYUNi update:",l);let d=l.status&&l.status.CardNo===!0&&l.status.CardExp===!0&&l.status.CardCvc===!0,v=document.getElementById(`${this.containerId}-submit-btn`);v&&(v.disabled=!d,console.log("[PaymentForm] Submit button disabled:",!d))}),this._initializationAborted?(console.log("[PaymentForm] Initialization aborted before saving session"),this._isInitializing=!1,null):(this._paymentSession=n,this.setupFormSubmission(),this._isInitializing=!1,n))}catch(i){throw this._isInitializing=!1,console.error("[PaymentForm] Failed to initialize payment:",i),i}}setupFormSubmission(){let t=document.getElementById(`${this.containerId}-submit-btn`);t&&t.addEventListener("click",async r=>{if(r.preventDefault(),t.classList.contains("loading"))return;let i,s,a=document.getElementById(`${this.containerId}-email`),n=document.getElementById(`${this.containerId}-name`);if(a&&n){if(i=a.value,s=n.value,!i||!s){this.showError("\u8ACB\u586B\u5BEB\u59D3\u540D\u548C\u96FB\u5B50\u90F5\u4EF6");return}}else if(i=this.getAttribute("customer-email")||void 0,s=this.getAttribute("customer-name")||void 0,!i||!s){this.showError("\u5BA2\u6236\u8CC7\u8A0A\u4E0D\u5B8C\u6574");return}this.clearError(),this.setButtonLoading(!0),this.dispatchEvent(new CustomEvent("submit",{detail:{customerEmail:i,customerName:s,paymentSession:this._paymentSession},bubbles:!0,composed:!0}))})}setButtonLoading(t){let r=document.getElementById(`${this.containerId}-submit-btn`);r&&(t?(r.classList.add("loading"),r.disabled=!0,r.innerHTML=`
|
|
1224
1224
|
<span class="recur-loading-spinner"></span>
|
|
1225
1225
|
<span>\u8655\u7406\u4E2D...</span>
|
|
1226
|
-
`):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}showError(t){let r=this.querySelector(".recur-sdk__error-container");r||(r=document.createElement("div"),r.className="recur-sdk__error-container",r.style.cssText="margin-bottom: 16px;",this.insertBefore(r,this.firstChild));let i=document.createElement("recur-error-display");i.setAttribute("error",t),i.setAttribute("dismissible","true"),r.innerHTML="",r.appendChild(i),i.scrollIntoView({behavior:"smooth",block:"center"})}clearError(){let t=this.querySelector(".recur-sdk__error-container");t&&(t.innerHTML="")}updateComplete(){return new Promise(t=>{if(this.isConnected)setTimeout(t,0);else{let r=new MutationObserver(()=>{this.isConnected&&(r.disconnect(),setTimeout(t,0))});r.observe(document.body,{childList:!0,subtree:!0})}})}getFormData(){return{email:document.getElementById(`${this.containerId}-email`)?.value,name:document.getElementById(`${this.containerId}-name`)?.value}}setFormData(t){if(t.email){let r=document.getElementById(`${this.containerId}-email`);r&&(r.value=t.email)}if(t.name){let r=document.getElementById(`${this.containerId}-name`);r&&(r.value=t.name)}}};customElements.get("recur-payment-form")||customElements.define("recur-payment-form",A)});var ae={};f(ae,{RecurCheckoutButton:()=>D});var D,ce=
|
|
1226
|
+
`):(r.classList.remove("loading"),r.disabled=!1,r.innerHTML='<span id="'+this.containerId+'-submit-text">\u78BA\u8A8D\u4ED8\u6B3E</span>'))}resetButton(){this.setButtonLoading(!1)}showError(t){let r=this.querySelector(".recur-sdk__error-container");r||(r=document.createElement("div"),r.className="recur-sdk__error-container",r.style.cssText="margin-bottom: 16px;",this.insertBefore(r,this.firstChild));let i=document.createElement("recur-error-display");i.setAttribute("error",t),i.setAttribute("dismissible","true"),r.innerHTML="",r.appendChild(i),i.scrollIntoView({behavior:"smooth",block:"center"})}clearError(){let t=this.querySelector(".recur-sdk__error-container");t&&(t.innerHTML="")}updateComplete(){return new Promise(t=>{if(this.isConnected)setTimeout(t,0);else{let r=new MutationObserver(()=>{this.isConnected&&(r.disconnect(),setTimeout(t,0))});r.observe(document.body,{childList:!0,subtree:!0})}})}getFormData(){return{email:document.getElementById(`${this.containerId}-email`)?.value,name:document.getElementById(`${this.containerId}-name`)?.value}}setFormData(t){if(t.email){let r=document.getElementById(`${this.containerId}-email`);r&&(r.value=t.email)}if(t.name){let r=document.getElementById(`${this.containerId}-name`);r&&(r.value=t.name)}}};customElements.get("recur-payment-form")||customElements.define("recur-payment-form",A)});var ae={};f(ae,{RecurCheckoutButton:()=>D});var D,ce=b(()=>{"use strict";D=class extends HTMLElement{constructor(){super();c(this,"_isLoading",!1);c(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("publishable-key"),i=this.getAttribute("product-id"),s=this.getAttribute("success-url"),a=this.getAttribute("cancel-url");if(!r){this.dispatchError("Missing required attribute: publishable-key");return}if(!i){this.dispatchError("Missing required attribute: product-id");return}if(!s){this.dispatchError("Missing required attribute: success-url");return}if(!a){this.dispatchError("Missing required attribute: cancel-url");return}this._isLoading=!0,this.render(),this.setupEventListeners();try{let n=await this.createCheckoutSession({publishableKey:r,productId:i,successUrl:this.resolveUrl(s),cancelUrl:this.resolveUrl(a),customerEmail:this.getAttribute("customer-email")||void 0,mode:this.getAttribute("mode")});this.dispatchEvent(new CustomEvent("checkout-started",{detail:{sessionId:n.id,url:n.url},bubbles:!0,composed:!0})),window.location.href=n.url}catch(n){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(n.message||"Failed to create checkout session")}});this.attachShadow({mode:"open"})}static get observedAttributes(){return["publishable-key","product-id","success-url","cancel-url","customer-email","mode","button-text","button-style","disabled"]}connectedCallback(){this.render(),this.setupEventListeners()}disconnectedCallback(){let t=this.shadowRoot?.querySelector("button");t&&t.removeEventListener("click",this.handleClick)}attributeChangedCallback(t,r,i){r!==i&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",i=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
|
|
1227
1227
|
<style>
|
|
1228
1228
|
:host {
|
|
1229
1229
|
display: inline-block;
|
|
@@ -1322,7 +1322,7 @@
|
|
|
1322
1322
|
${this._isLoading?'<span class="spinner"></span>':""}
|
|
1323
1323
|
<span>${this._isLoading?"\u8655\u7406\u4E2D...":t}</span>
|
|
1324
1324
|
</button>
|
|
1325
|
-
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),i={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(i.mode=t.mode),t.customerEmail&&(i.customerEmail=t.customerEmail);let s=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(i)});if(!s.ok){let a=await s.json().catch(()=>({}));throw new Error(a.error?.message||`HTTP ${s.status}: Failed to create checkout session`)}return s.json()}getApiBaseUrl(){let t=this.getAttribute("api-base-url");if(t)return t;if(typeof window<"u"){let r=window.location.hostname;if(r==="localhost"||r.includes(".test")||r.includes(".local")||r==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}resolveUrl(t){return t.startsWith("/")?`${window.location.origin}${t}`:t}dispatchError(t){console.error("[recur-checkout]",t),this.dispatchEvent(new CustomEvent("checkout-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-checkout")&&customElements.define("recur-checkout",D)});var le={};f(le,{RecurPortalButton:()=>z});var z,de=
|
|
1325
|
+
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}async createCheckoutSession(t){let r=this.getApiBaseUrl(),i={productId:t.productId,successUrl:t.successUrl,cancelUrl:t.cancelUrl};t.mode&&(i.mode=t.mode),t.customerEmail&&(i.customerEmail=t.customerEmail);let s=await fetch(`${r}/v1/checkout/sessions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},body:JSON.stringify(i)});if(!s.ok){let a=await s.json().catch(()=>({}));throw new Error(a.error?.message||`HTTP ${s.status}: Failed to create checkout session`)}return s.json()}getApiBaseUrl(){let t=this.getAttribute("api-base-url");if(t)return t;if(typeof window<"u"){let r=window.location.hostname;if(r==="localhost"||r.includes(".test")||r.includes(".local")||r==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}resolveUrl(t){return t.startsWith("/")?`${window.location.origin}${t}`:t}dispatchError(t){console.error("[recur-checkout]",t),this.dispatchEvent(new CustomEvent("checkout-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-checkout")&&customElements.define("recur-checkout",D)});var le={};f(le,{RecurPortalButton:()=>z});var z,de=b(()=>{"use strict";z=class extends HTMLElement{constructor(){super();c(this,"_isLoading",!1);c(this,"handleClick",async t=>{if(t.preventDefault(),this._isLoading||this.hasAttribute("disabled"))return;let r=this.getAttribute("portal-url"),i=this.getAttribute("api-endpoint");if(r){this.redirectToPortal(r);return}if(i){await this.fetchAndRedirect(i);return}this.dispatchError("Missing required attribute: either portal-url or api-endpoint must be provided")});this.attachShadow({mode:"open"})}static get observedAttributes(){return["portal-url","api-endpoint","customer-id","return-url","button-text","button-style","disabled","target"]}connectedCallback(){this.render(),this.setupEventListeners()}disconnectedCallback(){let t=this.shadowRoot?.querySelector("button");t&&t.removeEventListener("click",this.handleClick)}attributeChangedCallback(t,r,i){r!==i&&this.render()}render(){let t=this.getAttribute("button-text")||this.textContent?.trim()||"\u7BA1\u7406\u8A02\u95B1",r=this.getAttribute("button-style")||"primary",i=this.hasAttribute("disabled")||this._isLoading;this.shadowRoot.innerHTML=`
|
|
1326
1326
|
<style>
|
|
1327
1327
|
:host {
|
|
1328
1328
|
display: inline-block;
|
|
@@ -1449,7 +1449,7 @@
|
|
|
1449
1449
|
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
|
|
1450
1450
|
<circle cx="12" cy="7" r="4"/>
|
|
1451
1451
|
</svg>
|
|
1452
|
-
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}redirectToPortal(t){let r=this.getAttribute("target");this.dispatchEvent(new CustomEvent("portal-redirect",{detail:{url:t},bubbles:!0,composed:!0})),r==="_blank"?window.open(t,"_blank","noopener,noreferrer"):window.location.href=t}async fetchAndRedirect(t){let r=this.getAttribute("customer-id"),i=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let s={};r&&(s.customerId=r),i&&(s.returnUrl=i);let a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!a.ok){let d=await a.json().catch(()=>({}));throw new Error(d.error?.message||d.message||`HTTP ${a.status}: Failed to create portal session`)}let n=await a.json(),l=n.url||n.portalUrl;if(!l)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(l)}catch(s){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(s.message||"Failed to create portal session")}}dispatchError(t){console.error("[recur-portal]",t),this.dispatchEvent(new CustomEvent("portal-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-portal")&&customElements.define("recur-portal",z)});var
|
|
1452
|
+
`}setupEventListeners(){let t=this.shadowRoot?.querySelector("button");t&&t.addEventListener("click",this.handleClick)}redirectToPortal(t){let r=this.getAttribute("target");this.dispatchEvent(new CustomEvent("portal-redirect",{detail:{url:t},bubbles:!0,composed:!0})),r==="_blank"?window.open(t,"_blank","noopener,noreferrer"):window.location.href=t}async fetchAndRedirect(t){let r=this.getAttribute("customer-id"),i=this.getAttribute("return-url");this._isLoading=!0,this.render(),this.setupEventListeners();try{let s={};r&&(s.customerId=r),i&&(s.returnUrl=i);let a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!a.ok){let d=await a.json().catch(()=>({}));throw new Error(d.error?.message||d.message||`HTTP ${a.status}: Failed to create portal session`)}let n=await a.json(),l=n.url||n.portalUrl;if(!l)throw new Error("Portal URL not found in response");this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(l)}catch(s){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(s.message||"Failed to create portal session")}}dispatchError(t){console.error("[recur-portal]",t),this.dispatchEvent(new CustomEvent("portal-error",{detail:{message:t},bubbles:!0,composed:!0}))}};typeof window<"u"&&!customElements.get("recur-portal")&&customElements.define("recur-portal",z)});var Pe={};f(Pe,{RecurCheckout:()=>C,RecurElements:()=>w,createElements:()=>j,default:()=>Re,init:()=>pe});async function we(){if(typeof window>"u"){console.warn("[Recur SDK] Cannot register Web Components: window is not defined");return}await Promise.all([Promise.resolve().then(()=>(X(),V)),Promise.resolve().then(()=>(W(),J)),Promise.resolve().then(()=>(Z(),G)),Promise.resolve().then(()=>(ee(),Q)),Promise.resolve().then(()=>(re(),te)),Promise.resolve().then(()=>(se(),ie)),Promise.resolve().then(()=>(ne(),oe)),Promise.resolve().then(()=>(ce(),ae)),Promise.resolve().then(()=>(de(),le))]);let e=["recur-loading-spinner","recur-success-message","recur-error-display","recur-skeleton-loader","recur-payment-form-skeleton","recur-toast","recur-toast-container","recur-payment-form","recur-checkout","recur-portal"].filter(t=>!customElements.get(t));e.length>0&&console.warn("[Recur SDK] The following components are not registered:",e)}typeof window<"u"&&we();function Ee(o){return o.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function g(o){if(o==null)return o;if(Array.isArray(o))return o.map(e=>g(e));if(o instanceof Date)return o;if(typeof o=="object"){let e={};for(let[t,r]of Object.entries(o)){let i=Ee(t);e[i]=g(r)}return e}return o}var Se="0.8.1",Ce="vanilla",H=class{constructor(e){c(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}getHeaders(){return{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey,"X-Recur-SDK-Type":Ce,"X-Recur-SDK-Version":Se,"X-Recur-Source":typeof window<"u"?window.location.origin:"server"}}async createSubscription(e){let{customerName:t,customerEmail:r}=e,i=e.productId||e.planId,s=e.productSlug;if(!i&&!s)throw new Error("Either productId or productSlug is required");let a={customerName:t,customerEmail:r};i&&(a.productId=i),s&&(a.productSlug=s);let n=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(a)});if(!n.ok){let d=await n.json().catch(()=>({}));throw{code:d.error||"CHECKOUT_FAILED",message:d.message||"Failed to initiate checkout",details:d}}let l=await n.json();return g(l)}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set("type",e.type);let r=await fetch(t.toString(),{method:"GET",headers:this.getHeaders()});if(!r.ok){let s=await r.json().catch(()=>({}));throw{code:s.error||"FETCH_PRODUCTS_FAILED",message:s.message||"Failed to fetch products",details:s}}let i=await r.json();return g(i)}async fetchPlans(){return{plans:(await this.fetchProducts({type:"SUBSCRIPTION"})).products}}getConfig(){return{...this.config}}};var $=class{constructor(e,t){c(this,"config");c(this,"options");c(this,"container");c(this,"checkoutId",null);c(this,"sdkToken",null);c(this,"sdkEnv","S");c(this,"payuniSDK",null);c(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let r=document.querySelector(t.container);if(!r)throw new Error(`Container not found: ${t.container}`);this.container=r}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<"u"){let e=window.location.hostname;if(e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let s=await t.json().catch(()=>({}));throw new Error(s.error||"Failed to initialize checkout")}let r=await t.json(),i=g(r);this.checkoutId=i.checkout.id,this.sdkToken=i.sdkToken,this.sdkEnv=this.config.publishableKey.startsWith("pk_test_")?"S":"P"}renderHTML(){this.container.innerHTML=`
|
|
1453
1453
|
<div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
|
|
1454
1454
|
<div class="recur-checkout-header" style="margin-bottom: 24px;">
|
|
1455
1455
|
<h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
|
|
@@ -1526,7 +1526,7 @@
|
|
|
1526
1526
|
display: block;
|
|
1527
1527
|
user-select: none;
|
|
1528
1528
|
transition: height 0.35s ease, opacity 0.4s ease 0.1s;
|
|
1529
|
-
`.replace(/\s+/g," ").trim(),this.iframe.allow="payment *",this.iframe.setAttribute("title","Secure payment input frame"),this.iframe.setAttribute("role","presentation"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("frameBorder","0"),this.container.innerHTML="",this.container.appendChild(this.iframe),new Promise((r,i)=>{let s=setTimeout(()=>{i(new Error("Elements initialization timeout"))},3e4),a=()=>{clearTimeout(s),this.off("ready",a),r()},n=l=>{clearTimeout(s),this.off("error",n),i(new Error(l.message||"Elements initialization failed"))};this.on("ready",a),this.on("error",n)})}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){let r=this.eventHandlers.get(e);r&&r.delete(t)}emit(e,t){let r=this.eventHandlers.get(e);r&&r.forEach(i=>i(t))}handleMessage(e){if(!this.isValidOrigin(e.origin))return;let{type:t,data:r}=e.data||{};switch(t){case"RECUR_ELEMENTS_READY":this.sessionId=r.sessionId,this.timestamp=r.timestamp,this.creditToken=r.creditToken,this.sdkToken=r.sdkToken,this.emit("ready",r);break;case"RECUR_CARD_VALID":this.emit("change",{valid:r.valid});break;case"RECUR_CARD_TOKENIZED":this.cardToken=r.cardToken,this.cardTimestamp=r.timestamp,this.emit("tokenized",r);break;case"RECUR_PAYMENT_ERROR":this.emit("error",r);break;case"RECUR_RESIZE":this.iframe&&r?.height&&(this.iframe.style.height=`${r.height}px`);break}}isValidOrigin(e){try{let t=new URL(this.embedUrl).origin;return e===t}catch{return!1}}async tokenize(){if(!this.iframe||!this.iframe.contentWindow)throw new Error("Elements not mounted");let e=new URL(this.embedUrl).origin;return this.iframe.contentWindow.postMessage({type:"RECUR_TOKENIZE"},e),new Promise((t,r)=>{let i=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),s=n=>{clearTimeout(i),this.off("tokenized",s),t(n)},a=n=>{clearTimeout(i),this.off("error",a),r(new Error(n.message||"Tokenization failed"))};this.on("tokenized",s),this.on("error",a)})}async confirmPayment(e){if(!this.sessionId||!this.cardToken)throw new Error("Card not tokenized. Call tokenize() first.");let t=await fetch(`${this.baseUrl}/api/v1/elements/confirm`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.publishableKey},body:JSON.stringify({sessionId:this.sessionId,productId:e.productId,email:e.email,name:e.name,phone:e.phone,externalCustomerId:e.externalCustomerId,metadata:e.metadata,successUrl:e.successUrl,cancelUrl:e.cancelUrl})}),r=await t.json();return t.ok?r:{status:"failed",message:r.error||r.message||"Payment failed",canRetry:r.canRetry??!0}}getDefaultBaseUrl(){if(typeof window>"u")return"https://app.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3000`:"https://app.recur.tw"}getDefaultEmbedUrl(){if(typeof window>"u")return"https://embed.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3002`:"https://embed.recur.tw"}destroy(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.container=null,this.sessionId=null,this.timestamp=null,this.creditToken=null,this.sdkToken=null,this.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function j(o){return new w(o)}var
|
|
1529
|
+
`.replace(/\s+/g," ").trim(),this.iframe.allow="payment *",this.iframe.setAttribute("title","Secure payment input frame"),this.iframe.setAttribute("role","presentation"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("frameBorder","0"),this.container.innerHTML="",this.container.appendChild(this.iframe),new Promise((r,i)=>{let s=setTimeout(()=>{i(new Error("Elements initialization timeout"))},3e4),a=()=>{clearTimeout(s),this.off("ready",a),r()},n=l=>{clearTimeout(s),this.off("error",n),i(new Error(l.message||"Elements initialization failed"))};this.on("ready",a),this.on("error",n)})}on(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}off(e,t){let r=this.eventHandlers.get(e);r&&r.delete(t)}emit(e,t){let r=this.eventHandlers.get(e);r&&r.forEach(i=>i(t))}handleMessage(e){if(!this.isValidOrigin(e.origin))return;let{type:t,data:r}=e.data||{};switch(t){case"RECUR_ELEMENTS_READY":this.sessionId=r.sessionId,this.timestamp=r.timestamp,this.creditToken=r.creditToken,this.sdkToken=r.sdkToken,this.emit("ready",r);break;case"RECUR_CARD_VALID":this.emit("change",{valid:r.valid});break;case"RECUR_CARD_TOKENIZED":this.cardToken=r.cardToken,this.cardTimestamp=r.timestamp,this.emit("tokenized",r);break;case"RECUR_PAYMENT_ERROR":this.emit("error",r);break;case"RECUR_RESIZE":this.iframe&&r?.height&&(this.iframe.style.height=`${r.height}px`);break}}isValidOrigin(e){try{let t=new URL(this.embedUrl).origin;return e===t}catch{return!1}}async tokenize(){if(!this.iframe||!this.iframe.contentWindow)throw new Error("Elements not mounted");let e=new URL(this.embedUrl).origin;return this.iframe.contentWindow.postMessage({type:"RECUR_TOKENIZE"},e),new Promise((t,r)=>{let i=setTimeout(()=>{r(new Error("Tokenization timeout"))},6e4),s=n=>{clearTimeout(i),this.off("tokenized",s),t(n)},a=n=>{clearTimeout(i),this.off("error",a),r(new Error(n.message||"Tokenization failed"))};this.on("tokenized",s),this.on("error",a)})}async confirmPayment(e){if(!this.sessionId||!this.cardToken)throw new Error("Card not tokenized. Call tokenize() first.");let t=await fetch(`${this.baseUrl}/api/v1/elements/confirm`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.publishableKey},body:JSON.stringify({sessionId:this.sessionId,productId:e.productId,email:e.email,name:e.name,phone:e.phone,externalCustomerId:e.externalCustomerId,metadata:e.metadata,successUrl:e.successUrl,cancelUrl:e.cancelUrl})}),r=await t.json();return t.ok?r:{status:"failed",message:r.error||r.message||"Payment failed",canRetry:r.canRetry??!0}}getDefaultBaseUrl(){if(typeof window>"u")return"https://app.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3000`:"https://app.recur.tw"}getDefaultEmbedUrl(){if(typeof window>"u")return"https://embed.recur.tw";let e=window.location.hostname;return e==="localhost"||e.includes(".test")||e.includes(".local")||e==="127.0.0.1"?`${window.location.protocol}//${e}:3002`:"https://embed.recur.tw"}destroy(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,this.container=null,this.sessionId=null,this.timestamp=null,this.creditToken=null,this.sdkToken=null,this.cardToken=null,this.cardTimestamp=null,this.eventHandlers.clear(),window.removeEventListener("message",this.handleMessage.bind(this))}};function j(o){return new w(o)}var Te="https://vendor.payuni.com.tw/sdk/uni-payment.js",Ie="https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",ue=!1,N=!1,S=null;async function me(o=!1){return ue&&window.UniPayment?Promise.resolve():(N&&S||(N=!0,S=new Promise((e,t)=>{let r=document.createElement("script");r.src=o?Ie:Te,r.async=!0,r.onload=()=>{ue=!0,N=!1,e()},r.onerror=()=>{N=!1,S=null,t(new Error("Failed to load PAYUNi SDK"))},document.head.appendChild(r)})),S)}var C=class{constructor(e){c(this,"core");c(this,"currentModal",null);c(this,"currentIframe",null);c(this,"currentModalOverlay",null);this.core=new H(e)}async fetchProducts(e){return await this.core.fetchProducts(e)}async fetchPlans(){return await this.core.fetchPlans()}async createEmbeddedCheckout(e){let t=this.core.getConfig();await new $(t,e).render()}async redirectToCheckout(e){let t=await this.createCheckoutSession(e);window.location.href=t.url}async createCheckoutSession(e){let t=this.getBaseUrl();if(!e.productId&&!e.productSlug)throw new Error("Either productId or productSlug is required");let r={};e.productId&&(r.productId=e.productId),e.productSlug&&(r.productSlug=e.productSlug),e.customerEmail&&(r.customerEmail=e.customerEmail),e.customerName&&(r.customerName=e.customerName),e.externalCustomerId&&(r.externalCustomerId=e.externalCustomerId),e.successUrl&&(r.successUrl=e.successUrl),e.cancelUrl&&(r.cancelUrl=e.cancelUrl);let i=this.core.getConfig(),s=await fetch(`${t}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":i.publishableKey},body:JSON.stringify(r)});if(!s.ok){let l=await s.json().catch(()=>({}));throw new Error(l.error?.message||l.error||"Failed to create checkout session")}let a=await s.json(),n=g(a);return{id:n.checkout.id,url:n.url,expiresAt:n.checkout.expiresAt}}async checkout(e){let t=this.core.getConfig(),r=null,i=e.productId||e.planId,s=e.productSlug;try{if(console.log("[Recur SDK] Starting checkout flow...",{productId:i,productSlug:s,mode:e.mode}),!i&&!s)throw new Error("Either productId or productSlug is required");if(!t.publishableKey)throw new Error("publishableKey is required");let a=this.getBaseUrl();console.log("[Recur SDK] Base URL:",a);let n={"Content-Type":"application/json","X-Recur-Publishable-Key":t.publishableKey},l=e.mode||"modal",d=null;if(l==="modal"){let m=this.createModalWithSkeleton(e.onClose);r=m.overlay,d=m.container}else if(l==="iframe"){if(d=this.getEmbeddedContainer(e.container),!d)throw new Error("Container is required for iframe mode");d.innerHTML="";let m=document.createElement("recur-payment-form-skeleton");d.appendChild(m)}console.log("[Recur SDK] Step 1: Creating checkout session...");let v={customerName:e.customerName,customerEmail:e.customerEmail};i&&(v.productId=i),s&&(v.productSlug=s),e.externalCustomerId&&(v.externalCustomerId=e.externalCustomerId);let B=await fetch(`${a}/v1/checkouts`,{method:"POST",headers:n,body:JSON.stringify(v)});if(!B.ok){let m=await B.json().catch(()=>({}));console.error("[Recur SDK] Failed to create checkout:",m);let O=m.details||m.error||"Failed to create checkout";throw new Error(O)}let he=await B.json(),u=g(he);if(console.log("[Recur SDK] Checkout created successfully:",u),e.onSuccess?.(u),l==="redirect"){let m=`https://checkout.recur.tw/${u.checkout.id}`;console.log("[Recur SDK] Redirecting to hosted checkout:",m),window.location.href=m;return}if(console.log("[Recur SDK] Step 2: Extracting SDK token..."),!u.sdkToken)throw new Error("SDK token missing from checkout response");console.log("[Recur SDK] Step 3: Loading PAYUNi SDK...");let q=!0;if(await me(q),console.log("[Recur SDK] PAYUNi SDK loaded successfully"),!window.UniPayment)throw new Error("PAYUNi SDK failed to load");if(console.log("[Recur SDK] Step 4: Rendering payment form..."),!d)throw new Error("Payment container not available");d.innerHTML="";let h=document.createElement("recur-payment-form");if(h.setAttribute("container-id",d.id||"recur-payment-container"),e.customerName&&h.setAttribute("customer-name",e.customerName),e.customerEmail&&h.setAttribute("customer-email",e.customerEmail),u.plan?.name&&h.setAttribute("plan-name",u.plan.name),u.checkout?.amount&&h.setAttribute("amount",u.checkout.amount.toString()),u.plan?.billingPeriod&&h.setAttribute("billing-period",u.plan.billingPeriod),h.setAttribute("custom-styles",`
|
|
1530
1530
|
.form-input-focus {
|
|
1531
1531
|
border-color: var(--ring, hsl(215 16% 47%)) !important;
|
|
1532
1532
|
outline: 0 !important;
|
|
@@ -1572,7 +1572,7 @@
|
|
|
1572
1572
|
border-radius: 50%;
|
|
1573
1573
|
z-index: 10;
|
|
1574
1574
|
transition: background 0.2s;
|
|
1575
|
-
`,i.onmouseover=()=>{i.style.background="rgba(0, 0, 0, 0.1)"},i.onmouseout=()=>{i.style.background="rgba(0, 0, 0, 0.05)"},i.onclick=()=>{t.remove(),e?.()};let s=document.createElement("div");s.id="recur-modal-payment-container";let a=document.createElement("recur-payment-form-skeleton");return s.appendChild(a),r.appendChild(i),r.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:s}}getEmbeddedContainer(e){return e?typeof e=="string"?document.getElementById(e)||document.querySelector(e):e:null}closeModal(){this.currentModal&&(this.currentModal.close(),this.currentModal=null),this.currentModalOverlay&&(this.currentModalOverlay.remove(),this.currentModalOverlay=null)}removeIframe(){this.currentIframe&&(this.currentIframe.remove(),this.currentIframe=null)}close(){this.closeModal(),this.removeIframe()}async createPortalSession(e,t){let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({customerId:t.customerId,returnUrl:t.returnUrl})});if(!r.ok){let s=await r.json().catch(()=>({}));throw new Error(s.error?.message||s.message||"Failed to create portal session")}let i=await r.json();return{id:i.id,url:i.url||i.portalUrl,expiresAt:i.expiresAt}}async redirectToPortal(e,t){let r=await this.createPortalSession(e,t);window.location.href=r.url}};function pe(o){return new
|
|
1575
|
+
`,i.onmouseover=()=>{i.style.background="rgba(0, 0, 0, 0.1)"},i.onmouseout=()=>{i.style.background="rgba(0, 0, 0, 0.05)"},i.onclick=()=>{t.remove(),e?.()};let s=document.createElement("div");s.id="recur-modal-payment-container";let a=document.createElement("recur-payment-form-skeleton");return s.appendChild(a),r.appendChild(i),r.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.currentModalOverlay=t,{overlay:t,container:s}}getEmbeddedContainer(e){return e?typeof e=="string"?document.getElementById(e)||document.querySelector(e):e:null}closeModal(){this.currentModal&&(this.currentModal.close(),this.currentModal=null),this.currentModalOverlay&&(this.currentModalOverlay.remove(),this.currentModalOverlay=null)}removeIframe(){this.currentIframe&&(this.currentIframe.remove(),this.currentIframe=null)}close(){this.closeModal(),this.removeIframe()}async createPortalSession(e,t){let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({customerId:t.customerId,returnUrl:t.returnUrl})});if(!r.ok){let s=await r.json().catch(()=>({}));throw new Error(s.error?.message||s.message||"Failed to create portal session")}let i=await r.json();return{id:i.id,url:i.url||i.portalUrl,expiresAt:i.expiresAt}}async redirectToPortal(e,t){let r=await this.createPortalSession(e,t);window.location.href=r.url}};function pe(o){return new C(o)}var Re={init:pe,RecurCheckout:C,RecurElements:w,createElements:j};return xe(Pe);})();
|
|
1576
1576
|
if (typeof window !== "undefined") {
|
|
1577
1577
|
window.RecurCheckout = RecurCheckout.default;
|
|
1578
1578
|
window.RecurElements = RecurCheckout.RecurElements;
|
package/dist/server.cjs
CHANGED
|
@@ -19,6 +19,8 @@ var RecurAPIError = class extends Error {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
// src/server/resources/portal.ts
|
|
22
|
+
var SDK_VERSION = "0.8.1";
|
|
23
|
+
var SDK_TYPE = "server";
|
|
22
24
|
var PortalSessions = class {
|
|
23
25
|
constructor(config) {
|
|
24
26
|
__publicField(this, "config");
|
|
@@ -75,7 +77,10 @@ var PortalSessions = class {
|
|
|
75
77
|
method: "POST",
|
|
76
78
|
headers: {
|
|
77
79
|
"Authorization": `Bearer ${this.config.secretKey}`,
|
|
78
|
-
"Content-Type": "application/json"
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
82
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
83
|
+
"X-Recur-Source": "server"
|
|
79
84
|
},
|
|
80
85
|
body: JSON.stringify({
|
|
81
86
|
customerId: params.customer,
|
package/dist/server.js
CHANGED
|
@@ -17,6 +17,8 @@ var RecurAPIError = class extends Error {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// src/server/resources/portal.ts
|
|
20
|
+
var SDK_VERSION = "0.8.1";
|
|
21
|
+
var SDK_TYPE = "server";
|
|
20
22
|
var PortalSessions = class {
|
|
21
23
|
constructor(config) {
|
|
22
24
|
__publicField(this, "config");
|
|
@@ -73,7 +75,10 @@ var PortalSessions = class {
|
|
|
73
75
|
method: "POST",
|
|
74
76
|
headers: {
|
|
75
77
|
"Authorization": `Bearer ${this.config.secretKey}`,
|
|
76
|
-
"Content-Type": "application/json"
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
80
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
81
|
+
"X-Recur-Source": "server"
|
|
77
82
|
},
|
|
78
83
|
body: JSON.stringify({
|
|
79
84
|
customerId: params.customer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recur-tw",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -28,14 +28,6 @@
|
|
|
28
28
|
"url": "https://github.com/kaikhq/recur.tw/issues"
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/kaikhq/recur.tw/tree/main/packages/recur-sdk#readme",
|
|
31
|
-
"scripts": {
|
|
32
|
-
"build": "tsup",
|
|
33
|
-
"dev": "tsup --watch",
|
|
34
|
-
"lint": "eslint .",
|
|
35
|
-
"lint:fix": "eslint . --fix",
|
|
36
|
-
"type-check": "tsc --noEmit",
|
|
37
|
-
"examples": "npx http-server examples -p 8080 -o"
|
|
38
|
-
},
|
|
39
31
|
"main": "./dist/index.js",
|
|
40
32
|
"module": "./dist/index.js",
|
|
41
33
|
"types": "./dist/index.d.ts",
|
|
@@ -58,6 +50,7 @@
|
|
|
58
50
|
"files": [
|
|
59
51
|
"dist",
|
|
60
52
|
"README.md",
|
|
53
|
+
"AGENTS.md",
|
|
61
54
|
"LICENSE"
|
|
62
55
|
],
|
|
63
56
|
"sideEffects": [
|
|
@@ -81,7 +74,6 @@
|
|
|
81
74
|
"@types/node": "^20.19.9",
|
|
82
75
|
"@types/react": "^19.1.9",
|
|
83
76
|
"@types/react-dom": "^19.1.7",
|
|
84
|
-
"@workspace/typescript-config": "workspace:*",
|
|
85
77
|
"autoprefixer": "^10.4.22",
|
|
86
78
|
"eslint": "^9",
|
|
87
79
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -92,6 +84,15 @@
|
|
|
92
84
|
"tailwindcss": "^4.1.11",
|
|
93
85
|
"tsup": "^8.3.5",
|
|
94
86
|
"typescript": "^5.9.2",
|
|
95
|
-
"typescript-eslint": "^8.47.0"
|
|
87
|
+
"typescript-eslint": "^8.47.0",
|
|
88
|
+
"@workspace/typescript-config": "0.0.0"
|
|
89
|
+
},
|
|
90
|
+
"scripts": {
|
|
91
|
+
"build": "tsup",
|
|
92
|
+
"dev": "tsup --watch",
|
|
93
|
+
"lint": "eslint .",
|
|
94
|
+
"lint:fix": "eslint . --fix",
|
|
95
|
+
"type-check": "tsc --noEmit",
|
|
96
|
+
"examples": "npx http-server examples -p 8080 -o"
|
|
96
97
|
}
|
|
97
|
-
}
|
|
98
|
+
}
|