Skip to content

feat: code redemption in billing settings#42377

Merged
kevcodez merged 15 commits intomasterfrom
k-dog/billing-1939-allow-redeeming-code-in-billing-org-settings
Feb 4, 2026
Merged

feat: code redemption in billing settings#42377
kevcodez merged 15 commits intomasterfrom
k-dog/billing-1939-allow-redeeming-code-in-billing-org-settings

Conversation

@kevcodez
Copy link
Copy Markdown
Contributor

@kevcodez kevcodez commented Feb 2, 2026

Adds a new code redemption button for the new credit system. Feature Flag toggled off on production for the time being.

Screenshot 2026-02-04 at 12 09 07 PM Screenshot 2026-02-04 at 12 00 06 PM

Summary by CodeRabbit

  • New Features
    • Added credit code redemption feature in billing settings, allowing users to redeem codes for account credits.
    • Code preview displays credit amount, expiration date, and potential charges before redemption.
    • Integrated security verification for the redemption flow.

@kevcodez kevcodez requested a review from a team as a code owner February 2, 2026 07:32
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 2, 2026

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Feb 4, 2026 6:42am
studio-self-hosted Ready Ready Preview, Comment Feb 4, 2026 6:42am
studio-staging Ready Ready Preview, Comment Feb 4, 2026 6:42am
zone-www-dot-com Ready Ready Preview, Comment Feb 4, 2026 6:42am
4 Skipped Deployments
Project Deployment Actions Updated (UTC)
cms Ignored Ignored Feb 4, 2026 6:42am
studio Ignored Ignored Feb 4, 2026 6:42am
design-system Skipped Skipped Feb 4, 2026 6:42am
ui-library Skipped Skipped Feb 4, 2026 6:42am

Request Review

@supabase
Copy link
Copy Markdown

supabase bot commented Feb 2, 2026

This pull request has been ignored for the connected project xguihxuzqibwxjnimxev because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@kevcodez kevcodez changed the title initial code redemption feat: code redemption in billing settings Feb 2, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 2, 2026

Walkthrough

This PR introduces credit code redemption functionality to the organization billing interface. It adds a new modal component for redeeming credit codes with form validation and HCaptcha verification, implements supporting backend API endpoints for code preview and redemption, and refactors the CreditTopUp component structure.

Changes

Cohort / File(s) Summary
Billing UI Components
apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx, CreditTopUp.tsx, CreditCodeRedemption.tsx
CreditBalance imports and conditionally renders new CreditCodeRedemption component alongside CreditTopUp. CreditTopUp refactors internal structure to use Dialog-based layout. CreditCodeRedemption is a new feature component with modal dialog, form validation, HCaptcha integration, and redemption mutation.
Data Query & Mutation
apps/studio/data/organizations/organization-credit-code-redemption-mutation.ts, organizationKeys.ts, organization-customer-profile-query.ts
New mutation hook and async function for redeeming credit codes. New query key for previewing credit codes. Enhanced permission checking in customer profile query to pass organizationSlug argument.
API Contracts
packages/api-types/types/platform.d.ts
Two new POST endpoints: preview-code and redeem under organization billing credits path. Three new schemas: CreditRedemptionRequest, CreditRedemptionPreviewResponse, CreditRedemptionResponse with amount, expiry, and validity details.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Modal as CreditCodeRedemption<br/>Modal
    participant Form as Form Validation<br/>(zod)
    participant HCaptcha as HCaptcha<br/>Service
    participant API as Platform API
    participant Cache as React Query<br/>Cache

    User->>Modal: Opens redemption modal
    User->>Modal: Enters credit code
    Modal->>API: Preview code (debounced)
    API->>Modal: Returns preview data (amount, expiry)
    Modal->>Modal: Displays new balance calculation
    
    User->>Modal: Clicks redeem button
    Modal->>Form: Validates code input
    Form-->>Modal: ✓ Valid
    
    Modal->>HCaptcha: Request invisible token
    HCaptcha-->>Modal: Returns captcha token
    
    Modal->>API: POST redeem with code & token
    API->>API: Validates and processes redemption
    API-->>Modal: Success response
    
    Modal->>Cache: Invalidate queries<br/>(customerProfile, orgSubscription,<br/>previewCreditCode)
    Cache-->>Modal: ✓ Queries refreshed
    
    Modal->>User: Close modal + show toast
    Modal->>HCaptcha: Reset captcha
