Skip to content

feat(clerk-js,localizations,shared,ui): Add support for discounts/promo codes - #9316

Open
dstaley wants to merge 3 commits into
mainfrom
ds.feat/billing-discounts
Open

feat(clerk-js,localizations,shared,ui): Add support for discounts/promo codes#9316
dstaley wants to merge 3 commits into
mainfrom
ds.feat/billing-discounts

Conversation

@dstaley

@dstaley dstaley commented Aug 3, 2026

Copy link
Copy Markdown
Member

Description

This PR adds support for rendering discounts on subscription items. It also adds support for promo codes at checkout.

image

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@dstaley
dstaley requested a review from a team August 3, 2026 18:26
@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4e89c8c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/localizations Minor
@clerk/clerk-js Minor
@clerk/shared Minor
@clerk/ui Minor
@clerk/backend Patch
@clerk/react Patch
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo Patch
@clerk/astro Patch
@clerk/expo-passkeys Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 5, 2026 8:11pm
swingset Ready Ready Preview Aug 5, 2026 8:11pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Aug 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9316

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9316

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9316

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9316

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9316

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9316

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9316

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9316

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9316

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9316

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9316

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9316

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9316

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9316

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9316

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9316

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9316

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9316

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9316

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9316

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9316

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9316

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9316

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9316

commit: 4e89c8c

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-05T20:12:32.138Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 1
🔴 Breaking changes 0
🟡 Non-breaking changes 1
🟢 Additions 57

🤖 This report was reviewed by claude-sonnet-4-6.


@clerk/shared

Current version: 4.26.0
Recommended bump: MINOR → 4.27.0

