mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
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:
@@ -34,8 +34,6 @@ router.use(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(process.env.DATABASE_URL);
|
|
||||||
|
|
||||||
const validateSchema = z.object({
|
const validateSchema = z.object({
|
||||||
licenseKey: z.string(),
|
licenseKey: z.string(),
|
||||||
serverIp: z.string(),
|
serverIp: z.string(),
|
||||||
@@ -161,6 +159,7 @@ router.post("/stripe/webhook", async (c) => {
|
|||||||
"invoice.payment_succeeded",
|
"invoice.payment_succeeded",
|
||||||
"invoice.payment_failed",
|
"invoice.payment_failed",
|
||||||
"customer.subscription.deleted",
|
"customer.subscription.deleted",
|
||||||
|
"invoice.paid",
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!allowedEvents.includes(event.type)) {
|
if (!allowedEvents.includes(event.type)) {
|
||||||
@@ -169,58 +168,6 @@ router.post("/stripe/webhook", async (c) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
switch (event.type) {
|
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": {
|
case "customer.subscription.updated": {
|
||||||
const subscription = event.data.object as Stripe.Subscription;
|
const subscription = event.data.object as Stripe.Subscription;
|
||||||
|
|
||||||
@@ -231,6 +178,55 @@ router.post("/stripe/webhook", async (c) => {
|
|||||||
if (subscription.status !== "active" || customerResponse.deleted) {
|
if (subscription.status !== "active" || customerResponse.deleted) {
|
||||||
await deactivateLicense(subscription.id);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ interface LicenseEmailProps {
|
|||||||
customerName: string;
|
customerName: string;
|
||||||
licenseKey: string;
|
licenseKey: string;
|
||||||
productName: string;
|
productName: string;
|
||||||
expirationDate: Date;
|
|
||||||
features: string[];
|
features: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +27,8 @@ export const LicenseEmail = ({
|
|||||||
customerName = "John Doe",
|
customerName = "John Doe",
|
||||||
licenseKey = "1234567890",
|
licenseKey = "1234567890",
|
||||||
productName = "Dokploy",
|
productName = "Dokploy",
|
||||||
expirationDate = new Date(),
|
|
||||||
features = ["Feature 1", "Feature 2", "Feature 3"],
|
features = ["Feature 1", "Feature 2", "Feature 3"],
|
||||||
}: LicenseEmailProps): React.ReactElement => {
|
}: LicenseEmailProps): React.ReactElement => {
|
||||||
const formattedDate = expirationDate.toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@@ -69,12 +61,6 @@ export const LicenseEmail = ({
|
|||||||
<Text style={licenseKeyStyle}>{licenseKey}</Text>
|
<Text style={licenseKeyStyle}>{licenseKey}</Text>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section style={validitySection}>
|
|
||||||
<Text style={validityText}>
|
|
||||||
🗓️ Next billing date: <strong>{formattedDate}</strong>
|
|
||||||
</Text>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
<Section style={featuresContainer}>
|
<Section style={featuresContainer}>
|
||||||
<Heading as="h2" style={h2}>
|
<Heading as="h2" style={h2}>
|
||||||
🎉 Your Premium Features
|
🎉 Your Premium Features
|
||||||
|
|||||||
Reference in New Issue
Block a user