Loading

Suggested reviewers

  • ecktoteckto
  • alaister
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
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.
Description check ⚠️ Warning The PR description does not follow the required template structure with sections for 'What kind of change', 'Current behavior', and 'New behavior'. Rewrite the description following the template: confirm reading CONTRIBUTING.md, specify change type (feature), link relevant issues, describe current behavior and new behavior with details, and add context about feature flag and screenshots.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: code redemption in billing settings' clearly and specifically describes the main feature being added: a code redemption capability in the billing settings UI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch k-dog/billing-1939-allow-redeeming-code-in-billing-org-settings

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.

❤️ Share

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

@coveralls
Copy link
Copy Markdown

coveralls commented Feb 2, 2026

Coverage Status

coverage: 66.278%. remained the same
when pulling 0d17ef3 on k-dog/billing-1939-allow-redeeming-code-in-billing-org-settings
into 0760733 on master.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In
`@apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx`:
- Around line 11-31: The file has excessive blank lines in the imports/spacing
of the CreditBalance component causing Prettier to fail; open
apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx,
collapse the extra empty lines around the import block (where
CreditCodeRedemption and CreditTopUp are imported) and run `prettier --write`
(or apply your project's Prettier rules) to reformat the file so it matches
linting/Prettier expectations.

In
`@apps/studio/components/interfaces/Organization/BillingSettings/CreditCodeRedemption.tsx`:
- Around line 96-105: The useEffect in CreditCodeRedemption reads router and
calls form.setValue but form is missing from the dependency array; update the
effect dependencies to include either the form object or the stable setter
(extract setValue from form and include setValue) so the effect watches the
setter correctly (i.e., reference useEffect that sets form.setValue — add form
or form.setValue/setValue to the dependency array to satisfy ESLint and ensure
correct behavior).
- Around line 152-154: The useEffect in CreditCodeRedemption calls initHcaptcha
but doesn't list it in the dependency array causing an ESLint
react-hooks/exhaustive-deps failure; fix by adding initHcaptcha to the
dependency array of the useEffect (useEffect(() => { initHcaptcha() },
[codeRedemptionModalVisible, captchaRef, initHcaptcha])) and refactor the
initHcaptcha function into a stable reference by wrapping it with useCallback
inside the component so it doesn’t trigger infinite re-renders.

In `@apps/studio/data/organizations/keys.ts`:
- Line 1: Remove the unused import previewCreditCode from this module: the file
defines its own inline key factory for preview credit codes, so delete the
import statement "import { previewCreditCode } from
'./organization-credit-code-preview-query'" to avoid an unused symbol; ensure no
other references to previewCreditCode remain in this file after removal.

In `@apps/studio/data/organizations/organization-credit-code-preview-query.ts`:
- Around line 69-138: Remove the duplicate/type-conflicting block: delete the
OrganizationCreditCodeRedemptionVariables type, previewCode function,
OrganizationCreditCodePreviewData type alias, and the
useOrganizationCreditCodeRedemptionMutation hook from this file to avoid
clashing with the canonical implementations in
organization-credit-code-redemption-mutation.ts; if the captcha preview behavior
is truly required, rename previewCode to a distinct name (e.g.,
previewCreditCodeWithCaptcha) and relocate it to a separate file so it does not
shadow the existing mutation hook, ensuring the canonical
useOrganizationCreditCodeRedemptionMutation (the one that invalidates queries)
remains the single source of truth.
- Line 59: Remove the stray debug console.log statement that prints {
canApplyCreditCode, slug } in organization-credit-code-preview-query.ts: locate
the line with console.log({ canApplyCreditCode, slug }) and delete it (or
replace it with a proper logger call such as processLogger.debug(...) if you
need retained debug info), ensuring no plain console.log remains in the
codebase.

In
`@apps/studio/data/organizations/organization-credit-code-redemption-mutation.ts`:
- Line 5: Remove the unused import orgSSOKeys from this module: locate the
import statement "import { orgSSOKeys } from '../sso/keys'" and delete it so
there are no unused imports in organization-credit-code-redemption-mutation.ts;
verify no other references to orgSSOKeys exist in that file and run the
linter/tests to confirm the cleanup.
- Line 45: The type alias OrganizationTaxIdUpdateData is misnamed and should be
renamed to reflect credit code redemption (e.g.,
OrganizationCreditCodeRedemptionData or RedeemCodeResult) since it equals
Awaited<ReturnType<typeof redeemCode>>; rename the alias and then update all
local references/usages of OrganizationTaxIdUpdateData in this module (places
that consume the redeemCode return type) to the new name so the type meaning
matches redeemCode.
🧹 Nitpick comments (4)
apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx (1)

119-121: Consider adding initHcaptcha to the dependency array.

The useEffect calls initHcaptcha but it's not in the dependency array. While the current pattern works because initHcaptcha is redefined on each render and captures the necessary state, adding it to deps would make the intent clearer. This is a minor observation and the current implementation is functional.

apps/studio/components/interfaces/Organization/BillingSettings/CreditCodeRedemption.tsx (1)

156-171: Use the destructured code parameter instead of form.getValues.

The code value is already available from the destructured parameter. Using form.getValues('code') is redundant and could introduce subtle timing issues.

♻️ Proposed fix
   const onSubmit: SubmitHandler<CreditCodeRedemptionForm> = async ({ code }) => {
     const token = await initHcaptcha()
 
     await redeemCode(
       {
         slug,
-        code: form.getValues('code'),
+        code,
         hcaptchaToken: token,
       },
       {
         onSuccess: (data) => {
           resetCaptcha()
         },
       }
     )
   }
apps/studio/data/organizations/organization-credit-code-redemption-mutation.ts (2)

9-13: Duplicate type definition.

OrganizationCreditCodeRedemptionVariables is also defined in organization-credit-code-preview-query.ts (lines 69-73). Consider exporting from a single location to avoid drift.


67-74: Consider parallel invalidation for better performance.

The sequential await calls could be parallelized since they're independent operations.

♻️ Proposed fix
     async onSuccess(data, variables, context) {
       const { slug, code } = variables
 
-      await queryClient.invalidateQueries({ queryKey: organizationKeys.customerProfile(slug) })
-      await queryClient.invalidateQueries({ queryKey: subscriptionKeys.orgSubscription(slug) })
-      await queryClient.invalidateQueries({
-        queryKey: organizationKeys.previewCreditCode(slug, code),
-      })
+      await Promise.all([
+        queryClient.invalidateQueries({ queryKey: organizationKeys.customerProfile(slug) }),
+        queryClient.invalidateQueries({ queryKey: subscriptionKeys.orgSubscription(slug) }),
+        queryClient.invalidateQueries({ queryKey: organizationKeys.previewCreditCode(slug, code) }),
+      ])
       await onSuccess?.(data, variables, context)
     },

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 2, 2026

🎭 Playwright Test Results

passed  85 passed
flaky  1 flaky
skipped  4 skipped

Details

stats  90 tests across 14 suites
duration  12 minutes, 59 seconds
commit  374ebee

Flaky tests

Features › table-editor.spec.ts › table editor › can create and remove foreign key with column selection

Skipped tests

Features › sql-editor.spec.ts › SQL Editor › snippet favourite works as expected
Features › sql-editor.spec.ts › SQL Editor › share with team works as expected
Features › sql-editor.spec.ts › SQL Editor › folders works as expected
Features › sql-editor.spec.ts › SQL Editor › other SQL snippets actions work as expected

Copy link
Copy Markdown
Contributor

@ignaciodob ignaciodob left a comment

Choose a reason for hiding this comment

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

The UX of making a request whenever the coupon code changes is very nice and snappy - but I think that a coupon form is not the best place to use it. We don't want users to be encouraged to easily test a list of coupons, in my opinion. It's a minor thing, though - and it's not like the codes are guessable.

But in the spirit of reducing the amount of code, I would like to simplify it to just check validity on "Redeem"

@kevcodez
Copy link
Copy Markdown
Contributor Author

kevcodez commented Feb 2, 2026

@ignaciodob in that case, how would you give the user a preview of how many credits are going to be applied, when they expire etc. Only as a follow up once applied in another modal?

There are also some pretty hard rate limits (10ish per hour) on the validation/redemption in the API, so customers cannot really easily brute-force

@ignaciodob
Copy link
Copy Markdown
Contributor

@ignaciodob in that case, how would you give the user a preview of how many credits are going to be applied, when they expire etc. Only as a follow up once applied in another modal?

There are also some pretty hard rate limits (10ish per hour) on the validation/redemption in the API, so customers cannot really easily brute-force

I didn't know about the rate limits, that's pretty good already!
I don't think I'd introduce a second modal.

My preference would be:

  • Validate only on Redeem.
  • If valid, apply immediately.
  • Show a success state in the same modal with credits applied, expiration (if any) and updated balance

@kevcodez kevcodez force-pushed the k-dog/billing-1939-allow-redeeming-code-in-billing-org-settings branch from 0d17ef3 to 2565e6a Compare February 4, 2026 06:36
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 (1)
apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx (1)

167-174: ⚠️ Potential issue | 🟡 Minor

Clear hCaptcha state when closing the dialog.
Otherwise a stale token can be reused after reopening the modal and trigger a failed top‑up.

🧹 Proposed fix
 const onTopUpDialogVisibilityChange = (visible: boolean) => {
   setTopUpModalVisible(visible)
   if (!visible) {
+    resetCaptcha()
     setCaptchaRef(null)
     setPaymentIntentConfirmation(undefined)
     setPaymentIntentSecret('')
   }
 }
🧹 Nitpick comments (3)
apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx (1)

100-121: Make initHcaptcha an effect-safe stable callback.
It reads component state but is invoked from useEffect without a stable reference, which can trigger exhaustive‑deps warnings and stale closures.

♻️ Suggested update
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useStaticEffectEvent } from 'hooks/misc/useStaticEffectEvent'
@@
-  const initHcaptcha = async () => {
+  const initHcaptcha = useStaticEffectEvent(async () => {
     if (topUpModalVisible && captchaRef) {
       let token = captchaToken
@@
-  }
+  })
 
   useEffect(() => {
     initHcaptcha()
-  }, [topUpModalVisible, captchaRef])
+  }, [topUpModalVisible, captchaRef, initHcaptcha])
As per coding guidelines: Use `useStaticEffectEvent` hook when you need to read latest state/props inside an Effect without re-triggering it.
apps/studio/components/interfaces/Organization/BillingSettings/CreditCodeRedemption.tsx (2)

