mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-06 09:59:00 +02:00
📸 Sync stripe subscriptions with space data (#1777)
This commit is contained in:
parent
87ab11834a
commit
2fe17e7f32
6 changed files with 74 additions and 38 deletions
|
@ -1,7 +1,7 @@
|
|||
import type { Stripe } from "@rallly/billing";
|
||||
import { prisma } from "@rallly/database";
|
||||
|
||||
import { getDefaultSpace, getSpace } from "@/features/spaces/queries";
|
||||
import { getSpace } from "@/features/spaces/queries";
|
||||
import { subscriptionMetadataSchema } from "@/features/subscription/schema";
|
||||
import {
|
||||
getExpandedSubscription,
|
||||
|
@ -22,7 +22,7 @@ export async function onCustomerSubscriptionCreated(event: Stripe.Event) {
|
|||
const res = subscriptionMetadataSchema.safeParse(subscription.metadata);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error("Missing user ID");
|
||||
throw new Error("Invalid subscription metadata");
|
||||
}
|
||||
|
||||
const userId = res.data.userId;
|
||||
|
@ -39,37 +39,16 @@ export async function onCustomerSubscriptionCreated(event: Stripe.Event) {
|
|||
throw new Error(`User with ID ${userId} not found`);
|
||||
}
|
||||
|
||||
let spaceId: string;
|
||||
const space = await getSpace({ id: res.data.spaceId });
|
||||
|
||||
// The space should be in the metadata,
|
||||
// but if it's not, we fallback to the default space.
|
||||
// This is temporary while we haven't run a data migration
|
||||
// to add the spaceId to the metadata for all existing subscriptions
|
||||
if (res.data.spaceId) {
|
||||
const space = await getSpace({ id: res.data.spaceId });
|
||||
if (!space) {
|
||||
throw new Error(`Space with ID ${res.data.spaceId} not found`);
|
||||
}
|
||||
|
||||
if (space.ownerId !== userId) {
|
||||
throw new Error(
|
||||
`Space with ID ${res.data.spaceId} does not belong to user ${userId}`,
|
||||
);
|
||||
}
|
||||
|
||||
spaceId = space.id;
|
||||
} else {
|
||||
// TODO: Remove this fallback once all subscriptions have
|
||||
// a spaceId in their metadata
|
||||
const space = await getDefaultSpace({ ownerId: userId });
|
||||
|
||||
if (!space) {
|
||||
throw new Error(`Default space with owner ID ${userId} not found`);
|
||||
}
|
||||
|
||||
spaceId = space.id;
|
||||
if (space.ownerId !== userId) {
|
||||
throw new Error(
|
||||
`Space with ID ${res.data.spaceId} does not belong to user ${userId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const spaceId = space.id;
|
||||
|
||||
// If user already has a subscription, update it or replace it
|
||||
if (existingUser.subscription) {
|
||||
// Update the existing subscription with new data
|
||||
|
|
|
@ -26,7 +26,7 @@ export async function onCustomerSubscriptionUpdated(event: Stripe.Event) {
|
|||
const res = subscriptionMetadataSchema.safeParse(subscription.metadata);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error("Missing user ID");
|
||||
throw new Error("Invalid subscription metadata");
|
||||
}
|
||||
|
||||
// Update the subscription in the database
|
||||
|
|
|
@ -28,11 +28,9 @@ export async function getDefaultSpace({ ownerId }: { ownerId: string }) {
|
|||
}
|
||||
|
||||
export async function getSpace({ id }: { id: string }) {
|
||||
const space = await prisma.space.findUnique({
|
||||
return await prisma.space.findUniqueOrThrow({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
return space;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export type SubscriptionCheckoutMetadata = z.infer<
|
|||
|
||||
export const subscriptionMetadataSchema = z.object({
|
||||
userId: z.string(),
|
||||
spaceId: z.string().optional(),
|
||||
spaceId: z.string(),
|
||||
});
|
||||
|
||||
export type SubscriptionMetadata = z.infer<typeof subscriptionMetadataSchema>;
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
"./*": "./src/*.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"checkout-expiry": "dotenv -e ../../.env -- tsx ./src/scripts/checkout-expiry.ts",
|
||||
"subscription-data-sync": "dotenv -e ../../.env -- tsx ./src/scripts/subscription-data-sync.ts",
|
||||
"sync-payment-methods": "dotenv -e ../../.env -- tsx ./src/scripts/sync-payment-methods.ts",
|
||||
"checkout-expiry": "dotenv -e .env -- pnpx tsx ./src/scripts/checkout-expiry.ts",
|
||||
"subscription-data-sync": "dotenv -e .env -- pnpx tsx ./src/scripts/subscription-data-sync.ts",
|
||||
"sync-payment-methods": "dotenv -e .env -- pnpx tsx ./src/scripts/sync-payment-methods.ts",
|
||||
"sync-space-subscription": "dotenv -e .env -- pnpx tsx ./src/scripts/sync-space-subscription.ts",
|
||||
"type-check": "tsc --pretty --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
58
packages/billing/src/scripts/sync-space-subscription.ts
Normal file
58
packages/billing/src/scripts/sync-space-subscription.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { prisma } from "@rallly/database";
|
||||
|
||||
import { stripe } from "../lib/stripe";
|
||||
|
||||
(async function syncSpaceSubscription() {
|
||||
let processed = 0;
|
||||
let failed = 0;
|
||||
|
||||
const subscriptions = await prisma.subscription.findMany({
|
||||
where: {
|
||||
active: true,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.info(`🚀 Syncing ${subscriptions.length} subscriptions...`);
|
||||
|
||||
for (const subscription of subscriptions) {
|
||||
const space = await prisma.space.findFirst({
|
||||
where: {
|
||||
ownerId: subscription.userId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
console.info(`Space not found for user ${subscription.userId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await stripe.subscriptions.update(subscription.id, {
|
||||
metadata: {
|
||||
userId: subscription.userId,
|
||||
spaceId: space.id,
|
||||
},
|
||||
});
|
||||
|
||||
console.info(
|
||||
`✅ Space subscription synced for subscription ${subscription.id}`,
|
||||
);
|
||||
processed++;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`❌ Failed to sync space subscription for subscription ${subscription.id}:`,
|
||||
error,
|
||||
);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`📊 Sync complete: ${processed} processed, ${failed} failed`);
|
||||
})();
|
Loading…
Add table
Add a link
Reference in a new issue