Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6b896ea
Add Payment Methods Session functionality
sarimrmalik Apr 27, 2026
d8aba17
Refactor loading messages in MissingPaymentMethodDialog
sarimrmalik Apr 28, 2026
f33b1b3
Refactor payment methods session parsing in BillingRepository
sarimrmalik Apr 28, 2026
296f39d
Remove unused code
sarimrmalik Apr 28, 2026
521b47d
Refactor team blockage handling in dashboard components
sarimrmalik Apr 28, 2026
4f87894
Refactor MissingPaymentMethodDialog components to use arrow function …
sarimrmalik Apr 28, 2026
1af0c55
Refactor MissingPaymentMethodDialog to streamline error handling and …
sarimrmalik Apr 28, 2026
8e09eeb
Run biome format
sarimrmalik Apr 28, 2026
6d2000b
Merge remote-tracking branch 'origin/main' into fix/team-blocked-miss…
sarimrmalik Apr 30, 2026
e9221af
feat: add verification payment functionality
sarimrmalik Apr 30, 2026
e028bff
Merge remote-tracking branch 'origin/main' into fix/team-blocked-miss…
sarimrmalik May 5, 2026
9a4451d
Enhance payment method dialogs with success toasts and update verific…
sarimrmalik May 5, 2026
5e8ec1d
Add constants for blocked reasons in team module
sarimrmalik May 5, 2026
6918c6b
Refactor TeamBlockedIndicator to streamline dialog management
sarimrmalik May 5, 2026
11a1d68
Remove unused capitalize function from formatting utility
sarimrmalik May 5, 2026
ec810df
Enhance payment method and verification dialogs with improved error h…
sarimrmalik May 5, 2026
3ba3b46
Implement setup intent handling in MissingPaymentMethodDialog
sarimrmalik May 5, 2026
674fdfe
Run biome format
sarimrmalik May 5, 2026
304113c
Refactor useBlockedMessage logic in TeamBlockedIndicator to handle mu…
sarimrmalik May 5, 2026
5b7bfc1
Refactor setup intent handling in MissingPaymentMethodDialog
sarimrmalik May 5, 2026
6e58d37
Refactor payment methods session handling in MissingPaymentMethodDialog
sarimrmalik May 5, 2026
9e2fcec
Implement team unblock polling and enhance verification payment handl…
sarimrmalik May 5, 2026
238610f
Refactor team-blocked dialogs and consolidate payment handling
sarimrmalik May 6, 2026
80fd811
Enhance verification payment response and update dialog payment display
sarimrmalik May 6, 2026
bd31f5b
Add session storage hook and enhance team blocked dialog handling
sarimrmalik May 6, 2026
06166fa
Update team unblock polling configuration for improved responsiveness
sarimrmalik May 6, 2026
d2e0655
Refactor team-blocked dialogs to remove unnecessary useEffect hooks
sarimrmalik May 6, 2026
731a798
Refactor TeamBlockedIndicator to use Button component for improved UI…
sarimrmalik May 6, 2026
540448a
Add PostHog event tracking for payment method and verification submis…
sarimrmalik May 6, 2026
7f513d2
Refactor Stripe return handling in useStripeReturnHandler for improve…
sarimrmalik May 6, 2026
044abce
Refactor team blocked reason handling and update dialog components
sarimrmalik May 6, 2026
c9bb235
Run biome format
sarimrmalik May 6, 2026
4303729
Refactor TeamBlockedIndicator and related components for improved cla…
sarimrmalik May 6, 2026
4553e53
Refactor dialog components to improve state management and clarity
sarimrmalik May 6, 2026
f7137b9
Request setup intent for payment method recovery
matthewlouisbrockman May 7, 2026
79a7e0e
Clarify payment setup session types
matthewlouisbrockman May 7, 2026
b43f89f
Fix customer session billing route
matthewlouisbrockman May 7, 2026
3853c98
Match blocked indicator CTA to surrounding label style
ben-fornefeld May 8, 2026
5b3979b
Clear blocked-dialog dismissed flags when team is unblocked
ben-fornefeld May 8, 2026
bbc7960
Revert indicator-level dismissed-flag cleanup
ben-fornefeld May 8, 2026
7cf7b2d
Add mocked team block recovery tests
matthewlouisbrockman May 8, 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
14 changes: 14 additions & 0 deletions src/core/modules/billing/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,24 @@ export interface AddOnOrderConfirmResponse {
client_secret: string
}

