-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathbilling.webhook.controller.js
More file actions
119 lines (113 loc) · 4.4 KB
/
Copy pathbilling.webhook.controller.js
File metadata and controls
119 lines (113 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* Module dependencies
*/
import config from '../../../config/index.js';
import logger from '../../../lib/services/logger.js';
import responses from '../../../lib/helpers/responses.js';
import getStripe from '../lib/stripe.js';
import BillingWebhookService from '../services/billing.webhook.service.js';
/**
* @desc Endpoint to handle Stripe webhook events
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<void>}
*/
// biome-ignore lint/correctness/useQwikValidLexicalScope: false positive — Node.js controller, not Qwik
const handleWebhook = async (req, res) => {
const stripe = getStripe();
if (!stripe) {
return responses.error(res, 400, 'Bad Request', 'Stripe is not configured')(new Error('Stripe is not configured'));
}
const sig = req.headers['stripe-signature'];
const { webhookSecret } = config.stripe;
// req.id is injected by the global requestId middleware (lib/middlewares/requestId.js).
// Propagate it through to enable end-to-end traceability across webhook log lines.
const requestId = req.id;
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
} catch (err) {
return responses.error(res, 400, 'Bad Request', 'Webhook signature verification failed')(err);
}
logger.info('[billing.webhook] received', { type: event.type, id: event.id, requestId });
try {
switch (event.type) {
case 'checkout.session.completed':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleCheckoutSessionCompleted(e),
{ requestId },
);
break;
case 'customer.subscription.created':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleSubscriptionCreated(e.data.object, e),
{ requestId },
);
break;
case 'customer.subscription.updated':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleSubscriptionUpdated(e.data.object, e),
{ requestId },
);
break;
case 'customer.subscription.deleted':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleSubscriptionDeleted(e.data.object, e),
{ requestId },
);
break;
case 'invoice.payment_failed':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleInvoicePaymentFailed(e.data.object, e),
{ requestId },
);
break;
case 'invoice.payment_succeeded':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleInvoicePaymentSucceeded(e.data.object, e),
{ requestId },
);
break;
case 'charge.refunded':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleChargeRefunded(e.data.object),
{ requestId },
);
break;
case 'customer.deleted':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleCustomerDeleted(e.data.object, e),
{ requestId },
);
break;
case 'charge.dispute.created':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleChargeDisputeCreated(e.data.object, e),
{ requestId },
);
break;
case 'charge.dispute.funds_withdrawn':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleChargeDisputeFundsWithdrawn(e.data.object, e),
{ requestId },
);
break;
case 'charge.dispute.funds_reinstated':
await BillingWebhookService.withIdempotency(event, (e) =>
BillingWebhookService.handleChargeDisputeFundsReinstated(e.data.object, e),
{ requestId },
);
break;
default:
logger.info('[billing.webhook] unhandled event type', { type: event.type, id: event.id, requestId });
break;
}
return res.status(200).json({ received: true });
} catch (err) {
logger.error('[billing.webhook] handler error', { error: err?.message ?? String(err), stack: err?.stack, requestId });
return responses.error(res, 500, 'Internal Server Error', 'Webhook handler failed')(err);
}
};
export default {
handleWebhook,
};