recur-tw 0.0.3-beta.3 → 0.0.3-beta.5

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/README.md CHANGED
@@ -1,18 +1,20 @@
1
1
  # Recur SDK (Taiwan)
2
2
 
3
- A React & Vanilla JS SDK for embedding subscription checkout flows in your application.
3
+ A React & Vanilla JS SDK for embedding subscription checkout flows with PAYUNi payment integration.
4
4
 
5
5
  **專為台灣市場設計** - 使用 PAYUNi 支付網關處理訂閱式付款。
6
6
 
7
- Taiwan-specific subscription checkout SDK.
7
+ Taiwan-specific subscription checkout SDK with embedded payment forms powered by Web Components.
8
8
 
9
9
  ## 🚀 Features
10
10
 
11
- - ✅ **React SDK** - Full React integration with hooks
11
+ - ✅ **React SDK** - Full React integration with hooks (`usePlans`, `useRecur`)
12
12
  - ✅ **Vanilla JS** - Use with plain HTML/JavaScript (no framework required)
13
- - ✅ **Multiple Modes** - Modal, iframe, or redirect checkout flows
13
+ - ✅ **Embedded Checkout** - Native payment forms with PAYUNi credit card fields
14
+ - ✅ **Web Components** - Modern, encapsulated UI components
15
+ - ✅ **Custom Styling** - Fully customizable with shadcn/ui compatibility
14
16
  - ✅ **TypeScript** - Full type definitions included
15
- - ✅ **Secure** - API key authentication
17
+ - ✅ **SSR Safe** - Works with Next.js, Remix, and other SSR frameworks
16
18
  - ✅ **Taiwan-focused** - PAYUNi payment integration
17
19
 
18
20
  ---
@@ -43,7 +45,61 @@ yarn add recur-tw
43
45
 
44
46
  ## 🎯 Quick Start
45
47
 
46
- ### Option 1: Vanilla JavaScript (Static HTML)
48
+ ### Option 1: React/Next.js
49
+
50
+ Full framework integration with React hooks and embedded checkout:
51
+
52
+ ```tsx
53
+ 'use client';
54
+
55
+ import { RecurProvider, usePlans, useRecur } from 'recur-tw';
56
+
57
+ // 1. Wrap your app with RecurProvider
58
+ export default function App() {
59
+ return (
60
+ <RecurProvider
61
+ config={{
62
+ publishableKey: 'pk_test_xxx',
63
+ containerElementId: 'recur-payment-container' // For embedded checkout
64
+ }}
65
+ >
66
+ <SubscriptionPage />
67
+ </RecurProvider>
68
+ );
69
+ }
70
+
71
+ // 2. Fetch plans and checkout
72
+ function SubscriptionPage() {
73
+ const { data: plans, isLoading } = usePlans();
74
+ const { checkout, isCheckingOut } = useRecur();
75
+
76
+ if (isLoading) return <div>Loading plans...</div>;
77
+
78
+ return (
79
+ <div>
80
+ {plans?.map((plan) => (
81
+ <button
82
+ key={plan.id}
83
+ onClick={() => checkout({
84
+ planId: plan.id,
85
+ customerEmail: 'user@example.com',
86
+ customerName: 'John Doe',
87
+ customerPhone: '+886912345678',
88
+ })}
89
+ disabled={isCheckingOut}
90
+ >
91
+ Subscribe to {plan.name} - NT${plan.price}
92
+ </button>
93
+ ))}
94
+
95
+ {/* Embedded payment form will appear here */}
96
+ <div id="recur-payment-container"></div>
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ### Option 2: Vanilla JavaScript (Static HTML)
47
103
 
48
104
  Perfect for landing pages, marketing sites, or any static HTML:
49
105
 
@@ -66,7 +122,7 @@ Perfect for landing pages, marketing sites, or any static HTML:
66
122
  planId: 'plan_xxx',
67
123
  customerName: '王小明',
68
124
  customerEmail: 'user@example.com',
69
- mode: 'modal' // 'modal', 'iframe', or 'redirect'
125
+ mode: 'redirect' // 'redirect' or 'embedded'
70
126
  });
71
127
  });
72
128
  </script>
@@ -74,375 +130,370 @@ Perfect for landing pages, marketing sites, or any static HTML:
74
130
  </html>
75
131
  ```