export interface VerificationPaymentResponse {
client_secret: string
amount_due_cents: number
}

export interface PaymentMethodsCustomerSession {
client_secret: string
}

export interface PaymentMethodsSession {
client_secret: string
setup_intent_client_secret?: string
}

export interface PaymentMethodsSetupSession extends PaymentMethodsSession {
setup_intent_client_secret: string
}

export interface TierLimits {
sandbox_concurrency: number
max_cpu: number
Expand Down
73 changes: 72 additions & 1 deletion src/core/modules/billing/repository.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'server-only'

import { z } from 'zod'
import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
import type {
AddOnOrderConfirmResponse,
Expand All @@ -8,8 +9,10 @@ import type {
CustomerPortalResponse,
Invoice,
PaymentMethodsCustomerSession,
PaymentMethodsSetupSession,
TeamItems,
UsageResponse,
VerificationPaymentResponse,
} from '@/core/modules/billing/models'
import { repoErrorFromHttp } from '@/core/shared/errors'
import type { TeamRequestScope } from '@/core/shared/repository-scope'
Expand All @@ -35,12 +38,24 @@ export interface BillingRepository {
createOrder(itemId: string): Promise<RepoResult<AddOnOrderCreateResponse>>
confirmOrder(orderId: string): Promise<RepoResult<AddOnOrderConfirmResponse>>
getCustomerSession(): Promise<RepoResult<PaymentMethodsCustomerSession>>
createPaymentMethodsSession(): Promise<RepoResult<PaymentMethodsSetupSession>>
createVerificationPayment(): Promise<RepoResult<VerificationPaymentResponse>>
}

async function parseText(response: Response): Promise<string> {
return (await response.text()) || 'Request failed'
}

const PaymentMethodsSessionResponseSchema = z.object({
client_secret: z.string().min(1),
setup_intent_client_secret: z.string().min(1),
})

const VerificationPaymentResponseSchema = z.object({
client_secret: z.string().min(1),
amount_due_cents: z.number().int().positive(),
})

export function createBillingRepository(
scope: BillingScope,
deps: BillingRepositoryDeps = {
Expand Down Expand Up @@ -249,7 +264,7 @@ export function createBillingRepository(
},
async getCustomerSession() {
const res = await fetch(
`${deps.billingApiUrl}/teams/${scope.teamId}/payment-methods/customer-session`,
`${deps.billingApiUrl}/teams/${scope.teamId}/payment-methods-session`,
{
method: 'POST',
headers: {
Expand All @@ -265,5 +280,61 @@ export function createBillingRepository(

return ok((await res.json()) as PaymentMethodsCustomerSession)
},
async createPaymentMethodsSession() {
const res = await fetch(
`${deps.billingApiUrl}/teams/${scope.teamId}/payment-methods-session?include_setup_intent=true`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...SUPABASE_AUTH_HEADERS(scope.accessToken, scope.teamId),
},
}
)

if (!res.ok) {
return err(repoErrorFromHttp(res.status, await parseText(res)))
}

const parseResult = PaymentMethodsSessionResponseSchema.safeParse(
await res.json()
)

if (!parseResult.success) {
return err(
repoErrorFromHttp(500, 'Invalid payment methods session response')
)
}

return ok(parseResult.data)
},
async createVerificationPayment() {
const res = await fetch(
`${deps.billingApiUrl}/teams/${scope.teamId}/verification-payment`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...SUPABASE_AUTH_HEADERS(scope.accessToken, scope.teamId),
},
}
)

if (!res.ok) {
return err(repoErrorFromHttp(res.status, await parseText(res)))
}

const parseResult = VerificationPaymentResponseSchema.safeParse(
await res.json()
)

if (!parseResult.success) {
return err(
repoErrorFromHttp(500, 'Invalid verification payment response')
)
}

return ok(parseResult.data)
},
}
}
6 changes: 6 additions & 0 deletions src/core/modules/teams/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const TEAM_BLOCKED_REASONS = {
missingPayment: 'missing payment method',
verification: 'verification required',
} as const

export { TEAM_BLOCKED_REASONS }
3 changes: 3 additions & 0 deletions src/core/modules/teams/models.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { components as DashboardComponents } from '@/contracts/dashboard-api'
import type { TEAM_BLOCKED_REASONS } from './constants'

export type TeamModel = DashboardComponents['schemas']['UserTeam']
export type TeamLimits = DashboardComponents['schemas']['UserTeamLimits']
export type TeamBlockedReason =
(typeof TEAM_BLOCKED_REASONS)[keyof typeof TEAM_BLOCKED_REASONS]

export type TeamMemberInfo = {
id: string
Expand Down
22 changes: 15 additions & 7 deletions src/core/server/api/routers/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { TRPCError } from '@trpc/server'
import { headers } from 'next/headers'
import { z } from 'zod'
import { createBillingRepository } from '@/core/modules/billing/repository.server'
import { createTeamsRepository } from '@/core/modules/teams/teams-repository.server'
import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors'
import { withTeamAuthedRequestRepository } from '@/core/server/api/middlewares/repository'
import { createTRPCRouter } from '@/core/server/trpc/init'
Expand All @@ -25,12 +24,6 @@ const billingRepositoryProcedure = protectedTeamProcedure.use(
)
)

const billingAndTeamsRepositoryProcedure = billingRepositoryProcedure.use(
withTeamAuthedRequestRepository(createTeamsRepository, (teamsRepository) => ({
teamsRepository,
}))
)

export const billingRouter = createTRPCRouter({
createCheckout: billingRepositoryProcedure
.input(z.object({ tierId: z.string() }))
Expand Down Expand Up @@ -136,4 +129,19 @@ export const billingRouter = createTRPCRouter({
if (!result.ok) throwTRPCErrorFromRepoError(result.error)
return result.data
}),

createPaymentMethodsSession: billingRepositoryProcedure.mutation(
async ({ ctx }) => {
const result = await ctx.billingRepository.createPaymentMethodsSession()
if (!result.ok) throwTRPCErrorFromRepoError(result.error)
return result.data
}
),
createVerificationPayment: billingRepositoryProcedure.mutation(
async ({ ctx }) => {
const result = await ctx.billingRepository.createVerificationPayment()
if (!result.ok) throwTRPCErrorFromRepoError(result.error)
return result.data
}
),
})
15 changes: 15 additions & 0 deletions src/features/dashboard/billing/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ export function usePaymentElementAppearance() {
'.AccordionButton--selected:hover': {
backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2',
},
'.CheckboxInput': {
backgroundColor: isDark ? '#1f1f1f' : '#ffffff',
border: isDark ? '1px solid #424242' : '1px solid #707070',
boxShadow: 'none',
},
'.CheckboxInput:hover': {
border: isDark ? '1px solid #848484' : '1px solid #333333',
},
'.CheckboxInput--checked': {
backgroundColor: isDark ? '#ff8800' : '#e56f00',
border: isDark ? '1px solid #ff8800' : '1px solid #e56f00',
},
'.CheckboxLabel': {
color: isDark ? '#e6e6e6' : '#333333',
},
'.Spinner': {
color: isDark ? '#ff8800' : '#e56f00', // accent-main-highlight
borderColor: isDark
Expand Down
Loading
Loading