@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/TESTING.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Testing Guide for xStripe
|
|
2
|
+
|
|
3
|
+
This guide shows how to test your Stripe webhook handlers locally and in production.
|
|
4
|
+
|
|
5
|
+
## Local Testing with Stripe CLI
|
|
6
|
+
|
|
7
|
+
### 1. Install Stripe CLI
|
|
8
|
+
|
|
9
|
+
**macOS:**
|
|
10
|
+
```bash
|
|
11
|
+
brew install stripe/stripe-cli/stripe
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Other platforms:**
|
|
15
|
+
Download from https://stripe.com/docs/stripe-cli
|
|
16
|
+
|
|
17
|
+
### 2. Login to Stripe
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
stripe login
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This will open your browser for authentication.
|
|
24
|
+
|
|
25
|
+
### 3. Forward Webhooks to Local Server
|
|
26
|
+
|
|
27
|
+
Start your local server first:
|
|
28
|
+
```bash
|
|
29
|
+
npm run dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then forward webhooks:
|
|
33
|
+
```bash
|
|
34
|
+
stripe listen --forward-to localhost:3000/stripe/webhook
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This will output a webhook signing secret. Copy it to your `.env` file:
|
|
38
|
+
```
|
|
39
|
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 4. Trigger Test Events
|
|
43
|
+
|
|
44
|
+
In a new terminal, trigger specific events:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Test subscription creation
|
|
48
|
+
stripe trigger customer.subscription.created
|
|
49
|
+
|
|
50
|
+
# Test payment failure
|
|
51
|
+
stripe trigger invoice.payment_failed
|
|
52
|
+
|
|
53
|
+
# Test subscription cancellation
|
|
54
|
+
stripe trigger customer.subscription.deleted
|
|
55
|
+
|
|
56
|
+
# Test trial ending
|
|
57
|
+
stripe trigger customer.subscription.trial_will_end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Testing Specific Scenarios
|
|
61
|
+
|
|
62
|
+
### Subscription Lifecycle
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 1. Create subscription
|
|
66
|
+
stripe trigger customer.subscription.created
|
|
67
|
+
|
|
68
|
+
# 2. Update subscription (plan change)
|
|
69
|
+
stripe trigger customer.subscription.updated
|
|
70
|
+
|
|
71
|
+
# 3. Trial ending soon
|
|
72
|
+
stripe trigger customer.subscription.trial_will_end
|
|
73
|
+
|
|
74
|
+
# 4. Cancel subscription
|
|
75
|
+
stripe trigger customer.subscription.deleted
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Payment Scenarios
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Successful payment
|
|
82
|
+
stripe trigger invoice.paid
|
|
83
|
+
|
|
84
|
+
# Failed payment
|
|
85
|
+
stripe trigger invoice.payment_failed
|
|
86
|
+
|
|
87
|
+
# Upcoming payment reminder
|
|
88
|
+
stripe trigger invoice.upcoming
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Customer Events
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# New customer
|
|
95
|
+
stripe trigger customer.created
|
|
96
|
+
|
|
97
|
+
# Customer updated
|
|
98
|
+
stripe trigger customer.updated
|
|
99
|
+
|
|
100
|
+
# Payment method attached
|
|
101
|
+
stripe trigger payment_method.attached
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Testing Handlers Directly
|
|
105
|
+
|
|
106
|
+
Since handlers are pure functions, you can test them in isolation:
|
|
107
|
+
|
|
108
|
+
### Example Test File
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// test/handlers.test.js
|
|
112
|
+
import { test } from 'node:test';
|
|
113
|
+
import assert from 'node:assert';
|
|
114
|
+
|
|
115
|
+
test('subscription created handler updates database', async () => {
|
|
116
|
+
const mockEvent = {
|
|
117
|
+
type: 'customer.subscription.created',
|
|
118
|
+
data: {
|
|
119
|
+
object: {
|
|
120
|
+
id: 'sub_123',
|
|
121
|
+
customer: 'cus_123',
|
|
122
|
+
status: 'active',
|
|
123
|
+
items: {
|
|
124
|
+
data: [{
|
|
125
|
+
price: {
|
|
126
|
+
id: 'price_123',
|
|
127
|
+
product: 'prod_123',
|
|
128
|
+
},
|
|
129
|
+
}],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
let updatedUser = null;
|
|
136
|
+
|
|
137
|
+
const mockFastify = {
|
|
138
|
+
log: {
|
|
139
|
+
info: () => {},
|
|
140
|
+
error: () => {},
|
|
141
|
+
warn: () => {},
|
|
142
|
+
},
|
|
143
|
+
prisma: {
|
|
144
|
+
user: {
|
|
145
|
+
update: async (data) => {
|
|
146
|
+
updatedUser = data;
|
|
147
|
+
return { id: 1 };
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const mockStripe = {};
|
|
154
|
+
|
|
155
|
+
// Import your handler
|
|
156
|
+
const handler = customHandlers['customer.subscription.created'];
|
|
157
|
+
await handler(mockEvent, mockFastify, mockStripe);
|
|
158
|
+
|
|
159
|
+
// Assert database was updated
|
|
160
|
+
assert.equal(updatedUser.where.stripeCustomerId, 'cus_123');
|
|
161
|
+
assert.equal(updatedUser.data.subscriptionId, 'sub_123');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('payment failure handler sends notification', async () => {
|
|
165
|
+
const mockEvent = {
|
|
166
|
+
type: 'invoice.payment_failed',
|
|
167
|
+
data: {
|
|
168
|
+
object: {
|
|
169
|
+
id: 'in_123',
|
|
170
|
+
customer: 'cus_123',
|
|
171
|
+
subscription: 'sub_123',
|
|
172
|
+
attempt_count: 2,
|
|
173
|
+
amount_due: 2000,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
let emailSent = false;
|
|
179
|
+
|
|
180
|
+
const mockFastify = {
|
|
181
|
+
log: {
|
|
182
|
+
info: () => {},
|
|
183
|
+
error: () => {},
|
|
184
|
+
warn: () => {},
|
|
185
|
+
},
|
|
186
|
+
email: {
|
|
187
|
+
send: async (to, subject, body) => {
|
|
188
|
+
emailSent = true;
|
|
189
|
+
assert.equal(subject, 'Payment Failed');
|
|
190
|
+
return { success: true };
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const mockStripe = {
|
|
196
|
+
customers: {
|
|
197
|
+
retrieve: async (id) => ({
|
|
198
|
+
id,
|
|
199
|
+
email: 'test@example.com',
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const handler = customHandlers['invoice.payment_failed'];
|
|
205
|
+
await handler(mockEvent, mockFastify, mockStripe);
|
|
206
|
+
|
|
207
|
+
assert.equal(emailSent, true);
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Run tests:
|
|
212
|
+
```bash
|
|
213
|
+
node --test test/**/*.test.js
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Manual Testing with cURL
|
|
217
|
+
|
|
218
|
+
You can also send webhook events manually (useful for CI/CD):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Get a test event from Stripe
|
|
222
|
+
stripe events list --limit 1
|
|
223
|
+
|
|
224
|
+
# Send it to your local server
|
|
225
|
+
curl -X POST http://localhost:3000/stripe/webhook \
|
|
226
|
+
-H "Content-Type: application/json" \
|
|
227
|
+
-H "Stripe-Signature: YOUR_SIGNATURE" \
|
|
228
|
+
-d @test-event.json
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Testing in Production
|
|
232
|
+
|
|
233
|
+
### 1. Configure Webhook Endpoint
|
|
234
|
+
|
|
235
|
+
In your Stripe Dashboard:
|
|
236
|
+
1. Go to Developers → Webhooks
|
|
237
|
+
2. Click "Add endpoint"
|
|
238
|
+
3. Enter your production URL: `https://yourdomain.com/stripe/webhook`
|
|
239
|
+
4. Select events to listen for (or select "Select all events")
|
|
240
|
+
5. Copy the signing secret to your production environment
|
|
241
|
+
|
|
242
|
+
### 2. Monitor Webhook Deliveries
|
|
243
|
+
|
|
244
|
+
In Stripe Dashboard:
|
|
245
|
+
- View webhook delivery attempts
|
|
246
|
+
- See request/response details
|
|
247
|
+
- Retry failed deliveries
|
|
248
|
+
|
|
249
|
+
### 3. Set Up Alerts
|
|
250
|
+
|
|
251
|
+
Monitor for:
|
|
252
|
+
- Failed webhook deliveries
|
|
253
|
+
- Handler errors in your logs
|
|
254
|
+
- Unusual event patterns
|
|
255
|
+
|
|
256
|
+
## Common Test Scenarios
|
|
257
|
+
|
|
258
|
+
### Test New Subscriber Flow
|
|
259
|
+
|
|
260
|
+
1. Create customer
|
|
261
|
+
2. Create checkout session
|
|
262
|
+
3. Complete checkout (use test card `4242 4242 4242 4242`)
|
|
263
|
+
4. Verify `customer.subscription.created` handler runs
|
|
264
|
+
5. Verify `invoice.paid` handler runs
|
|
265
|
+
6. Check database for new subscription
|
|
266
|
+
|
|
267
|
+
### Test Failed Payment Flow
|
|
268
|
+
|
|
269
|
+
1. Update payment method to failing card (`4000 0000 0000 0341`)
|
|
270
|
+
2. Wait for billing date or trigger manually
|
|
271
|
+
3. Verify `invoice.payment_failed` handler runs
|
|
272
|
+
4. Check email notifications sent
|
|
273
|
+
5. Verify retry logic
|
|
274
|
+
|
|
275
|
+
### Test Subscription Cancellation
|
|
276
|
+
|
|
277
|
+
1. Cancel subscription via API
|
|
278
|
+
2. Verify `customer.subscription.updated` handler runs
|
|
279
|
+
3. If `cancel_at_period_end=false`, verify `customer.subscription.deleted` runs
|
|
280
|
+
4. Check access revoked in database
|
|
281
|
+
|
|
282
|
+
## Debugging Tips
|
|
283
|
+
|
|
284
|
+
### Enable Verbose Logging
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
const fastify = Fastify({
|
|
288
|
+
logger: {
|
|
289
|
+
level: 'debug',
|
|
290
|
+
transport: {
|
|
291
|
+
target: 'pino-pretty',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Log All Webhook Events
|
|
298
|
+
|
|
299
|
+
Add this to see raw events:
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
handlers: {
|
|
303
|
+
'*': async (event, fastify, stripe) => {
|
|
304
|
+
fastify.log.debug({
|
|
305
|
+
event: event.type,
|
|
306
|
+
data: event.data.object,
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Check Webhook Signatures
|
|
313
|
+
|
|
314
|
+
If signatures fail:
|
|
315
|
+
1. Verify webhook secret matches Stripe Dashboard
|
|
316
|
+
2. Check raw body is being used (not parsed JSON)
|
|
317
|
+
3. Verify headers are forwarded correctly (if using proxy)
|
|
318
|
+
|
|
319
|
+
### Test Mode vs Live Mode
|
|
320
|
+
|
|
321
|
+
- Use `sk_test_` keys for development
|
|
322
|
+
- Use `sk_live_` keys for production
|
|
323
|
+
- Test and live webhooks are separate endpoints
|
|
324
|
+
- Events don't cross between modes
|
|
325
|
+
|
|
326
|
+
## Best Practices
|
|
327
|
+
|
|
328
|
+
1. **Test all critical paths** - Subscription creation, payment failures, cancellations
|
|
329
|
+
2. **Use idempotency** - Handlers should be safe to run multiple times
|
|
330
|
+
3. **Log everything** - You can't debug what you can't see
|
|
331
|
+
4. **Monitor production** - Set up alerts for failed handlers
|
|
332
|
+
5. **Test error cases** - What happens if database is down?
|
|
333
|
+
6. **Verify async operations** - Do emails actually send?
|
|
334
|
+
7. **Check edge cases** - What if customer has no email?
|
|
335
|
+
|
|
336
|
+
## Troubleshooting
|
|
337
|
+
|
|
338
|
+
### Webhooks Not Received
|
|
339
|
+
|
|
340
|
+
- Check webhook endpoint is accessible publicly
|
|
341
|
+
- Verify webhook secret is correct
|
|
342
|
+
- Check firewall/security group allows Stripe IPs
|
|
343
|
+
- Review Stripe Dashboard for delivery attempts
|
|
344
|
+
|
|
345
|
+
### Handler Errors
|
|
346
|
+
|
|
347
|
+
- Check logs for error messages
|
|
348
|
+
- Verify database connections
|
|
349
|
+
- Test handler functions in isolation
|
|
350
|
+
- Ensure all required services are available
|
|
351
|
+
|
|
352
|
+
### Signature Verification Fails
|
|
353
|
+
|
|
354
|
+
- Verify using raw body, not parsed JSON
|
|
355
|
+
- Check webhook secret matches exactly
|
|
356
|
+
- Ensure no middleware modifies the body
|
|
357
|
+
- Verify Content-Type header is correct
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xStripe - Fastify Plugin for Stripe Webhook Handling
|
|
3
|
+
* TypeScript Type Definitions
|
|
4
|
+
*
|
|
5
|
+
* @module @xenterprises/fastify-xstripe
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { FastifyPluginAsync, FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
|
10
|
+
import Stripe from 'stripe';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stripe Webhook Event (from Stripe SDK)
|
|
14
|
+
*/
|
|
15
|
+
export type StripeWebhookEvent = Stripe.Event;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Webhook Event Handler Function
|
|
19
|
+
*/
|
|
20
|
+
export type WebhookEventHandler = (
|
|
21
|
+
event: StripeWebhookEvent,
|
|
22
|
+
fastify: FastifyInstance,
|
|
23
|
+
stripe: Stripe
|
|
24
|
+
) => Promise<void>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default Event Handlers Map
|
|
28
|
+
*/
|
|
29
|
+
export interface DefaultHandlers {
|
|
30
|
+
// Subscription Events
|
|
31
|
+
'customer.subscription.created': WebhookEventHandler;
|
|
32
|
+
'customer.subscription.updated': WebhookEventHandler;
|
|
33
|
+
'customer.subscription.deleted': WebhookEventHandler;
|
|
34
|
+
'customer.subscription.paused': WebhookEventHandler;
|
|
35
|
+
'customer.subscription.resumed': WebhookEventHandler;
|
|
36
|
+
'customer.subscription.trial_will_end': WebhookEventHandler;
|
|
37
|
+
|
|
38
|
+
// Invoice Events
|
|
39
|
+
'invoice.created': WebhookEventHandler;
|
|
40
|
+
'invoice.finalized': WebhookEventHandler;
|
|
41
|
+
'invoice.paid': WebhookEventHandler;
|
|
42
|
+
'invoice.payment_failed': WebhookEventHandler;
|
|
43
|
+
'invoice.upcoming': WebhookEventHandler;
|
|
44
|
+
|
|
45
|
+
// Payment Intent Events
|
|
46
|
+
'payment_intent.succeeded': WebhookEventHandler;
|
|
47
|
+
'payment_intent.payment_failed': WebhookEventHandler;
|
|
48
|
+
|
|
49
|
+
// Customer Events
|
|
50
|
+
'customer.created': WebhookEventHandler;
|
|
51
|
+
'customer.updated': WebhookEventHandler;
|
|
52
|
+
'customer.deleted': WebhookEventHandler;
|
|
53
|
+
|
|
54
|
+
// Payment Method Events
|
|
55
|
+
'payment_method.attached': WebhookEventHandler;
|
|
56
|
+
'payment_method.detached': WebhookEventHandler;
|
|
57
|
+
|
|
58
|
+
// Charge Events
|
|
59
|
+
'charge.succeeded': WebhookEventHandler;
|
|
60
|
+
'charge.failed': WebhookEventHandler;
|
|
61
|
+
'charge.refunded': WebhookEventHandler;
|
|
62
|
+
|
|
63
|
+
// Checkout Events
|
|
64
|
+
'checkout.session.completed': WebhookEventHandler;
|
|
65
|
+
'checkout.session.expired': WebhookEventHandler;
|
|
66
|
+
|
|
67
|
+
// Allow any other event type
|
|
68
|
+
[eventType: string]: WebhookEventHandler;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Plugin Configuration Options
|
|
73
|
+
*/
|
|
74
|
+
export interface XStripePluginOptions {
|
|
75
|
+
/** Stripe API Key (Secret Key) */
|
|
76
|
+
apiKey?: string;
|
|
77
|
+
|
|
78
|
+
/** Stripe Webhook Signing Secret */
|
|
79
|
+
webhookSecret?: string;
|
|
80
|
+
|
|
81
|
+
/** Webhook endpoint path */
|
|
82
|
+
webhookPath?: string;
|
|
83
|
+
|
|
84
|
+
/** Custom event handlers (override defaults) */
|
|
85
|
+
handlers?: Partial<DefaultHandlers>;
|
|
86
|
+
|
|
87
|
+
/** Whether to fail on handler errors */
|
|
88
|
+
failOnError?: boolean;
|
|
89
|
+
|
|
90
|
+
/** Request timeout in milliseconds */
|
|
91
|
+
requestTimeout?: number;
|
|
92
|
+
|
|
93
|
+
/** Enable event logging */
|
|
94
|
+
logEvents?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Helper Functions
|
|
99
|
+
*/
|
|
100
|
+
export namespace helpers {
|
|
101
|
+
/**
|
|
102
|
+
* Format amount as currency string
|
|
103
|
+
* @param amount Amount in cents
|
|
104
|
+
* @param currency Currency code (USD, EUR, etc.)
|
|
105
|
+
* @returns Formatted currency string (e.g., "$20.00")
|
|
106
|
+
*/
|
|
107
|
+
function formatAmount(amount: number, currency: string): string;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get plan name from subscription
|
|
111
|
+
* @param subscription Subscription object
|
|
112
|
+
* @returns Plan name or product name
|
|
113
|
+
*/
|
|
114
|
+
function getPlanName(subscription: Stripe.Subscription): string;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if subscription is active
|
|
118
|
+
* @param subscription Subscription object
|
|
119
|
+
* @returns True if subscription is active
|
|
120
|
+
*/
|
|
121
|
+
function isActiveSubscription(subscription: Stripe.Subscription): boolean;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get customer email from various objects
|
|
125
|
+
* @param obj Stripe object with customer reference
|
|
126
|
+
* @param stripe Stripe client instance
|
|
127
|
+
* @returns Customer email
|
|
128
|
+
*/
|
|
129
|
+
function getCustomerEmail(obj: any, stripe: Stripe): Promise<string>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create idempotent request ID from event
|
|
133
|
+
* @param event Stripe webhook event
|
|
134
|
+
* @returns Idempotency key
|
|
135
|
+
*/
|
|
136
|
+
function createIdempotencyKey(event: StripeWebhookEvent): string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* xStripe Service Methods (available on fastify.xStripe)
|
|
141
|
+
*/
|
|
142
|
+
export interface XStripeService {
|
|
143
|
+
/**
|
|
144
|
+
* Register custom event handler
|
|
145
|
+
* @param eventType Stripe event type
|
|
146
|
+
* @param handler Event handler function
|
|
147
|
+
*/
|
|
148
|
+
onEvent(eventType: string, handler: WebhookEventHandler): void;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get registered handler for event type
|
|
152
|
+
* @param eventType Stripe event type
|
|
153
|
+
* @returns Handler function or undefined
|
|
154
|
+
*/
|
|
155
|
+
getHandler(eventType: string): WebhookEventHandler | undefined;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Execute event handler
|
|
159
|
+
* @param event Stripe webhook event
|
|
160
|
+
* @returns Promise that resolves when handler completes
|
|
161
|
+
*/
|
|
162
|
+
executeHandler(event: StripeWebhookEvent): Promise<void>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Fastify Instance with xStripe Decoration
|
|
167
|
+
*/
|
|
168
|
+
declare module 'fastify' {
|
|
169
|
+
interface FastifyInstance {
|
|
170
|
+
/** xStripe service methods */
|
|
171
|
+
xStripe: XStripeService;
|
|
172
|
+
|
|
173
|
+
/** Stripe client instance */
|
|
174
|
+
stripe: Stripe;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Webhook Request with Stripe Event
|
|
180
|
+
*/
|
|
181
|
+
export interface WebhookRequest extends FastifyRequest {
|
|
182
|
+
body: {
|
|
183
|
+
/** Stripe event object */
|
|
184
|
+
id: string;
|
|
185
|
+
type: string;
|
|
186
|
+
data: any;
|
|
187
|
+
[key: string]: any;
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Webhook Response
|
|
193
|
+
*/
|
|
194
|
+
export interface WebhookResponse {
|
|
195
|
+
/** Status message */
|
|
196
|
+
status: 'success' | 'error';
|
|
197
|
+
|
|
198
|
+
/** Event ID that was processed */
|
|
199
|
+
eventId: string;
|
|
200
|
+
|
|
201
|
+
/** Event type that was processed */
|
|
202
|
+
eventType: string;
|
|
203
|
+
|
|
204
|
+
/** Optional error message */
|
|
205
|
+
error?: string;
|
|
206
|
+
|
|
207
|
+
/** Timestamp of processing */
|
|
208
|
+
processedAt: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* API Response Types
|
|
213
|
+
*/
|
|
214
|
+
export interface Plan {
|
|
215
|
+
productId: string;
|
|
216
|
+
name: string;
|
|
217
|
+
description?: string;
|
|
218
|
+
images?: string[];
|
|
219
|
+
prices: PriceInfo[];
|
|
220
|
+
metadata?: Record<string, any>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface PriceInfo {
|
|
224
|
+
priceId: string;
|
|
225
|
+
amount: number;
|
|
226
|
+
currency: string;
|
|
227
|
+
interval?: string;
|
|
228
|
+
intervalCount?: number;
|
|
229
|
+
trialPeriodDays?: number;
|
|
230
|
+
nickname?: string;
|
|
231
|
+
metadata?: Record<string, any>;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface PaymentMethod {
|
|
235
|
+
id: string;
|
|
236
|
+
type: string;
|
|
237
|
+
isDefault: boolean;
|
|
238
|
+
card?: {
|
|
239
|
+
brand: string;
|
|
240
|
+
last4: string;
|
|
241
|
+
expMonth: number;
|
|
242
|
+
expYear: number;
|
|
243
|
+
};
|
|
244
|
+
billingDetails?: Record<string, any>;
|
|
245
|
+
createdAt: Date;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface SubscriptionInfo {
|
|
249
|
+
id: string;
|
|
250
|
+
status: string;
|
|
251
|
+
planId?: string;
|
|
252
|
+
planName?: string;
|
|
253
|
+
amount?: number;
|
|
254
|
+
currency?: string;
|
|
255
|
+
interval?: string;
|
|
256
|
+
createdAt: Date;
|
|
257
|
+
currentPeriodEnd: Date;
|
|
258
|
+
cancelAtPeriodEnd: boolean;
|
|
259
|
+
canceledAt?: Date;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* xStripe Plugin
|
|
264
|
+
* Registers Stripe webhook handling and provides convenient API endpoints
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* import Fastify from 'fastify';
|
|
269
|
+
* import xStripe from '@xenterprises/fastify-xstripe';
|
|
270
|
+
*
|
|
271
|
+
* const fastify = Fastify();
|
|
272
|
+
*
|
|
273
|
+
* await fastify.register(xStripe, {
|
|
274
|
+
* apiKey: process.env.STRIPE_API_KEY,
|
|
275
|
+
* webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
|
|
276
|
+
* webhookPath: '/webhooks/stripe'
|
|
277
|
+
* });
|
|
278
|
+
*
|
|
279
|
+
* // Register custom handler
|
|
280
|
+
* fastify.xStripe.onEvent('customer.subscription.created', async (event, fastify, stripe) => {
|
|
281
|
+
* const subscription = event.data.object;
|
|
282
|
+
* console.log('New subscription:', subscription.id);
|
|
283
|
+
* });
|
|
284
|
+
*
|
|
285
|
+
* // Webhook route automatically registered at webhookPath
|
|
286
|
+
* // POST /webhooks/stripe
|
|
287
|
+
*
|
|
288
|
+
* // Use included API endpoints
|
|
289
|
+
* // GET /plans
|
|
290
|
+
* // GET /plans/:productId
|
|
291
|
+
* // POST /create-checkout-session
|
|
292
|
+
* // POST /create-payment-session
|
|
293
|
+
* // GET /customer/:customerId/subscriptions
|
|
294
|
+
* // POST /subscription/:id/update
|
|
295
|
+
* // GET /customer/:customerId/payment-methods
|
|
296
|
+
* // POST /customer/:customerId/payment-methods
|
|
297
|
+
* // POST /customer/:customerId/payment-methods/:paymentMethodId/default
|
|
298
|
+
* // DELETE /customer/:customerId/payment-methods/:paymentMethodId
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
declare const xStripe: FastifyPluginAsync<XStripePluginOptions>;
|
|
302
|
+
|
|
303
|
+
export default xStripe;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Re-export Stripe types for convenience
|
|
307
|
+
*/
|
|
308
|
+
export { Stripe };
|
|
309
|
+
export type { StripeWebhookEvent as WebhookEvent };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xenterprises/fastify-xstripe",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Fastify plugin for Stripe webhooks with simplified, testable handlers for subscription events.",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./handlers": "./src/handlers/index.js",
|
|
10
|
+
"./helpers": "./src/utils/helpers.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "fastify start -l info server/app.js",
|
|
14
|
+
"dev": "fastify start -w -l info -P server/app.js",
|
|
15
|
+
"test": "node --test test/handlers.test.js"
|
|
16
|
+
},
|
|
17
|
+
"author": "Tim Mushen",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20.0.0",
|
|
21
|
+
"npm": ">=10.0.0"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://gitlab.com/x-enterprises/fastify-plugins/fastify-xstripe"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://gitlab.com/x-enterprises/fastify-plugins/fastify-xstripe/-/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://gitlab.com/x-enterprises/fastify-plugins/fastify-xstripe#readme",
|
|
31
|
+
"keywords": [
|
|
32
|
+
"fastify",
|
|
33
|
+
"stripe",
|
|
34
|
+
"webhooks",
|
|
35
|
+
"subscription",
|
|
36
|
+
"billing",
|
|
37
|
+
"payments",
|
|
38
|
+
"plugin"
|
|
39
|
+
],
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.7.4",
|
|
42
|
+
"fastify": "^5.1.0",
|
|
43
|
+
"fastify-plugin": "^5.0.0",
|
|
44
|
+
"typescript": "^5.6.3"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"fastify-plugin": "^5.0.0",
|
|
48
|
+
"stripe": "^16.12.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"fastify": "^5.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|