torque-checkout 1.1.10 → 2.0.1
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 +737 -259
- package/dist/index.d.ts +97 -4
- package/dist/index.esm.js +436 -108
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +440 -107
- package/dist/index.js.map +1 -1
- package/dist/nextjs.d.ts +111 -0
- package/dist/nextjs.esm.js +180 -0
- package/dist/nextjs.esm.js.map +1 -0
- package/dist/nextjs.js +185 -0
- package/dist/nextjs.js.map +1 -0
- package/dist/react.d.ts +64 -0
- package/dist/react.esm.js +233 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/react.js +236 -0
- package/dist/react.js.map +1 -0
- package/package.json +49 -7
package/README.md
CHANGED
|
@@ -1,99 +1,199 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Torque Checkout SDK
|
|
2
2
|
|
|
3
|
-
Official Torque checkout SDK for seamless eCommerce integrations
|
|
3
|
+
> **Official Torque checkout SDK for seamless eCommerce integrations with Next.js**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The easiest way to integrate Torque checkout into your Next.js eCommerce application. Accept crypto payments with just a few lines of code.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/torque-checkout)
|
|
8
|
+
[](https://www.npmjs.com/package/torque-checkout)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
## Table of Contents
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Documentation](#documentation)
|
|
18
|
+
- [Examples](#examples)
|
|
19
|
+
- [TypeScript](#typescript)
|
|
20
|
+
- [Error Handling](#error-handling)
|
|
21
|
+
- [Security](#security)
|
|
22
|
+
- [Troubleshooting](#troubleshooting)
|
|
23
|
+
- [Migration Guide](#migration-guide)
|
|
24
|
+
- [Support](#support)
|
|
25
|
+
|
|
26
|
+
## Features
|
|
17
27
|
|
|
18
|
-
|
|
28
|
+
- **Lightweight & Fast** - Minimal bundle size (~40KB), optimized for performance
|
|
29
|
+
- **Zero Configuration** - Works out of the box with environment variables
|
|
30
|
+
- **TypeScript First** - Full TypeScript support with comprehensive types and IntelliSense
|
|
31
|
+
- **React Hooks** - Built-in React hooks for seamless client-side integration
|
|
32
|
+
- **Next.js Optimized** - Server-side utilities for App Router and Pages Router
|
|
33
|
+
- **Production Ready** - Comprehensive error handling, validation, and retry logic
|
|
34
|
+
- **Multi-Product Support** - Handle complex carts with multiple items and variants
|
|
35
|
+
- **Subscription Support** - Built-in subscription and recurring payment management
|
|
36
|
+
- **Universal** - Works in browser, Node.js, and edge environments
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
19
39
|
|
|
20
40
|
```bash
|
|
21
41
|
npm install torque-checkout
|
|
22
42
|
# or
|
|
23
43
|
yarn add torque-checkout
|
|
44
|
+
# or
|
|
45
|
+
pnpm add torque-checkout
|
|
24
46
|
```
|
|
25
47
|
|
|
26
|
-
###
|
|
48
|
+
### Requirements
|
|
27
49
|
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
- **Node.js**: >= 16.0.0
|
|
51
|
+
- **Next.js**: >= 13.0.0 (for Next.js utilities)
|
|
52
|
+
- **React**: >= 16.8.0 (for React hooks, optional)
|
|
30
53
|
|
|
31
|
-
|
|
32
|
-
businessId: 'your_business_id',
|
|
33
|
-
apiKey: 'your_api_key'
|
|
34
|
-
})
|
|
54
|
+
## Quick Start
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
const checkoutUrl = await torque.generateCartCheckoutUrl({
|
|
38
|
-
items: [
|
|
39
|
-
{ productId: 'prod_123', quantity: 2, variant: 'large' },
|
|
40
|
-
{ productId: 'prod_456', quantity: 1 }
|
|
41
|
-
],
|
|
42
|
-
customer: {
|
|
43
|
-
email: 'customer@example.com',
|
|
44
|
-
firstName: 'John',
|
|
45
|
-
lastName: 'Doe'
|
|
46
|
-
}
|
|
47
|
-
})
|
|
56
|
+
### Step 1: Get Your API Credentials
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
1. **Visit [Torque Business Dashboard](https://app.torque.fi/business/settings)**
|
|
59
|
+
2. **Connect your wallet** (MetaMask, WalletConnect, etc.)
|
|
60
|
+
3. **Complete your business profile**:
|
|
61
|
+
- Business name, email, website
|
|
62
|
+
- **Payment wallet address** (where payments will be sent)
|
|
63
|
+
4. **Save your profile** - API credentials are generated automatically
|
|
64
|
+
5. **Copy your credentials** from the API Integration section:
|
|
65
|
+
- `Business ID`
|
|
66
|
+
- `API Key`
|
|
67
|
+
|
|
68
|
+
> **Tip**: Your payment wallet address is set once in business settings. All successful payments automatically go to this wallet - no code needed!
|
|
69
|
+
|
|
70
|
+
### Step 2: Configure Environment Variables
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
Create a `.env.local` file in your Next.js project root:
|
|
73
|
+
|
|
74
|
+
```env
|
|
75
|
+
# Required
|
|
76
|
+
TORQUE_BUSINESS_ID=your_business_id_here
|
|
77
|
+
TORQUE_API_KEY=your_api_key_here
|
|
78
|
+
|
|
79
|
+
# Optional (defaults to production)
|
|
80
|
+
TORQUE_BASE_URL=https://app.torque.fi
|
|
54
81
|
```
|
|
55
82
|
|
|
56
|
-
|
|
83
|
+
> **Security**: Never commit `.env.local` to version control. Add it to `.gitignore`.
|
|
57
84
|
|
|
58
|
-
|
|
85
|
+
### Step 3: Choose Your Integration Method
|
|
59
86
|
|
|
60
|
-
-
|
|
61
|
-
- **Multi-Product Cart**: `https://torque.fi/checkout/{businessId}/cart?items={encoded_cart_data}`
|
|
87
|
+
#### Option A: React Hook (Client-Side) - Recommended for Most Cases
|
|
62
88
|
|
|
63
|
-
|
|
89
|
+
```tsx
|
|
90
|
+
'use client'
|
|
64
91
|
|
|
65
|
-
|
|
92
|
+
import { useTorqueCheckout } from 'torque-checkout/react'
|
|
66
93
|
|
|
67
|
-
|
|
94
|
+
export default function CheckoutButton() {
|
|
95
|
+
const { generateProductCheckout, isLoading } = useTorqueCheckout({
|
|
96
|
+
autoRedirect: true // Automatically redirects to checkout
|
|
97
|
+
})
|
|
68
98
|
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
return (
|
|
100
|
+
<button
|
|
101
|
+
onClick={() => generateProductCheckout('prod_123', 1, {
|
|
102
|
+
email: 'customer@example.com'
|
|
103
|
+
})}
|
|
104
|
+
disabled={isLoading}
|
|
105
|
+
>
|
|
106
|
+
{isLoading ? 'Loading...' : 'Buy Now'}
|
|
107
|
+
</button>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
71
110
|
```
|
|
72
111
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
#### Option B: Server-Side API Route (Most Secure)
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// app/api/checkout/route.ts
|
|
116
|
+
import { handleCheckoutRequest } from 'torque-checkout/nextjs'
|
|
77
117
|
|
|
78
|
-
|
|
118
|
+
export const POST = handleCheckoutRequest()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Then call from your frontend:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
const response = await fetch('/api/checkout', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
items: [{ productId: 'prod_123', quantity: 1 }],
|
|
129
|
+
customer: { email: 'customer@example.com' }
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const { checkoutUrl } = await response.json()
|
|
134
|
+
window.location.href = checkoutUrl
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Option C: Direct SDK Usage
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { createTorqueCheckoutFromEnv } from 'torque-checkout'
|
|
141
|
+
|
|
142
|
+
const torque = createTorqueCheckoutFromEnv()
|
|
143
|
+
|
|
144
|
+
const checkoutUrl = await torque.generateCartCheckoutUrl({
|
|
145
|
+
items: [{ productId: 'prod_123', quantity: 1 }],
|
|
146
|
+
customer: { email: 'customer@example.com' }
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
window.location.href = checkoutUrl
|
|
150
|
+
```
|
|
79
151
|
|
|
80
|
-
|
|
152
|
+
## Documentation
|
|
81
153
|
|
|
82
|
-
|
|
154
|
+
### Core SDK
|
|
155
|
+
|
|
156
|
+
#### Initialization
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { createTorqueCheckout, createTorqueCheckoutFromEnv } from 'torque-checkout'
|
|
160
|
+
|
|
161
|
+
// Method 1: From environment variables (recommended)
|
|
162
|
+
const torque = createTorqueCheckoutFromEnv()
|
|
163
|
+
|
|
164
|
+
// Method 2: Explicit configuration
|
|
165
|
+
const torque = createTorqueCheckout({
|
|
166
|
+
businessId: process.env.TORQUE_BUSINESS_ID!,
|
|
167
|
+
apiKey: process.env.TORQUE_API_KEY!,
|
|
168
|
+
baseUrl: 'https://app.torque.fi', // Optional
|
|
169
|
+
timeout: 30000 // Optional, default: 30000ms
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### Methods
|
|
174
|
+
|
|
175
|
+
##### `generateCartCheckoutUrl(cart: CartData): Promise<string>`
|
|
83
176
|
|
|
84
177
|
Generate a checkout URL for a multi-product cart.
|
|
85
178
|
|
|
86
|
-
```
|
|
179
|
+
```ts
|
|
87
180
|
const checkoutUrl = await torque.generateCartCheckoutUrl({
|
|
88
181
|
items: [
|
|
89
|
-
{
|
|
182
|
+
{
|
|
183
|
+
productId: 'prod_123',
|
|
184
|
+
quantity: 2,
|
|
185
|
+
price: 29.99, // Optional: override product price
|
|
186
|
+
variant: 'large', // Optional: product variant
|
|
187
|
+
metadata: { customField: 'value' } // Optional: custom data
|
|
188
|
+
},
|
|
90
189
|
{ productId: 'prod_456', quantity: 1 }
|
|
91
190
|
],
|
|
92
191
|
customer: {
|
|
93
|
-
email: 'customer@example.com',
|
|
94
|
-
firstName: 'John',
|
|
95
|
-
lastName: 'Doe',
|
|
96
|
-
|
|
192
|
+
email: 'customer@example.com', // Required
|
|
193
|
+
firstName: 'John', // Optional
|
|
194
|
+
lastName: 'Doe', // Optional
|
|
195
|
+
phone: '+1234567890', // Optional
|
|
196
|
+
shippingAddress: { // Optional: pre-fill shipping
|
|
97
197
|
street: '123 Main St',
|
|
98
198
|
city: 'New York',
|
|
99
199
|
state: 'NY',
|
|
@@ -102,311 +202,689 @@ const checkoutUrl = await torque.generateCartCheckoutUrl({
|
|
|
102
202
|
}
|
|
103
203
|
},
|
|
104
204
|
options: {
|
|
105
|
-
metadata: {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
},
|
|
109
|
-
expiresIn: 24 * 60 * 60 * 1000 // 24 hours
|
|
205
|
+
metadata: { orderId: 'order_123', source: 'website' }, // Optional
|
|
206
|
+
expiresIn: 24 * 60 * 60 * 1000, // Optional: 24 hours
|
|
207
|
+
redirectUrl: 'https://yoursite.com/thank-you' // Optional
|
|
110
208
|
}
|
|
111
209
|
})
|
|
112
210
|
```
|
|
113
211
|
|
|
114
|
-
|
|
212
|
+
##### `generateProductCheckoutUrl(productId, quantity?, customer?, options?): Promise<string>`
|
|
115
213
|
|
|
116
|
-
|
|
214
|
+
Quick method for single product checkout.
|
|
117
215
|
|
|
118
|
-
```
|
|
216
|
+
```ts
|
|
119
217
|
const checkoutUrl = await torque.generateProductCheckoutUrl(
|
|
120
|
-
'prod_123',
|
|
121
|
-
2,
|
|
122
|
-
{ email: 'customer@example.com' }
|
|
218
|
+
'prod_123', // Product ID
|
|
219
|
+
2, // Quantity (default: 1)
|
|
220
|
+
{ email: 'customer@example.com' }, // Customer data (optional)
|
|
221
|
+
{ redirectUrl: 'https://yoursite.com/success' } // Options (optional)
|
|
123
222
|
)
|
|
124
223
|
```
|
|
125
224
|
|
|
126
|
-
|
|
225
|
+
##### `generateSubscriptionCheckoutUrl(productId, paymentPlanId, customer?, options?): Promise<string>`
|
|
127
226
|
|
|
128
|
-
|
|
227
|
+
Generate checkout for subscription products.
|
|
129
228
|
|
|
130
|
-
```
|
|
229
|
+
```ts
|
|
230
|
+
const checkoutUrl = await torque.generateSubscriptionCheckoutUrl(
|
|
231
|
+
'prod_subscription_123', // Subscription product ID
|
|
232
|
+
'plan_monthly_123', // Payment plan ID
|
|
233
|
+
{ email: 'customer@example.com' },
|
|
234
|
+
{ redirectUrl: 'https://yoursite.com/subscription-success' }
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
##### `validateCart(cart: CartData): Promise<CartValidation>`
|
|
239
|
+
|
|
240
|
+
Validate cart before checkout to catch errors early.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
131
243
|
const validation = await torque.validateCart({
|
|
132
|
-
items: [
|
|
133
|
-
{ productId: 'prod_123', quantity: 2 },
|
|
134
|
-
{ productId: 'prod_456', quantity: 1 }
|
|
135
|
-
]
|
|
244
|
+
items: [{ productId: 'prod_123', quantity: 2 }]
|
|
136
245
|
})
|
|
137
246
|
|
|
138
247
|
if (validation.valid) {
|
|
139
|
-
console.log('Cart is valid
|
|
248
|
+
console.log('Cart is valid')
|
|
249
|
+
console.log('Estimated total:', validation.estimatedTotal)
|
|
140
250
|
} else {
|
|
141
|
-
console.
|
|
251
|
+
console.error('Validation errors:', validation.errors)
|
|
252
|
+
console.warn('Warnings:', validation.warnings)
|
|
142
253
|
}
|
|
143
254
|
```
|
|
144
255
|
|
|
145
|
-
|
|
256
|
+
##### `getOrderStatus(orderId: string): Promise<OrderStatus>`
|
|
257
|
+
|
|
258
|
+
Check order status after checkout.
|
|
146
259
|
|
|
147
|
-
|
|
260
|
+
```ts
|
|
261
|
+
const order = await torque.getOrderStatus('order_123')
|
|
148
262
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
console.log('
|
|
152
|
-
console.log('
|
|
263
|
+
console.log('Status:', order.status) // e.g., 'completed', 'pending', 'failed'
|
|
264
|
+
console.log('Total:', order.totals.total)
|
|
265
|
+
console.log('Payment status:', order.paymentStatus)
|
|
266
|
+
console.log('Items:', order.items)
|
|
153
267
|
```
|
|
154
268
|
|
|
155
|
-
|
|
269
|
+
### React Hooks
|
|
270
|
+
|
|
271
|
+
#### `useTorqueCheckout(options?)`
|
|
272
|
+
|
|
273
|
+
React hook for client-side checkout with built-in state management.
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { useTorqueCheckout } from 'torque-checkout/react'
|
|
277
|
+
|
|
278
|
+
function CheckoutButton() {
|
|
279
|
+
const {
|
|
280
|
+
generateCheckout,
|
|
281
|
+
generateProductCheckout,
|
|
282
|
+
generateSubscriptionCheckout,
|
|
283
|
+
validateCart,
|
|
284
|
+
getOrderStatus,
|
|
285
|
+
isLoading,
|
|
286
|
+
error,
|
|
287
|
+
checkoutUrl
|
|
288
|
+
} = useTorqueCheckout({
|
|
289
|
+
// Optional: explicit config (or use env vars)
|
|
290
|
+
// config: {
|
|
291
|
+
// businessId: 'your_business_id',
|
|
292
|
+
// apiKey: 'your_api_key'
|
|
293
|
+
// },
|
|
294
|
+
autoRedirect: true, // Auto-redirect to checkout URL
|
|
295
|
+
onSuccess: (url) => {
|
|
296
|
+
console.log('Checkout URL generated:', url)
|
|
297
|
+
// Optional: track analytics
|
|
298
|
+
},
|
|
299
|
+
onError: (error) => {
|
|
300
|
+
console.error('Checkout error:', error)
|
|
301
|
+
// Optional: show error toast
|
|
302
|
+
}
|
|
303
|
+
})
|
|
156
304
|
|
|
157
|
-
|
|
305
|
+
const handleCheckout = async () => {
|
|
306
|
+
const url = await generateProductCheckout('prod_123', 1, {
|
|
307
|
+
email: 'customer@example.com'
|
|
308
|
+
})
|
|
309
|
+
// If autoRedirect is false, you can handle the URL manually
|
|
310
|
+
if (url && !autoRedirect) {
|
|
311
|
+
window.location.href = url
|
|
312
|
+
}
|
|
313
|
+
}
|
|
158
314
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
315
|
+
return (
|
|
316
|
+
<div>
|
|
317
|
+
<button onClick={handleCheckout} disabled={isLoading}>
|
|
318
|
+
{isLoading ? 'Processing...' : 'Checkout'}
|
|
319
|
+
</button>
|
|
320
|
+
{error && <p className="error">{error.message}</p>}
|
|
321
|
+
{checkoutUrl && <p>Checkout URL: {checkoutUrl}</p>}
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
164
325
|
```
|
|
165
326
|
|
|
166
|
-
#### `
|
|
327
|
+
#### `useCart()`
|
|
328
|
+
|
|
329
|
+
Shopping cart state management hook.
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { useCart } from 'torque-checkout/react'
|
|
333
|
+
|
|
334
|
+
function ShoppingCart() {
|
|
335
|
+
const {
|
|
336
|
+
items,
|
|
337
|
+
addItem,
|
|
338
|
+
removeItem,
|
|
339
|
+
updateQuantity,
|
|
340
|
+
clearCart,
|
|
341
|
+
getTotal,
|
|
342
|
+
getItemCount
|
|
343
|
+
} = useCart()
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<div>
|
|
347
|
+
<h2>Shopping Cart ({getItemCount()} items)</h2>
|
|
348
|
+
|
|
349
|
+
{items.map(item => (
|
|
350
|
+
<div key={`${item.productId}-${item.variant || ''}`}>
|
|
351
|
+
<span>Product {item.productId}</span>
|
|
352
|
+
<span>Quantity: {item.quantity}</span>
|
|
353
|
+
<button onClick={() => updateQuantity(item.productId, item.quantity + 1, item.variant)}>
|
|
354
|
+
+
|
|
355
|
+
</button>
|
|
356
|
+
<button onClick={() => updateQuantity(item.productId, item.quantity - 1, item.variant)}>
|
|
357
|
+
-
|
|
358
|
+
</button>
|
|
359
|
+
<button onClick={() => removeItem(item.productId, item.variant)}>
|
|
360
|
+
Remove
|
|
361
|
+
</button>
|
|
362
|
+
</div>
|
|
363
|
+
))}
|
|
364
|
+
|
|
365
|
+
<p>Total: ${getTotal().toFixed(2)}</p>
|
|
366
|
+
<button onClick={clearCart}>Clear Cart</button>
|
|
367
|
+
</div>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
```
|
|
167
371
|
|
|
168
|
-
|
|
372
|
+
### Next.js Server Utilities
|
|
169
373
|
|
|
170
|
-
|
|
171
|
-
await torque.trackCartView('cart_123', {
|
|
172
|
-
items: [{ productId: 'prod_123', quantity: 2 }]
|
|
173
|
-
})
|
|
174
|
-
```
|
|
374
|
+
#### `handleCheckoutRequest(options?)`
|
|
175
375
|
|
|
176
|
-
|
|
376
|
+
Complete API route handler with error handling.
|
|
177
377
|
|
|
178
|
-
|
|
378
|
+
```ts
|
|
379
|
+
// app/api/checkout/route.ts
|
|
380
|
+
import { handleCheckoutRequest } from 'torque-checkout/nextjs'
|
|
179
381
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
382
|
+
export const POST = handleCheckoutRequest({
|
|
383
|
+
onSuccess: async (checkoutUrl, cart) => {
|
|
384
|
+
// Optional: Log, track analytics, send notifications
|
|
385
|
+
console.log('Checkout generated:', checkoutUrl)
|
|
386
|
+
await logCheckoutEvent(cart)
|
|
387
|
+
},
|
|
388
|
+
onError: async (error, cart) => {
|
|
389
|
+
// Optional: Error logging, notifications
|
|
390
|
+
console.error('Checkout error:', error)
|
|
391
|
+
await logError(error, cart)
|
|
392
|
+
}
|
|
184
393
|
})
|
|
185
394
|
```
|
|
186
395
|
|
|
187
|
-
#### `
|
|
396
|
+
#### `handleWebhook(options?)`
|
|
188
397
|
|
|
189
|
-
|
|
398
|
+
Webhook handler for order events.
|
|
190
399
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
|
|
400
|
+
```ts
|
|
401
|
+
// app/api/webhooks/torque/route.ts
|
|
402
|
+
import { handleWebhook } from 'torque-checkout/nextjs'
|
|
403
|
+
|
|
404
|
+
export const POST = handleWebhook({
|
|
405
|
+
secret: process.env.TORQUE_WEBHOOK_SECRET, // Optional: verify signatures
|
|
406
|
+
onOrderCompleted: async (event) => {
|
|
407
|
+
// Fulfill order
|
|
408
|
+
await fulfillOrder(event.orderId!)
|
|
409
|
+
await sendConfirmationEmail(event.data.customerEmail)
|
|
410
|
+
},
|
|
411
|
+
onOrderFailed: async (event) => {
|
|
412
|
+
// Handle failed payment
|
|
413
|
+
await notifyCustomer(event.orderId!)
|
|
414
|
+
},
|
|
415
|
+
onSubscriptionCreated: async (event) => {
|
|
416
|
+
// Activate subscription
|
|
417
|
+
await activateSubscription(event.subscriptionId!)
|
|
418
|
+
}
|
|
194
419
|
})
|
|
195
420
|
```
|
|
196
421
|
|
|
197
|
-
|
|
422
|
+
#### `generateCheckoutUrl(cart, config?)`
|
|
423
|
+
|
|
424
|
+
Server-side checkout URL generation.
|
|
198
425
|
|
|
199
|
-
|
|
426
|
+
```ts
|
|
427
|
+
// app/api/checkout/route.ts
|
|
428
|
+
import { generateCheckoutUrl } from 'torque-checkout/nextjs'
|
|
200
429
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
430
|
+
export async function POST(request: Request) {
|
|
431
|
+
const cart = await request.json()
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const checkoutUrl = await generateCheckoutUrl(cart)
|
|
435
|
+
return Response.json({ checkoutUrl })
|
|
436
|
+
} catch (error: any) {
|
|
437
|
+
return Response.json(
|
|
438
|
+
{ error: error.message, code: error.code },
|
|
439
|
+
{ status: error.statusCode || 500 }
|
|
440
|
+
)
|
|
441
|
+
}
|
|
208
442
|
}
|
|
209
443
|
```
|
|
210
444
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
445
|
+
## Complete Examples
|
|
446
|
+
|
|
447
|
+
### E-Commerce Product Page
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
// app/products/[id]/page.tsx
|
|
451
|
+
'use client'
|
|
452
|
+
|
|
453
|
+
import { useTorqueCheckout, useCart } from 'torque-checkout/react'
|
|
454
|
+
import { useState } from 'react'
|
|
455
|
+
|
|
456
|
+
export default function ProductPage({ params }: { params: { id: string } }) {
|
|
457
|
+
const cart = useCart()
|
|
458
|
+
const { generateProductCheckout, isLoading } = useTorqueCheckout({
|
|
459
|
+
autoRedirect: true
|
|
460
|
+
})
|
|
461
|
+
const [quantity, setQuantity] = useState(1)
|
|
462
|
+
|
|
463
|
+
const handleAddToCart = () => {
|
|
464
|
+
cart.addItem({
|
|
465
|
+
productId: params.id,
|
|
466
|
+
quantity,
|
|
467
|
+
price: 29.99
|
|
468
|
+
})
|
|
225
469
|
}
|
|
226
|
-
|
|
227
|
-
|
|
470
|
+
|
|
471
|
+
const handleBuyNow = async () => {
|
|
472
|
+
await generateProductCheckout(
|
|
473
|
+
params.id,
|
|
474
|
+
quantity,
|
|
475
|
+
{
|
|
476
|
+
email: 'customer@example.com' // Get from auth/session
|
|
477
|
+
}
|
|
478
|
+
)
|
|
228
479
|
}
|
|
480
|
+
|
|
481
|
+
return (
|
|
482
|
+
<div>
|
|
483
|
+
<h1>Product {params.id}</h1>
|
|
484
|
+
<p>$29.99</p>
|
|
485
|
+
|
|
486
|
+
<div>
|
|
487
|
+
<label>Quantity:</label>
|
|
488
|
+
<input
|
|
489
|
+
type="number"
|
|
490
|
+
value={quantity}
|
|
491
|
+
onChange={(e) => setQuantity(Number(e.target.value))}
|
|
492
|
+
min="1"
|
|
493
|
+
/>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<button onClick={handleAddToCart}>
|
|
497
|
+
Add to Cart ({cart.getItemCount()} items)
|
|
498
|
+
</button>
|
|
499
|
+
|
|
500
|
+
<button onClick={handleBuyNow} disabled={isLoading}>
|
|
501
|
+
{isLoading ? 'Processing...' : 'Buy Now'}
|
|
502
|
+
</button>
|
|
503
|
+
</div>
|
|
504
|
+
)
|
|
229
505
|
}
|
|
230
506
|
```
|
|
231
507
|
|
|
232
|
-
###
|
|
508
|
+
### Shopping Cart with Checkout
|
|
233
509
|
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
expiresIn?: number // Optional: Link expiration time (ms)
|
|
238
|
-
metadata?: Record<string, any> // Optional: Custom metadata
|
|
239
|
-
redirectUrl?: string // Optional: Post-checkout redirect URL
|
|
240
|
-
}
|
|
241
|
-
```
|
|
510
|
+
```tsx
|
|
511
|
+
// app/cart/page.tsx
|
|
512
|
+
'use client'
|
|
242
513
|
|
|
243
|
-
|
|
514
|
+
import { useTorqueCheckout, useCart } from 'torque-checkout/react'
|
|
244
515
|
|
|
245
|
-
|
|
516
|
+
export default function CartPage() {
|
|
517
|
+
const cart = useCart()
|
|
518
|
+
const { generateCheckout, isLoading } = useTorqueCheckout()
|
|
246
519
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const { cart, customer } = req.body
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
const checkoutUrl = await torque.generateCartCheckoutUrl({
|
|
254
|
-
items: cart.items.map(item => ({
|
|
255
|
-
productId: item.product_id,
|
|
256
|
-
quantity: item.quantity,
|
|
257
|
-
variant: item.variant_title,
|
|
258
|
-
price: item.price
|
|
259
|
-
})),
|
|
520
|
+
const handleCheckout = async () => {
|
|
521
|
+
const url = await generateCheckout({
|
|
522
|
+
items: cart.items,
|
|
260
523
|
customer: {
|
|
261
|
-
email: customer.
|
|
262
|
-
firstName: customer.first_name,
|
|
263
|
-
lastName: customer.last_name,
|
|
264
|
-
shippingAddress: customer.shipping_address
|
|
524
|
+
email: 'customer@example.com' // Get from auth
|
|
265
525
|
},
|
|
266
526
|
options: {
|
|
267
|
-
|
|
268
|
-
orderId: cart.order_id,
|
|
269
|
-
source: 'shopify'
|
|
270
|
-
}
|
|
527
|
+
redirectUrl: window.location.origin + '/thank-you'
|
|
271
528
|
}
|
|
272
529
|
})
|
|
273
530
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
531
|
+
if (url) {
|
|
532
|
+
window.location.href = url
|
|
533
|
+
}
|
|
277
534
|
}
|
|
278
|
-
|
|
535
|
+
|
|
536
|
+
if (cart.items.length === 0) {
|
|
537
|
+
return <p>Your cart is empty</p>
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return (
|
|
541
|
+
<div>
|
|
542
|
+
<h1>Shopping Cart</h1>
|
|
543
|
+
{cart.items.map(item => (
|
|
544
|
+
<div key={`${item.productId}-${item.variant || ''}`}>
|
|
545
|
+
<span>Product {item.productId}</span>
|
|
546
|
+
<span>Qty: {item.quantity}</span>
|
|
547
|
+
<span>${((item.price || 0) * item.quantity).toFixed(2)}</span>
|
|
548
|
+
<button onClick={() => cart.removeItem(item.productId, item.variant)}>
|
|
549
|
+
Remove
|
|
550
|
+
</button>
|
|
551
|
+
</div>
|
|
552
|
+
))}
|
|
553
|
+
<div>
|
|
554
|
+
<strong>Total: ${cart.getTotal().toFixed(2)}</strong>
|
|
555
|
+
</div>
|
|
556
|
+
<button onClick={handleCheckout} disabled={isLoading}>
|
|
557
|
+
{isLoading ? 'Processing...' : 'Proceed to Checkout'}
|
|
558
|
+
</button>
|
|
559
|
+
</div>
|
|
560
|
+
)
|
|
561
|
+
}
|
|
279
562
|
```
|
|
280
563
|
|
|
281
|
-
###
|
|
564
|
+
### Server Component Checkout (Next.js 13+)
|
|
282
565
|
|
|
283
|
-
```
|
|
284
|
-
//
|
|
285
|
-
|
|
566
|
+
```tsx
|
|
567
|
+
// app/checkout/page.tsx
|
|
568
|
+
import { createTorqueCheckoutFromEnv } from 'torque-checkout'
|
|
569
|
+
import { redirect } from 'next/navigation'
|
|
570
|
+
import { cookies } from 'next/headers'
|
|
286
571
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
'firstName' => $order->get_billing_first_name(),
|
|
305
|
-
'lastName' => $order->get_billing_last_name()
|
|
306
|
-
]
|
|
307
|
-
];
|
|
308
|
-
|
|
309
|
-
// Use JavaScript SDK or direct API call
|
|
310
|
-
$response = wp_remote_post('https://dashboard.torque.fi/api/checkout/generate-link', [
|
|
311
|
-
'body' => json_encode($checkout_data),
|
|
312
|
-
'headers' => ['Content-Type' => 'application/json'],
|
|
313
|
-
'timeout' => 30
|
|
314
|
-
]);
|
|
315
|
-
|
|
316
|
-
if (!is_wp_error($response)) {
|
|
317
|
-
$body = json_decode(wp_remote_retrieve_body($response), true);
|
|
318
|
-
if ($body['checkoutUrl']) {
|
|
319
|
-
wp_redirect($body['checkoutUrl']);
|
|
320
|
-
exit;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
572
|
+
export default async function CheckoutPage({
|
|
573
|
+
searchParams
|
|
574
|
+
}: {
|
|
575
|
+
searchParams: { productId: string; quantity?: string }
|
|
576
|
+
}) {
|
|
577
|
+
const torque = createTorqueCheckoutFromEnv()
|
|
578
|
+
|
|
579
|
+
// Get customer email from session/cookies
|
|
580
|
+
const customerEmail = cookies().get('user_email')?.value || 'guest@example.com'
|
|
581
|
+
|
|
582
|
+
const checkoutUrl = await torque.generateProductCheckoutUrl(
|
|
583
|
+
searchParams.productId,
|
|
584
|
+
Number(searchParams.quantity) || 1,
|
|
585
|
+
{ email: customerEmail }
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
redirect(checkoutUrl)
|
|
323
589
|
}
|
|
324
590
|
```
|
|
325
591
|
|
|
326
|
-
|
|
592
|
+
### Pages Router API Route
|
|
327
593
|
|
|
328
|
-
|
|
594
|
+
```ts
|
|
595
|
+
// pages/api/checkout.ts
|
|
596
|
+
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
597
|
+
import { createTorqueCheckoutFromEnv } from 'torque-checkout'
|
|
329
598
|
|
|
330
|
-
|
|
599
|
+
export default async function handler(
|
|
600
|
+
req: NextApiRequest,
|
|
601
|
+
res: NextApiResponse
|
|
602
|
+
) {
|
|
603
|
+
if (req.method !== 'POST') {
|
|
604
|
+
return res.status(405).json({ error: 'Method not allowed' })
|
|
605
|
+
}
|
|
331
606
|
|
|
332
|
-
|
|
333
|
-
const torque =
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
})
|
|
607
|
+
try {
|
|
608
|
+
const torque = createTorqueCheckoutFromEnv()
|
|
609
|
+
const checkoutUrl = await torque.generateCartCheckoutUrl(req.body)
|
|
610
|
+
|
|
611
|
+
res.status(200).json({ checkoutUrl })
|
|
612
|
+
} catch (error: any) {
|
|
613
|
+
res.status(error.statusCode || 500).json({
|
|
614
|
+
error: error.message,
|
|
615
|
+
code: error.code
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## TypeScript
|
|
622
|
+
|
|
623
|
+
Full TypeScript support with comprehensive types:
|
|
624
|
+
|
|
625
|
+
```ts
|
|
626
|
+
import type {
|
|
627
|
+
// Cart types
|
|
628
|
+
CartItem,
|
|
629
|
+
CartData,
|
|
630
|
+
CartOptions,
|
|
631
|
+
CartValidation,
|
|
632
|
+
|
|
633
|
+
// Customer types
|
|
634
|
+
CustomerData,
|
|
635
|
+
|
|
636
|
+
// Order types
|
|
637
|
+
OrderStatus,
|
|
638
|
+
CheckoutResponse,
|
|
639
|
+
|
|
640
|
+
// Subscription types
|
|
641
|
+
Subscription,
|
|
642
|
+
SubscriptionProduct,
|
|
643
|
+
PaymentPlan,
|
|
644
|
+
CreateSubscriptionData,
|
|
645
|
+
UpdateSubscriptionData,
|
|
646
|
+
|
|
647
|
+
// Config types
|
|
648
|
+
TorqueConfig,
|
|
649
|
+
TorqueCheckoutError
|
|
650
|
+
} from 'torque-checkout'
|
|
337
651
|
```
|
|
338
652
|
|
|
339
|
-
###
|
|
653
|
+
### Type-Safe Usage
|
|
654
|
+
|
|
655
|
+
```ts
|
|
656
|
+
import { createTorqueCheckout } from 'torque-checkout'
|
|
657
|
+
import type { CartData, CustomerData } from 'torque-checkout'
|
|
340
658
|
|
|
341
|
-
|
|
342
|
-
|
|
659
|
+
const torque = createTorqueCheckout({
|
|
660
|
+
businessId: process.env.TORQUE_BUSINESS_ID!,
|
|
661
|
+
apiKey: process.env.TORQUE_API_KEY!
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
const cart: CartData = {
|
|
343
665
|
items: [
|
|
344
|
-
{ productId: '
|
|
666
|
+
{ productId: 'prod_123', quantity: 1 }
|
|
345
667
|
],
|
|
346
668
|
customer: {
|
|
347
|
-
email: '
|
|
348
|
-
}
|
|
669
|
+
email: 'customer@example.com'
|
|
670
|
+
} as CustomerData
|
|
349
671
|
}
|
|
350
672
|
|
|
351
|
-
const
|
|
673
|
+
const url: string = await torque.generateCartCheckoutUrl(cart)
|
|
352
674
|
```
|
|
353
675
|
|
|
354
676
|
## Error Handling
|
|
355
677
|
|
|
356
|
-
The SDK
|
|
678
|
+
The SDK uses custom error classes for better error handling:
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
import { TorqueCheckoutError } from 'torque-checkout'
|
|
357
682
|
|
|
358
|
-
```typescript
|
|
359
683
|
try {
|
|
360
|
-
const
|
|
684
|
+
const url = await torque.generateCartCheckoutUrl(cart)
|
|
361
685
|
} catch (error) {
|
|
362
|
-
if (error
|
|
363
|
-
console.error('
|
|
364
|
-
|
|
365
|
-
console.error('
|
|
366
|
-
|
|
367
|
-
|
|
686
|
+
if (error instanceof TorqueCheckoutError) {
|
|
687
|
+
console.error('Error code:', error.code)
|
|
688
|
+
console.error('Status code:', error.statusCode)
|
|
689
|
+
console.error('Details:', error.details)
|
|
690
|
+
|
|
691
|
+
// Handle specific error codes
|
|
692
|
+
switch (error.code) {
|
|
693
|
+
case 'VALIDATION_ERROR':
|
|
694
|
+
// Show validation errors to user
|
|
695
|
+
showErrors(error.details?.errors)
|
|
696
|
+
break
|
|
697
|
+
case 'BUSINESS_NOT_FOUND':
|
|
698
|
+
// Check business ID configuration
|
|
699
|
+
console.error('Invalid business ID')
|
|
700
|
+
break
|
|
701
|
+
case 'TIMEOUT':
|
|
702
|
+
// Retry or show timeout message
|
|
703
|
+
retryCheckout()
|
|
704
|
+
break
|
|
705
|
+
case 'NETWORK_ERROR':
|
|
706
|
+
// Check internet connection
|
|
707
|
+
showNetworkError()
|
|
708
|
+
break
|
|
709
|
+
default:
|
|
710
|
+
// Generic error handling
|
|
711
|
+
showGenericError(error.message)
|
|
712
|
+
}
|
|
368
713
|
} else {
|
|
369
|
-
|
|
714
|
+
// Unknown error
|
|
715
|
+
console.error('Unexpected error:', error)
|
|
370
716
|
}
|
|
371
717
|
}
|
|
372
718
|
```
|
|
373
719
|
|
|
374
|
-
|
|
720
|
+
### Common Error Codes
|
|
721
|
+
|
|
722
|
+
| Code | Description | Solution |
|
|
723
|
+
|------|-------------|----------|
|
|
724
|
+
| `VALIDATION_ERROR` | Cart validation failed | Check cart items and customer data |
|
|
725
|
+
| `BUSINESS_NOT_FOUND` | Invalid business ID | Verify `TORQUE_BUSINESS_ID` |
|
|
726
|
+
| `INVALID_API_KEY` | Invalid API key | Verify `TORQUE_API_KEY` |
|
|
727
|
+
| `TIMEOUT` | Request timed out | Check network, increase timeout |
|
|
728
|
+
| `NETWORK_ERROR` | Network request failed | Check internet connection |
|
|
729
|
+
| `ORDER_NOT_FOUND` | Order doesn't exist | Verify order ID |
|
|
730
|
+
|
|
731
|
+
## Security Best Practices
|
|
732
|
+
|
|
733
|
+
1. **Never expose API keys in client-side code**
|
|
734
|
+
```ts
|
|
735
|
+
// BAD - Don't do this
|
|
736
|
+
const torque = createTorqueCheckout({
|
|
737
|
+
businessId: 'business_123',
|
|
738
|
+
apiKey: 'sk_live_...' // Exposed in browser!
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
// GOOD - Use server-side API routes
|
|
742
|
+
// app/api/checkout/route.ts
|
|
743
|
+
export const POST = handleCheckoutRequest()
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
2. **Use environment variables**
|
|
747
|
+
```env
|
|
748
|
+
# .env.local (never commit)
|
|
749
|
+
TORQUE_BUSINESS_ID=your_business_id
|
|
750
|
+
TORQUE_API_KEY=your_api_key
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
3. **Validate user input**
|
|
754
|
+
```ts
|
|
755
|
+
// Always validate before sending to API
|
|
756
|
+
if (!cart.items || cart.items.length === 0) {
|
|
757
|
+
throw new Error('Cart is empty')
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
4. **Use HTTPS in production**
|
|
762
|
+
- Always use HTTPS for checkout URLs
|
|
763
|
+
- Never send sensitive data over HTTP
|
|
764
|
+
|
|
765
|
+
5. **Verify webhook signatures**
|
|
766
|
+
```ts
|
|
767
|
+
export const POST = handleWebhook({
|
|
768
|
+
secret: process.env.TORQUE_WEBHOOK_SECRET
|
|
769
|
+
})
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
6. **Set payment wallet in business settings**
|
|
773
|
+
- Payments automatically go to your configured wallet
|
|
774
|
+
- No need to handle wallet addresses in code
|
|
775
|
+
|
|
776
|
+
## Troubleshooting
|
|
777
|
+
|
|
778
|
+
### Issue: "TorqueCheckout not initialized"
|
|
779
|
+
|
|
780
|
+
**Solution**: Make sure environment variables are set:
|
|
781
|
+
```bash
|
|
782
|
+
# Check if variables are loaded
|
|
783
|
+
echo $TORQUE_BUSINESS_ID
|
|
784
|
+
echo $TORQUE_API_KEY
|
|
785
|
+
```
|
|
375
786
|
|
|
376
|
-
|
|
787
|
+
Or provide explicit config:
|
|
788
|
+
```ts
|
|
789
|
+
const torque = createTorqueCheckout({
|
|
790
|
+
businessId: 'your_business_id',
|
|
791
|
+
apiKey: 'your_api_key'
|
|
792
|
+
})
|
|
793
|
+
```
|
|
377
794
|
|
|
378
|
-
|
|
379
|
-
// Track cart abandonment
|
|
380
|
-
await torque.trackCartView('cart_123', cartData)
|
|
795
|
+
### Issue: "Request timeout"
|
|
381
796
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
797
|
+
**Solution**: Increase timeout or check network:
|
|
798
|
+
```ts
|
|
799
|
+
const torque = createTorqueCheckout({
|
|
800
|
+
businessId: process.env.TORQUE_BUSINESS_ID!,
|
|
801
|
+
apiKey: process.env.TORQUE_API_KEY!,
|
|
802
|
+
timeout: 60000 // 60 seconds
|
|
387
803
|
})
|
|
388
804
|
```
|
|
389
805
|
|
|
390
|
-
|
|
806
|
+
### Issue: "Business not found"
|
|
807
|
+
|
|
808
|
+
**Solution**:
|
|
809
|
+
1. Verify business ID in [Business Settings](https://app.torque.fi/business/settings)
|
|
810
|
+
2. Check environment variable is correct
|
|
811
|
+
3. Ensure business profile is saved and active
|
|
391
812
|
|
|
392
|
-
|
|
813
|
+
### Issue: React hooks not working
|
|
393
814
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
815
|
+
**Solution**: Make sure you're using the React entry point:
|
|
816
|
+
```tsx
|
|
817
|
+
// Correct
|
|
818
|
+
import { useTorqueCheckout } from 'torque-checkout/react'
|
|
398
819
|
|
|
399
|
-
|
|
820
|
+
// Wrong
|
|
821
|
+
import { useTorqueCheckout } from 'torque-checkout'
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
### Issue: TypeScript errors
|
|
400
825
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
826
|
+
**Solution**: Make sure TypeScript can find the types:
|
|
827
|
+
```json
|
|
828
|
+
// tsconfig.json
|
|
829
|
+
{
|
|
830
|
+
"compilerOptions": {
|
|
831
|
+
"moduleResolution": "node",
|
|
832
|
+
"esModuleInterop": true
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
## Migration Guide
|
|
838
|
+
|
|
839
|
+
### From v1.x to v2.0.0
|
|
840
|
+
|
|
841
|
+
v2.0.0 is **backward compatible** with v1.x. No code changes required!
|
|
842
|
+
|
|
843
|
+
**New features available (optional):**
|
|
844
|
+
- React hooks: `useTorqueCheckout()`, `useCart()`
|
|
845
|
+
- Next.js utilities: `handleCheckoutRequest()`, `handleWebhook()`
|
|
846
|
+
- Environment variable support: `createTorqueCheckoutFromEnv()`
|
|
847
|
+
|
|
848
|
+
**Migration example:**
|
|
849
|
+
|
|
850
|
+
```ts
|
|
851
|
+
// v1.x (still works)
|
|
852
|
+
import { TorqueCheckout } from 'torque-checkout'
|
|
853
|
+
const torque = new TorqueCheckout({ businessId, apiKey })
|
|
854
|
+
|
|
855
|
+
// v2.0.0 (new, optional)
|
|
856
|
+
import { createTorqueCheckoutFromEnv } from 'torque-checkout'
|
|
857
|
+
const torque = createTorqueCheckoutFromEnv() // Uses env vars
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
## API Reference
|
|
861
|
+
|
|
862
|
+
For complete API documentation, visit:
|
|
863
|
+
- **Full API Docs**: [https://docs.torque.fi/api](https://docs.torque.fi/api)
|
|
864
|
+
- **Integration Guide**: [https://docs.torque.fi/integrations](https://docs.torque.fi/integrations)
|
|
405
865
|
|
|
406
866
|
## Support
|
|
407
867
|
|
|
408
868
|
- **Documentation**: [https://docs.torque.fi](https://docs.torque.fi)
|
|
409
|
-
- **
|
|
410
|
-
- **
|
|
411
|
-
- **
|
|
412
|
-
|
|
869
|
+
- **Email**: hello@torque.fi
|
|
870
|
+
- **GitHub Issues**: [https://github.com/torque-fi/torque-checkout/issues](https://github.com/torque-fi/torque-checkout/issues)
|
|
871
|
+
- **Business Dashboard**: [https://app.torque.fi/business/settings](https://app.torque.fi/business/settings)
|
|
872
|
+
|
|
873
|
+
## License
|
|
874
|
+
|
|
875
|
+
MIT © [Torque](https://torque.fi)
|
|
876
|
+
|
|
877
|
+
## Getting Started Checklist
|
|
878
|
+
|
|
879
|
+
- [ ] Install: `npm install torque-checkout`
|
|
880
|
+
- [ ] Create business profile: [app.torque.fi/business/settings](https://app.torque.fi/business/settings)
|
|
881
|
+
- [ ] Set payment wallet address in business settings
|
|
882
|
+
- [ ] Copy Business ID and API Key
|
|
883
|
+
- [ ] Add environment variables to `.env.local`
|
|
884
|
+
- [ ] Import and use the SDK
|
|
885
|
+
- [ ] Test checkout flow
|
|
886
|
+
- [ ] Deploy to production!
|
|
887
|
+
|
|
888
|
+
---
|
|
889
|
+
|
|
890
|
+
**Made with love by [Torque](https://torque.fi)**
|