76
132
 
77
- [See full Vanilla JS examples →](./examples/)
78
-
79
- ### Option 2: React/Next.js
80
-
81
- Full framework integration with React hooks:
82
-
83
- ```tsx
84
- import { RecurProvider, useRecur } from 'recur-tw';
85
-
86
- // 1. Wrap your app with RecurProvider
87
- export default function App() {
88
- return (
89
- <RecurProvider config={{ publishableKey: 'pk_test_xxx' }}>
90
- <YourApp />
91
- </RecurProvider>
92
- );
93
- }
94
-
95
- // 2. Use checkout in your components
96
- function CheckoutButton() {
97
- const { checkout, isCheckingOut } = useRecur();
98
-
99
- return (
100
- <button onClick={() => checkout({ planId: 'pro' })} disabled={isCheckingOut}>
101
- {isCheckingOut ? 'Processing...' : 'Subscribe'}
102
- </button>
103
- );
104
- }
105
- ```
106
-
107
133
  ---
108
134
 
109
135
  ## 🔑 Get Your API Key
110
136
 
111
- 1. Go to your organization settings
112
- 2. Navigate to **API Keys** tab
113
- 3. Click **Create API Key**
114
- 4. Copy your `pk_test_...` key
137
+ 1. Go to your Recur dashboard
138
+ 2. Navigate to **Settings** → **API Keys**
139
+ 3. Click **Create Publishable Key**
140
+ 4. Copy your `pk_test_...` or `pk_live_...` key
115
141
 
116
- [Learn more about API Keys →](./API_KEYS_GUIDE.md)
142
+ > ⚠️ **Never** use your secret key (`sk_*`) in client-side code!
117
143
 
118
144
  ---
119
145
 
120
146
  ## 📖 Documentation
121
147
 
122
- ### For Vanilla JavaScript Users
148
+ ### Checkout Modes
123
149
 
124
- #### Checkout Modes
150
+ #### Embedded Mode (Recommended)
125
151
 
126
- **Modal Mode** - Open payment in a popup modal:
127
- ```javascript
128
- await recur.checkout({
129
- planId: 'plan_xxx',
130
- mode: 'modal',
131
- onClose: () => console.log('Modal closed')
132
- });
133
- ```
152
+ Embed the full payment form directly in your page with PAYUNi credit card fields:
134
153
 
