Source:
ocean/docs/adr/0016-graphql-billing-alignment-and-ui-standards.md| ✏️ Edit on GitHub
ADR-0016: GraphQL Billing Resolver Alignment and Billing UI Layout Standards
Date: 2025-08-12
Status
Accepted
Context
The billing page plan and the implemented code had important gaps:
- Frontend billing hooks expected GraphQL fields that were missing or misnamed on the server (e.g.,
billingDetails,availableProducts,availablePrices,paymentMethods). - Missing billing mutations caused runtime blockers (setup intent, portal session, payment method management).
- The invoices query returned a plain array while the plan/UI used a connection shape.
- The billing route layout risked visual misalignment; we want consistent shadcn/ui composition and TanStack Table usage per ADR-0001 and our design system ADRs.
- Stripe SDK/version usage was fragmented across functions.
These issues led to integration pain, inconsistent UI, and harder maintenance.
Decision
-
Align GraphQL schema and resolvers with the frontend contract and billing plan:
- Add/alias queries:
billingDetails(→billingOverview),availableProducts,availablePrices(productId),paymentMethods,invoices(limit, offset)returningInvoiceConnection, anddownloadInvoice(invoiceId). - Add mutations:
createSetupIntent,attachPaymentMethod,setDefaultPaymentMethod,detachPaymentMethod,createCustomerPortalSession(keepcreateSubscriptionet al.). - Keep Yoga running inside Supabase Edge Function
graphql-v2as our canonical API endpoint.
- Add/alias queries:
-
Standardize the Billing UI with shadcn/ui and TanStack patterns:
- Use a predictable two-column grid, shadcn
Card/Separator, avoid layout shifts, and keep invoice tables on TanStack Table + shadcn table primitives.
- Use a predictable two-column grid, shadcn
-
Move toward a unified Stripe API version across functions (target:
'2025-06-30') to reduce SDK behavior drift.
Implementation Details
Files/Modules
-
GraphQL schema:
supabase/functions/graphql-v2/schema.ts
-
Resolvers:
supabase/functions/graphql-v2/resolvers/billing/query-resolvers.tssupabase/functions/graphql-v2/resolvers/billing/mutation-resolvers.tssupabase/functions/graphql-v2/resolvers/billing-enhanced.ts
-
UI layout:
src/routes/dashboard.billing.tsx
GraphQL Additions (high-level)
type Query {
# Aliases/new
billingDetails: BillingDetails!
availableProducts: [Product!]!
availablePrices(productId: ID): [Price!]!
paymentMethods: [PaymentMethod!]!
invoices(limit: Int = 10, offset: Int = 0): InvoiceConnection!
downloadInvoice(invoiceId: ID!): DownloadInvoiceResult!
}
type Mutation {
createSetupIntent: SetupIntentResult!
attachPaymentMethod(paymentMethodId: String!): PaymentMethod!
setDefaultPaymentMethod(paymentMethodId: String!): PaymentMethod!
detachPaymentMethod(paymentMethodId: String!): Boolean!
createCustomerPortalSession(returnUrl: String!): PortalSessionResult!
}
type InvoiceConnection {
items: [Invoice!]!
totalCount: Int!
hasMore: Boolean!
}
type DownloadInvoiceResult {
url: String!
expiresAt: String
}
UI Layout Standards (Billing)
- Use a two-column responsive grid (
grid-cols-1 md:grid-cols-2) with consistent gaps and shadcnCardsections for:- Current Plan (left stack)
- Available Plans (left stack)
- Payment Methods (right stack)
- Billing History / Invoices (right stack)
- Tables must use
src/components/ui/table.tsxshadcn primitives paired with TanStack Table for sorting/pagination; avoid custom table markup.
Consequences
Positive
- Frontend hooks and server resolvers now match, eliminating runtime errors.
- Clear, consistent billing page layout; fewer visual regressions.
- Easier maintenance and future enhancements (proration preview, dunning, etc.).
- Moves us toward a unified Stripe API version footprint.
Negative
- Additional resolver surface area to maintain.
- Minor refactors required if Stripe SDK version is fully unified later.
Migration/Deployment Notes
- Supabase Edge Functions must be deployed after schema/resolver changes (see ADR-010):
supabase functions deploy graphql-v2- Ensure secrets:
SUPABASE_URL,SUPABASE_SECRET_KEY/SERVICE_ROLE_KEY,STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET.
- No database migration required for these additions if existing tables (
stripe_products,stripe_prices,payment_methods,stripe_invoices) are present.
Alternatives Considered
- Rename frontend hooks to match existing backend names (rejected: more churn on UI, still leaves missing mutations).
- Use only Stripe Portal for all actions (rejected: worse UX, limits self-serve flows).
References
- ADR-0012: GraphQL-Based Billing Architecture
- ADR-0001: Adopt TanStack Ecosystem for Forms and Data Fetching
- ADR-0011: Comprehensive Design System Enhancements
- ADR-003: Sentry v9 Monitoring (Edge Functions)
- ADR-003: PostHog Analytics Implementation
- ADR-010: Manual Supabase Deployment Process
Decision Makers
- Engineering Team
Related ADRs
- ADR-0010: Theme Management with next-themes
- ADR-004: GraphQL Schema-First TypeScript Code Generation