diff --git a/packages/api/src/controllers/stripe.ts b/packages/api/src/controllers/stripe.ts index d5ec239ef9..9ed3bb1fc1 100644 --- a/packages/api/src/controllers/stripe.ts +++ b/packages/api/src/controllers/stripe.ts @@ -45,8 +45,21 @@ export const reportUsage = async (req, adminToken) => { } ); + const [oldProPlanUsers] = await db.user.find( + [ + sql`users.data->>'oldProPlan' = true AND users.data->>'stripeProductId' IN ('hacker_1','prod_O9XuIjn7EqYRVW')`, + ], + { + limit: 9999999999, + useReplica: true, + } + ); + + // Join oldProPlanUsers and users + const payAsYouGoUsers = [...users, ...oldProPlanUsers]; + let updatedUsers = []; - for (const user of users) { + for (const user of payAsYouGoUsers) { const userSubscription = await req.stripe.subscriptions.retrieve( user.stripeCustomerSubscriptionId ); @@ -425,7 +438,135 @@ app.post("/migrate-personal-users", async (req, res) => { stripeCustomerId: user.stripeCustomerId, stripeProductId: "prod_O9XuIjn7EqYRVW", stripeCustomerSubscriptionId: subscription.id, - stripeCustomerPaymentMethodId: null, + }); + + migratedUsers.push(user.email); + + await sleep(200); + } + } + } + + cursor = newCursor; + } + + res.json(migratedUsers); +}); + +// Migrate Pro users to hacker with pay as you go +app.post("/migrate-pro-users", async (req, res) => { + if (req.config.stripeSecretKey != req.body.stripeSecretKey) { + res.status(403); + return res.json({ errors: ["unauthorized"] }); + } + + const batchSize = 100; + let cursor = null; + let users = []; + let keepGoing = true; + + let migratedUsers = []; + + while (keepGoing) { + const [currentUsers, newCursor] = await db.user.find( + [sql`users.data->>'stripeProductId' = 'prod_1'`], + { + cursor: cursor, + limit: batchSize, + useReplica: false, + } + ); + + if (currentUsers.length === 0) { + keepGoing = false; + continue; + } + + users = users.concat(currentUsers); + + for (let index = 0; index < currentUsers.length; index++) { + let user = currentUsers[index]; + + const { data } = await req.stripe.customers.list({ + email: user.email, + }); + + if (data.length > 0) { + if ( + user.stripeProductId === "prod_1" && + user.newStripeProductId !== "growth_1" && + user.newStripeProductId !== "scale_1" + ) { + const items = await req.stripe.prices.list({ + lookup_keys: products["prod_O9XuIjn7EqYRVW"].lookupKeys, + }); + let subscription; + + try { + subscription = await req.stripe.subscriptions.retrieve( + user.stripeCustomerSubscriptionId + ); + } catch (e) { + console.log(` + Unable to migrate pro user - subscription not found for user=${user.id} email=${user.email} subscriptionId=${user.stripeCustomerSubscriptionId} + `); + continue; + } + + if (subscription.status != "active") { + console.log(` + Unable to migrate pro user - user=${user.id} has a status=${subscription.status} subscription + `); + continue; + } + + const subscriptionItems = await req.stripe.subscriptionItems.list({ + subscription: user.stripeCustomerSubscriptionId, + }); + + let payAsYouGoItems = []; + if (products[user.stripeProductId].payAsYouGo) { + // Get the prices for the pay as you go product + const payAsYouGoPrices = await req.stripe.prices.list({ + lookup_keys: products["pay_as_you_go_1"].lookupKeys, + }); + + // Map the prices to the additional items array + payAsYouGoItems = payAsYouGoPrices.data.map((item) => ({ + price: item.id, + })); + } + + subscription = await req.stripe.subscriptions.update( + user.stripeCustomerSubscriptionId, + { + billing_cycle_anchor: "now", + cancel_at_period_end: false, + items: [ + ...subscriptionItems.data.map((item) => { + // Check if the item is metered + const isMetered = + item.price.recurring.usage_type === "metered"; + return { + id: item.id, + deleted: true, + clear_usage: isMetered ? true : undefined, // If metered, clear usage + price: item.price.id, + }; + }), + ...items.data.map((item) => ({ + price: item.id, + })), + ...payAsYouGoItems, + ], + } + ); + + await db.user.update(user.id, { + stripeCustomerId: user.stripeCustomerId, + stripeProductId: "prod_O9XuIjn7EqYRVW", + stripeCustomerSubscriptionId: subscription.id, + oldProPlan: true, }); migratedUsers.push(user.email); diff --git a/packages/api/src/schema/db-schema.yaml b/packages/api/src/schema/db-schema.yaml index 14701e038b..275accae59 100644 --- a/packages/api/src/schema/db-schema.yaml +++ b/packages/api/src/schema/db-schema.yaml @@ -1068,6 +1068,9 @@ components: - prod_O9XtHhI6rbTT1B - prod_O9XtcfOSMjSD5L - prod_O9XuWMU1Up6QKf + oldProPlan: + type: boolean + default: false stripeCustomerId: type: string example: cus_Jv6KvgT0DCH8HU diff --git a/packages/www/public/sitemap-0.xml b/packages/www/public/sitemap-0.xml index 1cae1fdf40..91c9097e16 100644 --- a/packages/www/public/sitemap-0.xml +++ b/packages/www/public/sitemap-0.xml @@ -1,20 +1,20 @@ -https://livepeer.studiodaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/contactdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboarddaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/assetsdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/billingdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/billing/plansdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/developers/api-keysdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/developers/signing-keysdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/developers/webhooksdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/sessionsdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/stream-healthdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/streamsdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/dashboard/usagedaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/forgot-passworddaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/registerdaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/reset-passworddaily0.72023-07-06T17:50:33.992Z -https://livepeer.studio/verifydaily0.72023-07-06T17:50:33.992Z +https://livepeer.studiodaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/contactdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboarddaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/assetsdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/billingdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/billing/plansdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/developers/api-keysdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/developers/signing-keysdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/developers/webhooksdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/sessionsdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/stream-healthdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/streamsdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/dashboard/usagedaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/forgot-passworddaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/registerdaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/reset-passworddaily0.72023-07-07T16:37:14.836Z +https://livepeer.studio/verifydaily0.72023-07-07T16:37:14.836Z \ No newline at end of file