Conversation
I have not tested this yet, I'm going to bed
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
📝 WalkthroughWalkthroughRemoves trial-related logic across billing UI, subscription creation, Stripe webhooks, docs, and internal emails. Trial fields and trial-specific UI/notifications are deleted; subscription creation no longer sets trial_period_days; trial-ended email and its send API are removed; support links in several error toasts switched to mailto. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant UI as Dashboard UI
participant Srv as Server (trpc)
participant Stripe as Stripe API
UI->>Srv: Create subscription (workspace, priceId)
Note right of Srv: removed previous-subscription check\nno trial_period_days set
Srv->>Stripe: subscriptions.create({ customer, items:[{price}] })
Stripe-->>Srv: Subscription (status)
Srv-->>UI: Subscription result
Note over UI,Srv: UI no longer receives or handles trialUntil/trialing state
sequenceDiagram
autonumber
participant Stripe as Stripe Webhooks
participant API as Webhook Handler
participant Slack as Slack
Stripe->>API: customer.subscription.created
Note right of API: no trial_end gating — process when price.id & customer present
API->>Slack: Post generic signup/new-subscription message
Slack-->>API: OK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/dashboard/app/(app)/settings/billing/client.tsx (1)
93-110: Free-tier banner shows for unpaid/past_due users — tighten the condition.Current
isFreeTiertreats any non‑active status as free, so users with payment issues will see both “Payment Required” and “Free tier” banners.Apply:
- const isFreeTier = !props.subscription || props.subscription.status !== "active"; + // Only show free-tier when there is no subscription or it is canceled. + const isFreeTier = + !props.subscription || props.subscription.status === "canceled";apps/engineering/content/docs/infrastructure/stripe/subscriptions.mdx (1)
27-28: Docs still claim “trials” — remove to reflect new flow.Inconsistent with this PR’s removal of trials.
-Unkey's Stripe billing integration manages user subscriptions through a tiered pricing model, with support for legacy usage-based pricing. The system handles payment methods, trials, subscription management, and customer portals. +Unkey's Stripe billing integration manages user subscriptions through a tiered pricing model, with support for legacy usage-based pricing. The system handles payment methods, subscription management, and customer portals.
🧹 Nitpick comments (6)
apps/dashboard/app/api/webhooks/stripe/route.ts (2)
112-116: Use the price’s currency instead of hardcoding USD.Prevents wrong formatting if we ever list non‑USD prices.
Apply:
- const formattedPrice = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(price.unit_amount / 100); + const currency = (price.currency ?? "usd").toUpperCase(); + const formattedPrice = new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }).format((price.unit_amount ?? 0) / 100);
154-162: PII in Slack: consider masking email and/or add a unique reference.Posting raw emails to Slack can be sensitive; mask it (e.g.,
a…@domain.com) or ensure the channel is restricted. Optionally include thesubscription.idto aid dedup/debug.Suggested masking inside
alertSlack:- text: `A new subscription for the ${product} tier has started at a price of ${price} by ${email} :moneybag: `, + text: `A new subscription for the ${product} tier has started at ${price} by ${email.replace(/(^.).*(@.*$)/, "$1…$2")} :moneybag: `,apps/dashboard/app/(app)/settings/billing/client.tsx (3)
90-93: Coerce flags to boolean for clearer types.Avoids truthy object leakage into flags.
- const allowUpdate = props.subscription && props.subscription.status === "active"; + const allowUpdate = !!(props.subscription && props.subscription.status === "active"); - const allowCancel = - props.subscription && props.subscription.status === "active" && !props.subscription.cancelAt; + const allowCancel = !!( + props.subscription && + props.subscription.status === "active" && + !props.subscription.cancelAt + );
105-105: Typo: Rename SusbcriptionStatus → SubscriptionStatus.Small polish; improves readability and searchability.
- {props.subscription ? <SusbcriptionStatus status={props.subscription.status} /> : null} + {props.subscription ? <SubscriptionStatus status={props.subscription.status} /> : null} @@ -const SusbcriptionStatus: React.FC<{ +const SubscriptionStatus: React.FC<{Also applies to: 305-335
185-189: Copy: avoid using “trial” in UI; use support link.Keeps UI consistent with “no trials” policy and provides a clickable URL.
- fineprint="Do you need a trial? Contact support.unkey.dev" + fineprint="Need an evaluation period? Contact us at https://support.unkey.dev"apps/engineering/content/docs/infrastructure/stripe/subscriptions.mdx (1)
20-24: Optional: Call out “No Trials” explicitly in objectives.Clarifies the policy right up front.
3. **Frictionless Upgrades** - Seamless transition from free to paid - - Transparent upgrade process with payment method collection upfront + - Transparent upgrade process with payment method collection upfront +4. **No Trials** + - Paid plans start immediately; evaluation access available by request via support
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/dashboard/app/(app)/settings/billing/client.tsx(4 hunks)apps/dashboard/app/(app)/settings/billing/page.tsx(0 hunks)apps/dashboard/app/api/webhooks/stripe/route.ts(2 hunks)apps/dashboard/lib/trpc/routers/stripe/createSubscription.ts(0 hunks)apps/engineering/content/docs/infrastructure/stripe/subscriptions.mdx(6 hunks)internal/resend/emails/trial_ended.tsx(0 hunks)internal/resend/src/client.tsx(0 hunks)
💤 Files with no reviewable changes (4)
- apps/dashboard/lib/trpc/routers/stripe/createSubscription.ts
- internal/resend/src/client.tsx
- apps/dashboard/app/(app)/settings/billing/page.tsx
- internal/resend/emails/trial_ended.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-04T20:44:38.489Z
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.
Applied to files:
apps/engineering/content/docs/infrastructure/stripe/subscriptions.mdx
📚 Learning: 2025-04-08T09:34:24.576Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
Applied to files:
apps/engineering/content/docs/infrastructure/stripe/subscriptions.mdx
🔇 Additional comments (3)
apps/dashboard/app/(app)/settings/billing/client.tsx (1)
2-2: Nice: Workspace type import matches our past guidance.This aligns with the retrieved learning to use the DB
Workspacetype instead of redefining it.apps/dashboard/app/api/webhooks/stripe/route.ts (1)
89-92: Resolved — workspace linking confirmed on subscription creationcreateSubscription calls stripe.subscriptions.create and then updates the workspace with stripeSubscriptionId = sub.id (db.update(...).set({ stripeSubscriptionId: sub.id }) in apps/dashboard), so the "created" webhook will find the workspace.
apps/engineering/content/docs/infrastructure/stripe/subscriptions.mdx (1)
10-13: Resolved — calendar-month billing and proration are implementedcreateSubscription.ts sets billing_cycle_anchor_config.day_of_month = 1 and proration_behavior = "always_invoice" (around lines 74–78).
|
Thank you for following the naming conventions for pull request titles! 🙏 |
perkinsjr
left a comment
There was a problem hiding this comment.
LGTM couple of quick things.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-delete-key.ts (1)
47-50: Standardize support contact to support@unkey.dev in user-facing stringsToasts currently show "support.unkey.dev" while UI actions open mailto:support@unkey.dev — replace the plain-text domain with the email (or a mailto link) for consistency.
- description: `We encountered an issue while deleting your ${keyText}. Please try again later or contact support at support.unkey.dev`, + description: `We encountered an issue while deleting your ${keyText}. Please try again later or contact support@unkey.dev`,Also apply the same replacement in these files found by the search:
- apps/dashboard/lib/trpc/routers/key/create.ts:63
- apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/hooks/use-create-key.tsx:26
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/.../use-edit-credits.ts:29
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/.../use-edit-ratelimits.ts:47
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/.../use-update-key-status.tsx:15
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/.../use-edit-key.tsx:37
- apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/.../use-delete-key.ts:49
🧹 Nitpick comments (15)
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-delete-key.ts (1)
62-63: Prefer location.href over window.open for mailtowindow.open with "_blank" is superfluous for mailto and can trigger pop-up blockers. Use location.href for higher reliability.
- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/index.tsx (1)
121-122: Use location.href for mailtoConsistent with other places and avoids pop-up issues.
- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-ratelimits.ts (2)
54-55: Use location.href for mailto- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),
45-48: Make support contact consistent with mailtoDescription still points to support.unkey.dev while action uses email.
- "We encountered an issue while updating your key. Please try again later or contact support at support.unkey.dev", + "We encountered an issue while updating your key. Please try again later or contact support at support@unkey.dev",Also applies to: 54-55
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/hooks/use-create-key.tsx (2)
37-38: Use location.href for mailto- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),
24-27: Unify support reference to emailKeep the description consistent with the action.
- "We encountered an issue while creating your key. Please try again later or contact support at support.unkey.dev", + "We encountered an issue while creating your key. Please try again later or contact support at support@unkey.dev",Also applies to: 37-38
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-key.tsx (2)
48-49: Use location.href for mailto- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),
35-38: Replace support URL in description with emailMatches the new contact method used in the action.
- "We encountered an issue while updating your key. Please try again later or contact support at support.unkey.dev", + "We encountered an issue while updating your key. Please try again later or contact support at support@unkey.dev",Also applies to: 48-49
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx (1)
88-89: Use location.href for mailto- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-update-key-status.tsx (2)
22-23: Use location.href for mailto- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),
13-16: Align description with email contactDescription references support.unkey.dev; switch to email to match the action.
- "We encountered an issue while updating your key(s). Please try again later or contact support at support.unkey.dev", + "We encountered an issue while updating your key(s). Please try again later or contact support at support@unkey.dev",Also applies to: 22-23
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-credits.ts (2)
36-37: Use location.href for mailto- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => (window.location.href = "mailto:support@unkey.dev"),
27-30: Switch remaining support URL in description to emailKeep messaging consistent across toasts.
- "We encountered an issue while updating your key. Please try again later or contact support at support.unkey.dev", + "We encountered an issue while updating your key. Please try again later or contact support at support@unkey.dev",Also applies to: 36-37
apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-credits/index.tsx (1)
76-79: Prefer location.href for mailto; prefill a subject for faster triageUsing window.open for mailto can spawn a blank tab in some browsers. Use location.href and add a helpful subject.
- onClick: () => window.open("mailto:support@unkey.dev", "_blank"), + onClick: () => { + window.location.href = + "mailto:support@unkey.dev?subject=Failed%20to%20Update%20Key%20Limits"; + },Optional: centralize the support address (e.g., NEXT_PUBLIC_SUPPORT_EMAIL) to avoid future drifts across files.
apps/dashboard/app/(app)/settings/billing/client.tsx (1)
105-105: Typo: SusbcriptionStatus → SubscriptionStatusRename the component and its usages when convenient to avoid lingering misspellings in the codebase.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
deployment/data/metald/metald.dbis excluded by!**/*.db
📒 Files selected for processing (10)
apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/key-created-success-dialog.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/hooks/use-create-key.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-credits/index.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-delete-key.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-credits.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-key.tsx(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-edit-ratelimits.ts(1 hunks)apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/hooks/use-update-key-status.tsx(1 hunks)apps/dashboard/app/(app)/settings/billing/client.tsx(4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test Go API Local / Test
- GitHub Check: Test Packages / Test
- GitHub Check: Build / Build
- GitHub Check: Test API / API Test Local
🔇 Additional comments (2)
apps/dashboard/app/(app)/settings/billing/client.tsx (2)
187-188: Copy change to “Upgrade” looks goodMatches the no-trial flow and keeps intent clear.
201-201: Copy update LGTMClearer guidance now that trials are removed.
| const allowUpdate = props.subscription && props.subscription.status === "active"; | ||
| const allowCancel = | ||
| props.subscription && | ||
| ["active", "trialing"].includes(props.subscription.status) && | ||
| !props.subscription.cancelAt; | ||
| const isFreeTier = | ||
| !props.subscription || !["active", "trialing"].includes(props.subscription.status); | ||
| props.subscription && props.subscription.status === "active" && !props.subscription.cancelAt; | ||
| const isFreeTier = !props.subscription || props.subscription.status !== "active"; | ||
| const selectedProductIndex = allowUpdate |
There was a problem hiding this comment.
Don’t show “Free tier” for delinquent/non-active paid statuses; align “Change vs Upgrade” behavior
Current isFreeTier = status !== "active" shows the Free tier alert alongside “Payment Required” for statuses like past_due/unpaid/incomplete. Also, the product list uses “updateSubscription” whenever subscription exists, which is wrong for canceled/paused; it should create a new subscription instead.
Apply:
- Only treat as free tier when there’s no subscription or status is canceled/paused/incomplete_expired.
- Drive the “update vs create” branch off allowUpdate, not truthiness of subscription.
@@
- const mutations = Mutations();
- const allowUpdate = props.subscription && props.subscription.status === "active";
- const allowCancel =
- props.subscription && props.subscription.status === "active" && !props.subscription.cancelAt;
- const isFreeTier = !props.subscription || props.subscription.status !== "active";
+ const mutations = Mutations();
+ const status = props.subscription?.status;
+ const allowUpdate = status === "active";
+ const allowCancel = status === "active" && !props.subscription?.cancelAt;
+ const isFreeTier =
+ !status || status === "canceled" || status === "paused" || status === "incomplete_expired";
@@
- {props.subscription ? (
+ {allowUpdate ? (
<Confirm
title={`${i > selectedProductIndex ? "Upgrade" : "Downgrade"} to ${p.name}`}
@@
- ) : (
+ ) : (
<Confirm
title={`Upgrade to ${p.name}`}Result: no contradictory banners; canceled/paused users get a clean “Upgrade” create flow instead of a failing “Change” update flow.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/settings/billing/client.tsx around lines 90 to 94,
the isFreeTier boolean and selectedProductIndex logic are incorrect: change
isFreeTier to be true only when there is no subscription OR the
subscription.status is explicitly one of "canceled", "paused", or
"incomplete_expired" (so delinquent statuses like
"past_due"/"unpaid"/"incomplete" are not treated as free tier), and change the
branch that decides whether to call update vs create to be driven by allowUpdate
(i.e., set selectedProductIndex/use updateSubscription only when allowUpdate is
true) rather than by the mere existence/truthiness of props.subscription; leave
allowUpdate/allowCancel logic as-is.

What does this PR do?
Remove trial functionality from subscription system
This PR removes the 14-day trial period from our subscription system, simplifying the upgrade flow. Users now directly upgrade to paid plans without a trial period. The changes include:
Fixes #0000
Type of change
How should this be tested?
Checklist
Required
pnpm buildpnpm fmtconsole.logsgit pull origin mainAppreciated
Summary by CodeRabbit