Skip to main content

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

  1. Align GraphQL schema and resolvers with the frontend contract and billing plan:

    • Add/alias queries: billingDetails (→ billingOverview), availableProducts, availablePrices(productId), paymentMethods, invoices(limit, offset) returning InvoiceConnection, and downloadInvoice(invoiceId).
    • Add mutations: createSetupIntent, attachPaymentMethod, setDefaultPaymentMethod, detachPaymentMethod, createCustomerPortalSession (keep createSubscription et al.).
    • Keep Yoga running inside Supabase Edge Function graphql-v2 as our canonical API endpoint.
  2. 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.
  3. 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.ts
    • supabase/functions/graphql-v2/resolvers/billing/mutation-resolvers.ts
    • supabase/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 shadcn Card sections 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.tsx shadcn 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
  • ADR-0010: Theme Management with next-themes
  • ADR-004: GraphQL Schema-First TypeScript Code Generation