107-129: Prefer useStaticEffectEvent over useLatest for effect callbacks.
This keeps the callback stable while always reading fresh state, and aligns with the studio hook guideline.

♻️ Suggested update
-import useLatest from '@/hooks/misc/useLatest'
+import { useStaticEffectEvent } from '@/hooks/misc/useStaticEffectEvent'
@@
-  const initHcaptcha = async () => {
+  const initHcaptcha = useStaticEffectEvent(async () => {
     let token = captchaTokenRef.current
@@
-  }
-  const initHcaptchaRef = useLatest(initHcaptcha)
+  })
 
   useEffect(() => {
     if (codeRedemptionModalVisible) {
-      initHcaptchaRef.current()
+      initHcaptcha()
     }
-  }, [codeRedemptionModalVisible, initHcaptchaRef])
+  }, [codeRedemptionModalVisible, initHcaptcha])
As per coding guidelines: Use `useStaticEffectEvent` hook when you need to read latest state/props inside an Effect without re-triggering it.

74-76: Gate the customer profile query when the feature flag is off.
This avoids extra API calls in production when redemption is disabled.

💡 Suggested update
-  const { data: customerProfile, isLoading: isCustomerProfileLoading } =
-    useOrganizationCustomerProfileQuery({ slug })
+  const { data: customerProfile, isLoading: isCustomerProfileLoading } =
+    useOrganizationCustomerProfileQuery(
+      { slug },
+      { enabled: redeemCodeEnabled && typeof slug !== 'undefined' }
+    )

@kevcodez kevcodez merged commit ed01625 into master Feb 4, 2026
24 of 25 checks passed
@kevcodez kevcodez deleted the k-dog/billing-1939-allow-redeeming-code-in-billing-org-settings branch February 4, 2026 06:58
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 4, 2026

Braintrust eval report

Evals in progress... ⌛

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants