Skip to content
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
b9e6a40
initial free trial structure
pepeladeira Mar 24, 2026
2fca55d
update trial limits
pepeladeira Mar 24, 2026
b445340
Merge branch 'main' into free-trial
pepeladeira Mar 24, 2026
6e0fe71
trial layout improvements
pepeladeira Mar 24, 2026
593ff86
feedback improvements
pepeladeira Mar 25, 2026
7c825e1
cron trial emails
pepeladeira Mar 25, 2026
c0265fd
email flows tests
pepeladeira Mar 25, 2026
4e9b097
Merge branch 'main' into free-trial
pepeladeira Mar 25, 2026
9dde927
small tweaks
pepeladeira Mar 25, 2026
3202ca2
advanced downgrade
pepeladeira Mar 25, 2026
4c42633
hit trial limit modal
pepeladeira Mar 25, 2026
7b46dec
Merge branch 'main' into free-trial
pepeladeira Mar 25, 2026
f832bec
subscriptionCancelAtPeriodEnd and planPeriod
pepeladeira Mar 25, 2026
72bccff
plan-usage subscription canceled
pepeladeira Mar 26, 2026
f8eb8ee
Merge branch 'main' into free-trial
pepeladeira Mar 26, 2026
d718a93
billing trial playwright
pepeladeira Mar 26, 2026
bcb1ab1
playwright urls
pepeladeira Mar 26, 2026
8a4d7e6
mock redirectToCheckout
pepeladeira Mar 26, 2026
d209ee0
Merge branch 'main' into free-trial
pepeladeira Mar 26, 2026
1de7561
use serverless-redis-http for Upstash Redis in E2E
pepeladeira Mar 26, 2026
457528e
Merge branch 'main' into free-trial
pepeladeira Mar 26, 2026
715422d
fix build error
pepeladeira Mar 26, 2026
6cead33
build errors
pepeladeira Mar 26, 2026
6f7a3e7
fix broken tests
pepeladeira Mar 26, 2026
bc9b876
broken e2e tests
pepeladeira Mar 26, 2026
b9f02c7
fix broken tests
pepeladeira Mar 26, 2026
650e671
Merge branch 'main' into free-trial
pepeladeira Mar 27, 2026
24015e2
Merge branch 'main' into free-trial
pepeladeira Mar 27, 2026
378791d
more billing e2e tests
pepeladeira Mar 27, 2026
3961b7a
fix Stripe types, rename fields, add more PlanPeriod enums
steven-tey Mar 30, 2026
ea04070
Merge branch 'main' into free-trial
steven-tey Mar 30, 2026
4f6b412
Update approve-partner-application-modal.tsx
steven-tey Mar 30, 2026
96e65c3
move trial emails to folder
steven-tey Mar 30, 2026
43dd9cf
limit → cap, kind → resource
steven-tey Mar 30, 2026
7f3c6a5
Merge branch 'main' into free-trial
pepeladeira Mar 30, 2026
52fc8c2
email cron improvements
pepeladeira Mar 30, 2026
c2cda11
stripe backfill script
pepeladeira Mar 30, 2026
3ba1732
free trial behind feature-flag
pepeladeira Mar 30, 2026
cbd8820
Merge branch 'main' into free-trial
pepeladeira Mar 31, 2026
2adcaf8
coderabbit improvements
pepeladeira Mar 31, 2026
90b6c17
coderabbit improvements
pepeladeira Mar 31, 2026
d889114
codereview improvements
pepeladeira Mar 31, 2026
ccb74c6
codereview improvements
pepeladeira Apr 1, 2026
87573f7
Merge branch 'main' into free-trial
pepeladeira Apr 1, 2026
fc633bf
Workspace onboarding updates
marcusljf Apr 17, 2026
eaccfca
Merge branch 'main' into free-trial
pepeladeira Apr 17, 2026
4f347ec
remove free trial feature flag
pepeladeira Apr 17, 2026
2d1ea5d
code improvements
pepeladeira Apr 17, 2026
9eaa8d3
code improvements
pepeladeira Apr 17, 2026
328b002
code improvements
pepeladeira Apr 17, 2026
72a7bea
Merge branch 'main' into free-trial
pepeladeira Apr 17, 2026
805b4c4
Merge branch 'main' into free-trial
pepeladeira Apr 17, 2026
e7af47e
Merge branch 'main' into free-trial
pepeladeira Apr 17, 2026
4e07dda
code improvements
pepeladeira Apr 17, 2026
d75d29d
fix build error
pepeladeira Apr 17, 2026
4e7b379
fix build error
pepeladeira Apr 17, 2026
cf38cb1
Merge branch 'main' into free-trial
steven-tey Apr 17, 2026
f75a33a
update onboarding copy
steven-tey Apr 17, 2026
787e2c2
small touch-ups
steven-tey Apr 18, 2026
4e29590
layout improvements
steven-tey Apr 18, 2026
0a4f1a2
fix updateWorkspacePlan, rearrange AppSidebarNav
steven-tey Apr 18, 2026
d58ec63
Update product-selector.tsx
steven-tey Apr 18, 2026
e927340
dynamic NAV_GROUPS, fix ts error
steven-tey Apr 18, 2026
c85e7d5
add support for switching periods
steven-tey Apr 18, 2026
cb7e076
wouldLoseAdvancedFeatures
steven-tey Apr 18, 2026
f138943
losesPartnerAccess / losesAdvancedFeatures confirmation modals
steven-tey Apr 18, 2026
3074a46
fix playwright, fix planPeriod
steven-tey Apr 18, 2026
7d48d96
add Dub Partners onboarding tests, fix Go to Dub → View workspace
steven-tey Apr 18, 2026
29c2312
fix playwright
steven-tey Apr 18, 2026
ec5c5d1
fix tests
steven-tey Apr 18, 2026
681609e
Update onboarding-dub-partners.spec.ts
steven-tey Apr 18, 2026
7f0e777
add useOnboardingTrialVariant + plausible tracking
steven-tey Apr 18, 2026
050fb08
fix useOnboardingTrialVariant
steven-tey Apr 18, 2026
dce3f52
Update use-onboarding-trial-variant.ts
steven-tey Apr 18, 2026
e167f9e
fix playwright tests for Dub Partners onboarding
steven-tey Apr 18, 2026
3719903
upgrade plausible
steven-tey Apr 18, 2026
3d8e811
use custom properties for plausible tracking
steven-tey Apr 18, 2026
5022c0d
move Started Onboarding tracking to NextButton
steven-tey Apr 18, 2026
5880a76
use init.customProperties instead
steven-tey Apr 19, 2026
01684fc
fix window build error
pepeladeira Apr 19, 2026
c9d878c
fix onboarding e2e tests
pepeladeira Apr 19, 2026
44f6477
Merge branch 'main' into free-trial
pepeladeira Apr 19, 2026
44aec60
Merge branch 'main' into free-trial
steven-tey Apr 20, 2026
7974ee2
Update onboarding-dub-partners.spec.ts
steven-tey Apr 20, 2026
b1bfe3b
Merge branch 'main' into free-trial
steven-tey Apr 20, 2026
21c3072
fix flaky tests
pepeladeira Apr 20, 2026
bb01fd0
Merge branch 'main' into free-trial
pepeladeira Apr 20, 2026
d389ee2
fix flaky dub links tests
pepeladeira Apr 20, 2026
59422fa
change trial test cta expect
pepeladeira Apr 20, 2026
5d17f94
add data-testid
pepeladeira Apr 20, 2026
ac9eded
Merge branch 'main' into free-trial
steven-tey Apr 20, 2026
187bd22
remove planTier for now
steven-tey Apr 20, 2026
ecb51b4
fix CTA text
steven-tey Apr 20, 2026
ab43f00
update TRIAL_LIMITS
steven-tey Apr 20, 2026
4aedf70
Update trial-limits.ts
steven-tey Apr 20, 2026
4ff339a
Merge branch 'main' into free-trial
steven-tey Apr 21, 2026
cebc741
improve throwIfTrialProgramEnrollmentLimitExceeded
steven-tey Apr 21, 2026
9234bfb
track program creation event
steven-tey Apr 21, 2026
b058d62
Merge branch 'main' into free-trial
steven-tey Apr 21, 2026
18d05de
Update start-paid-plan-modal.tsx
steven-tey Apr 21, 2026
d0cf683
Merge branch 'main' into free-trial
steven-tey Apr 21, 2026
5aa9603
fix trial emails – send TrialStartedEmail right away, send UpgradeEma…
steven-tey Apr 21, 2026
7c060e3
fix email tests
steven-tey Apr 21, 2026
949fb7c
simplify emails
steven-tey Apr 21, 2026
4843873
fix tests
steven-tey Apr 21, 2026
4dba3df
more changes
steven-tey Apr 21, 2026
c8071c8
Update create-program.ts
steven-tey Apr 22, 2026
3dbf0a4
Merge branch 'main' into free-trial
steven-tey Apr 22, 2026
1f2dd3c
update email flows
steven-tey Apr 22, 2026
3fe6e6e
stash
steven-tey Apr 22, 2026
ec95387
Merge branch 'main' into free-trial
steven-tey Apr 22, 2026
b1eb50e
simplify Dub trial email
steven-tey Apr 23, 2026
28cbfd2
Update checkout-session-completed.ts
steven-tey Apr 23, 2026
a57f790
address coderabbit feedback
steven-tey Apr 23, 2026
71d6e00
address more coderabbit feedback
steven-tey Apr 23, 2026
50c00a1
Update accept-program-invite.ts
steven-tey Apr 23, 2026
3c39ac1
improve emails
steven-tey Apr 23, 2026
54d53e8
update billingCycleEndsAt
steven-tey Apr 23, 2026
0a37f61
final fixes
steven-tey Apr 23, 2026
481e454
refactor email types: consolidate TrialMarketingEmailProps into a sin…
devkiran Apr 23, 2026
fa15c8f
Merge branch 'main' into free-trial
steven-tey Apr 23, 2026
49793e6
add claim .dub.link subdomain feature
steven-tey Apr 23, 2026
7369ccd
free-trial help article
steven-tey Apr 23, 2026
962c090
simplify UpgradeEmail
steven-tey Apr 23, 2026
fe14cb2
improve stripe checkout portal, fix trial cancellation flow
steven-tey Apr 23, 2026
7426993
avoid duplicate banner
steven-tey Apr 23, 2026
a85712c
remove payment_method_options
steven-tey Apr 23, 2026
5c86262
fix updateWorkspacePlan
steven-tey Apr 23, 2026
75de775
improve cancellation UX
steven-tey Apr 23, 2026
56a4418
improve resubscribe UX
steven-tey Apr 23, 2026
47ae6bf
revert to use current_period_end
steven-tey Apr 23, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/playwright.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ jobs:
- name: Check out code
uses: actions/checkout@v4

