@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/EXAMPLES.md ADDED
@@ -0,0 +1,883 @@
1
+ # Event Handler Examples
2
+
3
+ Real-world examples for each Stripe webhook event type.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Subscription Events](#subscription-events)
8
+ - [Invoice Events](#invoice-events)
9
+ - [Payment Events](#payment-events)
10
+ - [Customer Events](#customer-events)
11
+ - [Payment Method Events](#payment-method-events)
12
+ - [Checkout Events](#checkout-events)
13
+ - [Complete Use Cases](#complete-use-cases)
14
+
15
+ ---
16
+
17
+ ## Subscription Events
18
+
19
+ ### `customer.subscription.created`
20
+
21
+ **When it fires:** A new subscription is created (via Checkout or API).
22
+
23
+ **Common use cases:**
24
+ - Grant user access to premium features
25
+ - Send welcome email
26
+ - Start onboarding flow
27
+ - Track conversion metrics
28
+
29
+ ```javascript
30
+ 'customer.subscription.created': async (event, fastify, stripe) => {
31
+ const subscription = event.data.object;
32
+ const customer = await stripe.customers.retrieve(subscription.customer);
33
+
34
+ // Update database
35
+ await fastify.prisma.user.update({
36
+ where: { email: customer.email },
37
+ data: {
38
+ stripeCustomerId: subscription.customer,
39
+ stripeSubscriptionId: subscription.id,
40
+ subscriptionStatus: subscription.status,
41
+ planId: subscription.items.data[0]?.price.id,
42
+ trialEndsAt: subscription.trial_end
43
+ ? new Date(subscription.trial_end * 1000)
44
+ : null,
45
+ subscriptionStartedAt: new Date(),
46
+ },
47
+ });
48
+
49
+ // Send welcome email
50
+ await fastify.email.send(
51
+ customer.email,
52
+ 'Welcome to Premium!',
53
+ `
54
+ <h1>Welcome aboard! 🎉</h1>
55
+ <p>Your subscription is now active.</p>
56
+ <p>Plan: ${subscription.items.data[0]?.price.nickname}</p>
57
+ `
58
+ );
59
+
60
+ // Send Slack notification
61
+ await fastify.slack.send(`New subscriber: ${customer.email}`);
62
+
63
+ fastify.log.info({
64
+ event: 'subscription_created',
65
+ customerId: subscription.customer,
66
+ plan: subscription.items.data[0]?.price.id,
67
+ });
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ### `customer.subscription.updated`
74
+
75
+ **When it fires:** Subscription changes (status, plan, quantity, etc.).
76
+
77
+ **Common use cases:**
78
+ - Handle plan upgrades/downgrades
79
+ - Update feature access
80
+ - Track status changes
81
+ - Handle trial conversions
82
+
83
+ ```javascript
84
+ 'customer.subscription.updated': async (event, fastify, stripe) => {
85
+ const subscription = event.data.object;
86
+ const previous = event.data.previous_attributes || {};
87
+
88
+ // Check what changed
89
+ const statusChanged = 'status' in previous;
90
+ const planChanged = 'items' in previous;
91
+
92
+ // Update database
93
+ await fastify.prisma.user.update({
94
+ where: { stripeSubscriptionId: subscription.id },
95
+ data: {
96
+ subscriptionStatus: subscription.status,
97
+ planId: subscription.items.data[0]?.price.id,
98
+ },
99
+ });
100
+
101
+ // Handle status changes
102
+ if (statusChanged) {
103
+ const oldStatus = previous.status;
104
+ const newStatus = subscription.status;
105
+
106
+ // Trial converted to paid
107
+ if (newStatus === 'active' && oldStatus === 'trialing') {
108
+ await fastify.email.send(
109
+ subscription.customer.email,
110
+ 'Trial Converted!',
111
+ '<p>Your trial has successfully converted to a paid subscription.</p>'
112
+ );
113
+
114
+ fastify.log.info('Trial converted to paid', {
115
+ subscriptionId: subscription.id,
116
+ });
117
+ }
118
+
119
+ // Subscription past due
120
+ if (newStatus === 'past_due') {
121
+ await fastify.email.send(
122
+ subscription.customer.email,
123
+ 'Payment Issue',
124
+ '<p>We had trouble processing your payment. Please update your payment method.</p>'
125
+ );
126
+
127
+ await fastify.sms.send(
128
+ subscription.customer.phone,
129
+ 'Payment failed for your subscription. Please update your card.'
130
+ );
131
+
132
+ fastify.log.warn('Subscription past due', {
133
+ subscriptionId: subscription.id,
134
+ });
135
+ }
136
+
137
+ // Subscription canceled
138
+ if (newStatus === 'canceled') {
139
+ await fastify.prisma.user.update({
140
+ where: { stripeSubscriptionId: subscription.id },
141
+ data: { hasAccess: false },
142
+ });
143
+
144
+ fastify.log.info('Subscription canceled', {
145
+ subscriptionId: subscription.id,
146
+ });
147
+ }
148
+ }
149
+
150
+ // Handle plan changes (upgrade/downgrade)
151
+ if (planChanged) {
152
+ const oldPlan = previous.items?.data[0]?.price.id;
153
+ const newPlan = subscription.items.data[0]?.price.id;
154
+
155
+ await fastify.email.send(
156
+ subscription.customer.email,
157
+ 'Plan Updated',
158
+ `<p>Your plan has been updated from ${oldPlan} to ${newPlan}.</p>`
159
+ );
160
+
161
+ fastify.log.info('Plan changed', {
162
+ subscriptionId: subscription.id,
163
+ oldPlan,
164
+ newPlan,
165
+ });
166
+ }
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ### `customer.subscription.deleted`
173
+
174
+ **When it fires:** Subscription is canceled (immediately or at period end).
175
+
176
+ **Common use cases:**
177
+ - Revoke access to features
178
+ - Send cancellation feedback survey
179
+ - Trigger win-back campaign
180
+ - Clean up resources
181
+
182
+ ```javascript
183
+ 'customer.subscription.deleted': async (event, fastify, stripe) => {
184
+ const subscription = event.data.object;
185
+ const customer = await stripe.customers.retrieve(subscription.customer);
186
+
187
+ // Revoke access
188
+ await fastify.prisma.user.update({
189
+ where: { stripeSubscriptionId: subscription.id },
190
+ data: {
191
+ subscriptionStatus: 'canceled',
192
+ hasAccess: false,
193
+ canceledAt: new Date(),
194
+ subscriptionId: null,
195
+ },
196
+ });
197
+
198
+ // Send cancellation email with feedback survey
199
+ await fastify.email.send(
200
+ customer.email,
201
+ "We're sorry to see you go",
202
+ `
203
+ <h1>Subscription Canceled</h1>
204
+ <p>Your subscription has been canceled and will end on
205
+ ${new Date(subscription.current_period_end * 1000).toLocaleDateString()}.</p>
206
+
207
+ <p>We'd love your feedback:</p>
208
+ <a href="https://example.com/feedback?user=${customer.id}">
209
+ Take our 2-minute survey
210
+ </a>
211
+
212
+ <p>You can reactivate anytime at https://example.com/billing</p>
213
+ `
214
+ );
215
+
216
+ // Schedule win-back email for 7 days later
217
+ await fastify.scheduleEmail({
218
+ to: customer.email,
219
+ subject: 'Special offer to return',
220
+ sendAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
221
+ });
222
+
223
+ // Notify team
224
+ await fastify.slack.send(
225
+ `Customer canceled: ${customer.email} - Plan: ${subscription.items.data[0]?.price.nickname}`
226
+ );
227
+
228
+ fastify.log.info({
229
+ event: 'subscription_canceled',
230
+ customerId: subscription.customer,
231
+ reason: subscription.cancellation_details?.reason,
232
+ });
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ### `customer.subscription.trial_will_end`
239
+
240
+ **When it fires:** 3 days before trial ends.
241
+
242
+ **Common use cases:**
243
+ - Send trial ending reminder
244
+ - Encourage conversion
245
+ - Highlight value
246
+
247
+ ```javascript
248
+ 'customer.subscription.trial_will_end': async (event, fastify, stripe) => {
249
+ const subscription = event.data.object;
250
+ const customer = await stripe.customers.retrieve(subscription.customer);
251
+ const trialEnd = new Date(subscription.trial_end * 1000);
252
+ const daysRemaining = Math.ceil(
253
+ (subscription.trial_end * 1000 - Date.now()) / (1000 * 60 * 60 * 24)
254
+ );
255
+
256
+ // Send email reminder
257
+ await fastify.email.send(
258
+ customer.email,
259
+ `Your trial ends in ${daysRemaining} days`,
260
+ `
261
+ <h1>Your trial is ending soon!</h1>
262
+ <p>Your trial ends on ${trialEnd.toLocaleDateString()}.</p>
263
+
264
+ <h2>What happens next?</h2>
265
+ <p>Your card will be charged ${subscription.items.data[0]?.price.unit_amount / 100}
266
+ ${subscription.currency.toUpperCase()} to continue your subscription.</p>
267
+
268
+ <p><a href="https://example.com/billing">Manage your subscription</a></p>
269
+ <p><a href="https://example.com/cancel">Cancel anytime</a></p>
270
+ `
271
+ );
272
+
273
+ // Send SMS reminder
274
+ if (customer.phone) {
275
+ await fastify.sms.send(
276
+ customer.phone,
277
+ `Your trial ends in ${daysRemaining} days. Keep your access by doing nothing, or cancel anytime.`
278
+ );
279
+ }
280
+
281
+ // Track conversion intent
282
+ await fastify.analytics.track(customer.id, 'trial_ending_soon', {
283
+ daysRemaining,
284
+ plan: subscription.items.data[0]?.price.id,
285
+ });
286
+
287
+ fastify.log.info({
288
+ event: 'trial_ending_soon',
289
+ customerId: subscription.customer,
290
+ daysRemaining,
291
+ });
292
+ }
293
+ ```
294
+
295
+ ---
296
+
297
+ ### `customer.subscription.paused` / `customer.subscription.resumed`
298
+
299
+ **When it fires:** Subscription is paused or resumed.
300
+
301
+ **Common use cases:**
302
+ - Update user access
303
+ - Send confirmation
304
+ - Track churn prevention
305
+
306
+ ```javascript
307
+ 'customer.subscription.paused': async (event, fastify, stripe) => {
308
+ const subscription = event.data.object;
309
+
310
+ await fastify.prisma.user.update({
311
+ where: { stripeSubscriptionId: subscription.id },
312
+ data: {
313
+ subscriptionStatus: 'paused',
314
+ hasAccess: false,
315
+ },
316
+ });
317
+
318
+ fastify.log.info('Subscription paused', {
319
+ subscriptionId: subscription.id,
320
+ });
321
+ },
322
+
323
+ 'customer.subscription.resumed': async (event, fastify, stripe) => {
324
+ const subscription = event.data.object;
325
+
326
+ await fastify.prisma.user.update({
327
+ where: { stripeSubscriptionId: subscription.id },
328
+ data: {
329
+ subscriptionStatus: 'active',
330
+ hasAccess: true,
331
+ },
332
+ });
333
+
334
+ const customer = await stripe.customers.retrieve(subscription.customer);
335
+ await fastify.email.send(
336
+ customer.email,
337
+ 'Subscription Resumed',
338
+ '<p>Welcome back! Your subscription has been resumed.</p>'
339
+ );
340
+
341
+ fastify.log.info('Subscription resumed', {
342
+ subscriptionId: subscription.id,
343
+ });
344
+ }
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Invoice Events
350
+
351
+ ### `invoice.paid`
352
+
353
+ **When it fires:** Invoice payment succeeds.
354
+
355
+ **Common use cases:**
356
+ - Send receipt
357
+ - Reset failed payment counters
358
+ - Grant/extend access
359
+ - Track revenue
360
+
361
+ ```javascript
362
+ 'invoice.paid': async (event, fastify, stripe) => {
363
+ const invoice = event.data.object;
364
+ const customer = await stripe.customers.retrieve(invoice.customer);
365
+
366
+ // Update database
367
+ await fastify.prisma.user.update({
368
+ where: { stripeCustomerId: invoice.customer },
369
+ data: {
370
+ failedPaymentCount: 0,
371
+ lastSuccessfulPayment: new Date(),
372
+ },
373
+ });
374
+
375
+ // For subscription invoices
376
+ if (invoice.subscription) {
377
+ const isFirstPayment = invoice.billing_reason === 'subscription_create';
378
+ const isRenewal = invoice.billing_reason === 'subscription_cycle';
379
+
380
+ if (isRenewal) {
381
+ // Track renewal
382
+ await fastify.analytics.track(customer.id, 'subscription_renewed', {
383
+ amount: invoice.amount_paid / 100,
384
+ plan: invoice.lines.data[0]?.price?.id,
385
+ });
386
+
387
+ // Send renewal confirmation
388
+ await fastify.email.send(
389
+ customer.email,
390
+ 'Payment Received - Subscription Renewed',
391
+ `
392
+ <h1>Thank you for your payment!</h1>
393
+ <p>Amount: $${invoice.amount_paid / 100}</p>
394
+ <p>Your subscription has been renewed for another billing period.</p>
395
+ <a href="${invoice.hosted_invoice_url}">View Receipt</a>
396
+ `
397
+ );
398
+ }
399
+ }
400
+
401
+ // Track revenue
402
+ await fastify.analytics.revenue({
403
+ customerId: customer.id,
404
+ amount: invoice.amount_paid / 100,
405
+ currency: invoice.currency,
406
+ invoiceId: invoice.id,
407
+ });
408
+
409
+ fastify.log.info({
410
+ event: 'payment_successful',
411
+ customerId: invoice.customer,
412
+ amount: invoice.amount_paid / 100,
413
+ billingReason: invoice.billing_reason,
414
+ });
415
+ }
416
+ ```
417
+
418
+ ---
419
+
420
+ ### `invoice.payment_failed`
421
+
422
+ **When it fires:** Invoice payment fails.
423
+
424
+ **Common use cases:**
425
+ - Send payment failure notification
426
+ - Track failed payment attempts
427
+ - Suspend access after multiple failures
428
+ - Update payment method
429
+
430
+ ```javascript
431
+ 'invoice.payment_failed': async (event, fastify, stripe) => {
432
+ const invoice = event.data.object;
433
+ const customer = await stripe.customers.retrieve(invoice.customer);
434
+
435
+ // Track failed payment
436
+ const user = await fastify.prisma.user.update({
437
+ where: { stripeCustomerId: invoice.customer },
438
+ data: {
439
+ failedPaymentCount: { increment: 1 },
440
+ lastFailedPayment: new Date(),
441
+ },
442
+ });
443
+
444
+ // Send email notification
445
+ await fastify.email.send(
446
+ customer.email,
447
+ 'Payment Failed - Action Required',
448
+ `
449
+ <h1>We couldn't process your payment</h1>
450
+ <p>Amount: $${invoice.amount_due / 100}</p>
451
+ <p>Attempt: ${invoice.attempt_count} of 4</p>
452
+
453
+ ${invoice.next_payment_attempt
454
+ ? `<p>We'll retry on ${new Date(invoice.next_payment_attempt * 1000).toLocaleDateString()}</p>`
455
+ : '<p>No more automatic retries.</p>'
456
+ }
457
+
458
+ <a href="https://example.com/billing/update">Update Payment Method</a>
459
+ `
460
+ );
461
+
462
+ // Send SMS for urgent notification
463
+ if (customer.phone) {
464
+ await fastify.sms.send(
465
+ customer.phone,
466
+ `Payment failed for your subscription. Update your payment method: https://example.com/billing`
467
+ );
468
+ }
469
+
470
+ // Suspend access after 3 failed attempts
471
+ if (user.failedPaymentCount >= 3) {
472
+ await fastify.prisma.user.update({
473
+ where: { id: user.id },
474
+ data: {
475
+ hasAccess: false,
476
+ accountSuspended: true,
477
+ },
478
+ });
479
+
480
+ await fastify.email.send(
481
+ customer.email,
482
+ 'Account Suspended',
483
+ '<p>Your account has been suspended due to multiple failed payments.</p>'
484
+ );
485
+
486
+ fastify.log.warn('Account suspended', {
487
+ customerId: invoice.customer,
488
+ failedAttempts: user.failedPaymentCount,
489
+ });
490
+ }
491
+
492
+ // Notify team for high-value customers
493
+ if (invoice.amount_due > 10000) { // $100+
494
+ await fastify.slack.send(
495
+ `⚠️ Payment failed for high-value customer: ${customer.email} - $${invoice.amount_due / 100}`
496
+ );
497
+ }
498
+
499
+ fastify.log.error({
500
+ event: 'payment_failed',
501
+ customerId: invoice.customer,
502
+ amount: invoice.amount_due / 100,
503
+ attemptCount: invoice.attempt_count,
504
+ });
505
+ }
506
+ ```
507
+
508
+ ---
509
+
510
+ ### `invoice.upcoming`
511
+
512
+ **When it fires:** 7 days before invoice is finalized.
513
+
514
+ **Common use cases:**
515
+ - Send billing reminder
516
+ - Allow payment method update
517
+ - Prevent surprise charges
518
+
519
+ ```javascript
520
+ 'invoice.upcoming': async (event, fastify, stripe) => {
521
+ const invoice = event.data.object;
522
+ const customer = await stripe.customers.retrieve(invoice.customer);
523
+ const billingDate = new Date(invoice.period_end * 1000);
524
+ const daysUntilBilling = Math.ceil(
525
+ (invoice.period_end * 1000 - Date.now()) / (1000 * 60 * 60 * 24)
526
+ );
527
+
528
+ // Send upcoming charge email
529
+ await fastify.email.send(
530
+ customer.email,
531
+ `Upcoming Charge: $${invoice.amount_due / 100}`,
532
+ `
533
+ <h1>Upcoming Charge Notification</h1>
534
+ <p>Your card will be charged <strong>$${invoice.amount_due / 100}</strong>
535
+ on ${billingDate.toLocaleDateString()} (in ${daysUntilBilling} days).</p>
536
+
537
+ <h2>Billing Details:</h2>
538
+ <ul>
539
+ ${invoice.lines.data.map(line => `
540
+ <li>${line.description}: $${line.amount / 100}</li>
541
+ `).join('')}
542
+ </ul>
543
+
544
+ <p><a href="https://example.com/billing">Manage Billing</a></p>
545
+ <p><a href="https://example.com/billing/update">Update Payment Method</a></p>
546
+ `
547
+ );
548
+
549
+ // Check for usage-based pricing and notify if high
550
+ const usageLineItems = invoice.lines.data.filter(
551
+ line => line.price?.billing_scheme === 'tiered'
552
+ );
553
+
554
+ if (usageLineItems.length > 0) {
555
+ const usageAmount = usageLineItems.reduce((sum, item) => sum + item.amount, 0);
556
+
557
+ if (usageAmount > 5000) { // $50+ in usage
558
+ await fastify.email.send(
559
+ customer.email,
560
+ 'High Usage Alert',
561
+ `<p>Your usage this period is higher than usual: $${usageAmount / 100}</p>`
562
+ );
563
+ }
564
+ }
565
+
566
+ fastify.log.info({
567
+ event: 'upcoming_invoice',
568
+ customerId: invoice.customer,
569
+ amount: invoice.amount_due / 100,
570
+ daysUntilBilling,
571
+ });
572
+ }
573
+ ```
574
+
575
+ ---
576
+
577
+ ## Payment Events
578
+
579
+ ### `payment_intent.succeeded`
580
+
581
+ **When it fires:** One-time payment succeeds.
582
+
583
+ **Common use cases:**
584
+ - Fulfill order
585
+ - Send confirmation
586
+ - Grant access to purchased item
587
+
588
+ ```javascript
589
+ 'payment_intent.succeeded': async (event, fastify, stripe) => {
590
+ const paymentIntent = event.data.object;
591
+ const metadata = paymentIntent.metadata;
592
+
593
+ // Different handling based on what was purchased
594
+ if (metadata.type === 'course_purchase') {
595
+ await fastify.prisma.enrollment.create({
596
+ data: {
597
+ userId: metadata.userId,
598
+ courseId: metadata.courseId,
599
+ paymentIntentId: paymentIntent.id,
600
+ amount: paymentIntent.amount / 100,
601
+ },
602
+ });
603
+
604
+ const customer = await stripe.customers.retrieve(paymentIntent.customer);
605
+ await fastify.email.send(
606
+ customer.email,
607
+ 'Course Access Granted!',
608
+ `
609
+ <h1>Welcome to the course!</h1>
610
+ <p>Your payment of $${paymentIntent.amount / 100} has been received.</p>
611
+ <a href="https://example.com/courses/${metadata.courseId}">Start Learning</a>
612
+ `
613
+ );
614
+ }
615
+
616
+ if (metadata.type === 'credit_purchase') {
617
+ await fastify.prisma.user.update({
618
+ where: { id: metadata.userId },
619
+ data: {
620
+ credits: { increment: parseInt(metadata.credits) },
621
+ },
622
+ });
623
+ }
624
+
625
+ fastify.log.info({
626
+ event: 'payment_succeeded',
627
+ paymentIntentId: paymentIntent.id,
628
+ amount: paymentIntent.amount / 100,
629
+ type: metadata.type,
630
+ });
631
+ }
632
+ ```
633
+
634
+ ---
635
+
636
+ ## Customer Events
637
+
638
+ ### `customer.created`
639
+
640
+ **When it fires:** New customer created in Stripe.
641
+
642
+ **Common use cases:**
643
+ - Sync customer to CRM
644
+ - Create user record
645
+ - Send welcome message
646
+
647
+ ```javascript
648
+ 'customer.created': async (event, fastify, stripe) => {
649
+ const customer = event.data.object;
650
+
651
+ // Create or update user in database
652
+ await fastify.prisma.user.upsert({
653
+ where: { email: customer.email },
654
+ update: {
655
+ stripeCustomerId: customer.id,
656
+ },
657
+ create: {
658
+ email: customer.email,
659
+ name: customer.name,
660
+ stripeCustomerId: customer.id,
661
+ },
662
+ });
663
+
664
+ // Sync to CRM
665
+ await fastify.crm.createContact({
666
+ email: customer.email,
667
+ name: customer.name,
668
+ stripeId: customer.id,
669
+ });
670
+
671
+ fastify.log.info({
672
+ event: 'customer_created',
673
+ customerId: customer.id,
674
+ email: customer.email,
675
+ });
676
+ }
677
+ ```
678
+
679
+ ---
680
+
681
+ ## Checkout Events
682
+
683
+ ### `checkout.session.completed`
684
+
685
+ **When it fires:** Checkout session completes successfully.
686
+
687
+ **Common use cases:**
688
+ - Provision access
689
+ - Send welcome email
690
+ - Track conversion
691
+
692
+ ```javascript
693
+ 'checkout.session.completed': async (event, fastify, stripe) => {
694
+ const session = event.data.object;
695
+
696
+ if (session.mode === 'subscription') {
697
+ // Subscription checkout
698
+ const subscription = await stripe.subscriptions.retrieve(session.subscription);
699
+
700
+ await fastify.prisma.user.update({
701
+ where: { email: session.customer_details.email },
702
+ data: {
703
+ stripeCustomerId: session.customer,
704
+ stripeSubscriptionId: session.subscription,
705
+ subscriptionStatus: subscription.status,
706
+ hasAccess: true,
707
+ onboardedAt: new Date(),
708
+ },
709
+ });
710
+
711
+ // Send welcome email
712
+ await fastify.email.send(
713
+ session.customer_details.email,
714
+ 'Welcome to Premium!',
715
+ `
716
+ <h1>Welcome aboard! 🎉</h1>
717
+ <p>Your subscription is now active.</p>
718
+ <a href="https://example.com/dashboard">Get Started</a>
719
+ `
720
+ );
721
+
722
+ // Track conversion
723
+ await fastify.analytics.track(session.customer, 'subscription_started', {
724
+ plan: subscription.items.data[0]?.price.id,
725
+ amount: subscription.items.data[0]?.price.unit_amount / 100,
726
+ });
727
+ }
728
+
729
+ if (session.mode === 'payment') {
730
+ // One-time payment
731
+ const paymentIntent = await stripe.paymentIntents.retrieve(session.payment_intent);
732
+
733
+ // Handle based on metadata
734
+ // ... (similar to payment_intent.succeeded)
735
+ }
736
+
737
+ fastify.log.info({
738
+ event: 'checkout_completed',
739
+ sessionId: session.id,
740
+ customerId: session.customer,
741
+ mode: session.mode,
742
+ });
743
+ }
744
+ ```
745
+
746
+ ---
747
+
748
+ ## Complete Use Cases
749
+
750
+ ### SaaS Subscription Flow
751
+
752
+ ```javascript
753
+ const saasHandlers = {
754
+ // 1. User subscribes
755
+ 'checkout.session.completed': async (event, fastify, stripe) => {
756
+ const session = event.data.object;
757
+
758
+ await fastify.prisma.user.update({
759
+ where: { email: session.customer_details.email },
760
+ data: {
761
+ stripeCustomerId: session.customer,
762
+ stripeSubscriptionId: session.subscription,
763
+ plan: session.metadata.plan,
764
+ hasAccess: true,
765
+ },
766
+ });
767
+
768
+ await fastify.email.sendTemplate(
769
+ session.customer_details.email,
770
+ 'Welcome Email',
771
+ 'd-welcome123',
772
+ { name: session.customer_details.name }
773
+ );
774
+ },
775
+
776
+ // 2. Monthly renewal
777
+ 'invoice.paid': async (event, fastify, stripe) => {
778
+ if (event.data.object.billing_reason === 'subscription_cycle') {
779
+ await fastify.analytics.track(event.data.object.customer, 'subscription_renewed');
780
+ }
781
+ },
782
+
783
+ // 3. Payment fails
784
+ 'invoice.payment_failed': async (event, fastify, stripe) => {
785
+ const invoice = event.data.object;
786
+
787
+ if (invoice.attempt_count >= 3) {
788
+ await fastify.prisma.user.update({
789
+ where: { stripeCustomerId: invoice.customer },
790
+ data: { hasAccess: false },
791
+ });
792
+ }
793
+ },
794
+
795
+ // 4. User cancels
796
+ 'customer.subscription.deleted': async (event, fastify, stripe) => {
797
+ await fastify.prisma.user.update({
798
+ where: { stripeSubscriptionId: event.data.object.id },
799
+ data: {
800
+ hasAccess: false,
801
+ canceledAt: new Date(),
802
+ },
803
+ });
804
+ },
805
+ };
806
+ ```
807
+
808
+ ### Course Platform
809
+
810
+ ```javascript
811
+ const courseHandlers = {
812
+ 'payment_intent.succeeded': async (event, fastify, stripe) => {
813
+ const pi = event.data.object;
814
+ const { courseId, userId } = pi.metadata;
815
+
816
+ // Grant access
817
+ await fastify.prisma.enrollment.create({
818
+ data: {
819
+ userId,
820
+ courseId,
821
+ enrolledAt: new Date(),
822
+ },
823
+ });
824
+
825
+ // Send course materials
826
+ const user = await fastify.prisma.user.findUnique({ where: { id: userId } });
827
+ const course = await fastify.prisma.course.findUnique({ where: { id: courseId } });
828
+
829
+ await fastify.email.send(
830
+ user.email,
831
+ `Welcome to ${course.title}!`,
832
+ `<a href="https://example.com/courses/${courseId}/lesson/1">Start Lesson 1</a>`
833
+ );
834
+ },
835
+ };
836
+ ```
837
+
838
+ ### Usage-Based Billing
839
+
840
+ ```javascript
841
+ const usageHandlers = {
842
+ 'invoice.upcoming': async (event, fastify, stripe) => {
843
+ const invoice = event.data.object;
844
+
845
+ // Alert customer about high usage
846
+ const usageItems = invoice.lines.data.filter(
847
+ line => line.type === 'subscription' && line.proration === false
848
+ );
849
+
850
+ const totalUsage = usageItems.reduce((sum, item) => sum + item.amount, 0);
851
+
852
+ if (totalUsage > 10000) {
853
+ const customer = await stripe.customers.retrieve(invoice.customer);
854
+
855
+ await fastify.email.send(
856
+ customer.email,
857
+ 'High Usage Alert',
858
+ `Your usage this month is $${totalUsage / 100}. Review your usage to avoid surprises.`
859
+ );
860
+ }
861
+ },
862
+ };
863
+ ```
864
+
865
+ ---
866
+
867
+ ## Import and Use Examples
868
+
869
+ ```javascript
870
+ // In your server setup
871
+ import xStripe from '@xenterprises/fastify-xstripe';
872
+ import { formatAmount, getPlanName } from '@xenterprises/fastify-xstripe/helpers';
873
+
874
+ await fastify.register(xStripe, {
875
+ apiKey: process.env.STRIPE_API_KEY,
876
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
877
+ handlers: {
878
+ // Mix and match from examples above
879
+ ...saasHandlers,
880
+ ...customHandlers,
881
+ },
882
+ });
883
+ ```