feat(licenses): enhance Stripe webhook handling for license creation

- Added support for the "invoice.paid" event to create licenses upon successful payment.
- Refactored the handling of subscription updates to improve license deactivation logic.
- Removed the expiration date from the LicenseEmail component to simplify email content.
- Improved error handling for customer retrieval and subscription status checks.
This commit is contained in:
Mauricio Siu
2025-03-23 12:58:40 -06:00
parent c8e6df4c29
commit 5180c785b4
2 changed files with 50 additions and 68 deletions

View File

@@ -34,8 +34,6 @@ router.use(
}),
);
console.log(process.env.DATABASE_URL);
const validateSchema = z.object({
licenseKey: z.string(),
serverIp: z.string(),
@@ -161,6 +159,7 @@ router.post("/stripe/webhook", async (c) => {
"invoice.payment_succeeded",
"invoice.payment_failed",
"customer.subscription.deleted",
"invoice.paid",
];
if (!allowedEvents.includes(event.type)) {
@@ -169,58 +168,6 @@ router.post("/stripe/webhook", async (c) => {
try {
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
const customerResponse = await stripe.customers.retrieve(
session.customer as string,
);
if (customerResponse.deleted) {
throw new Error("Customer was deleted");
}
const lineItems = await stripe.checkout.sessions.listLineItems(
session.id,
);
const priceId = lineItems.data[0].price?.id;
const { type, billingType } = getLicenseTypeFromPriceId(priceId!);
const license = await createLicense({
productId: session.id,
type,
billingType,
email: session.customer_details?.email!,
stripeCustomerId: customerResponse.id,
stripeSubscriptionId: session.subscription as string,
});
console.log("License created", license);
const features = getLicenseFeatures(type);
console.log("Features", features);
const emailHtml = await render(
LicenseEmail({
customerName: customerResponse.name || "Customer",
licenseKey: license.licenseKey,
productName: `Dokploy Self Hosted ${type}`,
// TODO: Add expiration date
expirationDate: new Date(),
features: features,
}),
);
await transporter.sendMail({
from: process.env.SMTP_FROM_ADDRESS,
to: license.email,
subject: "Your Dokploy License Key",
html: emailHtml,
});
break;
}
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
@@ -231,6 +178,55 @@ router.post("/stripe/webhook", async (c) => {
if (subscription.status !== "active" || customerResponse.deleted) {
await deactivateLicense(subscription.id);
}
break;
}
case "invoice.paid": {
const invoice = event.data.object as Stripe.Invoice;
if (!invoice.subscription) break;
if (invoice.billing_reason === "subscription_create") {
const customerResponse = await stripe.customers.retrieve(
invoice.customer as string,
);
if (customerResponse.deleted) {
throw new Error("Customer was deleted");
}
const subscriptionId = invoice.subscription as string;
const subscription =
await stripe.subscriptions.retrieve(subscriptionId);
const priceId = subscription.items.data[0].price.id;
const { type, billingType } = getLicenseTypeFromPriceId(priceId);
const license = await createLicense({
productId: subscriptionId,
type,
billingType,
email: customerResponse.email!,
stripeCustomerId: customerResponse.id,
stripeSubscriptionId: subscriptionId,
});
const features = getLicenseFeatures(type);
const emailHtml = await render(
LicenseEmail({
customerName: customerResponse.name || "Customer",
licenseKey: license.licenseKey,
productName: `Dokploy Self Hosted ${type}`,
features: features,
}),
);
await transporter.sendMail({
from: process.env.SMTP_FROM_ADDRESS,
to: license.email,
subject: "Your Dokploy License Key ",
html: emailHtml,
});
}
break;
}

View File

@@ -18,7 +18,6 @@ interface LicenseEmailProps {
customerName: string;
licenseKey: string;
productName: string;
expirationDate: Date;
features: string[];
}
@@ -28,15 +27,8 @@ export const LicenseEmail = ({
customerName = "John Doe",
licenseKey = "1234567890",
productName = "Dokploy",
expirationDate = new Date(),
features = ["Feature 1", "Feature 2", "Feature 3"],
}: LicenseEmailProps): React.ReactElement => {
const formattedDate = expirationDate.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
return (
<Html>
<Head />
@@ -69,12 +61,6 @@ export const LicenseEmail = ({
<Text style={licenseKeyStyle}>{licenseKey}</Text>
</Section>
<Section style={validitySection}>
<Text style={validityText}>
🗓️ Next billing date: <strong>{formattedDate}</strong>
</Text>
</Section>
<Section style={featuresContainer}>
<Heading as="h2" style={h2}>
🎉 Your Premium Features