Customer Billing

Customer billing separates what customers can see from what operators can investigate.

On this page

Customer and operator surfaces

SurfaceWhat it showsNotes
/activityCurrent subscriptions, usage, credits, and recent billing events.Requires billing access and provides the current activity dashboard.
/billing/historyCustomer-owned purchases, payments, invoices, refunds, and subscription changes in one activity container.Uses normalized activity rows and does not expose raw webhook payloads or operator controls.
/api/billing/portalProvider-hosted portal launch for the signed-in subscription customer.Lemon Squeezy one-time customers use order receipt links because the provider returns no portal URL without a subscription.
/billing/customersOperator list of provider customers.Super-admin oriented customer operations.
/billing/customers/[providerCustomerId]Customer ledger detail, subscriptions, invoices, charges, refunds, disputes, entitlements, and webhooks.Provider capability differences are shown in UI.
/api/system/billing/exportCustomer CSV plus Stripe invoice or Lemon Squeezy one-time-order CSV exports.Lemon Squeezy exports include order totals, refund state, and receipt links.
Implementation reference3 areas

These Product code locations explain how the documented behavior is implemented. Expand them when you are ready to customize or maintain this area.

Customer-facing billing activity

Signed-in customers see account-owned normalized billing activity, not raw provider payloads.

  • src/app/(dashboard)/billing/page.tsx
  • src/features/billing/server/queries/activity.ts
  • src/features/billing/ui/hub/activity.tsx

Portal access

Portal launch uses the active provider and current customer record through a server service.

  • src/app/api/billing/portal/route.ts
  • src/features/billing/server/services/portal.ts
  • src/features/billing/server/providers/stripe/portal.ts
  • src/features/billing/server/providers/lemonsqueezy/portal.ts

Operator investigation

Customer detail pages, sync, refunds, and exports are admin-oriented workflows with provider capability differences.

  • src/app/(dashboard)/billing/customers/[providerCustomerId]/page.tsx
  • src/features/billing/server/queries/customer-history.ts
  • src/features/billing/server/services/customer-sync.ts
  • src/features/billing/server/services/admin-refund.ts
  • src/features/billing/server/export/service.ts

Reference paths are relative to the Shipflash-Product checkout.

Customer billing support flow

  1. 1

    Start from local history

    Customer and operator pages read projected subscriptions, orders, invoices, charges, refunds, disputes, and entitlements from local tables.

    Relevant Product code
    • src/features/billing/server/queries/current-profile-history.ts
    • src/features/billing/server/queries/customer-history.ts
  2. 2

    Use portal for customer changes

    The portal service resolves the active provider and customer before returning a provider-hosted URL.

    Relevant Product code
    • src/features/billing/server/services/portal.ts
  3. 3

    Sync provider state deliberately

    Provider refresh and refunds stay behind admin services so support actions remain auditable and provider-aware.

    Relevant Product code
    • src/features/billing/server/services/customer-sync.ts
    • src/features/billing/server/services/admin-refund.ts
  4. 4

    Export from local projections

    CSV exports are assembled from local customer and invoice rows after authorization checks.

    Relevant Product code
    • src/features/billing/server/export/request/auth.ts
    • src/features/billing/server/export/service.ts

Portal request schema

Use this example as a starting point, then adapt it to your product's rules and configuration.

Portal requests accept only relative return paths so provider redirects stay inside the app.

ts
const PortalBodySchema = z.object({
  return_path: z
    .string()
    .trim()
    .max(300)
    .refine((value) => value.startsWith("/") && !value.startsWith("//"), {
      message: "return_path must be a relative path",
    })
    .optional(),
  source: z.string().trim().max(80).optional().default("dashboard_billing_hub"),
});
Source reference
  • src/features/billing/server/services/portal.ts

Support workflow

  • Start from the customer detail page when investigating billing support.
  • Use provider sync to reconcile Stripe subscriptions or Lemon Squeezy one-time orders.
  • Use local projections as the dashboard source of truth after webhooks arrive.
  • Keep refunds behind super-admin checks; Lemon Squeezy chargebacks remain provider-managed because no dispute API is exposed.