# Linux runners do not always resolve *.localhost reliably; E2E uses partners + app subdomains.
- name: Map *.localhost subdomains to loopback
run: echo '127.0.0.1 partners.localhost app.localhost' | sudo tee -a /etc/hosts

- name: Start PlanetScale simulator
run: |
docker run -d --name ps-http-sim \
Expand Down
66 changes: 66 additions & 0 deletions apps/web/app/(ee)/api/cron/trial-emails/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { qstash } from "@/lib/cron";
import { withCron } from "@/lib/cron/with-cron";
import { runTrialEmailCron } from "@/lib/email/run-trial-email-cron";
import { prisma } from "@dub/prisma";
import { APP_DOMAIN_WITH_NGROK } from "@dub/utils";
import * as z from "zod/v4";
import { logAndRespond } from "../utils";

export const dynamic = "force-dynamic";

const QSTASH_CONTINUATION_DELAY_SECONDS = 2;

const postBodySchema = z.object({
startingAfter: z.string(),
});

/**
* Sends the paid-plan trial marketing sequence (see `lib/email/trial-email-schedule.ts`).
* Runs on a schedule (cron). Eligibility is `trialEndsAt` in the future + `SentEmail` dedupe.
*
* Complements the generic welcome email (`/api/cron/welcome-user`): free-only signups get welcome only;
* users who start a trial get welcome (QStash) + these emails when due.
*/

