import { AxiosError } from 'axios' import apiClient from './client' import { BillingPortalError, type BillingPortalErrorCode, type BillingPortalSessionResponse, type BillingStateApiResponse, type BillingStatePayload, type CheckoutSessionRequest, type CheckoutSessionResponse, } from '@/types/billing' /** * Single boundary where the snake_case backend payload is transformed * into the camelCase shape used by the rest of the frontend. * * Keeping the transform here means the store, hooks, and components * never see snake_case keys. */ function transformBillingState(raw: BillingStateApiResponse): BillingStatePayload { return { subscription: raw.subscription ?? null, planBilling: raw.plan_billing ?? null, planLimits: raw.plan_limits ?? {}, enabledFeatures: raw.enabled_features ?? {}, } } export const billingApi = { async getState(): Promise { const response = await apiClient.get('/billing/state') return transformBillingState(response.data) }, /** * Request a Stripe Customer Portal session URL for the active account. * * Throws a typed `BillingPortalError` when: * - HTTP 503 → `stripe_not_configured` (server-side Stripe is disabled) * - HTTP 400 + `error: 'no_stripe_customer'` → account hasn't been billed yet * * Other errors (5xx, network) propagate as the underlying AxiosError. */ async getPortalSession(): Promise { try { const response = await apiClient.get( '/billing/portal-session', ) return response.data } catch (err) { if (err instanceof AxiosError && err.response) { const { status, data } = err.response const code: BillingPortalErrorCode | null = status === 503 ? 'stripe_not_configured' : status === 400 && data?.detail?.error === 'no_stripe_customer' ? 'no_stripe_customer' : null if (code) { throw new BillingPortalError(code) } } throw err } }, async createCheckoutSession( payload: CheckoutSessionRequest, ): Promise { const response = await apiClient.post( '/billing/checkout-session', payload, ) return response.data }, } export default billingApi