135
- **iframe Mode** - Embed payment in your page:
136
- ```javascript
137
- await recur.checkout({
138
- planId: 'plan_xxx',
139
- mode: 'iframe',
140
- container: '#checkout-container' // CSS selector or HTMLElement
141
- });
142
- ```
154
+ ```tsx
155
+ const { checkout } = useRecur();
143
156
 
144
- **Redirect Mode** - Full page redirect (default):
145
- ```javascript
146
- await recur.checkout({
157
+ await checkout({
147
158
  planId: 'plan_xxx',
148
- mode: 'redirect' // or omit mode parameter
159
+ customerEmail: 'user@example.com',
160
+ customerName: 'John Doe',
161
+ customerPhone: '+886912345678',
162
+ // mode: 'embedded' is default when containerElementId is configured
149
163
  });
150
164
  ```
151
165
 
152
- #### API Reference
166
+ **Features:**
167
+ - ✅ Native PAYUNi credit card input fields
168
+ - ✅ No redirects - complete flow in your app
169
+ - ✅ Fully customizable styling
170
+ - ✅ Web Components based (modern, encapsulated)
153
171
 
154
- ```javascript
155
- // Initialize
156
- const recur = RecurCheckout.init({
157
- publishableKey: string, // Required
158
- baseUrl?: string // Optional, defaults to current origin
159
- });
172
+ #### Redirect Mode
160
173
 
161
- // Checkout
162
- await recur.checkout({
163
- planId: string, // Required
164
- customerName?: string,
165
- customerEmail?: string,
166
- customerPhone?: string,
167
- mode?: 'modal' | 'iframe' | 'redirect', // Default: 'redirect'
168
- container?: string | HTMLElement, // Required for iframe mode
169
- onSuccess?: (result) => void,
170
- onError?: (error) => void,
171
- onClose?: () => void
172
- });
174
+ Redirect to a full-page checkout:
173
175
 
174
- // Close modal or remove iframe manually
175
- recur.close();
176
+ ```tsx
177
+ await checkout({
178
+ planId: 'plan_xxx',
179
+ mode: 'redirect',
180
+ successUrl: 'https://yoursite.com/success',
181
+ cancelUrl: 'https://yoursite.com/cancel',
182
+ });
176
183
  ```
177
184
 
178
- [See complete examples →](./examples/)
179
-
180
185
  ---
181
186
 
182
- ### For React/Next.js Users
187
+ ### React Hooks
183
188
 
184
- #### 1. Wrap your app with RecurProvider
189
+ #### `usePlans()`
190
+
191
+ Fetch available subscription plans:
185
192
 
186
193
  ```tsx
187
- // app/layout.tsx or your root component
188
- import { RecurProvider } from 'recur-tw';
194
+ import { usePlans } from 'recur-tw';
195
+
196
+ function PlansPage() {
197
+ const { data: plans, isLoading, error } = usePlans();
198
+
199
+ if (isLoading) return <div>Loading...</div>;
200
+ if (error) return <div>Error: {error.message}</div>;
189
201
 
190
- export default function RootLayout({ children }) {
191
202
  return (
192
- <html>
193
- <body>
194
- <RecurProvider config={{ organizationId: 'your-org-id' }}>
195
- {children}
196
- </RecurProvider>
197
- </body>
198
- </html>
203
+ <div>
204
+ {plans?.map((plan) => (
205
+ <div key={plan.id}>
206
+ <h3>{plan.name}</h3>
207
+ <p>NT${plan.price} / {plan.billingPeriod}</p>
208
+ {plan.trialDays > 0 && <p>🎉 {plan.trialDays} days free trial</p>}
209
+ </div>
210
+ ))}
211
+ </div>
199
212
  );
200
213
  }
201
214
  ```
202
215
 
203
- ### 2. Use the checkout function in your components
216
+ #### `useRecur()`
204
217
 
205
- ```tsx
206
- // components/pricing-button.tsx
207
- 'use client';
218
+ Access checkout functionality:
208
219
 
220
+ ```tsx
209
221
  import { useRecur } from 'recur-tw';
210
222
 
211
- export function PricingButton({ planId }: { planId: string }) {
212
- const { checkout, isCheckingOut } = useRecur();
223
+ function CheckoutButton({ planId }: { planId: string }) {
224
+ const { checkout, isCheckingOut, config, updateConfig } = useRecur();
213
225
 
214
226
  return (
215
- <Button
216
- onClick={async () => {
217
- await checkout({ planId });
218
- }}
227
+ <button
228
+ onClick={() => checkout({ planId })}
219
229
  disabled={isCheckingOut}
220
230
  >
221
231
  {isCheckingOut ? 'Processing...' : 'Subscribe'}
222
- </Button>
232
+ </button>
223
233
  );
224
234
  }
225
235
  ```
226
236
 
227
- ## Configuration
237
+ **Returns:**
238
+ - `checkout(options)` - Initiate checkout flow
239
+ - `isCheckingOut` - Boolean indicating if checkout is in progress
240
+ - `config` - Current SDK configuration
241
+ - `updateConfig(newConfig)` - Update configuration dynamically
242
+ - `fetchPlans()` - Manually fetch plans
243
+
244
+ ---
245
+
246
+ ### Configuration
228
247
 
229
- ### RecurProvider Props
248
+ #### RecurProvider Props
230
249
 
231
250
  ```tsx
232
251
  interface RecurConfig {
233
- // Organization ID for the checkout
234
- organizationId?: string;
252
+ // Required: Your publishable API key
253
+ publishableKey: string;
235
254
 
236
- // Base URL for API calls (defaults to current origin)
255
+ // Optional: API base URL (defaults to production)
237
256
  baseUrl?: string;
238
257
 
239
- // Redirect mode: 'redirect' (default) or 'popup'
240
- redirectMode?: 'redirect' | 'popup';
241
-
242
- // Success callback URL
243
- successUrl?: string;
244
-
245
- // Cancel callback URL
246
- cancelUrl?: string;
258
+ // Optional: Container element ID for embedded checkout
259
+ containerElementId?: string;
247
260
  }
248
261
  ```
249
262
 
250
- ## Usage Examples
251
-
252
- ### Basic Checkout
263
+ Example:
253
264
 
254
265
  ```tsx
255
- const { checkout } = useRecur();
256
-
257
- await checkout({
258
- planId: 'pro-monthly',
259
- });
266
+ <RecurProvider
267
+ config={{
268
+ publishableKey: process.env.NEXT_PUBLIC_RECUR_KEY!,
269
+ baseUrl: 'https://api.recur.tw', // optional
270
+ containerElementId: 'recur-payment-container',
271
+ }}
272
+ >
273
+ {children}
274
+ </RecurProvider>
260
275
  ```
261
276
 
262
- ### Checkout with Customer Information
277
+ #### Checkout Options
263
278
 
264
279
  ```tsx
265
- const { checkout } = useRecur();
280
+ interface CheckoutOptions {
281
+ // Required: Plan ID to subscribe to
282
+ planId: string;
266
283
 
267
- await checkout({
268
- planId: 'pro-monthly',
269
- customerName: 'John Doe',
270
- customerEmail: 'john@example.com',
271
- customerPhone: '+886912345678',
272
- });
284
+ // Customer information
285
+ customerEmail?: string;
286
+ customerName?: string;
287
+ customerPhone?: string;
288
+
289
+ // Checkout mode
290
+ mode?: 'embedded' | 'redirect'; // Default: 'embedded' if containerElementId is set
291
+
292
+ // Redirect URLs (for redirect mode)
293
+ successUrl?: string;
294
+ cancelUrl?: string;
295
+
296
+ // Callbacks
297
+ onPaymentComplete?: (subscription: Subscription) => void;
298
+ onError?: (error: CheckoutError) => void;
299
+ }
273
300
  ```
274
301
 
275
- ### Checkout with Callbacks
302
+ ---
276
303
 
277
- ```tsx
278
- const { checkout } = useRecur();
304
+ ### Custom Styling
279
305
 
280
- await checkout({
281
- planId: 'pro-monthly',
282
- onSuccess: (result) => {
283
- console.log('Checkout initiated:', result.subscription.id);
284
- // Show success toast
285
- },
286
- onError: (error) => {
287
- console.error('Checkout failed:', error.message);
288
- // Show error toast
289
- },
290
- });
306
+ The SDK uses Web Components with customizable styles. You can override PAYUNi input styles to match your design system.
307
+
308
+ #### Default Styling (shadcn/ui compatible)
309
+
310
+ The SDK automatically applies shadcn-compatible focus rings:
311
+
312
+ ```css
313
+ .form-input-focus {
314
+ border-color: var(--ring, hsl(215 16% 47%)) !important;
315
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring, hsl(215 16% 47%)) 50%, transparent) !important;
316
+ }
291
317
  ```
292
318
 
293
- ### Popup Mode
319
+ #### Custom Styling
320
+
321
+ Override styles by providing custom CSS:
294
322
 
295
323
  ```tsx
296
- // In your RecurProvider config
297
- <RecurProvider config={{ redirectMode: 'popup' }}>
298
- {children}
299
- </RecurProvider>
324
+ const paymentForm = document.createElement('recur-payment-form');
325
+ paymentForm.setAttribute('custom-styles', `
326
+ /* Custom focus style */
327
+ .form-input-focus {
328
+ border-color: #3b82f6 !important;
329
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2) !important;
330
+ }
331
+
332
+ /* Adjust container heights */
333
+ #recur-payment-container-card-no,
334
+ #recur-payment-container-card-exp,
335
+ #recur-payment-container-card-cvc {
336
+ height: 40px !important;
337
+ }
338
+ `);
339
+ ```
300
340
 
301
- // In your component
302
- const { checkout } = useRecur();
341
+ The custom styles are injected into the Light DOM, allowing them to affect PAYUNi's iframe elements while keeping other UI styles encapsulated.
303
342
 
304
- await checkout({
305
- planId: 'pro-monthly',
306
- onPaymentComplete: (subscription) => {
307
- console.log('Payment completed!', subscription);
308
- // Refresh page or update UI
309
- },
310
- onPaymentCancel: () => {
311
- console.log('Payment cancelled');
312
- },
313
- });
314
- ```
343
+ ---
315
344
 
316
- ### Custom Form
345
+ ### Advanced Examples
346
+
347
+ #### Embedded Checkout with Error Handling
317
348
 
318
349
  ```tsx
319
350
  'use client';
320
351
 
321
352
  import { useState } from 'react';
322
- import { useRecur } from '@/lib/recur';
323
- import { Button } from '@/components/ui/button';
324
- import { Input } from '@/components/ui/input';
353
+ import { useRecur } from 'recur-tw';
325
354
 
326
- export function CustomCheckoutForm({ planId }: { planId: string }) {
355
+ export function CheckoutForm({ planId }: { planId: string }) {
327
356
  const { checkout, isCheckingOut } = useRecur();
328
- const [email, setEmail] = useState('');
329
- const [name, setName] = useState('');
357
+ const [error, setError] = useState<string | null>(null);
330
358
 
331
- const handleSubmit = async (e: React.FormEvent) => {
332
- e.preventDefault();
359
+ const handleCheckout = async () => {
360
+ setError(null);
333
361
 
334
362
  await checkout({
335
363
  planId,
336
- customerEmail: email,
337
- customerName: name,
338
- onSuccess: () => {
339
- // Show success message
364
+ customerEmail: 'user@example.com',
365
+ onPaymentComplete: (subscription) => {
366
+ console.log('Payment successful!', subscription);
367
+ // Redirect to success page or show confirmation
340
368
  },
341
- onError: (error) => {
342
- alert(error.message);
369
+ onError: (err) => {
370
+ setError(err.message);
343
371
  },
344
372
  });
345
373
  };
346
374
 
347
375
  return (
348
- <form onSubmit={handleSubmit}>
349
- <Input
350
- type="text"
351
- placeholder="Name"
352
- value={name}
353
- onChange={(e) => setName(e.target.value)}
354
- required
355
- />
356
- <Input
357
- type="email"
358
- placeholder="Email"
359
- value={email}
360
- onChange={(e) => setEmail(e.target.value)}
361
- required
362
- />
363
- <Button type="submit" disabled={isCheckingOut}>
364
- {isCheckingOut ? 'Processing...' : 'Subscribe'}
365
- </Button>
366
- </form>
376
+ <div>
377
+ <button onClick={handleCheckout} disabled={isCheckingOut}>
378
+ {isCheckingOut ? 'Processing...' : 'Subscribe Now'}
379
+ </button>
380
+
381
+ {error && <div className="error">{error}</div>}
382
+
383
+ {/* Payment form will appear here */}
384
+ <div id="recur-payment-container"></div>
385
+ </div>
367
386
  );
368
387
  }
369
388
  ```
370
389
 
371
- ### Dynamic Organization ID
390
+ #### Dynamic Plan Selection
372
391
 
373
392
  ```tsx
374
- // Override organization ID per checkout
375
- const { checkout } = useRecur();
393
+ 'use client';
376
394
 
377
- await checkout({
378
- planId: 'pro-monthly',
379
- organizationId: 'different-org-id',
380
- });
381
- ```
395
+ import { useState } from 'react';
396
+ import { usePlans, useRecur } from 'recur-tw';
382
397
 
383
- ### Update Configuration Dynamically
398
+ export function PricingTable() {
399
+ const { data: plans } = usePlans();
400
+ const { checkout, isCheckingOut } = useRecur();
401
+ const [selectedPlan, setSelectedPlan] = useState<string | null>(null);
384
402
 
385
- ```tsx
386
- const { updateConfig } = useRecur();
403
+ const handleSelectPlan = async (planId: string) => {
404
+ setSelectedPlan(planId);
387
405
 
388
- // Switch to popup mode
389
- updateConfig({ redirectMode: 'popup' });
406
+ await checkout({
407
+ planId,
408
+ customerEmail: 'user@example.com',
409
+ customerName: 'John Doe',
410
+ });
411
+ };
390
412
 
391
- // Update organization ID
392
- updateConfig({ organizationId: 'new-org-id' });
413
+ return (
414
+ <div>
415
+ {!selectedPlan ? (
416
+ // Plan selection
417
+ <div className="plans-grid">
418
+ {plans?.map((plan) => (
419
+ <div key={plan.id} className="plan-card">
420
+ <h3>{plan.name}</h3>
421
+ <p className="price">NT${plan.price} / {plan.billingPeriod}</p>
422
+ {plan.trialDays > 0 && (
423
+ <p className="trial">🎉 {plan.trialDays} day free trial</p>
424
+ )}
425
+ <button onClick={() => handleSelectPlan(plan.id)}>
426
+ Select Plan
427
+ </button>
428
+ </div>
429
+ ))}
430
+ </div>
431
+ ) : (
432
+ // Payment form
433
+ <div>
434
+ <button onClick={() => setSelectedPlan(null)}>← Back to plans</button>
435
+ <div id="recur-payment-container"></div>
436
+ </div>
437
+ )}
438
+ </div>
439
+ );
440
+ }
393
441
  ```
394
442
 
395
- ## API Reference
443
+ #### Update Config Dynamically
396
444
 
397
- ### `useRecur()`
398
-
399
- Returns a `RecurContextValue` object with the following properties:
445
+ ```tsx
446
+ const { config, updateConfig } = useRecur();
400
447
 
401
- #### `checkout(options: CheckoutOptions): Promise<void>`
448
+ // Switch to different API endpoint
449
+ updateConfig({
450
+ baseUrl: 'https://staging-api.recur.tw'
451
+ });
402
452
 
403
- Initiates a checkout flow.
453
+ // Update publishable key
454
+ updateConfig({
455
+ publishableKey: 'pk_live_xxx'
456
+ });
457
+ ```
404
458
 
405
- **Options:**
406
- - `planId` (required): The ID of the subscription plan
407
- - `customerName` (optional): Customer's name
408
- - `customerEmail` (optional): Customer's email
409
- - `customerPhone` (optional): Customer's phone number
410
- - `organizationId` (optional): Override the organization ID
411
- - `onSuccess` (optional): Callback when checkout is initiated successfully
412
- - `onError` (optional): Callback when checkout fails
413
- - `onPaymentComplete` (optional): Callback when payment is completed (popup mode only)
414
- - `onPaymentCancel` (optional): Callback when payment is cancelled (popup mode only)
459
+ ---
415
460
 
416
- #### `isCheckingOut: boolean`
461
+ ## Web Components Architecture
417
462
 
418
- Indicates whether a checkout is currently in progress.
463
+ The SDK uses modern Web Components for better encapsulation and reusability:
419
464
 
420
- #### `config: RecurConfig`
465
+ ### RecurPaymentForm Component
421
466
 
422
- Current configuration object.
467
+ The embedded payment form is a custom element:
423
468
 
424
- #### `updateConfig(config: Partial<RecurConfig>): void`
469
+ ```html
470
+ <recur-payment-form
471
+ container-id="recur-payment-container"
472
+ custom-styles="..."
473
+ ></recur-payment-form>
474
+ ```
425
475
 
426
- Updates the configuration.
476
+ **Features:**
477
+ - Shadow DOM for style isolation
478
+ - Light DOM for PAYUNi iframe integration
479
+ - Custom events for form submission
480
+ - Attribute-based configuration
427
481
 
428
- ## Error Handling
482
+ **Events:**
483
+ - `submit` - Fired when user submits payment
484
+ - `error` - Fired when an error occurs
429
485
 
430
- All checkout errors are caught and passed to the `onError` callback:
486
+ **Custom Styling:**
487
+ Styles are injected into Light DOM to affect PAYUNi iframes:
431
488
 
432
- ```tsx
433
- await checkout({
434
- planId: 'pro-monthly',
435
- onError: (error) => {
436
- console.error('Error code:', error.code);
437
- console.error('Error message:', error.message);
438
- console.error('Error details:', error.details);
439
- },
440
- });
489
+ ```javascript
490
+ paymentForm.setAttribute('custom-styles', `
491
+ .form-input-focus { /* PAYUNi focus styles */ }
492
+ #container-card-no { /* Card number field styles */ }
493
+ `);
441
494
  ```
442
495
 
443
- Common error codes:
444
- - `CHECKOUT_FAILED`: Failed to initiate checkout
445
- - `CHECKOUT_ERROR`: General checkout error
496
+ ---
446
497
 
447
498
  ## TypeScript Support
448
499
 
@@ -451,84 +502,140 @@ The SDK is written in TypeScript and provides full type definitions:
451
502
  ```tsx
452
503
  import type {
453
504
  RecurConfig,
505
+ RecurContextValue,
454
506
  CheckoutOptions,
455
507
  CheckoutResult,
456
508
  CheckoutError,
509
+ Plan,
510
+ PlansResult,
511
+ Subscription,
457
512
  SubscriptionResult,
458
513
  } from 'recur-tw';
459
514
  ```
460
515
 
461
- ## Advanced Usage
462
-
463
- ### Server-Side Organization Detection
516
+ ### Type Definitions
464
517
 
465
518
  ```tsx
466
- // app/layout.tsx
467
- import { RecurProvider } from '@/lib/recur';
468
- import { getOrganizationIdFromDomain } from '@/lib/utils';
519
+ interface Plan {
520
+ id: string;
521
+ name: string;
522
+ price: number;
523
+ billingPeriod: 'WEEKLY' | 'MONTHLY' | 'YEARLY' | 'CUSTOM';
524
+ trialDays: number;
525
+ description?: string;
526
+ features?: Record<string, any>;
527
+ }
469
528
 
470
- export default async function RootLayout({ children }) {
471
- const organizationId = await getOrganizationIdFromDomain();
529
+ interface Subscription {
530
+ id: string;
531
+ status: string;
532
+ planId: string;
533
+ amount: number;
534
+ billingPeriod: string;
535
+ currentPeriodStart: string;
536
+ currentPeriodEnd: string;
537
+ }
472
538
 
473
- return (
474
- <html>
475
- <body>
476
- <RecurProvider config={{ organizationId }}>
477
- {children}
478
- </RecurProvider>
479
- </body>
480
- </html>
481
- );
539
+ interface CheckoutError {
540
+ code: string;
541
+ message: string;
542
+ details?: any;
482
543
  }
483
544
  ```
484
545
 
485
- ### Multi-Organization Support
546
+ ---
486
547
 
487
- ```tsx
488
- // Let each checkout specify its own organization
489
- <RecurProvider>
490
- <App />
491
- </RecurProvider>
548
+ ## Error Handling
492
549
 
493
- // In component
494
- const { checkout } = useRecur();
550
+ All errors are caught and passed to callbacks:
495
551
 
496
- // Store A
497
- await checkout({ planId: 'plan-a', organizationId: 'store-a' });
552
+ ```tsx
553
+ await checkout({
554
+ planId: 'pro-monthly',
555
+ onError: (error) => {
556
+ console.error('Error code:', error.code);
557
+ console.error('Error message:', error.message);
498
558
 
499
- // Store B
500
- await checkout({ planId: 'plan-b', organizationId: 'store-b' });
559
+ // Handle specific errors
560
+ switch (error.code) {
561
+ case 'CHECKOUT_ERROR':
562
+ // Handle checkout initialization error
563
+ break;
564
+ case 'PAYMENT_FAILED':
565
+ // Handle payment failure
566
+ break;
567
+ default:
568
+ // Handle unknown error
569
+ }
570
+ },
571
+ });
501
572
  ```
502
573
 
503
- ### Integration with Toast Notifications
574
+ Common error codes:
575
+ - `CHECKOUT_ERROR` - Failed to initialize checkout
576
+ - `PAYMENT_FAILED` - Payment processing failed
577
+ - `INVALID_PLAN` - Plan ID not found
578
+ - `API_ERROR` - API request failed
504
579
 
505
- ```tsx
506
- import { useRecur } from '@/lib/recur';
507
- import { toast } from 'sonner';
580
+ ---
508
581
 
509
- export function CheckoutButton({ planId }: { planId: string }) {
510
- const { checkout, isCheckingOut } = useRecur();
582
+ ## Browser Support
511
583
 
512
- const handleCheckout = async () => {
513
- await checkout({
514
- planId,
515
- onSuccess: (result) => {
516
- toast.success('Redirecting to payment...');
517
- },
518
- onError: (error) => {
519
- toast.error(error.message);
520
- },
521
- });
522
- };
584
+ - Chrome/Edge 90+
585
+ - Firefox 88+
586
+ - Safari 14+
587
+ - Mobile browsers with Web Components support
523
588
 
524
- return (
525
- <button onClick={handleCheckout} disabled={isCheckingOut}>
526
- {isCheckingOut ? 'Loading...' : 'Subscribe Now'}
527
- </button>
528
- );
529
- }
589
+ The SDK uses modern Web Components (Custom Elements) which are widely supported in modern browsers.
590
+
591
+ ---
592
+
593
+ ## Examples
594
+
595
+ Check out the `/examples` directory for complete working examples:
596
+
597
+ - **Next.js Basic** - Full Next.js integration with App Router
598
+ - **Vanilla JS** - Static HTML examples
599
+ - **React SPA** - Create React App example
600
+
601
+ ---
602
+
603
+ ## Migration Guide
604
+
605
+ ### From v0.0.2 to v0.0.3
606
+
607
+ **Breaking Changes:**
608
+ - `organizationId` → `publishableKey` in config
609
+ - Checkout now uses Web Components instead of innerHTML
610
+ - New `usePlans()` hook for fetching plans
611
+ - Embedded mode is now the default when `containerElementId` is set
612
+
613
+ **Migration:**
614
+
615
+ ```tsx
616
+ // Before (v0.0.2)
617
+ <RecurProvider config={{ organizationId: 'org_xxx' }}>
618
+
619
+ // After (v0.0.3)
620
+ <RecurProvider config={{ publishableKey: 'pk_test_xxx' }}>
530
621
  ```
531
622
 
623
+ ---
624
+
625
+ ## Contributing
626
+
627
+ This is part of the Recur project. Please see the main repository for contribution guidelines.
628
+
629
+ ---
630
+
532
631
  ## License
533
632
 
534
- This SDK is part of the Recur project.
633
+ MIT
634
+
635
+ ---
636
+
637
+ ## Support
638
+
639
+ - Documentation: https://github.com/kaikhq/recur.tw
640
+ - Issues: https://github.com/kaikhq/recur.tw/issues
641
+ - Email: support@recur.tw