Customer Billing
Customer billing separates what customers can see from what operators can investigate.
On this page
Customer and operator surfaces
| Surface | What it shows | Notes |
|---|---|---|
| /activity | Current subscriptions, usage, credits, and recent billing events. | Requires billing access and provides the current activity dashboard. |
| /billing/history | Customer-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/portal | Provider-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/customers | Operator 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/export | Customer 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.tsxsrc/features/billing/server/queries/activity.tssrc/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.tssrc/features/billing/server/services/portal.tssrc/features/billing/server/providers/stripe/portal.tssrc/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.tsxsrc/features/billing/server/queries/customer-history.tssrc/features/billing/server/services/customer-sync.tssrc/features/billing/server/services/admin-refund.tssrc/features/billing/server/export/service.ts
Reference paths are relative to the Shipflash-Product checkout.
Customer billing support flow
- 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.tssrc/features/billing/server/queries/customer-history.ts
- 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
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.tssrc/features/billing/server/services/admin-refund.ts
- 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.tssrc/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.
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.