@xenterprises/fastify-xstripe 1.0.0
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/.dockerignore +62 -0
- package/.env.example +116 -0
- package/API.md +574 -0
- package/CHANGELOG.md +96 -0
- package/EXAMPLES.md +883 -0
- package/LICENSE +15 -0
- package/MIGRATION.md +374 -0
- package/QUICK_START.md +179 -0
- package/README.md +331 -0
- package/SECURITY.md +465 -0
- package/TESTING.md +357 -0
- package/index.d.ts +309 -0
- package/package.json +53 -0
- package/server/app.js +557 -0
- package/src/handlers/defaultHandlers.js +355 -0
- package/src/handlers/exampleHandlers.js +278 -0
- package/src/handlers/index.js +8 -0
- package/src/index.js +10 -0
- package/src/utils/helpers.js +220 -0
- package/src/webhooks/webhooks.js +72 -0
- package/src/xStripe.js +45 -0
- package/test/handlers.test.js +959 -0
- package/test/xStripe.integration.test.js +409 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tim Mushen
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
How to migrate from raw Stripe webhook handling to xStripe.
|
|
4
|
+
|
|
5
|
+
## Before: Raw Webhook Handling
|
|
6
|
+
|
|
7
|
+
### Typical raw implementation:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// Before - Manual webhook handling
|
|
11
|
+
import Stripe from 'stripe';
|
|
12
|
+
|
|
13
|
+
const stripe = new Stripe(process.env.STRIPE_API_KEY);
|
|
14
|
+
|
|
15
|
+
fastify.post('/webhook', async (request, reply) => {
|
|
16
|
+
const sig = request.headers['stripe-signature'];
|
|
17
|
+
let event;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
event = stripe.webhooks.constructEvent(
|
|
21
|
+
request.rawBody,
|
|
22
|
+
sig,
|
|
23
|
+
process.env.STRIPE_WEBHOOK_SECRET
|
|
24
|
+
);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return reply.code(400).send(`Webhook Error: ${err.message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle the event
|
|
30
|
+
switch (event.type) {
|
|
31
|
+
case 'customer.subscription.created':
|
|
32
|
+
const subscription = event.data.object;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await prisma.user.update({
|
|
36
|
+
where: { stripeCustomerId: subscription.customer },
|
|
37
|
+
data: {
|
|
38
|
+
subscriptionId: subscription.id,
|
|
39
|
+
subscriptionStatus: subscription.status,
|
|
40
|
+
planId: subscription.items.data[0]?.price.id,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log('Subscription created:', subscription.id);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Error processing subscription:', error);
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 'invoice.payment_failed':
|
|
51
|
+
const invoice = event.data.object;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const customer = await stripe.customers.retrieve(invoice.customer);
|
|
55
|
+
|
|
56
|
+
await sendEmail(
|
|
57
|
+
customer.email,
|
|
58
|
+
'Payment Failed',
|
|
59
|
+
'Please update your payment method'
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await prisma.user.update({
|
|
63
|
+
where: { stripeCustomerId: invoice.customer },
|
|
64
|
+
data: { failedPaymentCount: { increment: 1 } },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log('Payment failed for:', invoice.customer);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error handling payment failure:', error);
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case 'customer.subscription.deleted':
|
|
74
|
+
const deletedSub = event.data.object;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await prisma.user.update({
|
|
78
|
+
where: { stripeSubscriptionId: deletedSub.id },
|
|
79
|
+
data: {
|
|
80
|
+
subscriptionStatus: 'canceled',
|
|
81
|
+
hasAccess: false,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log('Subscription canceled:', deletedSub.id);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Error canceling subscription:', error);
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
|
|
91
|
+
// ... 20+ more event types
|
|
92
|
+
default:
|
|
93
|
+
console.log(`Unhandled event type: ${event.type}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
reply.send({ received: true });
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Problems with this approach:
|
|
101
|
+
|
|
102
|
+
1. ❌ **Hundreds of lines in one file** - Hard to read and maintain
|
|
103
|
+
2. ❌ **Difficult to test** - Everything coupled together
|
|
104
|
+
3. ❌ **Error handling scattered** - Try/catch everywhere
|
|
105
|
+
4. ❌ **No separation of concerns** - Business logic mixed with webhook handling
|
|
106
|
+
5. ❌ **Hard to debug** - Console.log statements everywhere
|
|
107
|
+
6. ❌ **Not reusable** - Can't share handler logic
|
|
108
|
+
|
|
109
|
+
## After: xStripe
|
|
110
|
+
|
|
111
|
+
### Same functionality with xStripe:
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
// After - Clean, testable handlers
|
|
115
|
+
import xStripe from '@xenterprises/fastify-xstripe';
|
|
116
|
+
|
|
117
|
+
await fastify.register(xStripe, {
|
|
118
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
119
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
120
|
+
handlers: {
|
|
121
|
+
'customer.subscription.created': async (event, fastify, stripe) => {
|
|
122
|
+
const subscription = event.data.object;
|
|
123
|
+
|
|
124
|
+
await fastify.prisma.user.update({
|
|
125
|
+
where: { stripeCustomerId: subscription.customer },
|
|
126
|
+
data: {
|
|
127
|
+
subscriptionId: subscription.id,
|
|
128
|
+
subscriptionStatus: subscription.status,
|
|
129
|
+
planId: subscription.items.data[0]?.price.id,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
fastify.log.info('Subscription created:', subscription.id);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
'invoice.payment_failed': async (event, fastify, stripe) => {
|
|
137
|
+
const invoice = event.data.object;
|
|
138
|
+
const customer = await stripe.customers.retrieve(invoice.customer);
|
|
139
|
+
|
|
140
|
+
await fastify.email.send(
|
|
141
|
+
customer.email,
|
|
142
|
+
'Payment Failed',
|
|
143
|
+
'Please update your payment method'
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
await fastify.prisma.user.update({
|
|
147
|
+
where: { stripeCustomerId: invoice.customer },
|
|
148
|
+
data: { failedPaymentCount: { increment: 1 } },
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
fastify.log.info('Payment failed for:', invoice.customer);
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
'customer.subscription.deleted': async (event, fastify, stripe) => {
|
|
155
|
+
const subscription = event.data.object;
|
|
156
|
+
|
|
157
|
+
await fastify.prisma.user.update({
|
|
158
|
+
where: { stripeSubscriptionId: subscription.id },
|
|
159
|
+
data: {
|
|
160
|
+
subscriptionStatus: 'canceled',
|
|
161
|
+
hasAccess: false,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
fastify.log.info('Subscription canceled:', subscription.id);
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Benefits:
|
|
172
|
+
|
|
173
|
+
1. ✅ **Clean, readable code** - Each handler is self-contained
|
|
174
|
+
2. ✅ **Easy to test** - Pure functions with clear inputs/outputs
|
|
175
|
+
3. ✅ **Error handling built-in** - Automatic error catching and logging
|
|
176
|
+
4. ✅ **Separation of concerns** - Business logic separate from webhook mechanics
|
|
177
|
+
5. ✅ **Better debugging** - Structured logging with Fastify
|
|
178
|
+
6. ✅ **Reusable** - Can extract handlers to separate files
|
|
179
|
+
|
|
180
|
+
## Step-by-Step Migration
|
|
181
|
+
|
|
182
|
+
### Step 1: Install xStripe
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm install @xenterprises/fastify-xstripe
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Step 2: Extract Your Handlers
|
|
189
|
+
|
|
190
|
+
Create a file for your handlers:
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
// handlers/stripeHandlers.js
|
|
194
|
+
export const stripeHandlers = {
|
|
195
|
+
'customer.subscription.created': async (event, fastify, stripe) => {
|
|
196
|
+
// Copy your logic from the switch case
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
'invoice.payment_failed': async (event, fastify, stripe) => {
|
|
200
|
+
// Copy your logic from the switch case
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// ... other handlers
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Step 3: Register xStripe
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
import xStripe from '@xenterprises/fastify-xstripe';
|
|
211
|
+
import { stripeHandlers } from './handlers/stripeHandlers.js';
|
|
212
|
+
|
|
213
|
+
await fastify.register(xStripe, {
|
|
214
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
215
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
216
|
+
handlers: stripeHandlers,
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Step 4: Remove Old Webhook Route
|
|
221
|
+
|
|
222
|
+
Delete your old `/webhook` endpoint - xStripe handles this now.
|
|
223
|
+
|
|
224
|
+
### Step 5: Update Stripe Dashboard
|
|
225
|
+
|
|
226
|
+
Update your webhook endpoint URL if it changed:
|
|
227
|
+
- Old: `https://yourdomain.com/webhook`
|
|
228
|
+
- New: `https://yourdomain.com/stripe/webhook` (or custom path)
|
|
229
|
+
|
|
230
|
+
### Step 6: Test
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Test with Stripe CLI
|
|
234
|
+
stripe listen --forward-to localhost:3000/stripe/webhook
|
|
235
|
+
stripe trigger customer.subscription.created
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Migration Checklist
|
|
239
|
+
|
|
240
|
+
- [ ] Install xStripe package
|
|
241
|
+
- [ ] Create handlers file
|
|
242
|
+
- [ ] Move business logic to handlers
|
|
243
|
+
- [ ] Register xStripe plugin
|
|
244
|
+
- [ ] Remove old webhook endpoint
|
|
245
|
+
- [ ] Update Stripe webhook URL
|
|
246
|
+
- [ ] Test all event types
|
|
247
|
+
- [ ] Update environment variables
|
|
248
|
+
- [ ] Deploy to production
|
|
249
|
+
- [ ] Monitor webhook deliveries
|
|
250
|
+
|
|
251
|
+
## Common Migration Issues
|
|
252
|
+
|
|
253
|
+
### Issue: Signature Verification Fails
|
|
254
|
+
|
|
255
|
+
**Before:**
|
|
256
|
+
```javascript
|
|
257
|
+
const event = stripe.webhooks.constructEvent(request.rawBody, sig, secret);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**After:**
|
|
261
|
+
xStripe handles this automatically. Make sure:
|
|
262
|
+
1. `webhookSecret` is set correctly
|
|
263
|
+
2. Fastify has `rawBody: true` option
|
|
264
|
+
|
|
265
|
+
### Issue: Can't Access Database
|
|
266
|
+
|
|
267
|
+
**Before:**
|
|
268
|
+
```javascript
|
|
269
|
+
const prisma = new PrismaClient();
|
|
270
|
+
await prisma.user.update(...);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**After:**
|
|
274
|
+
Use Fastify decorator:
|
|
275
|
+
```javascript
|
|
276
|
+
await fastify.prisma.user.update(...);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Issue: Custom Logging
|
|
280
|
+
|
|
281
|
+
**Before:**
|
|
282
|
+
```javascript
|
|
283
|
+
console.log('Subscription created');
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**After:**
|
|
287
|
+
Use Fastify logger:
|
|
288
|
+
```javascript
|
|
289
|
+
fastify.log.info('Subscription created');
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Issue: Accessing Stripe Client
|
|
293
|
+
|
|
294
|
+
**Before:**
|
|
295
|
+
```javascript
|
|
296
|
+
const stripe = new Stripe(apiKey);
|
|
297
|
+
const customer = await stripe.customers.retrieve(id);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**After:**
|
|
301
|
+
Stripe client is passed to handler:
|
|
302
|
+
```javascript
|
|
303
|
+
'my.event': async (event, fastify, stripe) => {
|
|
304
|
+
const customer = await stripe.customers.retrieve(id);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Gradual Migration
|
|
309
|
+
|
|
310
|
+
You can migrate gradually by handling some events with xStripe and others with your old code:
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
// Keep old endpoint for some events
|
|
314
|
+
fastify.post('/old-webhook', oldWebhookHandler);
|
|
315
|
+
|
|
316
|
+
// Use xStripe for new events
|
|
317
|
+
await fastify.register(xStripe, {
|
|
318
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
319
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
320
|
+
webhookPath: '/new-webhook',
|
|
321
|
+
handlers: {
|
|
322
|
+
'customer.subscription.created': newHandler,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Then gradually move handlers over and update Stripe webhook configuration.
|
|
328
|
+
|
|
329
|
+
## Testing After Migration
|
|
330
|
+
|
|
331
|
+
1. **Test locally with Stripe CLI**
|
|
332
|
+
```bash
|
|
333
|
+
stripe listen --forward-to localhost:3000/stripe/webhook
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
2. **Trigger test events**
|
|
337
|
+
```bash
|
|
338
|
+
stripe trigger customer.subscription.created
|
|
339
|
+
stripe trigger invoice.payment_failed
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
3. **Check logs**
|
|
343
|
+
```bash
|
|
344
|
+
# Should see structured logs from Fastify
|
|
345
|
+
{"level":30,"msg":"Received Stripe webhook: customer.subscription.created"}
|
|
346
|
+
{"level":30,"msg":"Successfully processed customer.subscription.created"}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
4. **Verify database changes**
|
|
350
|
+
- Check that database updates happen correctly
|
|
351
|
+
- Verify emails are sent
|
|
352
|
+
- Confirm business logic runs
|
|
353
|
+
|
|
354
|
+
## Rollback Plan
|
|
355
|
+
|
|
356
|
+
If issues arise, you can quickly rollback:
|
|
357
|
+
|
|
358
|
+
1. Keep old webhook handler in a separate file
|
|
359
|
+
2. Comment out xStripe registration
|
|
360
|
+
3. Re-enable old endpoint
|
|
361
|
+
4. Update Stripe webhook URL back
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// Rollback: Re-enable old handler
|
|
365
|
+
// await fastify.register(xStripe, { ... });
|
|
366
|
+
fastify.post('/webhook', oldWebhookHandler);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Need Help?
|
|
370
|
+
|
|
371
|
+
- Check the [README](./README.md) for full documentation
|
|
372
|
+
- See [TESTING.md](./TESTING.md) for testing guide
|
|
373
|
+
- Review example handlers in `src/handlers/exampleHandlers.js`
|
|
374
|
+
- Open an issue on GitHub
|
package/QUICK_START.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get up and running with xStripe in 5 minutes.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @xenterprises/fastify-xstripe stripe
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 2. Setup
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import Fastify from 'fastify';
|
|
15
|
+
import xStripe from '@xenterprises/fastify-xstripe';
|
|
16
|
+
|
|
17
|
+
const fastify = Fastify({
|
|
18
|
+
logger: true,
|
|
19
|
+
rawBody: true, // Important for webhook signature verification
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await fastify.register(xStripe, {
|
|
23
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
24
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await fastify.listen({ port: 3000 });
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 3. Environment Variables
|
|
31
|
+
|
|
32
|
+
Create `.env`:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
STRIPE_API_KEY=sk_test_...
|
|
36
|
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
37
|
+
PORT=3000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 4. Add Custom Handlers
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
await fastify.register(xStripe, {
|
|
44
|
+
apiKey: process.env.STRIPE_API_KEY,
|
|
45
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
46
|
+
handlers: {
|
|
47
|
+
// New subscription
|
|
48
|
+
'customer.subscription.created': async (event, fastify, stripe) => {
|
|
49
|
+
const sub = event.data.object;
|
|
50
|
+
console.log(`New subscriber: ${sub.customer}`);
|
|
51
|
+
|
|
52
|
+
// Your business logic here
|
|
53
|
+
// await fastify.prisma.user.update(...)
|
|
54
|
+
// await fastify.email.send(...)
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Payment failed
|
|
58
|
+
'invoice.payment_failed': async (event, fastify, stripe) => {
|
|
59
|
+
const invoice = event.data.object;
|
|
60
|
+
console.log(`Payment failed: ${invoice.customer}`);
|
|
61
|
+
|
|
62
|
+
// Send notification
|
|
63
|
+
// await fastify.email.send(...)
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 5. Test Locally
|
|
70
|
+
|
|
71
|
+
Install Stripe CLI:
|
|
72
|
+
```bash
|
|
73
|
+
brew install stripe/stripe-cli/stripe
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Forward webhooks:
|
|
77
|
+
```bash
|
|
78
|
+
stripe listen --forward-to localhost:3000/stripe/webhook
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Trigger test events:
|
|
82
|
+
```bash
|
|
83
|
+
stripe trigger customer.subscription.created
|
|
84
|
+
stripe trigger invoice.payment_failed
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 6. Deploy
|
|
88
|
+
|
|
89
|
+
1. Deploy your app
|
|
90
|
+
2. Add webhook endpoint in Stripe Dashboard:
|
|
91
|
+
- URL: `https://yourdomain.com/stripe/webhook`
|
|
92
|
+
- Events: Select events you handle
|
|
93
|
+
3. Copy webhook signing secret to production env
|
|
94
|
+
|
|
95
|
+
## Common Use Cases
|
|
96
|
+
|
|
97
|
+
### Update Database on New Subscription
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
'customer.subscription.created': async (event, fastify, stripe) => {
|
|
101
|
+
const subscription = event.data.object;
|
|
102
|
+
|
|
103
|
+
await fastify.prisma.user.update({
|
|
104
|
+
where: { stripeCustomerId: subscription.customer },
|
|
105
|
+
data: {
|
|
106
|
+
subscriptionId: subscription.id,
|
|
107
|
+
subscriptionStatus: subscription.status,
|
|
108
|
+
planId: subscription.items.data[0]?.price.id,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Send Email on Payment Failure
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
'invoice.payment_failed': async (event, fastify, stripe) => {
|
|
118
|
+
const invoice = event.data.object;
|
|
119
|
+
const customer = await stripe.customers.retrieve(invoice.customer);
|
|
120
|
+
|
|
121
|
+
await fastify.email.send(
|
|
122
|
+
customer.email,
|
|
123
|
+
'Payment Failed',
|
|
124
|
+
'Please update your payment method'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Revoke Access on Cancellation
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
'customer.subscription.deleted': async (event, fastify, stripe) => {
|
|
133
|
+
const subscription = event.data.object;
|
|
134
|
+
|
|
135
|
+
await fastify.prisma.user.update({
|
|
136
|
+
where: { stripeSubscriptionId: subscription.id },
|
|
137
|
+
data: {
|
|
138
|
+
hasAccess: false,
|
|
139
|
+
subscriptionStatus: 'canceled',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Handler Function Signature
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
async function handler(event, fastify, stripe) {
|
|
149
|
+
// event - Stripe webhook event
|
|
150
|
+
// fastify - Fastify instance (access decorators)
|
|
151
|
+
// stripe - Stripe client
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Supported Events
|
|
156
|
+
|
|
157
|
+
- **Subscriptions**: created, updated, deleted, paused, resumed, trial_will_end
|
|
158
|
+
- **Invoices**: created, finalized, paid, payment_failed, upcoming
|
|
159
|
+
- **Payments**: payment_intent.succeeded, payment_intent.payment_failed
|
|
160
|
+
- **Customers**: created, updated, deleted
|
|
161
|
+
- **Payment Methods**: attached, detached
|
|
162
|
+
- **Checkout**: session.completed, session.expired
|
|
163
|
+
|
|
164
|
+
See [README.md](./README.md) for full list.
|
|
165
|
+
|
|
166
|
+
## Next Steps
|
|
167
|
+
|
|
168
|
+
- [Full Documentation](./README.md)
|
|
169
|
+
- [Testing Guide](./TESTING.md)
|
|
170
|
+
- [Migration Guide](./MIGRATION.md)
|
|
171
|
+
- [Example Handlers](./src/handlers/exampleHandlers.js)
|
|
172
|
+
|
|
173
|
+
## Tips
|
|
174
|
+
|
|
175
|
+
1. **Use structured logging**: `fastify.log.info()` instead of `console.log()`
|
|
176
|
+
2. **Make handlers idempotent**: Safe to run multiple times
|
|
177
|
+
3. **Return 200 quickly**: Do heavy work async
|
|
178
|
+
4. **Test locally first**: Use Stripe CLI before deploying
|
|
179
|
+
5. **Monitor webhooks**: Check Stripe Dashboard for failures
|