async function executeTrialEmailCronPage(startingAfter?: string) {
const now = new Date();
const result = await runTrialEmailCron({
now,
prisma,
startingAfter,
});

if (result.workspaceCount === 0) {
if (startingAfter) {
return logAndRespond(
"Trial email cron: no workspaces on continuation page (done).",
);
}

return logAndRespond("No workspaces in active trial. Skipping.");
}

if (result.hasMore && result.nextStartingAfter) {
await qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/trial-emails`,
method: "POST",
delay: QSTASH_CONTINUATION_DELAY_SECONDS,
body: {
startingAfter: result.nextStartingAfter,
},
});
}

return logAndRespond(
`Trial email cron: sent ${result.sentCount} email(s) for ${result.workspaceCount} workspace(s)${result.hasMore ? "; next page enqueued." : "."}`,
);
}

// GET /api/cron/trial-emails
export const GET = withCron(async () => executeTrialEmailCronPage(undefined));

// POST /api/cron/trial-emails (QStash continuation)
export const POST = withCron(async ({ rawBody }) => {
const { startingAfter } = postBodySchema.parse(JSON.parse(rawBody));
return await executeTrialEmailCronPage(startingAfter);
});
3 changes: 3 additions & 0 deletions apps/web/app/(ee)/api/cron/welcome-user/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export const dynamic = "force-dynamic";
/*
This route is used to send a welcome email to new users + subscribe them to the corresponding Resend audience
It is called by QStash 45 minutes after a user is created.

Trial sequence: users who later start a paid-plan trial also receive marketing emails from
`/api/cron/trial-emails` when due; that flow is additive (this welcome is not skipped).
*/
export async function POST(req: Request) {
try {
Expand Down
81 changes: 56 additions & 25 deletions apps/web/app/(ee)/api/stripe/webhook/checkout-session-completed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,31 @@ import { sendBatchEmail } from "@dub/email";
import UpgradeEmail from "@dub/email/templates/upgrade-email";
import { prisma } from "@dub/prisma";
import { Program, User } from "@dub/prisma/client";
import { getPlanAndTierFromPriceId, log, prettyPrint } from "@dub/utils";
import {
getPlanAndTierFromPriceId,
getWorkspaceLimitsForStripeSubscriptionStatus,
log,
prettyPrint,
} from "@dub/utils";
import Stripe from "stripe";
import { getPlanPeriodFromStripeSubscription } from "./utils/stripe-plan-period";

export async function checkoutSessionCompleted(event: Stripe.Event) {
const checkoutSession = event.data.object as Stripe.Checkout.Session;

if (
checkoutSession.mode === "setup" ||
checkoutSession.payment_status !== "paid"
) {
return "Session is setup mode or not paid, skipping...";
if (checkoutSession.mode === "setup") {
return "Session is setup mode, skipping...";
}

if (checkoutSession.mode === "subscription") {
if (
checkoutSession.payment_status !== "paid" &&
checkoutSession.payment_status !== "no_payment_required"
) {
return "Subscription checkout session not completed (payment status), skipping...";
}
} else {
return `Session mode ${checkoutSession.mode} is not handled here, skipping...`;
}
Comment thread
pepeladeira marked this conversation as resolved.

if (
Expand All @@ -44,9 +58,20 @@ export async function checkoutSessionCompleted(event: Stripe.Event) {
return `Invalid price ID in checkout.session.completed event: ${priceId}`;
}

const limits = getWorkspaceLimitsForStripeSubscriptionStatus({
planLimits: plan.limits,
subscriptionStatus: subscription.status,
});

const trialEndsAt =
subscription.status === "trialing" && subscription.trial_end
? new Date(subscription.trial_end * 1000)
: null;

const stripeId = checkoutSession.customer.toString();
const workspaceId = checkoutSession.client_reference_id;
const planName = plan.name.toLowerCase();
const planPeriod = getPlanPeriodFromStripeSubscription(subscription);

// when the workspace subscribes to a plan, set their stripe customer ID
// in the database for easy identification in future webhook events
Expand All @@ -61,17 +86,19 @@ export async function checkoutSessionCompleted(event: Stripe.Event) {
billingCycleStart: new Date().getDate(),
plan: planName,
planTier: planTier,
usageLimit: plan.limits.clicks,
linksLimit: plan.limits.links,
payoutsLimit: plan.limits.payouts,
domainsLimit: plan.limits.domains,
aiLimit: plan.limits.ai,
tagsLimit: plan.limits.tags,
foldersLimit: plan.limits.folders,
groupsLimit: plan.limits.groups,
networkInvitesLimit: plan.limits.networkInvites,
usersLimit: plan.limits.users,
usageLimit: limits.clicks,
linksLimit: limits.links,
payoutsLimit: limits.payouts,
domainsLimit: limits.domains,
aiLimit: limits.ai,
tagsLimit: limits.tags,
foldersLimit: limits.folders,
groupsLimit: limits.groups,
networkInvitesLimit: limits.networkInvites,
usersLimit: limits.users,
trialEndsAt,
paymentFailedAt: null,
...(planPeriod !== undefined && { planPeriod }),
},
select: {
plan: true,
Expand Down Expand Up @@ -122,15 +149,19 @@ export async function checkoutSessionCompleted(event: Stripe.Event) {
variant: "marketing",
})),
),
// enable dub.link premium default domain for the workspace
prisma.defaultDomains.update({
where: {
projectId: workspaceId,
},
data: {
dublink: true,
},
}),
// enable dub.link premium default domain for the workspace (not during billing trial)
...(subscription.status !== "trialing"
? [
prisma.defaultDomains.update({
where: {
projectId: workspaceId,
},
data: {
dublink: true,
},
}),
]
: []),
Comment thread
pepeladeira marked this conversation as resolved.
Outdated
// expire tokens cache
tokenCache.expireMany({
hashedKeys: workspace.restrictedTokens.map(({ hashedKey }) => hashedKey),
Expand Down
117 changes: 88 additions & 29 deletions apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { pauseOrCancelCampaignsForProgramOnPlanDowngrade } from "@/lib/api/campaigns/pause-campaigns-on-plan-downgrade";
import { deleteWorkspaceFolders } from "@/lib/api/folders/delete-workspace-folders";
import { linkCache } from "@/lib/api/links/cache";
import { includeProgramEnrollment } from "@/lib/api/links/include-program-enrollment";
import { includeTags } from "@/lib/api/links/include-tags";
import { stripAdvancedRewardModifiersForProgram } from "@/lib/api/partners/strip-advanced-reward-modifiers";
import { deactivateProgram } from "@/lib/api/programs/deactivate-program";
import { tokenCache } from "@/lib/auth/token-cache";
import { isBlacklistedEmail } from "@/lib/edge-config/is-blacklisted-email";
import { sendAdvancedDowngradeNoticeEmailIfNeeded } from "@/lib/email/send-advanced-downgrade-notice-email";
import {
leftAdvancedPlan,
wouldLoseAdvancedRewardLogic,
} from "@/lib/plans/has-advanced-features";
import { stripe } from "@/lib/stripe";
import { recordLink } from "@/lib/tinybird";
import { webhookCache } from "@/lib/webhook/cache";
Expand All @@ -27,10 +34,12 @@ export async function customerSubscriptionDeleted(event: Stripe.Event) {
},
select: {
id: true,
name: true,
slug: true,
plan: true,
planTier: true,
foldersUsage: true,
subscriptionCanceledAt: true,
paymentFailedAt: true,
payoutsLimit: true,
defaultProgramId: true,
Expand Down Expand Up @@ -71,22 +80,32 @@ export async function customerSubscriptionDeleted(event: Stripe.Event) {
return `Workspace with Stripe ID ${stripeId} not found in customer.subscription.deleted callback.`;
}

// Check if the customer has another active subscription
const { data: activeSubscriptions } = await stripe.subscriptions.list({
customer: stripeId,
status: "active",
});
// Check if the customer has another subscription still in force (active or trialing)
const [{ data: activeSubscriptions }, { data: trialingSubscriptions }] =
await Promise.all([
stripe.subscriptions.list({
customer: stripeId,
status: "active",
}),
stripe.subscriptions.list({
customer: stripeId,
status: "trialing",
}),
]);

const fallbackSubscription =
activeSubscriptions[0] ?? trialingSubscriptions[0];

if (activeSubscriptions.length > 0) {
const activeSubscription = activeSubscriptions[0];
const priceId = activeSubscription.items.data[0].price.id;
if (fallbackSubscription) {
const priceId = fallbackSubscription.items.data[0].price.id;

await updateWorkspacePlan({
workspace,
priceId,
subscription: fallbackSubscription,
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return `Workspace ${workspace.slug} has another active subscription; updated plan.`;
return `Workspace ${workspace.slug} has another subscription; updated plan.`;
}

const workspaceLinks = workspace.links;
Expand All @@ -96,27 +115,33 @@ export async function customerSubscriptionDeleted(event: Stripe.Event) {
workspaceUsers.filter(({ email }) => email).map(({ email }) => email!),
);

await Promise.allSettled([
prisma.project.update({
where: {
stripeId,
},
data: {
plan: "free",
usageLimit: FREE_PLAN.limits.clicks!,
linksLimit: FREE_PLAN.limits.links!,
payoutsLimit: FREE_PLAN.limits.payouts!,
domainsLimit: FREE_PLAN.limits.domains!,
aiLimit: FREE_PLAN.limits.ai!,
tagsLimit: FREE_PLAN.limits.tags!,
foldersLimit: FREE_PLAN.limits.folders!,
groupsLimit: FREE_PLAN.limits.groups!,
networkInvitesLimit: FREE_PLAN.limits.networkInvites!,
usersLimit: FREE_PLAN.limits.users!,
paymentFailedAt: null,
},
}),
await prisma.project.update({
where: {
stripeId,
},
data: {
plan: "free",
trialEndsAt: null,
planPeriod: null,
billingCycleEndsAt: null,
subscriptionCanceledAt: workspace.subscriptionCanceledAt
? undefined
: new Date(),
usageLimit: FREE_PLAN.limits.clicks!,
linksLimit: FREE_PLAN.limits.links!,
payoutsLimit: FREE_PLAN.limits.payouts!,
domainsLimit: FREE_PLAN.limits.domains!,
aiLimit: FREE_PLAN.limits.ai!,
tagsLimit: FREE_PLAN.limits.tags!,
foldersLimit: FREE_PLAN.limits.folders!,
groupsLimit: FREE_PLAN.limits.groups!,
networkInvitesLimit: FREE_PLAN.limits.networkInvites!,
usersLimit: FREE_PLAN.limits.users!,
paymentFailedAt: null,
},
});

await Promise.allSettled([
// disable dub.link premium default domain for the workspace
prisma.defaultDomains.update({
where: {
Expand Down Expand Up @@ -229,5 +254,39 @@ export async function customerSubscriptionDeleted(event: Stripe.Event) {
await deactivateProgram(workspace.defaultProgramId);
}

if (
workspace.defaultProgramId &&
wouldLoseAdvancedRewardLogic({
currentPlan: workspace.plan,
newPlan: "free",
})
) {
await Promise.all([
stripAdvancedRewardModifiersForProgram({
programId: workspace.defaultProgramId,
}),
pauseOrCancelCampaignsForProgramOnPlanDowngrade({
programId: workspace.defaultProgramId,
}),
]);
}

const owner = workspaceUsers[0];
if (
owner &&
leftAdvancedPlan({
currentPlan: workspace.plan,
newPlan: "free",
})
) {
await sendAdvancedDowngradeNoticeEmailIfNeeded({
projectId: workspace.id,
dedupeType: `advanced-downgrade-notice:subscription-deleted:${subscriptionDeleted.id}`,
ownerEmail: owner.email,
workspaceName: workspace.name,
workspaceSlug: workspace.slug,
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

return `Workspace ${workspace.slug} subscription deleted; downgraded to free.`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export async function customerSubscriptionUpdated(event: Stripe.Event) {
},
select: {
id: true,
name: true,
slug: true,
plan: true,
planTier: true,
paymentFailedAt: true,
Expand Down Expand Up @@ -59,6 +61,7 @@ export async function customerSubscriptionUpdated(event: Stripe.Event) {
await updateWorkspacePlan({
workspace,
priceId,
subscription: subscriptionUpdated,
});

const subscriptionCanceled =
Expand Down
Loading
Loading