Skip to content

Commit

Permalink
Merge pull request #296 from giselles-ai/feat/charge-last-month-usage…
Browse files Browse the repository at this point in the history
…-on-subscription-deletion

feat(billing): Implement last month usage charging on subscription cancellation
  • Loading branch information
gentamura authored Feb 7, 2025
2 parents 36bcd98 + 0ff5641 commit 6042e96
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 17 deletions.
21 changes: 21 additions & 0 deletions app/webhooks/stripe/handle-invoice-creation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { stripe } from "@/services/external/stripe";
import type Stripe from "stripe";

export async function handleInvoiceCreation(invoice: Stripe.Invoice) {
if (!invoice.subscription || typeof invoice.subscription !== "string") {
throw new Error(
"Invoice is missing a subscription ID. Please check the invoice data.",
);
}

const subscription = await stripe.subscriptions.retrieve(
invoice.subscription,
);

if (subscription.status !== "canceled") {
return;
}

// When a subscription is canceled, we should charge for usage-based billing from the previous billing cycle. The final invoice, which includes these charges, will be automatically created but will not be processed for payment. Therefore, we need to handle this case manually.
await stripe.invoices.pay(invoice.id);
}
20 changes: 3 additions & 17 deletions app/webhooks/stripe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { stripe } from "@/services/external/stripe";
import { upsertSubscription } from "@/services/external/stripe/actions/upsert-subscription";
import { reportUserSeatUsage } from "@/services/usage-based-billing";
import type Stripe from "stripe";
import { handleInvoiceCreation } from "./handle-invoice-creation";
import { handleSubscriptionCancellation } from "./handle-subscription-cancellation";

const relevantEvents = new Set([
Expand Down Expand Up @@ -91,28 +92,13 @@ export async function POST(req: Request) {
);
}
await handleSubscriptionCancellation(event.data.object);
await upsertSubscription(event.data.object.id);
break;

case "invoice.created":
console.log(`🔔 Invoice created: ${event.data.object.id}`);

// TODO: Skip for now - will be handled when implementing subscription cancellation invoice processing
if (
event.data.object.subscription &&
typeof event.data.object.subscription === "string"
) {
const subscriptionId = event.data.object.subscription;
const subscription =
await stripe.subscriptions.retrieve(subscriptionId);

if (subscription.status === "canceled") {
console.log(
"Skipping processing for canceled subscription invoice: ",
subscriptionId,
);
break;
}
}
await handleInvoiceCreation(event.data.object);
break;

default:
Expand Down

0 comments on commit 6042e96

Please sign in to comment.