chub-dev 0.2.0-beta.3 → 0.2.0-beta.4
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/package.json +2 -2
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +9 -0
- package/src/commands/get.js +48 -4
- package/src/index.js +4 -2
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +14 -0
- package/src/lib/config.js +1 -1
- package/src/lib/registry.js +103 -20
- package/dist/anthropic/docs/sdk/javascript/DOC.md +0 -499
- package/dist/anthropic/docs/sdk/python/DOC.md +0 -382
- package/dist/openai/docs/chat/javascript/DOC.md +0 -350
- package/dist/openai/docs/chat/python/DOC.md +0 -526
- package/dist/pinecone/docs/sdk/javascript/DOC.md +0 -984
- package/dist/pinecone/docs/sdk/python/DOC.md +0 -1395
- package/dist/registry.json +0 -276
- package/dist/resend/docs/sdk/DOC.md +0 -1271
- package/dist/stripe/docs/api/DOC.md +0 -1726
- package/dist/supabase/docs/sdk/DOC.md +0 -1606
- package/dist/twilio/docs/sdk/python/DOC.md +0 -469
- package/dist/twilio/docs/sdk/typescript/DOC.md +0 -946
|
@@ -1,1726 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: api
|
|
3
|
-
description: "Payment processing platform with comprehensive payment and billing features including Payment Intents, Subscriptions, Checkout, customer management, webhooks, and Connect for marketplaces"
|
|
4
|
-
metadata:
|
|
5
|
-
languages: "javascript"
|
|
6
|
-
versions: "19.1.0"
|
|
7
|
-
updated-on: "2025-10-28"
|
|
8
|
-
source: maintainer
|
|
9
|
-
tags: "stripe,api,payments,billing"
|
|
10
|
-
---
|
|
11
|
-
# Stripe API Coding Guide
|
|
12
|
-
|
|
13
|
-
## 1. Golden Rule
|
|
14
|
-
|
|
15
|
-
**Always use the official Stripe SDK packages:**
|
|
16
|
-
- Server-side: `stripe` (Node.js library for Stripe API)
|
|
17
|
-
- Client-side: `@stripe/stripe-js` (ES module for browser)
|
|
18
|
-
- React: `@stripe/react-stripe-js` (React components and hooks)
|
|
19
|
-
|
|
20
|
-
**Never use deprecated or unofficial libraries.** These are the only supported Stripe packages maintained by Stripe, Inc.
|
|
21
|
-
|
|
22
|
-
**Current SDK Version:** v19.1.0 (Node.js server library)
|
|
23
|
-
|
|
24
|
-
**API Version:** Stripe uses date-based API versioning (e.g., 2025-02-24). Your account is automatically pinned to the API version from your first request. You can override this per-request or upgrade in the Stripe Dashboard.
|
|
25
|
-
|
|
26
|
-
## 2. Installation
|
|
27
|
-
|
|
28
|
-
### Server-Side (Node.js)
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install stripe
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
yarn add stripe
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pnpm add stripe
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Requirements:** Node.js 16+ (support for Node 16 is deprecated; use Node 18+ for production)
|
|
43
|
-
|
|
44
|
-
### Client-Side (Browser)
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npm install @stripe/stripe-js
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
yarn add @stripe/stripe-js
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### React Applications
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npm install @stripe/stripe-js @stripe/react-stripe-js
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
yarn add @stripe/stripe-js @stripe/react-stripe-js
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Environment Variables
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
# Required
|
|
68
|
-
STRIPE_SECRET_KEY=sk_test_51H... # Server-side only, NEVER expose in browser
|
|
69
|
-
STRIPE_PUBLISHABLE_KEY=pk_test_51H... # Safe for client-side use
|
|
70
|
-
|
|
71
|
-
# Optional
|
|
72
|
-
STRIPE_WEBHOOK_SECRET=whsec_... # For webhook signature verification
|
|
73
|
-
STRIPE_API_VERSION=2025-02-24 # Override default API version
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**CRITICAL:** Never commit secret keys to version control. Use environment variables or secure secret management systems.
|
|
77
|
-
|
|
78
|
-
## 3. Initialization
|
|
79
|
-
|
|
80
|
-
### Server-Side Initialization (Node.js)
|
|
81
|
-
|
|
82
|
-
```javascript
|
|
83
|
-
const Stripe = require('stripe');
|
|
84
|
-
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**With TypeScript:**
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
import Stripe from 'stripe';
|
|
91
|
-
|
|
92
|
-
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
93
|
-
apiVersion: '2025-02-24',
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Advanced Configuration:**
|
|
98
|
-
|
|
99
|
-
```javascript
|
|
100
|
-
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
|
101
|
-
apiVersion: '2025-02-24',
|
|
102
|
-
maxNetworkRetries: 2,
|
|
103
|
-
timeout: 80000, // 80 seconds
|
|
104
|
-
telemetry: true,
|
|
105
|
-
appInfo: {
|
|
106
|
-
name: 'MyApp',
|
|
107
|
-
version: '1.0.0',
|
|
108
|
-
url: 'https://myapp.com',
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Client-Side Initialization (Browser)
|
|
114
|
-
|
|
115
|
-
```javascript
|
|
116
|
-
import { loadStripe } from '@stripe/stripe-js';
|
|
117
|
-
|
|
118
|
-
// Load Stripe.js asynchronously
|
|
119
|
-
const stripePromise = loadStripe(process.env.STRIPE_PUBLISHABLE_KEY);
|
|
120
|
-
|
|
121
|
-
// Later, when you need to use it
|
|
122
|
-
const stripe = await stripePromise;
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Advanced Loading Options:**
|
|
126
|
-
|
|
127
|
-
```javascript
|
|
128
|
-
const stripePromise = loadStripe(
|
|
129
|
-
process.env.STRIPE_PUBLISHABLE_KEY,
|
|
130
|
-
{
|
|
131
|
-
locale: 'en',
|
|
132
|
-
betas: ['some_beta_feature'],
|
|
133
|
-
apiVersion: '2025-02-24',
|
|
134
|
-
}
|
|
135
|
-
);
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### React Initialization
|
|
139
|
-
|
|
140
|
-
```javascript
|
|
141
|
-
import { Elements } from '@stripe/react-stripe-js';
|
|
142
|
-
import { loadStripe } from '@stripe/stripe-js';
|
|
143
|
-
|
|
144
|
-
const stripePromise = loadStripe(process.env.STRIPE_PUBLISHABLE_KEY);
|
|
145
|
-
|
|
146
|
-
function App() {
|
|
147
|
-
return (
|
|
148
|
-
<Elements stripe={stripePromise}>
|
|
149
|
-
{/* Your payment components here */}
|
|
150
|
-
</Elements>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**With Options:**
|
|
156
|
-
|
|
157
|
-
```javascript
|
|
158
|
-
function App() {
|
|
159
|
-
const options = {
|
|
160
|
-
mode: 'payment',
|
|
161
|
-
amount: 1099,
|
|
162
|
-
currency: 'usd',
|
|
163
|
-
appearance: {
|
|
164
|
-
theme: 'stripe',
|
|
165
|
-
variables: {
|
|
166
|
-
colorPrimary: '#0570de',
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return (
|
|
172
|
-
<Elements stripe={stripePromise} options={options}>
|
|
173
|
-
<CheckoutForm />
|
|
174
|
-
</Elements>
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## 4. Core API Surfaces
|
|
180
|
-
|
|
181
|
-
### Payment Intents
|
|
182
|
-
|
|
183
|
-
Payment Intents are the recommended way to handle payments. They track the entire payment lifecycle and support Strong Customer Authentication (SCA).
|
|
184
|
-
|
|
185
|
-
**Minimal Server-Side Example:**
|
|
186
|
-
|
|
187
|
-
```javascript
|
|
188
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
189
|
-
amount: 1099, // Amount in cents ($10.99)
|
|
190
|
-
currency: 'usd',
|
|
191
|
-
payment_method_types: ['card'],
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Send clientSecret to client
|
|
195
|
-
res.json({ clientSecret: paymentIntent.client_secret });
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Advanced Server-Side Example:**
|
|
199
|
-
|
|
200
|
-
```javascript
|
|
201
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
202
|
-
amount: 2000,
|
|
203
|
-
currency: 'usd',
|
|
204
|
-
payment_method_types: ['card', 'us_bank_account'],
|
|
205
|
-
customer: 'cus_123456789',
|
|
206
|
-
description: 'Software subscription',
|
|
207
|
-
metadata: {
|
|
208
|
-
order_id: '6735',
|
|
209
|
-
customer_email: 'customer@example.com',
|
|
210
|
-
},
|
|
211
|
-
statement_descriptor: 'MYCOMPANY SUB',
|
|
212
|
-
receipt_email: 'customer@example.com',
|
|
213
|
-
automatic_payment_methods: {
|
|
214
|
-
enabled: true,
|
|
215
|
-
allow_redirects: 'never',
|
|
216
|
-
},
|
|
217
|
-
capture_method: 'manual', // For later capture
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Client-Side Confirmation (React):**
|
|
222
|
-
|
|
223
|
-
```javascript
|
|
224
|
-
import { PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
|
225
|
-
|
|
226
|
-
function CheckoutForm({ clientSecret }) {
|
|
227
|
-
const stripe = useStripe();
|
|
228
|
-
const elements = useElements();
|
|
229
|
-
|
|
230
|
-
const handleSubmit = async (event) => {
|
|
231
|
-
event.preventDefault();
|
|
232
|
-
|
|
233
|
-
if (!stripe || !elements) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const { error, paymentIntent } = await stripe.confirmPayment({
|
|
238
|
-
elements,
|
|
239
|
-
confirmParams: {
|
|
240
|
-
return_url: 'https://example.com/order/complete',
|
|
241
|
-
},
|
|
242
|
-
redirect: 'if_required',
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
if (error) {
|
|
246
|
-
console.error(error.message);
|
|
247
|
-
} else if (paymentIntent.status === 'succeeded') {
|
|
248
|
-
console.log('Payment successful!');
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<form onSubmit={handleSubmit}>
|
|
254
|
-
<PaymentElement />
|
|
255
|
-
<button disabled={!stripe}>Submit</button>
|
|
256
|
-
</form>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**Vanilla JavaScript Confirmation:**
|
|
262
|
-
|
|
263
|
-
```javascript
|
|
264
|
-
const stripe = await stripePromise;
|
|
265
|
-
|
|
266
|
-
const { error, paymentIntent } = await stripe.confirmCardPayment(
|
|
267
|
-
clientSecret,
|
|
268
|
-
{
|
|
269
|
-
payment_method: {
|
|
270
|
-
card: cardElement,
|
|
271
|
-
billing_details: {
|
|
272
|
-
name: 'John Doe',
|
|
273
|
-
email: 'john@example.com',
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
if (error) {
|
|
280
|
-
console.error(error.message);
|
|
281
|
-
} else if (paymentIntent.status === 'succeeded') {
|
|
282
|
-
console.log('Payment successful!');
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Checkout Sessions
|
|
287
|
-
|
|
288
|
-
Stripe Checkout provides a pre-built, hosted payment page.
|
|
289
|
-
|
|
290
|
-
**Minimal Server-Side Example:**
|
|
291
|
-
|
|
292
|
-
```javascript
|
|
293
|
-
const session = await stripe.checkout.sessions.create({
|
|
294
|
-
line_items: [
|
|
295
|
-
{
|
|
296
|
-
price_data: {
|
|
297
|
-
currency: 'usd',
|
|
298
|
-
product_data: {
|
|
299
|
-
name: 'T-shirt',
|
|
300
|
-
},
|
|
301
|
-
unit_amount: 2000,
|
|
302
|
-
},
|
|
303
|
-
quantity: 1,
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
mode: 'payment',
|
|
307
|
-
success_url: 'https://example.com/success',
|
|
308
|
-
cancel_url: 'https://example.com/cancel',
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// Redirect to Checkout
|
|
312
|
-
res.redirect(303, session.url);
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
**Advanced Server-Side Example:**
|
|
316
|
-
|
|
317
|
-
```javascript
|
|
318
|
-
const session = await stripe.checkout.sessions.create({
|
|
319
|
-
line_items: [
|
|
320
|
-
{
|
|
321
|
-
price: 'price_1234567890', // Existing price ID
|
|
322
|
-
quantity: 2,
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
mode: 'payment',
|
|
326
|
-
customer: 'cus_123456789',
|
|
327
|
-
customer_email: 'customer@example.com',
|
|
328
|
-
client_reference_id: 'order_12345',
|
|
329
|
-
success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
|
|
330
|
-
cancel_url: 'https://example.com/cancel',
|
|
331
|
-
automatic_tax: { enabled: true },
|
|
332
|
-
allow_promotion_codes: true,
|
|
333
|
-
billing_address_collection: 'required',
|
|
334
|
-
shipping_address_collection: {
|
|
335
|
-
allowed_countries: ['US', 'CA'],
|
|
336
|
-
},
|
|
337
|
-
payment_method_types: ['card', 'us_bank_account'],
|
|
338
|
-
metadata: {
|
|
339
|
-
order_id: '6735',
|
|
340
|
-
},
|
|
341
|
-
invoice_creation: {
|
|
342
|
-
enabled: true,
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
**Client-Side Redirect:**
|
|
348
|
-
|
|
349
|
-
```javascript
|
|
350
|
-
import { loadStripe } from '@stripe/stripe-js';
|
|
351
|
-
|
|
352
|
-
const stripe = await loadStripe(publishableKey);
|
|
353
|
-
|
|
354
|
-
// Redirect to Checkout
|
|
355
|
-
const { error } = await stripe.redirectToCheckout({
|
|
356
|
-
sessionId: session.id,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
if (error) {
|
|
360
|
-
console.error(error.message);
|
|
361
|
-
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Customers
|
|
365
|
-
|
|
366
|
-
Customers represent your users in Stripe.
|
|
367
|
-
|
|
368
|
-
**Minimal Example:**
|
|
369
|
-
|
|
370
|
-
```javascript
|
|
371
|
-
const customer = await stripe.customers.create({
|
|
372
|
-
email: 'customer@example.com',
|
|
373
|
-
});
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
**Advanced Example:**
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
```javascript
|
|
380
|
-
const customer = await stripe.customers.create({
|
|
381
|
-
email: 'customer@example.com',
|
|
382
|
-
name: 'John Doe',
|
|
383
|
-
phone: '+15555551234',
|
|
384
|
-
description: 'Premium customer',
|
|
385
|
-
metadata: {
|
|
386
|
-
user_id: 'user_12345',
|
|
387
|
-
plan: 'premium',
|
|
388
|
-
},
|
|
389
|
-
address: {
|
|
390
|
-
line1: '510 Townsend St',
|
|
391
|
-
city: 'San Francisco',
|
|
392
|
-
state: 'CA',
|
|
393
|
-
postal_code: '94103',
|
|
394
|
-
country: 'US',
|
|
395
|
-
},
|
|
396
|
-
payment_method: 'pm_card_visa',
|
|
397
|
-
invoice_settings: {
|
|
398
|
-
default_payment_method: 'pm_card_visa',
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
**Retrieve and Update:**
|
|
404
|
-
|
|
405
|
-
```javascript
|
|
406
|
-
// Retrieve customer
|
|
407
|
-
const customer = await stripe.customers.retrieve('cus_123456789');
|
|
408
|
-
|
|
409
|
-
// Update customer
|
|
410
|
-
const updatedCustomer = await stripe.customers.update('cus_123456789', {
|
|
411
|
-
metadata: { vip: 'true' },
|
|
412
|
-
email: 'newemail@example.com',
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// List customers
|
|
416
|
-
const customers = await stripe.customers.list({
|
|
417
|
-
limit: 100,
|
|
418
|
-
email: 'customer@example.com',
|
|
419
|
-
});
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
**Attach Payment Method to Customer:**
|
|
423
|
-
|
|
424
|
-
```javascript
|
|
425
|
-
const paymentMethod = await stripe.paymentMethods.attach(
|
|
426
|
-
'pm_card_visa',
|
|
427
|
-
{ customer: 'cus_123456789' }
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
// Set as default payment method
|
|
431
|
-
await stripe.customers.update('cus_123456789', {
|
|
432
|
-
invoice_settings: {
|
|
433
|
-
default_payment_method: 'pm_card_visa',
|
|
434
|
-
},
|
|
435
|
-
});
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Subscriptions
|
|
439
|
-
|
|
440
|
-
Subscriptions bill customers on a recurring basis.
|
|
441
|
-
|
|
442
|
-
**Minimal Example:**
|
|
443
|
-
|
|
444
|
-
```javascript
|
|
445
|
-
const subscription = await stripe.subscriptions.create({
|
|
446
|
-
customer: 'cus_123456789',
|
|
447
|
-
items: [
|
|
448
|
-
{ price: 'price_1234567890' },
|
|
449
|
-
],
|
|
450
|
-
});
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
**Advanced Example:**
|
|
454
|
-
|
|
455
|
-
```javascript
|
|
456
|
-
const subscription = await stripe.subscriptions.create({
|
|
457
|
-
customer: 'cus_123456789',
|
|
458
|
-
items: [
|
|
459
|
-
{
|
|
460
|
-
price: 'price_1234567890',
|
|
461
|
-
quantity: 1,
|
|
462
|
-
},
|
|
463
|
-
],
|
|
464
|
-
payment_behavior: 'default_incomplete',
|
|
465
|
-
payment_settings: {
|
|
466
|
-
payment_method_types: ['card', 'us_bank_account'],
|
|
467
|
-
save_default_payment_method: 'on_subscription',
|
|
468
|
-
},
|
|
469
|
-
expand: ['latest_invoice.payment_intent'],
|
|
470
|
-
trial_period_days: 14,
|
|
471
|
-
metadata: {
|
|
472
|
-
user_id: 'user_12345',
|
|
473
|
-
},
|
|
474
|
-
proration_behavior: 'create_prorations',
|
|
475
|
-
billing_cycle_anchor_config: {
|
|
476
|
-
day_of_month: 1,
|
|
477
|
-
},
|
|
478
|
-
automatic_tax: {
|
|
479
|
-
enabled: true,
|
|
480
|
-
},
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
// Return client secret for payment confirmation
|
|
484
|
-
const clientSecret = subscription.latest_invoice.payment_intent.client_secret;
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
**Update Subscription:**
|
|
488
|
-
|
|
489
|
-
```javascript
|
|
490
|
-
const updatedSubscription = await stripe.subscriptions.update(
|
|
491
|
-
'sub_1234567890',
|
|
492
|
-
{
|
|
493
|
-
items: [
|
|
494
|
-
{
|
|
495
|
-
id: 'si_1234567890',
|
|
496
|
-
price: 'price_new_plan',
|
|
497
|
-
},
|
|
498
|
-
],
|
|
499
|
-
proration_behavior: 'always_invoice',
|
|
500
|
-
}
|
|
501
|
-
);
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
**Cancel Subscription:**
|
|
505
|
-
|
|
506
|
-
```javascript
|
|
507
|
-
// Cancel at period end
|
|
508
|
-
const subscription = await stripe.subscriptions.update('sub_1234567890', {
|
|
509
|
-
cancel_at_period_end: true,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Cancel immediately
|
|
513
|
-
const canceledSubscription = await stripe.subscriptions.cancel('sub_1234567890');
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### Products and Prices
|
|
517
|
-
|
|
518
|
-
Products represent what you sell, Prices define how you charge for products.
|
|
519
|
-
|
|
520
|
-
**Create Product:**
|
|
521
|
-
|
|
522
|
-
```javascript
|
|
523
|
-
const product = await stripe.products.create({
|
|
524
|
-
name: 'Premium Subscription',
|
|
525
|
-
description: 'Access to all premium features',
|
|
526
|
-
metadata: {
|
|
527
|
-
category: 'subscription',
|
|
528
|
-
},
|
|
529
|
-
});
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
**Create Price (One-Time):**
|
|
533
|
-
|
|
534
|
-
```javascript
|
|
535
|
-
const price = await stripe.prices.create({
|
|
536
|
-
product: 'prod_1234567890',
|
|
537
|
-
unit_amount: 2000,
|
|
538
|
-
currency: 'usd',
|
|
539
|
-
});
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
**Create Price (Recurring):**
|
|
543
|
-
|
|
544
|
-
```javascript
|
|
545
|
-
const recurringPrice = await stripe.prices.create({
|
|
546
|
-
product: 'prod_1234567890',
|
|
547
|
-
unit_amount: 1500,
|
|
548
|
-
currency: 'usd',
|
|
549
|
-
recurring: {
|
|
550
|
-
interval: 'month',
|
|
551
|
-
interval_count: 1,
|
|
552
|
-
usage_type: 'licensed',
|
|
553
|
-
},
|
|
554
|
-
billing_scheme: 'per_unit',
|
|
555
|
-
tax_behavior: 'exclusive',
|
|
556
|
-
});
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
**Advanced Pricing (Tiered):**
|
|
560
|
-
|
|
561
|
-
```javascript
|
|
562
|
-
const tieredPrice = await stripe.prices.create({
|
|
563
|
-
product: 'prod_1234567890',
|
|
564
|
-
currency: 'usd',
|
|
565
|
-
recurring: {
|
|
566
|
-
interval: 'month',
|
|
567
|
-
usage_type: 'metered',
|
|
568
|
-
},
|
|
569
|
-
billing_scheme: 'tiered',
|
|
570
|
-
tiers_mode: 'graduated',
|
|
571
|
-
tiers: [
|
|
572
|
-
{
|
|
573
|
-
up_to: 10,
|
|
574
|
-
unit_amount: 1000,
|
|
575
|
-
},
|
|
576
|
-
{
|
|
577
|
-
up_to: 100,
|
|
578
|
-
unit_amount: 800,
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
up_to: 'inf',
|
|
582
|
-
unit_amount: 500,
|
|
583
|
-
},
|
|
584
|
-
],
|
|
585
|
-
});
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
### Payment Methods
|
|
589
|
-
|
|
590
|
-
Payment Methods represent customer payment details.
|
|
591
|
-
|
|
592
|
-
**Create Payment Method (Server-Side):**
|
|
593
|
-
|
|
594
|
-
```javascript
|
|
595
|
-
const paymentMethod = await stripe.paymentMethods.create({
|
|
596
|
-
type: 'card',
|
|
597
|
-
card: {
|
|
598
|
-
token: 'tok_visa',
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
|
-
```
|
|
602
|
-
|
|
603
|
-
**Create Payment Method (Client-Side with Elements):**
|
|
604
|
-
|
|
605
|
-
```javascript
|
|
606
|
-
const stripe = await stripePromise;
|
|
607
|
-
const elements = stripe.elements();
|
|
608
|
-
const cardElement = elements.create('card');
|
|
609
|
-
cardElement.mount('#card-element');
|
|
610
|
-
|
|
611
|
-
// When form is submitted
|
|
612
|
-
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
|
613
|
-
type: 'card',
|
|
614
|
-
card: cardElement,
|
|
615
|
-
billing_details: {
|
|
616
|
-
name: 'John Doe',
|
|
617
|
-
email: 'john@example.com',
|
|
618
|
-
},
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
if (error) {
|
|
622
|
-
console.error(error.message);
|
|
623
|
-
} else {
|
|
624
|
-
console.log('PaymentMethod created:', paymentMethod.id);
|
|
625
|
-
}
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
**List Customer Payment Methods:**
|
|
629
|
-
|
|
630
|
-
```javascript
|
|
631
|
-
const paymentMethods = await stripe.paymentMethods.list({
|
|
632
|
-
customer: 'cus_123456789',
|
|
633
|
-
type: 'card',
|
|
634
|
-
});
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
**Detach Payment Method:**
|
|
638
|
-
|
|
639
|
-
```javascript
|
|
640
|
-
const detachedPM = await stripe.paymentMethods.detach('pm_1234567890');
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
### Refunds
|
|
644
|
-
|
|
645
|
-
**Create Refund:**
|
|
646
|
-
|
|
647
|
-
```javascript
|
|
648
|
-
// Full refund
|
|
649
|
-
const refund = await stripe.refunds.create({
|
|
650
|
-
payment_intent: 'pi_1234567890',
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
// Partial refund
|
|
654
|
-
const partialRefund = await stripe.refunds.create({
|
|
655
|
-
payment_intent: 'pi_1234567890',
|
|
656
|
-
amount: 500, // Refund $5.00 of original charge
|
|
657
|
-
reason: 'requested_by_customer',
|
|
658
|
-
metadata: {
|
|
659
|
-
reason_detail: 'Customer changed mind',
|
|
660
|
-
},
|
|
661
|
-
});
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
**Retrieve Refund:**
|
|
665
|
-
|
|
666
|
-
```javascript
|
|
667
|
-
const refund = await stripe.refunds.retrieve('re_1234567890');
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
### Webhooks
|
|
671
|
-
|
|
672
|
-
Webhooks notify your application when events occur in your Stripe account.
|
|
673
|
-
|
|
674
|
-
**Setup Webhook Endpoint (Express.js):**
|
|
675
|
-
|
|
676
|
-
```javascript
|
|
677
|
-
const express = require('express');
|
|
678
|
-
const app = express();
|
|
679
|
-
|
|
680
|
-
// CRITICAL: Use raw body for webhook signature verification
|
|
681
|
-
app.post(
|
|
682
|
-
'/webhook',
|
|
683
|
-
express.raw({ type: 'application/json' }),
|
|
684
|
-
async (req, res) => {
|
|
685
|
-
const sig = req.headers['stripe-signature'];
|
|
686
|
-
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
687
|
-
|
|
688
|
-
let event;
|
|
689
|
-
|
|
690
|
-
try {
|
|
691
|
-
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
|
|
692
|
-
} catch (err) {
|
|
693
|
-
console.error(`Webhook signature verification failed: ${err.message}`);
|
|
694
|
-
return res.status(400).send(`Webhook Error: ${err.message}`);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Handle the event
|
|
698
|
-
switch (event.type) {
|
|
699
|
-
case 'payment_intent.succeeded':
|
|
700
|
-
const paymentIntent = event.data.object;
|
|
701
|
-
console.log('PaymentIntent succeeded:', paymentIntent.id);
|
|
702
|
-
// Fulfill the order
|
|
703
|
-
break;
|
|
704
|
-
case 'payment_intent.payment_failed':
|
|
705
|
-
const failedPayment = event.data.object;
|
|
706
|
-
console.log('Payment failed:', failedPayment.id);
|
|
707
|
-
// Notify customer
|
|
708
|
-
break;
|
|
709
|
-
case 'customer.subscription.created':
|
|
710
|
-
const subscription = event.data.object;
|
|
711
|
-
console.log('Subscription created:', subscription.id);
|
|
712
|
-
break;
|
|
713
|
-
case 'customer.subscription.updated':
|
|
714
|
-
const updatedSub = event.data.object;
|
|
715
|
-
console.log('Subscription updated:', updatedSub.id);
|
|
716
|
-
break;
|
|
717
|
-
case 'customer.subscription.deleted':
|
|
718
|
-
const canceledSub = event.data.object;
|
|
719
|
-
console.log('Subscription canceled:', canceledSub.id);
|
|
720
|
-
break;
|
|
721
|
-
default:
|
|
722
|
-
console.log(`Unhandled event type ${event.type}`);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
res.json({ received: true });
|
|
726
|
-
}
|
|
727
|
-
);
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
**Advanced Webhook Handling:**
|
|
731
|
-
|
|
732
|
-
```javascript
|
|
733
|
-
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
|
|
734
|
-
const sig = req.headers['stripe-signature'];
|
|
735
|
-
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
736
|
-
|
|
737
|
-
let event;
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
|
|
741
|
-
} catch (err) {
|
|
742
|
-
console.error(`Webhook Error: ${err.message}`);
|
|
743
|
-
return res.status(400).send(`Webhook Error: ${err.message}`);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Handle event asynchronously to respond quickly
|
|
747
|
-
processWebhookEvent(event)
|
|
748
|
-
.then(() => console.log('Event processed successfully'))
|
|
749
|
-
.catch((err) => console.error('Event processing failed:', err));
|
|
750
|
-
|
|
751
|
-
// Respond immediately
|
|
752
|
-
res.json({ received: true });
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
async function processWebhookEvent(event) {
|
|
756
|
-
const eventData = event.data.object;
|
|
757
|
-
|
|
758
|
-
switch (event.type) {
|
|
759
|
-
case 'checkout.session.completed':
|
|
760
|
-
const session = eventData;
|
|
761
|
-
// Fulfill order based on session
|
|
762
|
-
if (session.mode === 'payment') {
|
|
763
|
-
await fulfillOrder(session);
|
|
764
|
-
} else if (session.mode === 'subscription') {
|
|
765
|
-
await activateSubscription(session);
|
|
766
|
-
}
|
|
767
|
-
break;
|
|
768
|
-
|
|
769
|
-
case 'invoice.paid':
|
|
770
|
-
// Handle successful payment of invoice
|
|
771
|
-
await handleInvoicePaid(eventData);
|
|
772
|
-
break;
|
|
773
|
-
|
|
774
|
-
case 'invoice.payment_failed':
|
|
775
|
-
// Handle failed payment
|
|
776
|
-
await notifyCustomerPaymentFailed(eventData);
|
|
777
|
-
break;
|
|
778
|
-
|
|
779
|
-
default:
|
|
780
|
-
console.log(`Unhandled event type: ${event.type}`);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
### Invoices
|
|
786
|
-
|
|
787
|
-
**Create Invoice:**
|
|
788
|
-
|
|
789
|
-
```javascript
|
|
790
|
-
// Create invoice item
|
|
791
|
-
await stripe.invoiceItems.create({
|
|
792
|
-
customer: 'cus_123456789',
|
|
793
|
-
amount: 2500,
|
|
794
|
-
currency: 'usd',
|
|
795
|
-
description: 'One-time setup fee',
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
// Create and finalize invoice
|
|
799
|
-
const invoice = await stripe.invoices.create({
|
|
800
|
-
customer: 'cus_123456789',
|
|
801
|
-
auto_advance: true, // Auto-finalize
|
|
802
|
-
collection_method: 'charge_automatically',
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
// Finalize invoice (if not auto-advance)
|
|
806
|
-
const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice.id);
|
|
807
|
-
|
|
808
|
-
// Pay invoice
|
|
809
|
-
const paidInvoice = await stripe.invoices.pay(invoice.id);
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
### Customer Portal
|
|
813
|
-
|
|
814
|
-
The Customer Portal allows customers to manage their subscription and billing information.
|
|
815
|
-
|
|
816
|
-
**Create Portal Session:**
|
|
817
|
-
|
|
818
|
-
```javascript
|
|
819
|
-
const portalSession = await stripe.billingPortal.sessions.create({
|
|
820
|
-
customer: 'cus_123456789',
|
|
821
|
-
return_url: 'https://example.com/account',
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
// Redirect customer to portal
|
|
825
|
-
res.redirect(303, portalSession.url);
|
|
826
|
-
```
|
|
827
|
-
|
|
828
|
-
**Advanced Portal Configuration:**
|
|
829
|
-
|
|
830
|
-
```javascript
|
|
831
|
-
// Create a portal configuration
|
|
832
|
-
const configuration = await stripe.billingPortal.configurations.create({
|
|
833
|
-
business_profile: {
|
|
834
|
-
headline: 'Manage your subscription',
|
|
835
|
-
},
|
|
836
|
-
features: {
|
|
837
|
-
customer_update: {
|
|
838
|
-
enabled: true,
|
|
839
|
-
allowed_updates: ['email', 'address', 'shipping', 'phone', 'tax_id'],
|
|
840
|
-
},
|
|
841
|
-
invoice_history: {
|
|
842
|
-
enabled: true,
|
|
843
|
-
},
|
|
844
|
-
payment_method_update: {
|
|
845
|
-
enabled: true,
|
|
846
|
-
},
|
|
847
|
-
subscription_cancel: {
|
|
848
|
-
enabled: true,
|
|
849
|
-
mode: 'at_period_end',
|
|
850
|
-
cancellation_reason: {
|
|
851
|
-
enabled: true,
|
|
852
|
-
options: [
|
|
853
|
-
'too_expensive',
|
|
854
|
-
'missing_features',
|
|
855
|
-
'switched_service',
|
|
856
|
-
'unused',
|
|
857
|
-
'other',
|
|
858
|
-
],
|
|
859
|
-
},
|
|
860
|
-
},
|
|
861
|
-
subscription_update: {
|
|
862
|
-
enabled: true,
|
|
863
|
-
default_allowed_updates: ['price', 'quantity', 'promotion_code'],
|
|
864
|
-
proration_behavior: 'always_invoice',
|
|
865
|
-
products: [
|
|
866
|
-
{
|
|
867
|
-
product: 'prod_basic',
|
|
868
|
-
prices: ['price_basic_monthly', 'price_basic_yearly'],
|
|
869
|
-
},
|
|
870
|
-
{
|
|
871
|
-
product: 'prod_premium',
|
|
872
|
-
prices: ['price_premium_monthly', 'price_premium_yearly'],
|
|
873
|
-
},
|
|
874
|
-
],
|
|
875
|
-
},
|
|
876
|
-
},
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
// Use custom configuration
|
|
880
|
-
const portalSession = await stripe.billingPortal.sessions.create({
|
|
881
|
-
customer: 'cus_123456789',
|
|
882
|
-
return_url: 'https://example.com/account',
|
|
883
|
-
configuration: configuration.id,
|
|
884
|
-
});
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
### Charges (Legacy - Use Payment Intents Instead)
|
|
888
|
-
|
|
889
|
-
**Note:** Charges API is legacy. Use Payment Intents for new integrations.
|
|
890
|
-
|
|
891
|
-
```javascript
|
|
892
|
-
// Only use if you have a specific reason not to use Payment Intents
|
|
893
|
-
const charge = await stripe.charges.create({
|
|
894
|
-
amount: 2000,
|
|
895
|
-
currency: 'usd',
|
|
896
|
-
source: 'tok_visa',
|
|
897
|
-
description: 'Legacy charge',
|
|
898
|
-
});
|
|
899
|
-
```
|
|
900
|
-
|
|
901
|
-
## 5. Best Practices
|
|
902
|
-
|
|
903
|
-
### Idempotency
|
|
904
|
-
|
|
905
|
-
Stripe supports idempotency to safely retry requests without performing the same operation twice.
|
|
906
|
-
|
|
907
|
-
```javascript
|
|
908
|
-
// Generate unique idempotency key
|
|
909
|
-
const { v4: uuidv4 } = require('uuid');
|
|
910
|
-
|
|
911
|
-
const paymentIntent = await stripe.paymentIntents.create(
|
|
912
|
-
{
|
|
913
|
-
amount: 1000,
|
|
914
|
-
currency: 'usd',
|
|
915
|
-
},
|
|
916
|
-
{
|
|
917
|
-
idempotencyKey: uuidv4(), // Unique key per request
|
|
918
|
-
}
|
|
919
|
-
);
|
|
920
|
-
```
|
|
921
|
-
|
|
922
|
-
**Automatic Retries with Idempotency:**
|
|
923
|
-
|
|
924
|
-
```javascript
|
|
925
|
-
async function createPaymentWithRetry(paymentData, maxRetries = 3) {
|
|
926
|
-
const idempotencyKey = uuidv4();
|
|
927
|
-
|
|
928
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
929
|
-
try {
|
|
930
|
-
const paymentIntent = await stripe.paymentIntents.create(
|
|
931
|
-
paymentData,
|
|
932
|
-
{ idempotencyKey }
|
|
933
|
-
);
|
|
934
|
-
return paymentIntent;
|
|
935
|
-
} catch (err) {
|
|
936
|
-
if (attempt === maxRetries) throw err;
|
|
937
|
-
|
|
938
|
-
// Only retry on network errors or rate limits
|
|
939
|
-
if (err.type === 'StripeConnectionError' || err.statusCode === 429) {
|
|
940
|
-
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
|
|
941
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
942
|
-
} else {
|
|
943
|
-
throw err; // Don't retry on other errors
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
### Error Handling
|
|
951
|
-
|
|
952
|
-
Stripe errors include specific types for targeted handling.
|
|
953
|
-
|
|
954
|
-
```javascript
|
|
955
|
-
async function handleStripeOperation() {
|
|
956
|
-
try {
|
|
957
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
958
|
-
amount: 1000,
|
|
959
|
-
currency: 'usd',
|
|
960
|
-
});
|
|
961
|
-
return paymentIntent;
|
|
962
|
-
} catch (err) {
|
|
963
|
-
switch (err.type) {
|
|
964
|
-
case 'StripeCardError':
|
|
965
|
-
// Card was declined
|
|
966
|
-
console.error('Card declined:', err.message);
|
|
967
|
-
return { error: 'Your card was declined.' };
|
|
968
|
-
|
|
969
|
-
case 'StripeRateLimitError':
|
|
970
|
-
// Too many requests
|
|
971
|
-
console.error('Rate limit hit');
|
|
972
|
-
return { error: 'Too many requests. Please try again later.' };
|
|
973
|
-
|
|
974
|
-
case 'StripeInvalidRequestError':
|
|
975
|
-
// Invalid parameters
|
|
976
|
-
console.error('Invalid request:', err.message);
|
|
977
|
-
return { error: 'Invalid payment information.' };
|
|
978
|
-
|
|
979
|
-
case 'StripeAPIError':
|
|
980
|
-
// Stripe API error
|
|
981
|
-
console.error('API error:', err.message);
|
|
982
|
-
return { error: 'Payment processing error. Please try again.' };
|
|
983
|
-
|
|
984
|
-
case 'StripeConnectionError':
|
|
985
|
-
// Network error
|
|
986
|
-
console.error('Network error:', err.message);
|
|
987
|
-
return { error: 'Network error. Please check your connection.' };
|
|
988
|
-
|
|
989
|
-
case 'StripeAuthenticationError':
|
|
990
|
-
// Authentication error
|
|
991
|
-
console.error('Authentication failed:', err.message);
|
|
992
|
-
return { error: 'Payment system error.' };
|
|
993
|
-
|
|
994
|
-
default:
|
|
995
|
-
console.error('Unknown error:', err);
|
|
996
|
-
return { error: 'An unexpected error occurred.' };
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
**Comprehensive Error Handler:**
|
|
1003
|
-
|
|
1004
|
-
```javascript
|
|
1005
|
-
class StripeErrorHandler {
|
|
1006
|
-
static async execute(operation, options = {}) {
|
|
1007
|
-
const { maxRetries = 3, retryDelay = 1000 } = options;
|
|
1008
|
-
|
|
1009
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1010
|
-
try {
|
|
1011
|
-
return await operation();
|
|
1012
|
-
} catch (err) {
|
|
1013
|
-
const shouldRetry = this.shouldRetry(err, attempt, maxRetries);
|
|
1014
|
-
|
|
1015
|
-
if (!shouldRetry) {
|
|
1016
|
-
throw this.formatError(err);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
const delay = retryDelay * Math.pow(2, attempt - 1);
|
|
1020
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
static shouldRetry(err, attempt, maxRetries) {
|
|
1026
|
-
if (attempt >= maxRetries) return false;
|
|
1027
|
-
|
|
1028
|
-
return (
|
|
1029
|
-
err.type === 'StripeConnectionError' ||
|
|
1030
|
-
err.type === 'StripeAPIError' ||
|
|
1031
|
-
err.statusCode === 429 ||
|
|
1032
|
-
err.statusCode === 503
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
static formatError(err) {
|
|
1037
|
-
return {
|
|
1038
|
-
type: err.type,
|
|
1039
|
-
message: err.message,
|
|
1040
|
-
statusCode: err.statusCode,
|
|
1041
|
-
code: err.code,
|
|
1042
|
-
decline_code: err.decline_code,
|
|
1043
|
-
param: err.param,
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// Usage
|
|
1049
|
-
const paymentIntent = await StripeErrorHandler.execute(
|
|
1050
|
-
() => stripe.paymentIntents.create({ amount: 1000, currency: 'usd' }),
|
|
1051
|
-
{ maxRetries: 3, retryDelay: 1000 }
|
|
1052
|
-
);
|
|
1053
|
-
```
|
|
1054
|
-
|
|
1055
|
-
### Rate Limit Handling
|
|
1056
|
-
|
|
1057
|
-
Stripe enforces rate limits to ensure API stability.
|
|
1058
|
-
|
|
1059
|
-
```javascript
|
|
1060
|
-
async function rateLimitedRequest(requestFn, maxRetries = 5) {
|
|
1061
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
1062
|
-
try {
|
|
1063
|
-
return await requestFn();
|
|
1064
|
-
} catch (err) {
|
|
1065
|
-
if (err.statusCode === 429) {
|
|
1066
|
-
// Rate limited - use exponential backoff
|
|
1067
|
-
const delay = Math.min(1000 * Math.pow(2, i), 32000);
|
|
1068
|
-
console.log(`Rate limited. Retrying in ${delay}ms...`);
|
|
1069
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1070
|
-
} else {
|
|
1071
|
-
throw err;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
throw new Error('Max retries exceeded for rate limit');
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// Usage
|
|
1079
|
-
const customer = await rateLimitedRequest(() =>
|
|
1080
|
-
stripe.customers.create({ email: 'customer@example.com' })
|
|
1081
|
-
);
|
|
1082
|
-
```
|
|
1083
|
-
|
|
1084
|
-
**Advanced Rate Limit Strategy:**
|
|
1085
|
-
|
|
1086
|
-
```javascript
|
|
1087
|
-
class RateLimiter {
|
|
1088
|
-
constructor(requestsPerSecond = 100) {
|
|
1089
|
-
this.requestsPerSecond = requestsPerSecond;
|
|
1090
|
-
this.queue = [];
|
|
1091
|
-
this.processing = false;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
async execute(fn) {
|
|
1095
|
-
return new Promise((resolve, reject) => {
|
|
1096
|
-
this.queue.push({ fn, resolve, reject });
|
|
1097
|
-
this.processQueue();
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
async processQueue() {
|
|
1102
|
-
if (this.processing || this.queue.length === 0) return;
|
|
1103
|
-
|
|
1104
|
-
this.processing = true;
|
|
1105
|
-
const { fn, resolve, reject } = this.queue.shift();
|
|
1106
|
-
|
|
1107
|
-
try {
|
|
1108
|
-
const result = await fn();
|
|
1109
|
-
resolve(result);
|
|
1110
|
-
} catch (err) {
|
|
1111
|
-
if (err.statusCode === 429) {
|
|
1112
|
-
// Re-queue with higher priority
|
|
1113
|
-
this.queue.unshift({ fn, resolve, reject });
|
|
1114
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
1115
|
-
} else {
|
|
1116
|
-
reject(err);
|
|
1117
|
-
}
|
|
1118
|
-
} finally {
|
|
1119
|
-
this.processing = false;
|
|
1120
|
-
const delay = 1000 / this.requestsPerSecond;
|
|
1121
|
-
setTimeout(() => this.processQueue(), delay);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
const limiter = new RateLimiter(100); // 100 requests per second
|
|
1127
|
-
|
|
1128
|
-
// Usage
|
|
1129
|
-
const customer = await limiter.execute(() =>
|
|
1130
|
-
stripe.customers.create({ email: 'customer@example.com' })
|
|
1131
|
-
);
|
|
1132
|
-
```
|
|
1133
|
-
|
|
1134
|
-
### Pagination
|
|
1135
|
-
|
|
1136
|
-
Many Stripe API methods return paginated results.
|
|
1137
|
-
|
|
1138
|
-
```javascript
|
|
1139
|
-
// Manual pagination
|
|
1140
|
-
const customers = await stripe.customers.list({
|
|
1141
|
-
limit: 100,
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
// Iterate through pages
|
|
1145
|
-
let hasMore = customers.has_more;
|
|
1146
|
-
let startingAfter = customers.data[customers.data.length - 1].id;
|
|
1147
|
-
|
|
1148
|
-
while (hasMore) {
|
|
1149
|
-
const nextPage = await stripe.customers.list({
|
|
1150
|
-
limit: 100,
|
|
1151
|
-
starting_after: startingAfter,
|
|
1152
|
-
});
|
|
1153
|
-
|
|
1154
|
-
customers.data.push(...nextPage.data);
|
|
1155
|
-
hasMore = nextPage.has_more;
|
|
1156
|
-
|
|
1157
|
-
if (hasMore) {
|
|
1158
|
-
startingAfter = nextPage.data[nextPage.data.length - 1].id;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
```
|
|
1162
|
-
|
|
1163
|
-
**Auto-Pagination:**
|
|
1164
|
-
|
|
1165
|
-
```javascript
|
|
1166
|
-
// Stripe SDK provides auto-pagination
|
|
1167
|
-
const allCustomers = [];
|
|
1168
|
-
|
|
1169
|
-
for await (const customer of stripe.customers.list({ limit: 100 })) {
|
|
1170
|
-
allCustomers.push(customer);
|
|
1171
|
-
|
|
1172
|
-
// Process customer
|
|
1173
|
-
console.log(customer.email);
|
|
1174
|
-
|
|
1175
|
-
// Optional: Stop after certain condition
|
|
1176
|
-
if (allCustomers.length >= 500) break;
|
|
1177
|
-
}
|
|
1178
|
-
```
|
|
1179
|
-
|
|
1180
|
-
### Testing
|
|
1181
|
-
|
|
1182
|
-
Stripe provides test mode with test API keys and test card numbers.
|
|
1183
|
-
|
|
1184
|
-
**Test Card Numbers:**
|
|
1185
|
-
|
|
1186
|
-
```javascript
|
|
1187
|
-
// Use these in test mode
|
|
1188
|
-
const testCards = {
|
|
1189
|
-
visa: '4242424242424242',
|
|
1190
|
-
visaDebit: '4000056655665556',
|
|
1191
|
-
mastercard: '5555555555554444',
|
|
1192
|
-
amex: '378282246310005',
|
|
1193
|
-
discover: '6011111111111117',
|
|
1194
|
-
dinersClub: '3056930009020004',
|
|
1195
|
-
jcb: '3566002020360505',
|
|
1196
|
-
unionPay: '6200000000000005',
|
|
1197
|
-
|
|
1198
|
-
// Cards that trigger specific scenarios
|
|
1199
|
-
declined: '4000000000000002',
|
|
1200
|
-
insufficientFunds: '4000000000009995',
|
|
1201
|
-
lostCard: '4000000000009987',
|
|
1202
|
-
stolenCard: '4000000000009979',
|
|
1203
|
-
expiredCard: '4000000000000069',
|
|
1204
|
-
incorrectCvc: '4000000000000127',
|
|
1205
|
-
processingError: '4000000000000119',
|
|
1206
|
-
|
|
1207
|
-
// 3D Secure authentication required
|
|
1208
|
-
authRequired: '4000002500003155',
|
|
1209
|
-
authRequiredDeclined: '4000008400001629',
|
|
1210
|
-
};
|
|
1211
|
-
```
|
|
1212
|
-
|
|
1213
|
-
**Test Mode Detection:**
|
|
1214
|
-
|
|
1215
|
-
```javascript
|
|
1216
|
-
const isTestMode = process.env.STRIPE_SECRET_KEY.startsWith('sk_test_');
|
|
1217
|
-
|
|
1218
|
-
if (isTestMode) {
|
|
1219
|
-
console.log('Running in TEST mode');
|
|
1220
|
-
} else {
|
|
1221
|
-
console.log('Running in LIVE mode');
|
|
1222
|
-
}
|
|
1223
|
-
```
|
|
1224
|
-
|
|
1225
|
-
**Mock Stripe for Unit Tests:**
|
|
1226
|
-
|
|
1227
|
-
```javascript
|
|
1228
|
-
// Using jest
|
|
1229
|
-
jest.mock('stripe', () => {
|
|
1230
|
-
return jest.fn().mockImplementation(() => ({
|
|
1231
|
-
paymentIntents: {
|
|
1232
|
-
create: jest.fn().mockResolvedValue({
|
|
1233
|
-
id: 'pi_test_123',
|
|
1234
|
-
client_secret: 'pi_test_123_secret',
|
|
1235
|
-
status: 'requires_payment_method',
|
|
1236
|
-
}),
|
|
1237
|
-
retrieve: jest.fn().mockResolvedValue({
|
|
1238
|
-
id: 'pi_test_123',
|
|
1239
|
-
status: 'succeeded',
|
|
1240
|
-
}),
|
|
1241
|
-
},
|
|
1242
|
-
customers: {
|
|
1243
|
-
create: jest.fn().mockResolvedValue({
|
|
1244
|
-
id: 'cus_test_123',
|
|
1245
|
-
email: 'test@example.com',
|
|
1246
|
-
}),
|
|
1247
|
-
},
|
|
1248
|
-
}));
|
|
1249
|
-
});
|
|
1250
|
-
|
|
1251
|
-
// Test
|
|
1252
|
-
const stripe = require('stripe')();
|
|
1253
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
1254
|
-
amount: 1000,
|
|
1255
|
-
currency: 'usd',
|
|
1256
|
-
});
|
|
1257
|
-
expect(paymentIntent.id).toBe('pi_test_123');
|
|
1258
|
-
```
|
|
1259
|
-
|
|
1260
|
-
### Security Best Practices
|
|
1261
|
-
|
|
1262
|
-
**1. Never Expose Secret Keys:**
|
|
1263
|
-
|
|
1264
|
-
```javascript
|
|
1265
|
-
// NEVER do this
|
|
1266
|
-
const stripe = require('stripe')('sk_live_actual_secret_key_hardcoded');
|
|
1267
|
-
|
|
1268
|
-
// ALWAYS do this
|
|
1269
|
-
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
1270
|
-
```
|
|
1271
|
-
|
|
1272
|
-
**2. Validate Webhook Signatures:**
|
|
1273
|
-
|
|
1274
|
-
```javascript
|
|
1275
|
-
// ALWAYS verify webhook signatures
|
|
1276
|
-
try {
|
|
1277
|
-
const event = stripe.webhooks.constructEvent(
|
|
1278
|
-
req.body,
|
|
1279
|
-
req.headers['stripe-signature'],
|
|
1280
|
-
process.env.STRIPE_WEBHOOK_SECRET
|
|
1281
|
-
);
|
|
1282
|
-
} catch (err) {
|
|
1283
|
-
// Signature verification failed - reject the request
|
|
1284
|
-
return res.status(400).send(`Webhook Error: ${err.message}`);
|
|
1285
|
-
}
|
|
1286
|
-
```
|
|
1287
|
-
|
|
1288
|
-
**3. Use HTTPS in Production:**
|
|
1289
|
-
|
|
1290
|
-
```javascript
|
|
1291
|
-
// Ensure your server uses HTTPS
|
|
1292
|
-
if (process.env.NODE_ENV === 'production' && !req.secure) {
|
|
1293
|
-
return res.redirect('https://' + req.headers.host + req.url);
|
|
1294
|
-
}
|
|
1295
|
-
```
|
|
1296
|
-
|
|
1297
|
-
**4. Server-Side Validation:**
|
|
1298
|
-
|
|
1299
|
-
```javascript
|
|
1300
|
-
// NEVER trust client-side data
|
|
1301
|
-
app.post('/create-payment-intent', async (req, res) => {
|
|
1302
|
-
// Don't use amount from client
|
|
1303
|
-
// const { amount } = req.body; // UNSAFE
|
|
1304
|
-
|
|
1305
|
-
// Calculate amount server-side based on cart/order
|
|
1306
|
-
const order = await getOrderFromDatabase(req.body.orderId);
|
|
1307
|
-
const amount = calculateOrderTotal(order);
|
|
1308
|
-
|
|
1309
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
1310
|
-
amount,
|
|
1311
|
-
currency: 'usd',
|
|
1312
|
-
});
|
|
1313
|
-
|
|
1314
|
-
res.json({ clientSecret: paymentIntent.client_secret });
|
|
1315
|
-
});
|
|
1316
|
-
```
|
|
1317
|
-
|
|
1318
|
-
**5. PCI Compliance:**
|
|
1319
|
-
|
|
1320
|
-
```javascript
|
|
1321
|
-
// NEVER handle raw card data on your server
|
|
1322
|
-
// ALWAYS use Stripe.js or Elements to collect card information
|
|
1323
|
-
|
|
1324
|
-
// DON'T do this:
|
|
1325
|
-
app.post('/charge', async (req, res) => {
|
|
1326
|
-
const { cardNumber, cvc, expMonth, expYear } = req.body; // NEVER
|
|
1327
|
-
// ...
|
|
1328
|
-
});
|
|
1329
|
-
|
|
1330
|
-
// DO this instead:
|
|
1331
|
-
// Use Stripe.js on client to create PaymentMethod
|
|
1332
|
-
// Only send PaymentMethod ID to server
|
|
1333
|
-
app.post('/charge', async (req, res) => {
|
|
1334
|
-
const { paymentMethodId } = req.body; // Safe
|
|
1335
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
1336
|
-
amount: 1000,
|
|
1337
|
-
currency: 'usd',
|
|
1338
|
-
payment_method: paymentMethodId,
|
|
1339
|
-
});
|
|
1340
|
-
});
|
|
1341
|
-
```
|
|
1342
|
-
|
|
1343
|
-
### Metadata Best Practices
|
|
1344
|
-
|
|
1345
|
-
Metadata helps you store additional information on Stripe objects.
|
|
1346
|
-
|
|
1347
|
-
```javascript
|
|
1348
|
-
// Use metadata to link Stripe objects to your system
|
|
1349
|
-
const customer = await stripe.customers.create({
|
|
1350
|
-
email: 'customer@example.com',
|
|
1351
|
-
metadata: {
|
|
1352
|
-
user_id: '12345',
|
|
1353
|
-
account_type: 'premium',
|
|
1354
|
-
signup_date: new Date().toISOString(),
|
|
1355
|
-
},
|
|
1356
|
-
});
|
|
1357
|
-
|
|
1358
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
1359
|
-
amount: 2000,
|
|
1360
|
-
currency: 'usd',
|
|
1361
|
-
metadata: {
|
|
1362
|
-
order_id: 'order_789',
|
|
1363
|
-
customer_id: '12345',
|
|
1364
|
-
product_ids: 'prod_1,prod_2,prod_3',
|
|
1365
|
-
campaign: 'summer_sale',
|
|
1366
|
-
},
|
|
1367
|
-
});
|
|
1368
|
-
|
|
1369
|
-
// Search using metadata
|
|
1370
|
-
const orders = await stripe.paymentIntents.search({
|
|
1371
|
-
query: 'metadata["order_id"]:"order_789"',
|
|
1372
|
-
});
|
|
1373
|
-
```
|
|
1374
|
-
|
|
1375
|
-
### Logging and Monitoring
|
|
1376
|
-
|
|
1377
|
-
```javascript
|
|
1378
|
-
// Create a logging wrapper
|
|
1379
|
-
class StripeClient {
|
|
1380
|
-
constructor(apiKey) {
|
|
1381
|
-
this.stripe = require('stripe')(apiKey);
|
|
1382
|
-
this.logger = console; // Replace with your logger
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
async createPaymentIntent(params) {
|
|
1386
|
-
const startTime = Date.now();
|
|
1387
|
-
|
|
1388
|
-
try {
|
|
1389
|
-
this.logger.info('Creating payment intent', { params });
|
|
1390
|
-
|
|
1391
|
-
const paymentIntent = await this.stripe.paymentIntents.create(params);
|
|
1392
|
-
|
|
1393
|
-
const duration = Date.now() - startTime;
|
|
1394
|
-
this.logger.info('Payment intent created', {
|
|
1395
|
-
id: paymentIntent.id,
|
|
1396
|
-
amount: paymentIntent.amount,
|
|
1397
|
-
duration,
|
|
1398
|
-
});
|
|
1399
|
-
|
|
1400
|
-
return paymentIntent;
|
|
1401
|
-
} catch (err) {
|
|
1402
|
-
const duration = Date.now() - startTime;
|
|
1403
|
-
this.logger.error('Payment intent creation failed', {
|
|
1404
|
-
error: err.message,
|
|
1405
|
-
type: err.type,
|
|
1406
|
-
code: err.code,
|
|
1407
|
-
duration,
|
|
1408
|
-
});
|
|
1409
|
-
|
|
1410
|
-
throw err;
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
const stripeClient = new StripeClient(process.env.STRIPE_SECRET_KEY);
|
|
1416
|
-
```
|
|
1417
|
-
|
|
1418
|
-
### Production Deployment Checklist
|
|
1419
|
-
|
|
1420
|
-
**Environment Configuration:**
|
|
1421
|
-
|
|
1422
|
-
```javascript
|
|
1423
|
-
// config.js
|
|
1424
|
-
module.exports = {
|
|
1425
|
-
stripe: {
|
|
1426
|
-
secretKey: process.env.STRIPE_SECRET_KEY,
|
|
1427
|
-
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
|
|
1428
|
-
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
1429
|
-
apiVersion: '2025-02-24',
|
|
1430
|
-
},
|
|
1431
|
-
|
|
1432
|
-
// Validate required environment variables
|
|
1433
|
-
validate: () => {
|
|
1434
|
-
const required = [
|
|
1435
|
-
'STRIPE_SECRET_KEY',
|
|
1436
|
-
'STRIPE_PUBLISHABLE_KEY',
|
|
1437
|
-
'STRIPE_WEBHOOK_SECRET',
|
|
1438
|
-
];
|
|
1439
|
-
|
|
1440
|
-
for (const key of required) {
|
|
1441
|
-
if (!process.env[key]) {
|
|
1442
|
-
throw new Error(`Missing required environment variable: ${key}`);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
// Ensure production uses live keys
|
|
1447
|
-
if (process.env.NODE_ENV === 'production') {
|
|
1448
|
-
if (!process.env.STRIPE_SECRET_KEY.startsWith('sk_live_')) {
|
|
1449
|
-
throw new Error('Production must use live Stripe keys');
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
},
|
|
1453
|
-
};
|
|
1454
|
-
```
|
|
1455
|
-
|
|
1456
|
-
**Webhook Endpoint Security:**
|
|
1457
|
-
|
|
1458
|
-
```javascript
|
|
1459
|
-
// Dedicated webhook handler with security
|
|
1460
|
-
app.post(
|
|
1461
|
-
'/webhook',
|
|
1462
|
-
express.raw({ type: 'application/json' }),
|
|
1463
|
-
rateLimitMiddleware({ max: 100, windowMs: 60000 }), // Rate limiting
|
|
1464
|
-
async (req, res) => {
|
|
1465
|
-
const sig = req.headers['stripe-signature'];
|
|
1466
|
-
|
|
1467
|
-
let event;
|
|
1468
|
-
|
|
1469
|
-
try {
|
|
1470
|
-
// Verify signature
|
|
1471
|
-
event = stripe.webhooks.constructEvent(
|
|
1472
|
-
req.body,
|
|
1473
|
-
sig,
|
|
1474
|
-
process.env.STRIPE_WEBHOOK_SECRET
|
|
1475
|
-
);
|
|
1476
|
-
} catch (err) {
|
|
1477
|
-
logger.error('Webhook signature verification failed', {
|
|
1478
|
-
error: err.message,
|
|
1479
|
-
ip: req.ip,
|
|
1480
|
-
});
|
|
1481
|
-
return res.status(400).send(`Webhook Error: ${err.message}`);
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
// Log webhook received
|
|
1485
|
-
logger.info('Webhook received', {
|
|
1486
|
-
type: event.type,
|
|
1487
|
-
id: event.id,
|
|
1488
|
-
});
|
|
1489
|
-
|
|
1490
|
-
// Process asynchronously
|
|
1491
|
-
processWebhook(event).catch(err => {
|
|
1492
|
-
logger.error('Webhook processing failed', {
|
|
1493
|
-
type: event.type,
|
|
1494
|
-
id: event.id,
|
|
1495
|
-
error: err.message,
|
|
1496
|
-
});
|
|
1497
|
-
});
|
|
1498
|
-
|
|
1499
|
-
// Respond immediately
|
|
1500
|
-
res.json({ received: true });
|
|
1501
|
-
}
|
|
1502
|
-
);
|
|
1503
|
-
```
|
|
1504
|
-
|
|
1505
|
-
### Performance Optimization
|
|
1506
|
-
|
|
1507
|
-
**Parallel Requests:**
|
|
1508
|
-
|
|
1509
|
-
```javascript
|
|
1510
|
-
// Execute multiple independent requests in parallel
|
|
1511
|
-
const [customer, product, price] = await Promise.all([
|
|
1512
|
-
stripe.customers.retrieve('cus_123'),
|
|
1513
|
-
stripe.products.retrieve('prod_123'),
|
|
1514
|
-
stripe.prices.retrieve('price_123'),
|
|
1515
|
-
]);
|
|
1516
|
-
```
|
|
1517
|
-
|
|
1518
|
-
**Expand Related Objects:**
|
|
1519
|
-
|
|
1520
|
-
```javascript
|
|
1521
|
-
// Instead of multiple requests
|
|
1522
|
-
const subscription = await stripe.subscriptions.retrieve('sub_123');
|
|
1523
|
-
const customer = await stripe.customers.retrieve(subscription.customer);
|
|
1524
|
-
const invoice = await stripe.invoices.retrieve(subscription.latest_invoice);
|
|
1525
|
-
|
|
1526
|
-
// Do this - single request with expand
|
|
1527
|
-
const subscription = await stripe.subscriptions.retrieve('sub_123', {
|
|
1528
|
-
expand: [
|
|
1529
|
-
'customer',
|
|
1530
|
-
'latest_invoice',
|
|
1531
|
-
'latest_invoice.payment_intent',
|
|
1532
|
-
'default_payment_method',
|
|
1533
|
-
],
|
|
1534
|
-
});
|
|
1535
|
-
|
|
1536
|
-
// Access expanded objects
|
|
1537
|
-
console.log(subscription.customer.email);
|
|
1538
|
-
console.log(subscription.latest_invoice.amount_paid);
|
|
1539
|
-
```
|
|
1540
|
-
|
|
1541
|
-
**Batch Operations:**
|
|
1542
|
-
|
|
1543
|
-
```javascript
|
|
1544
|
-
// Create multiple objects efficiently
|
|
1545
|
-
async function createMultipleCustomers(customerData) {
|
|
1546
|
-
const BATCH_SIZE = 10;
|
|
1547
|
-
const results = [];
|
|
1548
|
-
|
|
1549
|
-
for (let i = 0; i < customerData.length; i += BATCH_SIZE) {
|
|
1550
|
-
const batch = customerData.slice(i, i + BATCH_SIZE);
|
|
1551
|
-
const batchPromises = batch.map(data =>
|
|
1552
|
-
stripe.customers.create(data)
|
|
1553
|
-
);
|
|
1554
|
-
|
|
1555
|
-
const batchResults = await Promise.allSettled(batchPromises);
|
|
1556
|
-
results.push(...batchResults);
|
|
1557
|
-
|
|
1558
|
-
// Small delay between batches to avoid rate limits
|
|
1559
|
-
if (i + BATCH_SIZE < customerData.length) {
|
|
1560
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
return results;
|
|
1565
|
-
}
|
|
1566
|
-
```
|
|
1567
|
-
|
|
1568
|
-
### TypeScript Usage
|
|
1569
|
-
|
|
1570
|
-
**Complete Type Safety:**
|
|
1571
|
-
|
|
1572
|
-
```typescript
|
|
1573
|
-
import Stripe from 'stripe';
|
|
1574
|
-
|
|
1575
|
-
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
1576
|
-
apiVersion: '2025-02-24',
|
|
1577
|
-
});
|
|
1578
|
-
|
|
1579
|
-
// Full type inference
|
|
1580
|
-
async function createSubscription(
|
|
1581
|
-
customerId: string,
|
|
1582
|
-
priceId: string
|
|
1583
|
-
): Promise<Stripe.Subscription> {
|
|
1584
|
-
const subscription = await stripe.subscriptions.create({
|
|
1585
|
-
customer: customerId,
|
|
1586
|
-
items: [{ price: priceId }],
|
|
1587
|
-
payment_behavior: 'default_incomplete',
|
|
1588
|
-
expand: ['latest_invoice.payment_intent'],
|
|
1589
|
-
});
|
|
1590
|
-
|
|
1591
|
-
return subscription;
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
// Type-safe event handling
|
|
1595
|
-
async function handleWebhook(
|
|
1596
|
-
body: string | Buffer,
|
|
1597
|
-
signature: string
|
|
1598
|
-
): Promise<void> {
|
|
1599
|
-
const event = stripe.webhooks.constructEvent(
|
|
1600
|
-
body,
|
|
1601
|
-
signature,
|
|
1602
|
-
process.env.STRIPE_WEBHOOK_SECRET!
|
|
1603
|
-
);
|
|
1604
|
-
|
|
1605
|
-
switch (event.type) {
|
|
1606
|
-
case 'payment_intent.succeeded': {
|
|
1607
|
-
const paymentIntent = event.data.object as Stripe.PaymentIntent;
|
|
1608
|
-
console.log(`PaymentIntent ${paymentIntent.id} succeeded`);
|
|
1609
|
-
break;
|
|
1610
|
-
}
|
|
1611
|
-
case 'customer.subscription.updated': {
|
|
1612
|
-
const subscription = event.data.object as Stripe.Subscription;
|
|
1613
|
-
console.log(`Subscription ${subscription.id} updated`);
|
|
1614
|
-
break;
|
|
1615
|
-
}
|
|
1616
|
-
default:
|
|
1617
|
-
console.log(`Unhandled event type: ${event.type}`);
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
// Custom types for your domain
|
|
1622
|
-
interface CreatePaymentParams {
|
|
1623
|
-
amount: number;
|
|
1624
|
-
currency: string;
|
|
1625
|
-
customerId?: string;
|
|
1626
|
-
metadata?: Record<string, string>;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
async function createPayment(
|
|
1630
|
-
params: CreatePaymentParams
|
|
1631
|
-
): Promise<Stripe.PaymentIntent> {
|
|
1632
|
-
return await stripe.paymentIntents.create({
|
|
1633
|
-
amount: params.amount,
|
|
1634
|
-
currency: params.currency,
|
|
1635
|
-
customer: params.customerId,
|
|
1636
|
-
metadata: params.metadata,
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1639
|
-
```
|
|
1640
|
-
|
|
1641
|
-
## 6. Production Checklist
|
|
1642
|
-
|
|
1643
|
-
### Pre-Launch Verification
|
|
1644
|
-
|
|
1645
|
-
- [ ] Switched from test keys to live keys
|
|
1646
|
-
- [ ] Environment variables properly configured in production
|
|
1647
|
-
- [ ] Webhook endpoints registered and verified in Stripe Dashboard
|
|
1648
|
-
- [ ] Webhook signature verification implemented
|
|
1649
|
-
- [ ] HTTPS enabled on all endpoints
|
|
1650
|
-
- [ ] Error handling and logging configured
|
|
1651
|
-
- [ ] Rate limiting implemented
|
|
1652
|
-
- [ ] Idempotency keys used for critical operations
|
|
1653
|
-
- [ ] Payment confirmation flow tested end-to-end
|
|
1654
|
-
- [ ] Refund process tested
|
|
1655
|
-
- [ ] Subscription lifecycle tested (create, update, cancel)
|
|
1656
|
-
- [ ] Tax calculation configured (if applicable)
|
|
1657
|
-
- [ ] Email receipts enabled
|
|
1658
|
-
- [ ] Monitoring and alerting set up
|
|
1659
|
-
- [ ] PCI compliance requirements met
|
|
1660
|
-
- [ ] Terms of service and privacy policy updated
|
|
1661
|
-
- [ ] Customer support process for payment issues established
|
|
1662
|
-
|
|
1663
|
-
### Monitoring
|
|
1664
|
-
|
|
1665
|
-
```javascript
|
|
1666
|
-
// Monitor key metrics
|
|
1667
|
-
const metrics = {
|
|
1668
|
-
paymentIntentsCreated: 0,
|
|
1669
|
-
paymentIntentsSucceeded: 0,
|
|
1670
|
-
paymentIntentsFailed: 0,
|
|
1671
|
-
webhooksProcessed: 0,
|
|
1672
|
-
webhooksFailed: 0,
|
|
1673
|
-
apiErrors: 0,
|
|
1674
|
-
};
|
|
1675
|
-
|
|
1676
|
-
// Increment metrics in your code
|
|
1677
|
-
app.post('/create-payment', async (req, res) => {
|
|
1678
|
-
metrics.paymentIntentsCreated++;
|
|
1679
|
-
|
|
1680
|
-
try {
|
|
1681
|
-
const paymentIntent = await stripe.paymentIntents.create({
|
|
1682
|
-
amount: req.body.amount,
|
|
1683
|
-
currency: 'usd',
|
|
1684
|
-
});
|
|
1685
|
-
|
|
1686
|
-
return res.json(paymentIntent);
|
|
1687
|
-
} catch (err) {
|
|
1688
|
-
metrics.apiErrors++;
|
|
1689
|
-
logger.error('Payment creation failed', { error: err });
|
|
1690
|
-
return res.status(500).json({ error: 'Payment failed' });
|
|
1691
|
-
}
|
|
1692
|
-
});
|
|
1693
|
-
|
|
1694
|
-
// Expose metrics endpoint
|
|
1695
|
-
app.get('/metrics', (req, res) => {
|
|
1696
|
-
res.json(metrics);
|
|
1697
|
-
});
|
|
1698
|
-
```
|
|
1699
|
-
|
|
1700
|
-
### Disaster Recovery
|
|
1701
|
-
|
|
1702
|
-
```javascript
|
|
1703
|
-
// Implement graceful degradation
|
|
1704
|
-
async function createPaymentWithFallback(params) {
|
|
1705
|
-
try {
|
|
1706
|
-
return await stripe.paymentIntents.create(params);
|
|
1707
|
-
} catch (err) {
|
|
1708
|
-
// Log error
|
|
1709
|
-
logger.error('Stripe API error', { error: err });
|
|
1710
|
-
|
|
1711
|
-
// If Stripe is down, queue for later processing
|
|
1712
|
-
if (err.type === 'StripeAPIError' || err.type === 'StripeConnectionError') {
|
|
1713
|
-
await queuePaymentForLater(params);
|
|
1714
|
-
return { status: 'queued', message: 'Payment will be processed shortly' };
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
throw err;
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
```
|
|
1721
|
-
|
|
1722
|
-
---
|
|
1723
|
-
|
|
1724
|
-
**Notes**
|
|
1725
|
-
|
|
1726
|
-
The Stripe API provides a comprehensive platform for payment processing, subscription management, and billing. The Node.js library (`stripe`) offers server-side functionality, while `@stripe/stripe-js` and `@stripe/react-stripe-js` provide client-side integration capabilities. Stripe uses date-based API versioning to ensure long-term compatibility while continuously adding new features. All payment data must be transmitted securely using HTTPS, and sensitive card information should only be handled by Stripe.js on the client side to maintain PCI compliance. Webhooks are essential for production deployments as they provide reliable event notifications independent of user sessions. The platform supports multiple payment methods, currencies, and billing models, making it suitable for businesses of all sizes. Always use test mode during development and thoroughly test your integration before switching to live mode.
|