From 63f51abd84fb61287d17e9e6762399bc80d2944e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Tue, 28 Mar 2023 12:38:01 -0700 Subject: [PATCH] Adds cron endpoint to keep team subscriptions in sync (#7992) * Fix missing edge case where team is paid but needs to be updated * Reuses cron endpoint to update team subscriptions * Update cron-downgradeUsers.yml * Update payments.ts * Update cron-downgradeUsers.yml * update to 90 writes per second Stripe allow up to 100 per second --- .github/workflows/cron-downgradeUsers.yml | 5 +-- apps/web/pages/api/cron/downgradeUsers.ts | 36 +++++++++++++++++++--- packages/features/ee/teams/lib/payments.ts | 3 ++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cron-downgradeUsers.yml b/.github/workflows/cron-downgradeUsers.yml index 8e3b9411b8..c4a782f0d5 100644 --- a/.github/workflows/cron-downgradeUsers.yml +++ b/.github/workflows/cron-downgradeUsers.yml @@ -1,11 +1,12 @@ name: Cron - downgradeUsers on: + workflow_dispatch: # "Scheduled workflows run on the latest commit on the default or base branch." # — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule schedule: - # Runs “At minute 0, 15, 30, and 45.” (see https://crontab.guru) - - cron: "0,15,30,45 * * * *" + # Runs “Every month at 1st (see https://crontab.guru) + - cron: "0 0 1 * *" jobs: cron-downgradeUsers: env: diff --git a/apps/web/pages/api/cron/downgradeUsers.ts b/apps/web/pages/api/cron/downgradeUsers.ts index 63f270e975..a0a7995176 100644 --- a/apps/web/pages/api/cron/downgradeUsers.ts +++ b/apps/web/pages/api/cron/downgradeUsers.ts @@ -1,5 +1,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; +import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/lib/payments"; +import prisma from "@calcom/prisma"; + export default async function handler(req: NextApiRequest, res: NextApiResponse) { const apiKey = req.headers.authorization || req.query.apiKey; if (process.env.CRON_API_KEY !== apiKey) { @@ -11,10 +14,35 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return; } - /** - * TODO: - * Remove this endpoint - */ + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const pageSize = 90; // Adjust this value based on the total number of teams and the available processing time + let pageNumber = 0; + + while (true) { + const teams = await prisma.team.findMany({ + where: { + slug: { + not: null, + }, + }, + select: { + id: true, + }, + skip: pageNumber * pageSize, + take: pageSize, + }); + + if (teams.length === 0) { + break; + } + + for (const team of teams) { + await updateQuantitySubscriptionFromStripe(team.id); + await delay(100); // Adjust the delay as needed to avoid rate limiting + } + + pageNumber++; + } res.json({ ok: true }); } diff --git a/packages/features/ee/teams/lib/payments.ts b/packages/features/ee/teams/lib/payments.ts index 6b2535c53b..d7197eab87 100644 --- a/packages/features/ee/teams/lib/payments.ts +++ b/packages/features/ee/teams/lib/payments.ts @@ -98,6 +98,9 @@ export const updateQuantitySubscriptionFromStripe = async (teamId: number) => { await stripe.subscriptions.update(subscriptionId, { items: [{ quantity: team.members.length, id: subscriptionItemId }], }); + console.info( + `Updated subscription ${subscriptionId} for team ${teamId} to ${team.members.length} seats.` + ); } catch (error) { let message = "Unknown error on updateQuantitySubscriptionFromStripe"; if (error instanceof Error) message = error.message;