mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-01 15:49:08 +00:00
144 lines
5.4 KiB
TypeScript
144 lines
5.4 KiB
TypeScript
/*
|
|
* This file is part of a proprietary work.
|
|
*
|
|
* Copyright (c) 2025 Fossorial, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This file is licensed under the Fossorial Commercial License.
|
|
* You may not use this file except in compliance with the License.
|
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
*
|
|
* This file is not licensed under the AGPLv3.
|
|
*/
|
|
|
|
import stripe from "#private/lib/stripe";
|
|
import privateConfig from "#private/lib/config";
|
|
import logger from "@server/logger";
|
|
import createHttpError from "http-errors";
|
|
import { response } from "@server/lib/response";
|
|
import { Request, Response, NextFunction } from "express";
|
|
import HttpCode from "@server/types/HttpCode";
|
|
import Stripe from "stripe";
|
|
import { handleCustomerCreated } from "./hooks/handleCustomerCreated";
|
|
import { handleSubscriptionCreated } from "./hooks/handleSubscriptionCreated";
|
|
import { handleSubscriptionUpdated } from "./hooks/handleSubscriptionUpdated";
|
|
import { handleCustomerUpdated } from "./hooks/handleCustomerUpdated";
|
|
import { handleSubscriptionDeleted } from "./hooks/handleSubscriptionDeleted";
|
|
import { handleCustomerDeleted } from "./hooks/handleCustomerDeleted";
|
|
|
|
export async function billingWebhookHandler(
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<any> {
|
|
let event: Stripe.Event = req.body;
|
|
const endpointSecret =
|
|
privateConfig.getRawPrivateConfig().stripe?.webhook_secret;
|
|
if (!endpointSecret) {
|
|
logger.warn(
|
|
"Stripe webhook secret is not configured. Webhook events will not be priocessed."
|
|
);
|
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, ""));
|
|
}
|
|
|
|
// Only verify the event if you have an endpoint secret defined.
|
|
// Otherwise use the basic event deserialized with JSON.parse
|
|
if (endpointSecret) {
|
|
// Get the signature sent by Stripe
|
|
const signature = req.headers["stripe-signature"];
|
|
|
|
if (!signature) {
|
|
logger.info("No stripe signature found in headers.");
|
|
return next(
|
|
createHttpError(
|
|
HttpCode.BAD_REQUEST,
|
|
"No stripe signature found in headers"
|
|
)
|
|
);
|
|
}
|
|
|
|
try {
|
|
event = stripe!.webhooks.constructEvent(
|
|
req.body,
|
|
signature,
|
|
endpointSecret
|
|
);
|
|
} catch (err) {
|
|
logger.error(`Webhook signature verification failed.`, err);
|
|
return next(
|
|
createHttpError(
|
|
HttpCode.UNAUTHORIZED,
|
|
"Webhook signature verification failed"
|
|
)
|
|
);
|
|
}
|
|
}
|
|
let subscription;
|
|
let previousAttributes;
|
|
// Handle the event
|
|
switch (event.type) {
|
|
case "customer.created":
|
|
const customer = event.data.object;
|
|
logger.info("Customer created: ", customer);
|
|
handleCustomerCreated(customer);
|
|
break;
|
|
case "customer.updated":
|
|
const customerUpdated = event.data.object;
|
|
logger.info("Customer updated: ", customerUpdated);
|
|
handleCustomerUpdated(customerUpdated);
|
|
break;
|
|
case "customer.deleted":
|
|
const customerDeleted = event.data.object;
|
|
logger.info("Customer deleted: ", customerDeleted);
|
|
handleCustomerDeleted(customerDeleted);
|
|
break;
|
|
case "customer.subscription.paused":
|
|
subscription = event.data.object;
|
|
previousAttributes = event.data.previous_attributes;
|
|
handleSubscriptionUpdated(subscription, previousAttributes);
|
|
break;
|
|
case "customer.subscription.resumed":
|
|
subscription = event.data.object;
|
|
previousAttributes = event.data.previous_attributes;
|
|
handleSubscriptionUpdated(subscription, previousAttributes);
|
|
break;
|
|
case "customer.subscription.deleted":
|
|
subscription = event.data.object;
|
|
handleSubscriptionDeleted(subscription);
|
|
break;
|
|
case "customer.subscription.created":
|
|
subscription = event.data.object;
|
|
handleSubscriptionCreated(subscription);
|
|
break;
|
|
case "customer.subscription.updated":
|
|
subscription = event.data.object;
|
|
previousAttributes = event.data.previous_attributes;
|
|
handleSubscriptionUpdated(subscription, previousAttributes);
|
|
break;
|
|
case "customer.subscription.trial_will_end":
|
|
subscription = event.data.object;
|
|
// Then define and call a method to handle the subscription trial ending.
|
|
// handleSubscriptionTrialEnding(subscription);
|
|
break;
|
|
case "entitlements.active_entitlement_summary.updated":
|
|
subscription = event.data.object;
|
|
logger.info(
|
|
`Active entitlement summary updated for ${subscription}.`
|
|
);
|
|
// Then define and call a method to handle active entitlement summary updated
|
|
// handleEntitlementUpdated(subscription);
|
|
break;
|
|
default:
|
|
// Unexpected event type
|
|
logger.info(`Unhandled event type ${event.type}.`);
|
|
}
|
|
// Return a 200 response to acknowledge receipt of the event
|
|
return response(res, {
|
|
data: null,
|
|
success: true,
|
|
error: false,
|
|
message: "Webhook event processed successfully",
|
|
status: HttpCode.CREATED
|
|
});
|
|
}
|