Subpath ./types

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 2066 lines, after: 2076 lines). Click to expand.
// ... 105 unchanged lines elided ...
    membershipRole__guestMember: LocalizationValue;
    billing: {
      month: LocalizationValue;
+     months: LocalizationValue;
      monthAbbreviation: LocalizationValue;
      monthPerUnit: LocalizationValue<'unitName'>;
      year: LocalizationValue;
+     years: LocalizationValue;
      yearAbbreviation: LocalizationValue;
      yearPerUnit: LocalizationValue<'unitName'>;
      free: LocalizationValue;
      getStarted: LocalizationValue;
      manage: LocalizationValue;
      manageSubscription: LocalizationValue;
      cancelSubscription: LocalizationValue;
      keepSubscription: LocalizationValue;
      reSubscribe: LocalizationValue;
      seats: LocalizationValue;
      seatsWithLimit: LocalizationValue<'limit'>;
      seatBreakdownSingular: LocalizationValue<'rate'>;
      seatBreakdownPlural: LocalizationValue<'chargeable' | 'rate'>;
      seatBreakdownIncludedSingular: LocalizationValue<'totalSeats' | 'included' | 'rate'>;
      seatBreakdownIncludedPlural: LocalizationValue<'totalSeats' | 'included' | 'chargeable' | 'rate'>;
      subscribe: LocalizationValue;
      startFreeTrial: LocalizationValue;
      startFreeTrial__days: LocalizationValue<'days'>;
      switchPlan: LocalizationValue;
      switchToMonthly: LocalizationValue;
      switchToAnnual: LocalizationValue;
      switchToMonthlyWithPrice: LocalizationValue<'price'>;
      switchToAnnualWithAnnualPrice: LocalizationValue<'price'>;
      billedAnnually: LocalizationValue;
      billedMonthly: LocalizationValue;
      billedMonthlyOnly: LocalizationValue;
      billedAnnuallyOnly: LocalizationValue;
      cancelFreeTrial: LocalizationValue<'plan'>;
      cancelFreeTrialTitle: LocalizationValue<'plan'>;
      cancelFreeTrialAccessUntil: LocalizationValue<'plan' | 'date'>;
      keepFreeTrial: LocalizationValue;
      alwaysFree: LocalizationValue;
      accountFunds: LocalizationValue;
      defaultFreePlanActive: LocalizationValue;
+     discountAmount: LocalizationValue<'amount'>;
+     discountCyclesRemaining: LocalizationValue<'cycles' | 'period'>;
+     discountDuration: LocalizationValue<'amount' | 'cycles' | 'period'>;
      viewFeatures: LocalizationValue;
      seeAllFeatures: LocalizationValue;
      viewPayment: LocalizationValue;
      availableFeatures: LocalizationValue;
      subtotal: LocalizationValue;
      subtotalRenewal: LocalizationValue;
      credit: LocalizationValue;
      prorationCredit: LocalizationValue;
      accountCredit: LocalizationValue;
      creditRemainder: LocalizationValue;
      payerCreditRemainder: LocalizationValue;
      proratedDiscount: LocalizationValue;
      totalDue: LocalizationValue;
      totalDueToday: LocalizationValue;
      totalDuePerPeriod: LocalizationValue;
      pastDue: LocalizationValue;
      pay: LocalizationValue<'amount'>;
      cancelSubscriptionTitle: LocalizationValue<'plan'>;
      cancelSubscriptionNoCharge: LocalizationValue;
      cancelSubscriptionAccessUntil: LocalizationValue<'plan' | 'date'>;
      cancelSubscriptionPastDue: LocalizationValue;
      highlightedPlanBadge: LocalizationValue;
      paymentMethods__label: LocalizationValue;
      addPaymentMethod__label: LocalizationValue;
      paymentMethod: {
        dev: {
          testCardInfo: LocalizationValue;
          developmentMode: LocalizationValue;
          cardNumber: LocalizationValue;
          expirationDate: LocalizationValue;
          cvcZip: LocalizationValue;
          anyNumbers: LocalizationValue;
        };
        applePayDescription: {
          monthly: LocalizationValue;
          annual: LocalizationValue;
        };
      };
      subscriptionDetails: {
        title: LocalizationValue;
        currentBillingCycle: LocalizationValue;
        nextPaymentOn: LocalizationValue;
        nextPaymentAmount: LocalizationValue;
        firstPaymentOn: LocalizationValue;
        firstPaymentAmount: LocalizationValue;
        subscribedOn: LocalizationValue;
        trialStartedOn: LocalizationValue;
        trialEndsOn: LocalizationValue;
        endsOn: LocalizationValue;
        renewsAt: LocalizationValue;
        beginsOn: LocalizationValue;
        pastDueAt: LocalizationValue;
      };
      monthly: LocalizationValue;
      annually: LocalizationValue;
      cannotSubscribeMonthly: LocalizationValue;
      cannotSubscribeUnrecoverable: LocalizationValue;
      pricingTable: {
        billingCycle: LocalizationValue;
        included: LocalizationValue;
        seatCost: {
          freeUpToSeats: LocalizationValue<'endsAfterBlock'>;
          upToSeats: LocalizationValue<'endsAfterBlock'>;
          perSeat: LocalizationValue<'feePerBlockAmount' | 'periodAbbreviation'>;
          includedSeats: LocalizationValue<'includedSeats'>;
          additionalSeats: LocalizationValue<'additionalTierFeePerBlockAmount' | 'periodAbbreviation'>;
          unlimitedSeats: LocalizationValue;
          tooltip: {
            freeForUpToSeats: LocalizationValue<'endsAfterBlock'>;
            additionalSeatsEach: LocalizationValue<'feePerBlockAmount' | 'period'>;
            firstSeatsIncludedInPlan: LocalizationValue<'endsAfterBlock'>;
          };
        };
      };
      checkout: {
+       addPromoCode: LocalizationValue;
+       applyPromoCode: LocalizationValue;
+       discount: LocalizationValue;
+       promoCodePlaceholder: LocalizationValue;
+       removePromoCode: LocalizationValue;
        title: LocalizationValue;
        title__paymentSuccessful: LocalizationValue;
        title__subscriptionSuccessful: LocalizationValue;
// ... 1843 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (80%): The diff shows only an increase in elided lines (1986 → 1996), meaning new localization keys were added to the type. __internal_LocalizationResource is used as the source for LocalizationResource via DeepPartial<DeepLocalizationWithoutObjects<...>>, making it effectively an output/read type; consumers only read (or partially override) localization values and do not construct the full __internal_LocalizationResource shape, so adding new required fields does not break existing well-typed consumer code.

🟢 Additions (57)

Click to expand 57 changes
Added: BillingAppliedDiscount
+ interface BillingAppliedDiscount

Added interface BillingAppliedDiscount

Added: BillingAppliedDiscount.amount
+ amount: BillingMoneyAmount;

Added property BillingAppliedDiscount.amount

Added: BillingAppliedDiscount.amountOff
+ amountOff?: BillingMoneyAmount;

Added property BillingAppliedDiscount.amountOff

Added: BillingAppliedDiscount.cyclesRemaining
+ cyclesRemaining: number | null;

Added property BillingAppliedDiscount.cyclesRemaining

Added: BillingAppliedDiscount.discountId
+ discountId: string;

Added property BillingAppliedDiscount.discountId

Added: BillingAppliedDiscount.effect
+ effect: 'percentage' | 'fixed_amount';

Added property BillingAppliedDiscount.effect

Added: BillingAppliedDiscount.name
+ name: string;

Added property BillingAppliedDiscount.name

Added: BillingAppliedDiscount.percentOff
+ percentOff?: number;

Added property BillingAppliedDiscount.percentOff

Added: BillingAppliedDiscount.promoCode
+ promoCode?: string;

Added property BillingAppliedDiscount.promoCode

Added: BillingAppliedDiscountJSON
+ interface BillingAppliedDiscountJSON

Added interface BillingAppliedDiscountJSON

Added: BillingAppliedDiscountJSON.amount_off
+ amount_off?: BillingMoneyAmountJSON;

Added property BillingAppliedDiscountJSON.amount_off

Added: BillingAppliedDiscountJSON.amount
+ amount: BillingMoneyAmountJSON;

Added property BillingAppliedDiscountJSON.amount

Added: BillingAppliedDiscountJSON.cycles_remaining
+ cycles_remaining: number | null;

Added property BillingAppliedDiscountJSON.cycles_remaining

Added: BillingAppliedDiscountJSON.discount_id
+ discount_id: string;

Added property BillingAppliedDiscountJSON.discount_id

Added: BillingAppliedDiscountJSON.effect
+ effect: 'percentage' | 'fixed_amount';

Added property BillingAppliedDiscountJSON.effect

Added: BillingAppliedDiscountJSON.name
+ name: string;

Added property BillingAppliedDiscountJSON.name

Added: BillingAppliedDiscountJSON.percent_off
+ percent_off?: number;

Added property BillingAppliedDiscountJSON.percent_off

Added: BillingAppliedDiscountJSON.promo_code
+ promo_code?: string;

Added property BillingAppliedDiscountJSON.promo_code

Added: BillingDiscountRedemption
+ interface BillingDiscountRedemption

Added interface BillingDiscountRedemption

Added: BillingDiscountRedemption.amount
+ amount?: BillingMoneyAmount;

Added property BillingDiscountRedemption.amount

Added: BillingDiscountRedemption.amountOff
+ amountOff?: BillingMoneyAmount;

Added property BillingDiscountRedemption.amountOff

Added: BillingDiscountRedemption.cyclesApplied
+ cyclesApplied: number;

Added property BillingDiscountRedemption.cyclesApplied

Added: BillingDiscountRedemption.cyclesRemaining
+ cyclesRemaining: number | null;

Added property BillingDiscountRedemption.cyclesRemaining

Added: BillingDiscountRedemption.discountId
+ discountId: string;

Added property BillingDiscountRedemption.discountId

Added: BillingDiscountRedemption.effect
+ effect?: 'percentage' | 'fixed_amount';

Added property BillingDiscountRedemption.effect

Added: BillingDiscountRedemption.id
+ id: string;

Added property BillingDiscountRedemption.id

Added: BillingDiscountRedemption.name
+ name: string;

Added property BillingDiscountRedemption.name

Added: BillingDiscountRedemption.percentOff
+ percentOff?: number;

Added property BillingDiscountRedemption.percentOff

Added: BillingDiscountRedemption.promoCode
+ promoCode?: string;

Added property BillingDiscountRedemption.promoCode

Added: BillingDiscountRedemption.redeemedAt
+ redeemedAt: Date;

Added property BillingDiscountRedemption.redeemedAt

Added: BillingDiscountRedemption.redeemedBy
+ redeemedBy: string | null;

Added property BillingDiscountRedemption.redeemedBy

Added: BillingDiscountRedemption.source
+ source: 'promotion' | 'manual' | 'promo_code';

Added property BillingDiscountRedemption.source

Added: BillingDiscountRedemption.status
+ status?: 'active' | 'exhausted' | 'removed';

Added property BillingDiscountRedemption.status

Added: BillingDiscountRedemption.subscriptionItemId
+ subscriptionItemId: string;

Added property BillingDiscountRedemption.subscriptionItemId

Added: BillingDiscountRedemptionJSON
+ interface BillingDiscountRedemptionJSON extends ClerkResourceJSON

Added interface BillingDiscountRedemptionJSON

Added: BillingDiscountRedemptionJSON.amount_off
+ amount_off?: BillingMoneyAmountJSON;

Added property BillingDiscountRedemptionJSON.amount_off

Added: BillingDiscountRedemptionJSON.amount
+ amount?: BillingMoneyAmountJSON;

Added property BillingDiscountRedemptionJSON.amount

Added: BillingDiscountRedemptionJSON.cycles_applied
+ cycles_applied: number;

Added property BillingDiscountRedemptionJSON.cycles_applied

Added: BillingDiscountRedemptionJSON.cycles_remaining
+ cycles_remaining: number | null;

Added property BillingDiscountRedemptionJSON.cycles_remaining

Added: BillingDiscountRedemptionJSON.discount_id
+ discount_id: string;

Added property BillingDiscountRedemptionJSON.discount_id

Added: BillingDiscountRedemptionJSON.effect
+ effect?: 'percentage' | 'fixed_amount';

Added property BillingDiscountRedemptionJSON.effect

Added: BillingDiscountRedemptionJSON.id
+ id: string;

Added property BillingDiscountRedemptionJSON.id

Added: BillingDiscountRedemptionJSON.name
+ name: string;

Added property BillingDiscountRedemptionJSON.name

Added: BillingDiscountRedemptionJSON.object
+ object: 'commerce_discount_redemption';

Added property BillingDiscountRedemptionJSON.object

Added: BillingDiscountRedemptionJSON.percent_off
+ percent_off?: number;

Added property BillingDiscountRedemptionJSON.percent_off

Added: BillingDiscountRedemptionJSON.promo_code
+ promo_code?: string;

Added property BillingDiscountRedemptionJSON.promo_code

Added: BillingDiscountRedemptionJSON.redeemed_at
+ redeemed_at: number;

Added property BillingDiscountRedemptionJSON.redeemed_at

Added: BillingDiscountRedemptionJSON.redeemed_by
+ redeemed_by: string | null;

Added property BillingDiscountRedemptionJSON.redeemed_by

Added: BillingDiscountRedemptionJSON.source
+ source: 'promotion' | 'manual' | 'promo_code';

Added property BillingDiscountRedemptionJSON.source

Added: BillingDiscountRedemptionJSON.status
+ status?: 'active' | 'exhausted' | 'removed';

Added property BillingDiscountRedemptionJSON.status

Added: BillingDiscountRedemptionJSON.subscription_item_id
+ subscription_item_id: string;

Added property BillingDiscountRedemptionJSON.subscription_item_id

Added: BillingDiscounts.discount
+ discount?: BillingAppliedDiscount;

Added property BillingDiscounts.discount

Added: BillingDiscountsJSON.discount
+ discount?: BillingAppliedDiscountJSON;

Added property BillingDiscountsJSON.discount

Added: BillingNamespace.updateCheckout
+ updateCheckout: (params: UpdateCheckoutParams) => Promise<BillingCheckoutResource>;

Added property BillingNamespace.updateCheckout

Added: BillingSubscriptionItemJSON.applied_discount
+ applied_discount?: BillingDiscountRedemptionJSON;

Added property BillingSubscriptionItemJSON.applied_discount

Added: BillingSubscriptionItemResource.appliedDiscount
+ appliedDiscount?: BillingDiscountRedemption;

Added property BillingSubscriptionItemResource.appliedDiscount

Added: UpdateCheckoutParams
+ type UpdateCheckoutParams = WithOptionalOrgType<{
+   id: string;
+   promoCode: string;
+ }>;

Added type alias UpdateCheckoutParams


Report generated by Break Check

Last ran on 4e89c8c.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added billing discount contracts and JSON conversion. Subscription items now expose applied discount redemptions. The subscription overview, payment attempts, and statements render discount details with localized amounts. Billing localization keys were added across supported resources. Tests cover fixed discounts, percentage discounts, and redemption mapping. Patch changesets and a bundle size limit update were added.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • clerk/javascript#9196: Both PRs modify billing conversion logic in packages/backend/src/util/billing.ts.
  • clerk/javascript#9256: Both PRs update packages/clerk-js/bundlewatch.config.json for bundle size limits.

Suggested reviewers: mauricioabreu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the added discount and promo code support across the affected packages.
Description check ✅ Passed The description directly explains the discount rendering and promo code support implemented by the pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/shared/src/types/billing.ts (1)

1032-1069: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add per-field JSDoc to the new discount interfaces.

BillingAppliedDiscount and BillingDiscountRedemption only carry an interface-level @experimental comment. Every other interface in this file, including BillingSubscriptionItemResource and BillingDiscounts, documents each property individually. Add a short doc comment to each field (amount, discountId, effect, percentOff, amountOff, promoCode, cyclesRemaining, source, status, redeemedAt, redeemedBy, etc.) so consumers of this new public, experimental API understand the field semantics without cross-referencing the JSON layer.

As per coding guidelines, "All public APIs must be documented with JSDoc" and "TypeScript is required for all packages... Maintain comprehensive JSDoc comments for public APIs."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/types/billing.ts` around lines 1032 - 1069, Add
individual JSDoc comments for every property in the public interfaces
BillingAppliedDiscount and BillingDiscountRedemption, including identifiers,
monetary fields, discount effects, cycle counters, source/status values,
redemption timestamps, and redeemer information. Keep the existing
interface-level experimental documentation and ensure each comment clearly
describes the field’s semantics without relying on JSON-layer definitions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/localizations/src/en-US.ts`:
- Around line 125-126: Update the discount localization usage associated with
SubscriptionDiscountRow so a single cycle uses the singular billing.month or
billing.year label, while multiple cycles retain billing.months or
billing.years. Ensure both discountCyclesRemaining and discountDuration render
grammatically correct singular and plural wording.

In `@packages/ui/src/components/Subscriptions/SubscriptionsList.tsx`:
- Around line 258-302: Update the period-label logic near discountTitle so
singular keys are selected when each relevant cycle count equals 1, reusing the
existing billing.month/year keys and plural keys otherwise. Replace the shared
period value in discountDuration with a label based on totalCycles and in
discountCyclesRemaining with a label based on appliedDiscount.cyclesRemaining,
using the existing getPeriodLabel helper if available.

---

Nitpick comments:
In `@packages/shared/src/types/billing.ts`:
- Around line 1032-1069: Add individual JSDoc comments for every property in the
public interfaces BillingAppliedDiscount and BillingDiscountRedemption,
including identifiers, monetary fields, discount effects, cycle counters,
source/status values, redemption timestamps, and redeemer information. Keep the
existing interface-level experimental documentation and ensure each comment
clearly describes the field’s semantics without relying on JSON-layer
definitions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b94bfa3-1098-4c76-ae70-37dedd04e586

📥 Commits

Reviewing files that changed from the base of the PR and between 8b420c2 and 5a98c57.

📒 Files selected for processing (57)
  • .changeset/green-dolphins-discount.md
  • packages/clerk-js/src/core/resources/BillingSubscription.ts
  • packages/clerk-js/src/utils/__tests__/billing.test.ts
  • packages/clerk-js/src/utils/billing.ts
  • packages/localizations/src/ar-SA.ts
  • packages/localizations/src/be-BY.ts
  • packages/localizations/src/bg-BG.ts
  • packages/localizations/src/bn-IN.ts
  • packages/localizations/src/ca-ES.ts
  • packages/localizations/src/cs-CZ.ts
  • packages/localizations/src/da-DK.ts
  • packages/localizations/src/de-DE.ts
  • packages/localizations/src/el-GR.ts
  • packages/localizations/src/en-GB.ts
  • packages/localizations/src/en-US.ts
  • packages/localizations/src/es-CR.ts
  • packages/localizations/src/es-ES.ts
  • packages/localizations/src/es-MX.ts
  • packages/localizations/src/es-UY.ts
  • packages/localizations/src/fa-IR.ts
  • packages/localizations/src/fi-FI.ts
  • packages/localizations/src/fr-FR.ts
  • packages/localizations/src/he-IL.ts
  • packages/localizations/src/hi-IN.ts
  • packages/localizations/src/hr-HR.ts
  • packages/localizations/src/hu-HU.ts
  • packages/localizations/src/id-ID.ts
  • packages/localizations/src/is-IS.ts
  • packages/localizations/src/it-IT.ts
  • packages/localizations/src/ja-JP.ts
  • packages/localizations/src/kk-KZ.ts
  • packages/localizations/src/ko-KR.ts
  • packages/localizations/src/mn-MN.ts
  • packages/localizations/src/ms-MY.ts
  • packages/localizations/src/nb-NO.ts
  • packages/localizations/src/nl-BE.ts
  • packages/localizations/src/nl-NL.ts
  • packages/localizations/src/pl-PL.ts
  • packages/localizations/src/pt-BR.ts
  • packages/localizations/src/pt-PT.ts
  • packages/localizations/src/ro-RO.ts
  • packages/localizations/src/ru-RU.ts
  • packages/localizations/src/sk-SK.ts
  • packages/localizations/src/sr-RS.ts
  • packages/localizations/src/sv-SE.ts
  • packages/localizations/src/ta-IN.ts
  • packages/localizations/src/te-IN.ts
  • packages/localizations/src/th-TH.ts
  • packages/localizations/src/tr-TR.ts
  • packages/localizations/src/uk-UA.ts
  • packages/localizations/src/vi-VN.ts
  • packages/localizations/src/zh-CN.ts
  • packages/localizations/src/zh-TW.ts
  • packages/shared/src/types/billing.ts
  • packages/shared/src/types/json.ts
  • packages/shared/src/types/localization.ts
  • packages/ui/src/components/Subscriptions/SubscriptionsList.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)

Comment thread packages/localizations/src/en-US.ts Outdated
Comment thread packages/ui/src/components/Subscriptions/SubscriptionsList.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ui/src/utils/billing.ts (1)

14-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit string return types.

getDiscountDescription and getBillingPeriodLabel are exported TypeScript functions. Declare their return type as string.

Proposed fix
 export function getDiscountDescription(
   discount: Discount,
   cycles: number | null,
   planPeriod: BillingSubscriptionPlanPeriod,
   { $, t }: Localizations,
-) {
+): string {
@@
 export function getBillingPeriodLabel(
   planPeriod: BillingSubscriptionPlanPeriod,
   cycles: number,
   t: Localizations['t'],
-) {
+): string {

Based on learnings and coding guidelines: exported TypeScript functions require explicit return types.

Also applies to: 35-39

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/utils/billing.ts` around lines 14 - 19, Update the exported
functions getDiscountDescription and getBillingPeriodLabel to explicitly declare
string return types in their signatures, without changing their existing
behavior or implementation.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/ui/src/utils/billing.ts`:
- Around line 14-19: Update the exported functions getDiscountDescription and
getBillingPeriodLabel to explicitly declare string return types in their
signatures, without changing their existing behavior or implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 098eb993-20bb-45ac-9216-e11d156c5681

📥 Commits

Reviewing files that changed from the base of the PR and between 73f5daf and aae5436.

📒 Files selected for processing (5)
  • .changeset/render-discounts-payment-statement.md
  • packages/backend/src/util/billing.ts
  • packages/ui/src/components/PaymentAttempts/PaymentAttemptPage.tsx
  • packages/ui/src/components/Statements/StatementPage.tsx
  • packages/ui/src/utils/billing.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)

@dstaley
dstaley force-pushed the ds.feat/billing-discounts branch 2 times, most recently from 73f5daf to a574667 Compare August 5, 2026 16:48
@dstaley dstaley changed the title feat(clerk-js,localizations,shared,ui): Add support for rendering discounts on subscription items feat(clerk-js,localizations,shared,ui): Add support for discounts/promo codes Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants