recur-tw 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +124 -0
- package/dist/index.cjs +10 -5
- package/dist/index.js +10 -5
- package/dist/recur.umd.js +12 -12
- package/dist/server.cjs +6 -1
- package/dist/server.js +6 -1
- package/package.json +2 -1
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,9 +2637,6 @@ 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
2640
|
if (!options.customerEmail) {
|
|
2642
2641
|
throw new Error("customerEmail is required");
|
|
2643
2642
|
}
|
|
@@ -2645,7 +2644,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2645
2644
|
console.log("[Recur SDK] Base URL:", baseUrl);
|
|
2646
2645
|
const headers = {
|
|
2647
2646
|
"Content-Type": "application/json",
|
|
2648
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
2647
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
2648
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
2649
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
2650
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
2649
2651
|
};
|
|
2650
2652
|
let modalContent = null;
|
|
2651
2653
|
let loadingContainer = null;
|
|
@@ -3010,7 +3012,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3010
3012
|
method: "GET",
|
|
3011
3013
|
headers: {
|
|
3012
3014
|
"Content-Type": "application/json",
|
|
3013
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
3015
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
3016
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
3017
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
3018
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
3014
3019
|
}
|
|
3015
3020
|
});
|
|
3016
3021
|
if (!response.ok) {
|
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,9 +2631,6 @@ 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
2634
|
if (!options.customerEmail) {
|
|
2636
2635
|
throw new Error("customerEmail is required");
|
|
2637
2636
|
}
|
|
@@ -2639,7 +2638,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
2639
2638
|
console.log("[Recur SDK] Base URL:", baseUrl);
|
|
2640
2639
|
const headers = {
|
|
2641
2640
|
"Content-Type": "application/json",
|
|
2642
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
2641
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
2642
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
2643
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
2644
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
2643
2645
|
};
|
|
2644
2646
|
let modalContent = null;
|
|
2645
2647
|
let loadingContainer = null;
|
|
@@ -3004,7 +3006,10 @@ function RecurProvider({ children, config: initialConfig = {} }) {
|
|
|
3004
3006
|
method: "GET",
|
|
3005
3007
|
headers: {
|
|
3006
3008
|
"Content-Type": "application/json",
|
|
3007
|
-
"X-Recur-Publishable-Key": config.publishableKey
|
|
3009
|
+
"X-Recur-Publishable-Key": config.publishableKey,
|
|
3010
|
+
"X-Recur-SDK-Type": SDK_TYPE,
|
|
3011
|
+
"X-Recur-SDK-Version": SDK_VERSION,
|
|
3012
|
+
"X-Recur-Source": typeof window !== "undefined" ? window.location.origin : "server"
|
|
3008
3013
|
}
|
|
3009
3014
|
});
|
|
3010
3015
|
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");if(!e.customerEmail)throw new Error("customerEmail 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.2",
|
|
4
4
|
"description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"files": [
|
|
59
59
|
"dist",
|
|
60
60
|
"README.md",
|
|
61
|
+
"AGENTS.md",
|
|
61
62
|
"LICENSE"
|
|
62
63
|
],
|
|
63
64
|
"sideEffects": [
|