Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,25 @@ async function processEvent(
break;
}

case "customer.deleted": {
const customer = event.data.object as StripeSDK.Customer;
await ctx.runMutation(component.private.handleCustomerDeleted, {
stripeCustomerId: customer.id,
});
break;
}

case "customer.subscription.created": {
const subscription = event.data.object as StripeSDK.Subscription;
const item = subscription.items.data[0];

await ctx.runMutation(component.private.handleSubscriptionCreated, {
stripeSubscriptionId: subscription.id,
stripeCustomerId: subscription.customer as string,
status: subscription.status,
currentPeriodEnd: item?.current_period_end || 0,
cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
cancelAt: subscription.cancel_at || undefined,
cancelAt: subscription.cancel_at ?? undefined,
quantity: subscription.items.data[0]?.quantity ?? 1,
priceId: item?.price?.id || "",
metadata: subscription.metadata || {},
Expand All @@ -488,10 +496,11 @@ async function processEvent(

await ctx.runMutation(component.private.handleSubscriptionUpdated, {
stripeSubscriptionId: subscription.id,
stripeCustomerId: subscription.customer as string,
status: subscription.status,
currentPeriodEnd: item?.current_period_end || 0,
cancelAtPeriodEnd: subscription.cancel_at_period_end ?? false,
cancelAt: subscription.cancel_at || undefined,
cancelAt: subscription.cancel_at ?? undefined,
quantity: subscription.items.data[0]?.quantity ?? 1,
priceId: item?.price?.id || undefined,
metadata: subscription.metadata || {},
Expand All @@ -501,8 +510,11 @@ async function processEvent(

case "customer.subscription.deleted": {
const subscription = event.data.object as StripeSDK.Subscription;
const item = subscription.items.data[0];
await ctx.runMutation(component.private.handleSubscriptionDeleted, {
stripeSubscriptionId: subscription.id,
currentPeriodEnd: item?.current_period_end || undefined,
cancelAt: subscription.cancel_at ?? undefined,
});
break;
}
Expand Down
105 changes: 78 additions & 27 deletions src/component/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const handleSubscriptionCreated = mutation({
status: args.status,
currentPeriodEnd: args.currentPeriodEnd,
cancelAtPeriodEnd: cancelAtPeriodEnd,
cancelAt: args.cancelAt || undefined,
cancelAt: args.cancelAt ?? undefined,
quantity: args.quantity,
priceId: args.priceId,
metadata: metadata,
Expand Down Expand Up @@ -169,6 +169,7 @@ export const handleSubscriptionCreated = mutation({
export const handleSubscriptionUpdated = mutation({
args: {
stripeSubscriptionId: v.string(),
stripeCustomerId: v.optional(v.string()),
status: v.string(),
currentPeriodEnd: v.number(),
cancelAtPeriodEnd: v.boolean(),
Expand All @@ -186,27 +187,39 @@ export const handleSubscriptionUpdated = mutation({
)
.unique();

if (subscription) {
// Extract orgId and userId from metadata if present
const metadata = args.metadata || {};
const orgId = metadata.orgId as string | undefined;
const userId = metadata.userId as string | undefined;
const metadata = args.metadata || {};
const orgId = metadata.orgId as string | undefined;
const userId = metadata.userId as string | undefined;

const cancelAtPeriodEnd = args.cancelAtPeriodEnd ||
deriveCancelAtPeriodEnd(args.cancelAt, args.currentPeriodEnd);
const cancelAtPeriodEnd = args.cancelAtPeriodEnd ||
deriveCancelAtPeriodEnd(args.cancelAt, args.currentPeriodEnd);

if (subscription) {
await ctx.db.patch(subscription._id, {
status: args.status,
currentPeriodEnd: args.currentPeriodEnd,
cancelAtPeriodEnd: cancelAtPeriodEnd,
cancelAt: args.cancelAt || undefined,
cancelAt: args.cancelAt ?? undefined,
quantity: args.quantity,
...(args.priceId !== undefined && { priceId: args.priceId }),
// Only update metadata fields if provided
...(args.metadata !== undefined && { metadata }),
...(orgId !== undefined && { orgId }),
...(userId !== undefined && { userId }),
});
} else if (args.stripeCustomerId && args.priceId) {
await ctx.db.insert("subscriptions", {
stripeSubscriptionId: args.stripeSubscriptionId,
stripeCustomerId: args.stripeCustomerId,
status: args.status,
currentPeriodEnd: args.currentPeriodEnd,
cancelAtPeriodEnd: cancelAtPeriodEnd,
cancelAt: args.cancelAt ?? undefined,
quantity: args.quantity,
priceId: args.priceId,
metadata: metadata,
orgId: orgId,
userId: userId,
});
}

return null;
Expand All @@ -216,6 +229,8 @@ export const handleSubscriptionUpdated = mutation({
export const handleSubscriptionDeleted = mutation({
args: {
stripeSubscriptionId: v.string(),
currentPeriodEnd: v.optional(v.number()),
cancelAt: v.optional(v.number()),
},
returns: v.null(),
handler: async (ctx, args) => {
Expand All @@ -229,13 +244,39 @@ export const handleSubscriptionDeleted = mutation({
if (subscription) {
await ctx.db.patch(subscription._id, {
status: "canceled",
cancelAtPeriodEnd: false,
...(args.currentPeriodEnd !== undefined && {
currentPeriodEnd: args.currentPeriodEnd,
}),
...(args.cancelAt !== undefined && { cancelAt: args.cancelAt }),
});
}

return null;
},
});

export const handleCustomerDeleted = mutation({
args: {
stripeCustomerId: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const customer = await ctx.db
.query("customers")
.withIndex("by_stripe_customer_id", (q) =>
q.eq("stripeCustomerId", args.stripeCustomerId),
)
.unique();

if (customer) {
await ctx.db.delete(customer._id);
}

return null;
},
});

export const handleCheckoutSessionCompleted = mutation({
args: {
stripeCheckoutSessionId: v.string(),
Expand Down Expand Up @@ -290,25 +331,35 @@ export const handleInvoiceCreated = mutation({
)
.unique();

if (!existing) {
// Look up orgId/userId from the subscription if available
let orgId: string | undefined;
let userId: string | undefined;

if (args.stripeSubscriptionId) {
const subscription = await ctx.db
.query("subscriptions")
.withIndex("by_stripe_subscription_id", (q) =>
q.eq("stripeSubscriptionId", args.stripeSubscriptionId!),
)
.unique();

if (subscription) {
orgId = subscription.orgId;
userId = subscription.userId;
}
let orgId: string | undefined;
let userId: string | undefined;

if (args.stripeSubscriptionId) {
const subscription = await ctx.db
.query("subscriptions")
.withIndex("by_stripe_subscription_id", (q) =>
q.eq("stripeSubscriptionId", args.stripeSubscriptionId!),
)
.unique();

if (subscription) {
orgId = subscription.orgId;
userId = subscription.userId;
}
}

if (existing) {
await ctx.db.patch(existing._id, {
status: args.status,
amountDue: args.amountDue,
amountPaid: args.amountPaid,
...(args.stripeSubscriptionId !== undefined && {
stripeSubscriptionId: args.stripeSubscriptionId,
}),
...(orgId && !existing.orgId && { orgId }),
...(userId && !existing.userId && { userId }),
});
} else {
await ctx.db.insert("invoices", {
stripeInvoiceId: args.stripeInvoiceId,
stripeCustomerId: args.stripeCustomerId,
Expand Down