@usethrottle/subscriptions 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.tsx","../src/hooks.ts","../../core-cadence/src/cadence.ts","../../core-cadence/src/installments.ts","../../core-cadence/src/labels.ts","../src/helpers.ts","../src/components.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useMemo, useReducer, type ReactNode } from 'react';\nimport { SubscriptionsClient } from './client';\nimport type {\n SubscriptionFetcher,\n SubscriptionProviderProps,\n SubscriptionsClientContract,\n} from './types';\n\ninterface SubscriptionContextValue {\n client: SubscriptionsClientContract;\n cacheVersion: number;\n invalidate: () => void;\n}\n\nconst SubscriptionContext = createContext<SubscriptionContextValue | null>(null);\n\nexport function SubscriptionProvider({\n children,\n client,\n fetcher,\n baseUrl,\n timeoutMs,\n}: SubscriptionProviderProps) {\n const [cacheVersion, invalidate] = useReducer((value: number) => value + 1, 0);\n const resolvedClient = useMemo(() => {\n if (client) return client;\n return new SubscriptionsClient({\n fetcher: fetcher ?? createSubscriptionFetcher('/api/throttle'),\n baseUrl,\n timeoutMs,\n });\n }, [baseUrl, client, fetcher, timeoutMs]);\n\n const value = useMemo(\n () => ({ client: resolvedClient, cacheVersion, invalidate }),\n [cacheVersion, resolvedClient],\n );\n\n return <SubscriptionContext.Provider value={value}>{children}</SubscriptionContext.Provider>;\n}\n\nexport function useSubscriptionClient(): SubscriptionsClientContract {\n return useSubscriptionContext().client;\n}\n\nexport function useSubscriptionInvalidation(): () => void {\n return useSubscriptionContext().invalidate;\n}\n\nexport function useSubscriptionCacheVersion(): number {\n return useSubscriptionContext().cacheVersion;\n}\n\nexport function createSubscriptionFetcher(basePath: string): SubscriptionFetcher {\n const cleanBase = basePath.replace(/\\/+$/, '');\n return (path, init) => {\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return fetch(`${cleanBase}${cleanPath}`, init);\n };\n}\n\nexport function useSubscriptionContext(): SubscriptionContextValue {\n const ctx = useContext(SubscriptionContext);\n if (!ctx) {\n throw new Error(\n '@usethrottle/subscriptions hooks must be used inside <SubscriptionProvider />',\n );\n }\n return ctx;\n}\n\nexport function SubscriptionProviderWithClient({\n children,\n client,\n}: {\n children: ReactNode;\n client: SubscriptionsClientContract;\n}) {\n const [cacheVersion, invalidate] = useReducer((value: number) => value + 1, 0);\n const value = useMemo(() => ({ client, cacheVersion, invalidate }), [cacheVersion, client]);\n return <SubscriptionContext.Provider value={value}>{children}</SubscriptionContext.Provider>;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n useSubscriptionCacheVersion,\n useSubscriptionClient,\n useSubscriptionInvalidation,\n} from './provider';\nimport type {\n CancelSubscriptionInput,\n ChangePlanInput,\n CheckoutSession,\n CreateCheckoutSessionInput,\n CreateSubscriptionInput,\n Customer,\n CustomerPaymentMethod,\n ListSubscriptionsParams,\n MutationState,\n PaginatedSubscriptions,\n QueryOptions,\n QueryState,\n Subscription,\n TrialEligibilityInput,\n TrialEligibilityResult,\n UpdateSubscriptionInput,\n} from './types';\n\nexport function useSubscriptions(\n params: ListSubscriptionsParams = {},\n options: QueryOptions<PaginatedSubscriptions> = {},\n): QueryState<PaginatedSubscriptions> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n const stableParams = useStableValue(params);\n return useAsyncQuery(\n () => client.list(stableParams),\n [client, stableParams, cacheVersion],\n options,\n );\n}\n\nexport function useSubscription(\n id: string | null | undefined,\n options: QueryOptions<Subscription> = {},\n): QueryState<Subscription> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n return useAsyncQuery(\n () => (id ? client.get(id) : Promise.resolve(null)),\n [client, id, cacheVersion],\n { ...options, enabled: options.enabled ?? Boolean(id) },\n );\n}\n\nexport function useCustomerByExternalId(\n externalId: string | null | undefined,\n options: QueryOptions<Customer> = {},\n): QueryState<Customer> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n return useAsyncQuery(\n () => (externalId ? client.getCustomerByExternalId(externalId) : Promise.resolve(null)),\n [client, externalId, cacheVersion],\n { ...options, enabled: options.enabled ?? Boolean(externalId) },\n );\n}\n\nexport function useCustomerPaymentMethods(\n customerId: string | null | undefined,\n options: QueryOptions<CustomerPaymentMethod[]> = {},\n): QueryState<CustomerPaymentMethod[]> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n return useAsyncQuery(\n () => (customerId ? client.listCustomerPaymentMethods(customerId) : Promise.resolve(null)),\n [client, customerId, cacheVersion],\n { ...options, enabled: options.enabled ?? Boolean(customerId) },\n );\n}\n\nexport function useTrialEligibility(): MutationState<\n TrialEligibilityResult,\n TrialEligibilityInput\n> {\n const client = useSubscriptionClient();\n return useAsyncMutation((input) => client.checkTrialEligibility(input));\n}\n\nexport function useCreateSubscription(): MutationState<Subscription, CreateSubscriptionInput> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.create(input), invalidate);\n}\n\nexport function useUpdateSubscription(): MutationState<\n Subscription,\n { id: string; data: UpdateSubscriptionInput }\n> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation(({ id, data }) => client.update(id, data), invalidate);\n}\n\nexport function useChangePlan(): MutationState<Subscription, ChangePlanInput> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.changePlan(input), invalidate);\n}\n\nexport function useCancelSubscription(): MutationState<Subscription, CancelSubscriptionInput> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation(\n (input) => client.cancel(input.id, { atPeriodEnd: input.atPeriodEnd }),\n invalidate,\n );\n}\n\nexport function usePauseSubscription(): MutationState<Subscription, { id: string }> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.pause(input.id), invalidate);\n}\n\nexport function useResumeSubscription(): MutationState<Subscription, { id: string }> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.resume(input.id), invalidate);\n}\n\nexport function useCreateCheckoutSession(): MutationState<\n CheckoutSession,\n CreateCheckoutSessionInput\n> {\n const client = useSubscriptionClient();\n return useAsyncMutation((input) => client.createCheckoutSession(input));\n}\n\nfunction useAsyncQuery<T>(\n load: () => Promise<T | null>,\n deps: readonly unknown[],\n options: QueryOptions<T>,\n): QueryState<T> {\n const enabled = options.enabled ?? true;\n const [data, setData] = useState<T | null>(options.initialData ?? null);\n const [error, setError] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(enabled && options.initialData === undefined);\n const [isFetching, setIsFetching] = useState(false);\n const loadRef = useRef(load);\n loadRef.current = load;\n\n const refetch = useCallback(async () => {\n if (!enabled) return data;\n setIsFetching(true);\n setError(null);\n try {\n const next = await loadRef.current();\n setData(next);\n return next;\n } catch (err) {\n setError(err);\n throw err;\n } finally {\n setIsFetching(false);\n setIsLoading(false);\n }\n }, [data, enabled]);\n\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n let cancelled = false;\n setIsFetching(true);\n setError(null);\n load()\n .then((next) => {\n if (!cancelled) setData(next);\n })\n .catch((err) => {\n if (!cancelled) setError(err);\n })\n .finally(() => {\n if (!cancelled) {\n setIsLoading(false);\n setIsFetching(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, ...deps]);\n\n return { data, error, isLoading, isFetching, refetch };\n}\n\nfunction useAsyncMutation<TData, TVariables>(\n action: (variables: TVariables) => Promise<TData>,\n onSuccess?: () => void,\n): MutationState<TData, TVariables> {\n const [data, setData] = useState<TData | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [isPending, setIsPending] = useState(false);\n\n const mutateAsync = useCallback(\n async (variables: TVariables) => {\n setIsPending(true);\n setError(null);\n try {\n const next = await action(variables);\n setData(next);\n onSuccess?.();\n return next;\n } catch (err) {\n setError(err);\n throw err;\n } finally {\n setIsPending(false);\n }\n },\n [action, onSuccess],\n );\n\n const mutate = useCallback(\n (variables: TVariables) => {\n void mutateAsync(variables).catch(() => undefined);\n },\n [mutateAsync],\n );\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setIsPending(false);\n }, []);\n\n return { data, error, isPending, mutate, mutateAsync, reset };\n}\n\nfunction useStableValue<T>(value: T): T {\n const key = JSON.stringify(value);\n return useMemo(() => value, [key]);\n}\n","/**\n * A subscription's billing cadence: \"every N units\".\n *\n * Every subscription row stores a unit and a count; the five named intervals\n * are shorthand for particular combinations. This module is the only place\n * that knows the vocabulary, the bounds, and how a period boundary is\n * computed — the engine, the renewal cron, the API, the dashboard and the\n * buyer surfaces all import it. Before it existed there were three divergent\n * copies of the period math, and the renewal cron's advanced months with\n * `setMonth`, which overflows: a subscription started on Jan 31 renewed on\n * Mar 3 and never returned to the 31st.\n */\n\nexport const INTERVAL_UNITS = ['hour', 'day', 'week', 'month'] as const;\nexport type IntervalUnit = (typeof INTERVAL_UNITS)[number];\n\nexport const NAMED_INTERVALS = ['weekly', 'biweekly', 'monthly', 'quarterly', 'yearly'] as const;\nexport type NamedInterval = (typeof NAMED_INTERVALS)[number];\n\n/** What the `interval` field holds: a named interval, or `custom` for everything else. */\nexport const INTERVAL_VALUES = [...NAMED_INTERVALS, 'custom'] as const;\nexport type IntervalValue = (typeof INTERVAL_VALUES)[number];\n\nexport interface Cadence {\n interval: IntervalValue;\n unit: IntervalUnit;\n count: number;\n}\n\n/** Anything that describes a cadence the way the API and the database do. */\nexport interface CadenceLike {\n interval: string;\n intervalUnit?: string | null;\n intervalCount?: number | null;\n}\n\nexport interface CadenceInput {\n interval?: string | null;\n intervalUnit?: string | null;\n intervalCount?: number | null;\n}\n\nexport const NAMED_SHORTHAND: Readonly<Record<NamedInterval, { unit: IntervalUnit; count: number }>> = {\n weekly: { unit: 'week', count: 1 },\n biweekly: { unit: 'week', count: 2 },\n monthly: { unit: 'month', count: 1 },\n quarterly: { unit: 'month', count: 3 },\n // month × 12 rather than a year unit, so a Feb 29 anniversary clamps to Feb\n // 28 and comes back to Feb 29 in leap years like every other month cadence.\n yearly: { unit: 'month', count: 12 },\n};\n\n/** At most one year in every unit. The shortest cadence is 1 hour. */\nexport const MAX_INTERVAL_COUNT: Readonly<Record<IntervalUnit, number>> = {\n hour: 8760,\n day: 365,\n week: 52,\n month: 12,\n};\n\nexport const HOUR_MS = 3_600_000;\nexport const DAY_MS = 24 * HOUR_MS;\nexport const WEEK_MS = 7 * DAY_MS;\n\nconst FIXED_UNIT_MS = { hour: HOUR_MS, day: DAY_MS, week: WEEK_MS } as const;\n\nexport class CadenceError extends Error {\n readonly statusCode = 400;\n constructor(\n readonly code: 'invalid_interval' | 'interval_conflict',\n message: string,\n ) {\n super(message);\n this.name = 'CadenceError';\n }\n}\n\nexport function isNamedInterval(value: unknown): value is NamedInterval {\n return typeof value === 'string' && (NAMED_INTERVALS as readonly string[]).includes(value);\n}\n\nexport function isIntervalUnit(value: unknown): value is IntervalUnit {\n return typeof value === 'string' && (INTERVAL_UNITS as readonly string[]).includes(value);\n}\n\nfunction isIntervalValue(value: unknown): value is IntervalValue {\n return typeof value === 'string' && (INTERVAL_VALUES as readonly string[]).includes(value);\n}\n\nfunction reduceToLargestUnit(unit: IntervalUnit, count: number): { unit: IntervalUnit; count: number } {\n let u = unit;\n let c = count;\n if (u === 'hour' && c % 24 === 0) {\n u = 'day';\n c = c / 24;\n }\n if (u === 'day' && c % 7 === 0) {\n u = 'week';\n c = c / 7;\n }\n return { unit: u, count: c };\n}\n\nfunction namedFor(unit: IntervalUnit, count: number): NamedInterval | null {\n for (const name of NAMED_INTERVALS) {\n const s = NAMED_SHORTHAND[name];\n if (s.unit === unit && s.count === count) return name;\n }\n return null;\n}\n\n/**\n * Turn whatever a caller sent into the one stored form. Accepts a named\n * interval alone, a unit and count alone, or both — in which case they must\n * agree. The result is reduced to its largest exact unit and mapped to a named\n * interval whenever one matches, so there is exactly one representation per\n * cadence and \"every 2 weeks\" can never be filed apart from \"biweekly\".\n */\nexport function canonicalizeCadence(input: CadenceInput): Cadence {\n const interval = input.interval ?? undefined;\n if (interval !== undefined && !isIntervalValue(interval)) {\n throw new CadenceError('invalid_interval', `Unknown interval \"${interval}\"`);\n }\n const hasUnit = input.intervalUnit != null;\n const hasCount = input.intervalCount != null;\n if (hasUnit !== hasCount) {\n throw new CadenceError('invalid_interval', 'intervalUnit and intervalCount must be sent together');\n }\n if (!hasUnit) {\n if (isNamedInterval(interval)) return { interval, ...NAMED_SHORTHAND[interval] };\n throw new CadenceError(\n 'invalid_interval',\n interval === 'custom'\n ? 'A custom interval needs intervalUnit and intervalCount'\n : 'Send an interval, or intervalUnit and intervalCount',\n );\n }\n\n const unit = input.intervalUnit;\n const count = input.intervalCount as number;\n if (!isIntervalUnit(unit)) {\n throw new CadenceError('invalid_interval', 'intervalUnit must be one of hour, day, week, month');\n }\n if (!Number.isInteger(count) || count < 1) {\n throw new CadenceError('invalid_interval', 'intervalCount must be a whole number of at least 1');\n }\n if (count > MAX_INTERVAL_COUNT[unit]) {\n throw new CadenceError(\n 'invalid_interval',\n `A cadence can be at most one year: ${MAX_INTERVAL_COUNT[unit]} ${unit}s`,\n );\n }\n\n const reduced = reduceToLargestUnit(unit, count);\n const cadence: Cadence = { interval: namedFor(reduced.unit, reduced.count) ?? 'custom', ...reduced };\n if (interval !== undefined && interval !== 'custom' && interval !== cadence.interval) {\n throw new CadenceError(\n 'interval_conflict',\n `interval \"${interval}\" does not match every ${count} ${unit}${count === 1 ? '' : 's'}`,\n );\n }\n return cadence;\n}\n\n/**\n * Read the cadence off a stored subscription. A named interval always means\n * its shorthand, whatever the unit/count columns hold: a canonical row's\n * columns match the shorthand anyway, and code that predates the columns\n * (still serving between migration 0224 and the deploy) changes `interval`\n * without touching them, so for a named row they can be stale. Only a\n * `custom` row is read from its unit and count. The same rule applies to the\n * pending cadence — pass `{ interval: pendingInterval, intervalUnit:\n * pendingIntervalUnit, intervalCount: pendingIntervalCount }`.\n */\nexport function cadenceOf(row: CadenceLike): Cadence {\n if (isNamedInterval(row.interval)) return { interval: row.interval, ...NAMED_SHORTHAND[row.interval] };\n if (row.interval === 'custom' && row.intervalUnit != null && row.intervalCount != null && isIntervalUnit(row.intervalUnit)) {\n return { interval: 'custom', unit: row.intervalUnit, count: row.intervalCount };\n }\n throw new Error(`Unknown subscription interval: ${row.interval}`);\n}\n\n/** The exact period length for hour/day/week cadences; null for months, which vary. */\nexport function fixedPeriodMs(cadence: Cadence): number | null {\n return cadence.unit === 'month' ? null : cadence.count * FIXED_UNIT_MS[cadence.unit];\n}\n\n/** The shortest a period can be. A month counts as 28 days. */\nexport function minimumPeriodMs(cadence: Cadence): number {\n return fixedPeriodMs(cadence) ?? cadence.count * 28 * DAY_MS;\n}\n\n/** anchor + months, keeping the anchor's day-of-month (clamped to month end) and UTC time of day. */\nexport function addMonthsAnchored(anchor: Date, months: number): Date {\n const monthIndex = anchor.getUTCMonth() + months;\n const year = anchor.getUTCFullYear() + Math.floor(monthIndex / 12);\n const month = ((monthIndex % 12) + 12) % 12;\n const lastDay = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();\n return new Date(\n Date.UTC(\n year,\n month,\n Math.min(anchor.getUTCDate(), lastDay),\n anchor.getUTCHours(),\n anchor.getUTCMinutes(),\n anchor.getUTCSeconds(),\n anchor.getUTCMilliseconds(),\n ),\n );\n}\n\n/**\n * The first period boundary strictly after `after`, on the grid measured from\n * `anchor`. Never chained from a previous boundary — that is what made the\n * month-end clamp stick on the 28th. An `after` that is off the grid (a late\n * tick, a recovery) resolves to the next grid date.\n */\nexport function nextPeriodEnd(args: { anchor: Date; cadence: Cadence; after: Date }): Date {\n const { anchor, cadence, after } = args;\n const step = fixedPeriodMs(cadence);\n if (step !== null) {\n const k = Math.max(1, Math.floor((after.getTime() - anchor.getTime()) / step) + 1);\n return new Date(anchor.getTime() + k * step);\n }\n const monthsElapsed =\n (after.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + (after.getUTCMonth() - anchor.getUTCMonth());\n let k = Math.max(1, Math.floor(monthsElapsed / cadence.count));\n let boundary = addMonthsAnchored(anchor, k * cadence.count);\n while (boundary.getTime() <= after.getTime()) {\n k += 1;\n boundary = addMonthsAnchored(anchor, k * cadence.count);\n }\n return boundary;\n}\n\n/**\n * The grid period that contains `at`: the latest boundary at or before it (never\n * before the anchor) and the one after. A subscription that fell more than a\n * period behind is billed once, for this period, and the periods it missed are\n * skipped rather than charged back to back (D38).\n */\nexport function periodContaining(args: { anchor: Date; cadence: Cadence; at: Date }): { start: Date; end: Date } {\n const { anchor, cadence, at } = args;\n const step = fixedPeriodMs(cadence);\n if (step !== null) {\n const k = Math.max(0, Math.floor((at.getTime() - anchor.getTime()) / step));\n const start = new Date(anchor.getTime() + k * step);\n return { start, end: new Date(start.getTime() + step) };\n }\n const monthsElapsed =\n (at.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + (at.getUTCMonth() - anchor.getUTCMonth());\n let k = Math.max(0, Math.floor(monthsElapsed / cadence.count));\n let start = addMonthsAnchored(anchor, k * cadence.count);\n while (k > 0 && start.getTime() > at.getTime()) {\n k -= 1;\n start = addMonthsAnchored(anchor, k * cadence.count);\n }\n return { start, end: addMonthsAnchored(anchor, (k + 1) * cadence.count) };\n}\n\nconst RETRY_CAP_DAYS = [1, 3, 7] as const;\n\n/**\n * How long to wait before retry number `failureNumber` (1..3). Short cadences\n * retry proportionally faster; anything of a week or longer lands on the\n * original 1 / 3 / 7 days exactly.\n */\nexport function dunningRetryDelayMs(cadence: Cadence, failureNumber: number): number {\n const n = Math.min(Math.max(Math.trunc(failureNumber), 1), RETRY_CAP_DAYS.length);\n return Math.min(n * minimumPeriodMs(cadence), RETRY_CAP_DAYS[n - 1] * DAY_MS);\n}\n\n/** The 3-days-ahead renewal reminder only makes sense for cadences of a week or longer. */\nexport function sendsRenewalReminder(cadence: Cadence): boolean {\n return minimumPeriodMs(cadence) >= WEEK_MS;\n}\n\n/**\n * Multiply a period's charge by this to get its monthly equivalent. Chosen so\n * `weekly` is exactly 52/12 and `biweekly` exactly 26/12, the factors\n * analytics already report — no existing MRR number moves.\n */\nexport function monthlyEquivalentFactor(cadence: Cadence): number {\n if (cadence.unit === 'month') return 1 / cadence.count;\n return (52 / 12) * (WEEK_MS / (fixedPeriodMs(cadence) as number));\n}\n","/**\n * Installment plans: a subscription with a fixed number of payments that ends\n * itself — status `completed` — the moment the last one succeeds.\n */\n\nexport const MIN_TOTAL_PAYMENTS = 2;\nexport const MAX_TOTAL_PAYMENTS = 60;\n\nexport type InstallmentErrorCode =\n | 'invalid_total_payments'\n | 'invalid_combination'\n | 'total_payments_immutable'\n | 'installment_plan_locked'\n | 'installment_plan_merchant_only';\n\nexport class InstallmentError extends Error {\n constructor(\n readonly code: InstallmentErrorCode,\n message: string,\n readonly statusCode: number,\n ) {\n super(message);\n this.name = 'InstallmentError';\n }\n}\n\nexport function validateTotalPayments(value: unknown): number | null {\n if (value === undefined || value === null) return null;\n if (\n typeof value !== 'number' ||\n !Number.isInteger(value) ||\n value < MIN_TOTAL_PAYMENTS ||\n value > MAX_TOTAL_PAYMENTS\n ) {\n throw new InstallmentError(\n 'invalid_total_payments',\n `totalPayments must be a whole number from ${MIN_TOTAL_PAYMENTS} to ${MAX_TOTAL_PAYMENTS}`,\n 400,\n );\n }\n return value;\n}\n\nexport function isInstallmentPlan(sub: { totalPayments?: number | null }): boolean {\n return sub.totalPayments != null;\n}\n\n/** After one counted payment — a charge or a waive — the new count and whether the plan is paid in full. */\nexport function countInstallmentPayment(sub: {\n totalPayments?: number | null;\n paymentsMade?: number | null;\n}): { paymentsMade: number; completes: boolean } | null {\n if (sub.totalPayments == null) return null;\n const paymentsMade = (sub.paymentsMade ?? 0) + 1;\n return { paymentsMade, completes: paymentsMade >= sub.totalPayments };\n}\n\nexport interface InstallmentProgress {\n paymentsMade: number;\n totalPayments: number;\n remainingPayments: number;\n remainingCents: number;\n}\n\nexport function installmentProgress(sub: {\n totalPayments?: number | null;\n paymentsMade?: number | null;\n amount: number;\n quantity?: number | null;\n}): InstallmentProgress | null {\n if (sub.totalPayments == null) return null;\n const paymentsMade = Math.min(sub.paymentsMade ?? 0, sub.totalPayments);\n const remainingPayments = sub.totalPayments - paymentsMade;\n return {\n paymentsMade,\n totalPayments: sub.totalPayments,\n remainingPayments,\n remainingCents: remainingPayments * sub.amount * (sub.quantity ?? 1),\n };\n}\n\nexport function installmentLocked(): InstallmentError {\n return new InstallmentError(\n 'installment_plan_locked',\n \"An installment plan's amount, cadence, quantity and plan are fixed when it is created. Cancel it and create a new one to change the terms.\",\n 409,\n );\n}\n\nexport function merchantOnly(): InstallmentError {\n return new InstallmentError(\n 'installment_plan_merchant_only',\n 'Installment plans can only be paused, resumed or cancelled by the merchant',\n 403,\n );\n}\n\nexport function totalPaymentsImmutable(): InstallmentError {\n return new InstallmentError(\n 'total_payments_immutable',\n 'totalPayments is set when a subscription is created and cannot be changed',\n 400,\n );\n}\n","import { isIntervalUnit, isNamedInterval, type CadenceLike, type NamedInterval } from './cadence';\n\nconst NAMED_NOUN: Record<NamedInterval, string> = {\n weekly: 'week',\n biweekly: '2 weeks',\n monthly: 'month',\n quarterly: 'quarter',\n yearly: 'year',\n};\n\nconst NAMED_LABEL: Record<NamedInterval, string> = {\n weekly: 'Weekly',\n biweekly: 'Biweekly',\n monthly: 'Monthly',\n quarterly: 'Quarterly',\n yearly: 'Yearly',\n};\n\n/**\n * The period as a noun — what reads correctly after a slash: \"$49.00 / 10\n * days\". This replaces the per-file `INTERVAL_WORD` maps whose drift shipped\n * \"$49.00 / biweekly\" once already. Unknown input falls through unchanged so a\n * value this build doesn't know still renders instead of vanishing.\n */\nexport function cadenceNoun(cadence: CadenceLike): string {\n if (isNamedInterval(cadence.interval)) return NAMED_NOUN[cadence.interval];\n if (isIntervalUnit(cadence.intervalUnit) && cadence.intervalCount != null) {\n return cadence.intervalCount === 1 ? cadence.intervalUnit : `${cadence.intervalCount} ${cadence.intervalUnit}s`;\n }\n return cadence.interval;\n}\n\n/** The cadence as a standalone label, e.g. an Interval column: \"Monthly\", \"Every 10 days\". */\nexport function cadenceLabel(cadence: CadenceLike): string {\n if (isNamedInterval(cadence.interval)) return NAMED_LABEL[cadence.interval];\n if (isIntervalUnit(cadence.intervalUnit) && cadence.intervalCount != null) return `Every ${cadenceNoun(cadence)}`;\n return cadence.interval;\n}\n\n/** Sub-daily cadences show the time of the next charge, not just its date. */\nexport function isSubDaily(cadence: CadenceLike): boolean {\n return cadence.intervalUnit === 'hour';\n}\n\n/** A subscription line's terms, the fields a label needs. */\nexport interface RecurringTermsLike extends CadenceLike {\n totalPayments?: number | null;\n trialDays?: number | null;\n}\n\n/**\n * One short label for a subscription line's terms, for carts and quotes:\n * \"$20.00 / month\", \"$20.00 / 10 days · 3 payments\", \"Free for 14 days, then\n * $20.00 / month\". `each` is the amount billed every period (renewal price ×\n * seats), already formatted, so each surface keeps its own money style.\n */\nexport function recurringTermsLabel(terms: RecurringTermsLike, each: string): string {\n const noun = cadenceNoun(terms);\n const trialDays = terms.trialDays ?? 0;\n if (trialDays > 0) return `Free for ${trialDays} day${trialDays === 1 ? '' : 's'}, then ${each} / ${noun}`;\n return `${each} / ${noun}` + (terms.totalPayments != null ? ` · ${terms.totalPayments} payments` : '');\n}\n","import {\n cadenceNoun as coreCadenceNoun,\n cadenceLabel as coreCadenceLabel,\n installmentProgress as coreInstallmentProgress,\n} from '@core/cadence';\nimport type { Subscription, SubscriptionStatus } from './types';\n\n/** `cancelled` and `completed` (an installment plan paid in full) never bill again. */\nexport function isTerminalSubscription(status: SubscriptionStatus | Subscription): boolean {\n const s = getStatus(status);\n return s === 'cancelled' || s === 'completed';\n}\n\nexport function canPauseSubscription(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'active';\n}\n\nexport function canResumeSubscription(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'paused';\n}\n\nexport function canCancelSubscription(status: SubscriptionStatus | Subscription): boolean {\n return !isTerminalSubscription(status);\n}\n\nexport function isSubscriptionPastDue(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'past_due';\n}\n\nexport function isSubscriptionTrialing(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'trialing';\n}\n\nexport function daysUntil(date: string | Date, now = new Date()): number {\n const end = date instanceof Date ? date : new Date(date);\n const ms = end.getTime() - now.getTime();\n if (!Number.isFinite(ms)) return 0;\n return Math.max(0, Math.ceil(ms / 86_400_000));\n}\n\nfunction getStatus(input: SubscriptionStatus | Subscription): SubscriptionStatus {\n return typeof input === 'string' ? input : input.status;\n}\n\n/**\n * Anything that describes a cadence the way the API and the database do.\n * Structurally mirrors the private cadence engine's own shape — kept local\n * so the published `.d.ts` never names that unpublished package.\n */\nexport interface CadenceLike {\n interval: string;\n intervalUnit?: string | null;\n intervalCount?: number | null;\n}\n\nexport interface InstallmentProgress {\n paymentsMade: number;\n totalPayments: number;\n remainingPayments: number;\n remainingCents: number;\n}\n\n/** \"10 days\", \"2 weeks\", \"month\" — the period after a slash: `$49.00 / ${cadenceNoun(sub)}`. */\nexport function cadenceNoun(cadence: CadenceLike): string {\n return coreCadenceNoun(cadence);\n}\n\n/** The cadence as a standalone label, e.g. an Interval column: \"Monthly\", \"Every 10 days\". */\nexport function cadenceLabel(cadence: CadenceLike): string {\n return coreCadenceLabel(cadence);\n}\n\nexport function installmentProgress(sub: {\n totalPayments?: number | null;\n paymentsMade?: number | null;\n amount: number;\n quantity?: number | null;\n}): InstallmentProgress | null {\n return coreInstallmentProgress(sub);\n}\n","import type { CSSProperties } from 'react';\nimport { daysUntil } from './helpers';\nimport type {\n DunningBannerProps,\n SubscriptionStatus,\n SubscriptionStatusBadgeProps,\n TrialCountdownProps,\n} from './types';\n\nconst STATUS_LABELS: Record<SubscriptionStatus, string> = {\n active: 'Active',\n paused: 'Paused',\n cancelled: 'Cancelled',\n past_due: 'Past due',\n trialing: 'Trialing',\n unpaid: 'Unpaid',\n completed: 'Paid in full',\n};\n\nconst STATUS_STYLES: Record<SubscriptionStatus, CSSProperties> = {\n active: { color: '#035c4f', background: '#dff8ef', borderColor: '#a8e8d2' },\n paused: { color: '#735c00', background: '#fff4ca', borderColor: '#f3d46b' },\n cancelled: { color: '#6c2632', background: '#ffe1e7', borderColor: '#f4a9b8' },\n past_due: { color: '#7a2d00', background: '#ffe8d6', borderColor: '#f5b47f' },\n trialing: { color: '#124c73', background: '#dff1ff', borderColor: '#9ed3fb' },\n unpaid: { color: '#7a2d00', background: '#ffe8d6', borderColor: '#f5b47f' },\n completed: { color: '#035c4f', background: '#dff8ef', borderColor: '#a8e8d2' },\n};\n\nexport function SubscriptionStatusBadge({\n status,\n className,\n style,\n labels,\n}: SubscriptionStatusBadgeProps) {\n return (\n <span\n className={className}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n border: '1px solid',\n borderRadius: 999,\n padding: '2px 8px',\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.6,\n whiteSpace: 'nowrap',\n ...STATUS_STYLES[status],\n ...style,\n }}\n >\n {labels?.[status] ?? STATUS_LABELS[status]}\n </span>\n );\n}\n\nexport function TrialCountdown({\n endDate,\n now,\n className,\n style,\n expiredLabel = 'Trial ended',\n prefix = 'Trial ends in',\n}: TrialCountdownProps) {\n const days = daysUntil(endDate, now);\n const label = days <= 0 ? expiredLabel : `${prefix} ${days} ${days === 1 ? 'day' : 'days'}`;\n\n return (\n <span className={className} style={style}>\n {label}\n </span>\n );\n}\n\nexport function DunningBanner({\n subscription,\n onUpdateCard,\n title = 'Payment needs attention',\n message,\n actionLabel = 'Update card',\n className,\n style,\n}: DunningBannerProps) {\n if (subscription.status !== 'past_due') return null;\n const fallbackMessage =\n subscription.failureCount > 0\n ? `We could not renew this subscription after ${subscription.failureCount} failed payment attempt${\n subscription.failureCount === 1 ? '' : 's'\n }.`\n : 'We could not renew this subscription with the saved payment method.';\n\n return (\n <div\n role=\"status\"\n className={className}\n style={{\n border: '1px solid #f5b47f',\n borderRadius: 8,\n background: '#fff7ed',\n color: '#7a2d00',\n padding: 16,\n display: 'grid',\n gap: 10,\n ...style,\n }}\n >\n <strong>{title}</strong>\n <span>{message ?? fallbackMessage}</span>\n {onUpdateCard ? (\n <button\n type=\"button\"\n onClick={onUpdateCard}\n style={{\n width: 'fit-content',\n border: '1px solid #7a2d00',\n borderRadius: 6,\n background: '#7a2d00',\n color: '#fff',\n padding: '8px 12px',\n fontWeight: 700,\n cursor: 'pointer',\n }}\n >\n {actionLabel}\n </button>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":["value","useCallback","useMemo","useCallback","useMemo","cadenceNoun","cadenceLabel","installmentProgress","jsx"]}
1
+ {"version":3,"sources":["../src/provider.tsx","../src/hooks.ts","../../core-cadence/src/cadence.ts","../../core-cadence/src/installments.ts","../../core-cadence/src/labels.ts","../src/helpers.ts","../src/components.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useMemo, useReducer, type ReactNode } from 'react';\nimport { SubscriptionsClient } from './client';\nimport type {\n SubscriptionFetcher,\n SubscriptionProviderProps,\n SubscriptionsClientContract,\n} from './types';\n\ninterface SubscriptionContextValue {\n client: SubscriptionsClientContract;\n cacheVersion: number;\n invalidate: () => void;\n}\n\nconst SubscriptionContext = createContext<SubscriptionContextValue | null>(null);\n\nexport function SubscriptionProvider({\n children,\n client,\n fetcher,\n baseUrl,\n timeoutMs,\n}: SubscriptionProviderProps) {\n const [cacheVersion, invalidate] = useReducer((value: number) => value + 1, 0);\n const resolvedClient = useMemo(() => {\n if (client) return client;\n return new SubscriptionsClient({\n fetcher: fetcher ?? createSubscriptionFetcher('/api/throttle'),\n baseUrl,\n timeoutMs,\n });\n }, [baseUrl, client, fetcher, timeoutMs]);\n\n const value = useMemo(\n () => ({ client: resolvedClient, cacheVersion, invalidate }),\n [cacheVersion, resolvedClient],\n );\n\n return <SubscriptionContext.Provider value={value}>{children}</SubscriptionContext.Provider>;\n}\n\nexport function useSubscriptionClient(): SubscriptionsClientContract {\n return useSubscriptionContext().client;\n}\n\nexport function useSubscriptionInvalidation(): () => void {\n return useSubscriptionContext().invalidate;\n}\n\nexport function useSubscriptionCacheVersion(): number {\n return useSubscriptionContext().cacheVersion;\n}\n\nexport function createSubscriptionFetcher(basePath: string): SubscriptionFetcher {\n const cleanBase = basePath.replace(/\\/+$/, '');\n return (path, init) => {\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return fetch(`${cleanBase}${cleanPath}`, init);\n };\n}\n\nexport function useSubscriptionContext(): SubscriptionContextValue {\n const ctx = useContext(SubscriptionContext);\n if (!ctx) {\n throw new Error(\n '@usethrottle/subscriptions hooks must be used inside <SubscriptionProvider />',\n );\n }\n return ctx;\n}\n\nexport function SubscriptionProviderWithClient({\n children,\n client,\n}: {\n children: ReactNode;\n client: SubscriptionsClientContract;\n}) {\n const [cacheVersion, invalidate] = useReducer((value: number) => value + 1, 0);\n const value = useMemo(() => ({ client, cacheVersion, invalidate }), [cacheVersion, client]);\n return <SubscriptionContext.Provider value={value}>{children}</SubscriptionContext.Provider>;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n useSubscriptionCacheVersion,\n useSubscriptionClient,\n useSubscriptionInvalidation,\n} from './provider';\nimport type {\n CancelSubscriptionInput,\n ChangePlanInput,\n CheckoutSession,\n CreateCheckoutSessionInput,\n CreateSubscriptionInput,\n Customer,\n CustomerPaymentMethod,\n ListSubscriptionsParams,\n MutationState,\n PaginatedSubscriptions,\n QueryOptions,\n QueryState,\n Subscription,\n TrialEligibilityInput,\n TrialEligibilityResult,\n UpdateSubscriptionInput,\n} from './types';\n\nexport function useSubscriptions(\n params: ListSubscriptionsParams = {},\n options: QueryOptions<PaginatedSubscriptions> = {},\n): QueryState<PaginatedSubscriptions> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n const stableParams = useStableValue(params);\n return useAsyncQuery(\n () => client.list(stableParams),\n [client, stableParams, cacheVersion],\n options,\n );\n}\n\nexport function useSubscription(\n id: string | null | undefined,\n options: QueryOptions<Subscription> = {},\n): QueryState<Subscription> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n return useAsyncQuery(\n () => (id ? client.get(id) : Promise.resolve(null)),\n [client, id, cacheVersion],\n { ...options, enabled: options.enabled ?? Boolean(id) },\n );\n}\n\nexport function useCustomerByExternalId(\n externalId: string | null | undefined,\n options: QueryOptions<Customer> = {},\n): QueryState<Customer> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n return useAsyncQuery(\n () => (externalId ? client.getCustomerByExternalId(externalId) : Promise.resolve(null)),\n [client, externalId, cacheVersion],\n { ...options, enabled: options.enabled ?? Boolean(externalId) },\n );\n}\n\nexport function useCustomerPaymentMethods(\n customerId: string | null | undefined,\n options: QueryOptions<CustomerPaymentMethod[]> = {},\n): QueryState<CustomerPaymentMethod[]> {\n const client = useSubscriptionClient();\n const cacheVersion = useSubscriptionCacheVersion();\n return useAsyncQuery(\n () => (customerId ? client.listCustomerPaymentMethods(customerId) : Promise.resolve(null)),\n [client, customerId, cacheVersion],\n { ...options, enabled: options.enabled ?? Boolean(customerId) },\n );\n}\n\nexport function useTrialEligibility(): MutationState<\n TrialEligibilityResult,\n TrialEligibilityInput\n> {\n const client = useSubscriptionClient();\n return useAsyncMutation((input) => client.checkTrialEligibility(input));\n}\n\nexport function useCreateSubscription(): MutationState<Subscription, CreateSubscriptionInput> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.create(input), invalidate);\n}\n\nexport function useUpdateSubscription(): MutationState<\n Subscription,\n { id: string; data: UpdateSubscriptionInput }\n> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation(({ id, data }) => client.update(id, data), invalidate);\n}\n\nexport function useChangePlan(): MutationState<Subscription, ChangePlanInput> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.changePlan(input), invalidate);\n}\n\nexport function useCancelSubscription(): MutationState<Subscription, CancelSubscriptionInput> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation(\n (input) => client.cancel(input.id, { atPeriodEnd: input.atPeriodEnd }),\n invalidate,\n );\n}\n\nexport function usePauseSubscription(): MutationState<Subscription, { id: string }> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.pause(input.id), invalidate);\n}\n\nexport function useResumeSubscription(): MutationState<Subscription, { id: string }> {\n const client = useSubscriptionClient();\n const invalidate = useSubscriptionInvalidation();\n return useAsyncMutation((input) => client.resume(input.id), invalidate);\n}\n\nexport function useCreateCheckoutSession(): MutationState<\n CheckoutSession,\n CreateCheckoutSessionInput\n> {\n const client = useSubscriptionClient();\n return useAsyncMutation((input) => client.createCheckoutSession(input));\n}\n\nfunction useAsyncQuery<T>(\n load: () => Promise<T | null>,\n deps: readonly unknown[],\n options: QueryOptions<T>,\n): QueryState<T> {\n const enabled = options.enabled ?? true;\n const [data, setData] = useState<T | null>(options.initialData ?? null);\n const [error, setError] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(enabled && options.initialData === undefined);\n const [isFetching, setIsFetching] = useState(false);\n const loadRef = useRef(load);\n loadRef.current = load;\n\n const refetch = useCallback(async () => {\n if (!enabled) return data;\n setIsFetching(true);\n setError(null);\n try {\n const next = await loadRef.current();\n setData(next);\n return next;\n } catch (err) {\n setError(err);\n throw err;\n } finally {\n setIsFetching(false);\n setIsLoading(false);\n }\n }, [data, enabled]);\n\n useEffect(() => {\n if (!enabled) {\n setIsLoading(false);\n return;\n }\n let cancelled = false;\n setIsFetching(true);\n setError(null);\n load()\n .then((next) => {\n if (!cancelled) setData(next);\n })\n .catch((err) => {\n if (!cancelled) setError(err);\n })\n .finally(() => {\n if (!cancelled) {\n setIsLoading(false);\n setIsFetching(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, ...deps]);\n\n return { data, error, isLoading, isFetching, refetch };\n}\n\nfunction useAsyncMutation<TData, TVariables>(\n action: (variables: TVariables) => Promise<TData>,\n onSuccess?: () => void,\n): MutationState<TData, TVariables> {\n const [data, setData] = useState<TData | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [isPending, setIsPending] = useState(false);\n\n const mutateAsync = useCallback(\n async (variables: TVariables) => {\n setIsPending(true);\n setError(null);\n try {\n const next = await action(variables);\n setData(next);\n onSuccess?.();\n return next;\n } catch (err) {\n setError(err);\n throw err;\n } finally {\n setIsPending(false);\n }\n },\n [action, onSuccess],\n );\n\n const mutate = useCallback(\n (variables: TVariables) => {\n void mutateAsync(variables).catch(() => undefined);\n },\n [mutateAsync],\n );\n\n const reset = useCallback(() => {\n setData(null);\n setError(null);\n setIsPending(false);\n }, []);\n\n return { data, error, isPending, mutate, mutateAsync, reset };\n}\n\nfunction useStableValue<T>(value: T): T {\n const key = JSON.stringify(value);\n return useMemo(() => value, [key]);\n}\n","/**\n * A subscription's billing cadence: \"every N units\".\n *\n * Every subscription row stores a unit and a count; the five named intervals\n * are shorthand for particular combinations. This module is the only place\n * that knows the vocabulary, the bounds, and how a period boundary is\n * computed — the engine, the renewal cron, the API, the dashboard and the\n * buyer surfaces all import it. Before it existed there were three divergent\n * copies of the period math, and the renewal cron's advanced months with\n * `setMonth`, which overflows: a subscription started on Jan 31 renewed on\n * Mar 3 and never returned to the 31st.\n */\n\nexport const INTERVAL_UNITS = ['hour', 'day', 'week', 'month'] as const;\nexport type IntervalUnit = (typeof INTERVAL_UNITS)[number];\n\nexport const NAMED_INTERVALS = ['weekly', 'biweekly', 'monthly', 'quarterly', 'yearly'] as const;\nexport type NamedInterval = (typeof NAMED_INTERVALS)[number];\n\n/** What the `interval` field holds: a named interval, or `custom` for everything else. */\nexport const INTERVAL_VALUES = [...NAMED_INTERVALS, 'custom'] as const;\nexport type IntervalValue = (typeof INTERVAL_VALUES)[number];\n\nexport interface Cadence {\n interval: IntervalValue;\n unit: IntervalUnit;\n count: number;\n}\n\n/** Anything that describes a cadence the way the API and the database do. */\nexport interface CadenceLike {\n interval: string;\n intervalUnit?: string | null;\n intervalCount?: number | null;\n}\n\nexport interface CadenceInput {\n interval?: string | null;\n intervalUnit?: string | null;\n intervalCount?: number | null;\n}\n\nexport const NAMED_SHORTHAND: Readonly<Record<NamedInterval, { unit: IntervalUnit; count: number }>> = {\n weekly: { unit: 'week', count: 1 },\n biweekly: { unit: 'week', count: 2 },\n monthly: { unit: 'month', count: 1 },\n quarterly: { unit: 'month', count: 3 },\n // month × 12 rather than a year unit, so a Feb 29 anniversary clamps to Feb\n // 28 and comes back to Feb 29 in leap years like every other month cadence.\n yearly: { unit: 'month', count: 12 },\n};\n\n/** At most one year in every unit. The shortest cadence is 1 hour. */\nexport const MAX_INTERVAL_COUNT: Readonly<Record<IntervalUnit, number>> = {\n hour: 8760,\n day: 365,\n week: 52,\n month: 12,\n};\n\nexport const HOUR_MS = 3_600_000;\nexport const DAY_MS = 24 * HOUR_MS;\nexport const WEEK_MS = 7 * DAY_MS;\n\nconst FIXED_UNIT_MS = { hour: HOUR_MS, day: DAY_MS, week: WEEK_MS } as const;\n\nexport class CadenceError extends Error {\n readonly statusCode = 400;\n constructor(\n readonly code: 'invalid_interval' | 'interval_conflict',\n message: string,\n ) {\n super(message);\n this.name = 'CadenceError';\n }\n}\n\nexport function isNamedInterval(value: unknown): value is NamedInterval {\n return typeof value === 'string' && (NAMED_INTERVALS as readonly string[]).includes(value);\n}\n\nexport function isIntervalUnit(value: unknown): value is IntervalUnit {\n return typeof value === 'string' && (INTERVAL_UNITS as readonly string[]).includes(value);\n}\n\nfunction isIntervalValue(value: unknown): value is IntervalValue {\n return typeof value === 'string' && (INTERVAL_VALUES as readonly string[]).includes(value);\n}\n\nfunction reduceToLargestUnit(unit: IntervalUnit, count: number): { unit: IntervalUnit; count: number } {\n let u = unit;\n let c = count;\n if (u === 'hour' && c % 24 === 0) {\n u = 'day';\n c = c / 24;\n }\n if (u === 'day' && c % 7 === 0) {\n u = 'week';\n c = c / 7;\n }\n return { unit: u, count: c };\n}\n\nfunction namedFor(unit: IntervalUnit, count: number): NamedInterval | null {\n for (const name of NAMED_INTERVALS) {\n const s = NAMED_SHORTHAND[name];\n if (s.unit === unit && s.count === count) return name;\n }\n return null;\n}\n\n/**\n * Turn whatever a caller sent into the one stored form. Accepts a named\n * interval alone, a unit and count alone, or both — in which case they must\n * agree. The result is reduced to its largest exact unit and mapped to a named\n * interval whenever one matches, so there is exactly one representation per\n * cadence and \"every 2 weeks\" can never be filed apart from \"biweekly\".\n */\nexport function canonicalizeCadence(input: CadenceInput): Cadence {\n const interval = input.interval ?? undefined;\n if (interval !== undefined && !isIntervalValue(interval)) {\n throw new CadenceError('invalid_interval', `Unknown interval \"${interval}\"`);\n }\n const hasUnit = input.intervalUnit != null;\n const hasCount = input.intervalCount != null;\n if (hasUnit !== hasCount) {\n throw new CadenceError('invalid_interval', 'intervalUnit and intervalCount must be sent together');\n }\n if (!hasUnit) {\n if (isNamedInterval(interval)) return { interval, ...NAMED_SHORTHAND[interval] };\n throw new CadenceError(\n 'invalid_interval',\n interval === 'custom'\n ? 'A custom interval needs intervalUnit and intervalCount'\n : 'Send an interval, or intervalUnit and intervalCount',\n );\n }\n\n const unit = input.intervalUnit;\n const count = input.intervalCount as number;\n if (!isIntervalUnit(unit)) {\n throw new CadenceError('invalid_interval', 'intervalUnit must be one of hour, day, week, month');\n }\n if (!Number.isInteger(count) || count < 1) {\n throw new CadenceError('invalid_interval', 'intervalCount must be a whole number of at least 1');\n }\n if (count > MAX_INTERVAL_COUNT[unit]) {\n throw new CadenceError(\n 'invalid_interval',\n `A cadence can be at most one year: ${MAX_INTERVAL_COUNT[unit]} ${unit}s`,\n );\n }\n\n const reduced = reduceToLargestUnit(unit, count);\n const cadence: Cadence = { interval: namedFor(reduced.unit, reduced.count) ?? 'custom', ...reduced };\n if (interval !== undefined && interval !== 'custom' && interval !== cadence.interval) {\n throw new CadenceError(\n 'interval_conflict',\n `interval \"${interval}\" does not match every ${count} ${unit}${count === 1 ? '' : 's'}`,\n );\n }\n return cadence;\n}\n\n/**\n * Read the cadence off a stored subscription. A named interval always means\n * its shorthand, whatever the unit/count columns hold: a canonical row's\n * columns match the shorthand anyway, and code that predates the columns\n * (still serving between migration 0224 and the deploy) changes `interval`\n * without touching them, so for a named row they can be stale. Only a\n * `custom` row is read from its unit and count. The same rule applies to the\n * pending cadence — pass `{ interval: pendingInterval, intervalUnit:\n * pendingIntervalUnit, intervalCount: pendingIntervalCount }`.\n */\nexport function cadenceOf(row: CadenceLike): Cadence {\n if (isNamedInterval(row.interval)) return { interval: row.interval, ...NAMED_SHORTHAND[row.interval] };\n if (row.interval === 'custom' && row.intervalUnit != null && row.intervalCount != null && isIntervalUnit(row.intervalUnit)) {\n return { interval: 'custom', unit: row.intervalUnit, count: row.intervalCount };\n }\n throw new Error(`Unknown subscription interval: ${row.interval}`);\n}\n\n/** The exact period length for hour/day/week cadences; null for months, which vary. */\nexport function fixedPeriodMs(cadence: Cadence): number | null {\n return cadence.unit === 'month' ? null : cadence.count * FIXED_UNIT_MS[cadence.unit];\n}\n\n/** The shortest a period can be. A month counts as 28 days. */\nexport function minimumPeriodMs(cadence: Cadence): number {\n return fixedPeriodMs(cadence) ?? cadence.count * 28 * DAY_MS;\n}\n\n/** anchor + months, keeping the anchor's day-of-month (clamped to month end) and UTC time of day. */\nexport function addMonthsAnchored(anchor: Date, months: number): Date {\n const monthIndex = anchor.getUTCMonth() + months;\n const year = anchor.getUTCFullYear() + Math.floor(monthIndex / 12);\n const month = ((monthIndex % 12) + 12) % 12;\n const lastDay = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();\n return new Date(\n Date.UTC(\n year,\n month,\n Math.min(anchor.getUTCDate(), lastDay),\n anchor.getUTCHours(),\n anchor.getUTCMinutes(),\n anchor.getUTCSeconds(),\n anchor.getUTCMilliseconds(),\n ),\n );\n}\n\n/**\n * The first period boundary strictly after `after`, on the grid measured from\n * `anchor`. Never chained from a previous boundary — that is what made the\n * month-end clamp stick on the 28th. An `after` that is off the grid (a late\n * tick, a recovery) resolves to the next grid date.\n */\nexport function nextPeriodEnd(args: { anchor: Date; cadence: Cadence; after: Date }): Date {\n const { anchor, cadence, after } = args;\n const step = fixedPeriodMs(cadence);\n if (step !== null) {\n const k = Math.max(1, Math.floor((after.getTime() - anchor.getTime()) / step) + 1);\n return new Date(anchor.getTime() + k * step);\n }\n const monthsElapsed =\n (after.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + (after.getUTCMonth() - anchor.getUTCMonth());\n let k = Math.max(1, Math.floor(monthsElapsed / cadence.count));\n let boundary = addMonthsAnchored(anchor, k * cadence.count);\n while (boundary.getTime() <= after.getTime()) {\n k += 1;\n boundary = addMonthsAnchored(anchor, k * cadence.count);\n }\n return boundary;\n}\n\n/**\n * The grid period that contains `at`: the latest boundary at or before it (never\n * before the anchor) and the one after. A subscription that fell more than a\n * period behind is billed once, for this period, and the periods it missed are\n * skipped rather than charged back to back (D38).\n */\nexport function periodContaining(args: { anchor: Date; cadence: Cadence; at: Date }): { start: Date; end: Date } {\n const { anchor, cadence, at } = args;\n const step = fixedPeriodMs(cadence);\n if (step !== null) {\n const k = Math.max(0, Math.floor((at.getTime() - anchor.getTime()) / step));\n const start = new Date(anchor.getTime() + k * step);\n return { start, end: new Date(start.getTime() + step) };\n }\n const monthsElapsed =\n (at.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + (at.getUTCMonth() - anchor.getUTCMonth());\n let k = Math.max(0, Math.floor(monthsElapsed / cadence.count));\n let start = addMonthsAnchored(anchor, k * cadence.count);\n while (k > 0 && start.getTime() > at.getTime()) {\n k -= 1;\n start = addMonthsAnchored(anchor, k * cadence.count);\n }\n return { start, end: addMonthsAnchored(anchor, (k + 1) * cadence.count) };\n}\n\nconst RETRY_CAP_DAYS = [1, 3, 7] as const;\n\n/**\n * How long to wait before retry number `failureNumber` (1..3). Short cadences\n * retry proportionally faster; anything of a week or longer lands on the\n * original 1 / 3 / 7 days exactly.\n */\nexport function dunningRetryDelayMs(cadence: Cadence, failureNumber: number): number {\n const n = Math.min(Math.max(Math.trunc(failureNumber), 1), RETRY_CAP_DAYS.length);\n return Math.min(n * minimumPeriodMs(cadence), RETRY_CAP_DAYS[n - 1] * DAY_MS);\n}\n\n/** The 3-days-ahead renewal reminder only makes sense for cadences of a week or longer. */\nexport function sendsRenewalReminder(cadence: Cadence): boolean {\n return minimumPeriodMs(cadence) >= WEEK_MS;\n}\n\n/**\n * Multiply a period's charge by this to get its monthly equivalent. Chosen so\n * `weekly` is exactly 52/12 and `biweekly` exactly 26/12, the factors\n * analytics already report — no existing MRR number moves.\n */\nexport function monthlyEquivalentFactor(cadence: Cadence): number {\n if (cadence.unit === 'month') return 1 / cadence.count;\n return (52 / 12) * (WEEK_MS / (fixedPeriodMs(cadence) as number));\n}\n","/**\n * Installment plans: a subscription with a fixed number of payments that ends\n * itself — status `completed` — the moment the last one succeeds.\n */\n\nexport const MIN_TOTAL_PAYMENTS = 2;\nexport const MAX_TOTAL_PAYMENTS = 60;\n\nexport type InstallmentErrorCode =\n | 'invalid_total_payments'\n | 'invalid_combination'\n | 'total_payments_immutable'\n | 'installment_plan_locked'\n | 'installment_plan_merchant_only';\n\nexport class InstallmentError extends Error {\n constructor(\n readonly code: InstallmentErrorCode,\n message: string,\n readonly statusCode: number,\n ) {\n super(message);\n this.name = 'InstallmentError';\n }\n}\n\nexport function validateTotalPayments(value: unknown): number | null {\n if (value === undefined || value === null) return null;\n if (\n typeof value !== 'number' ||\n !Number.isInteger(value) ||\n value < MIN_TOTAL_PAYMENTS ||\n value > MAX_TOTAL_PAYMENTS\n ) {\n throw new InstallmentError(\n 'invalid_total_payments',\n `totalPayments must be a whole number from ${MIN_TOTAL_PAYMENTS} to ${MAX_TOTAL_PAYMENTS}`,\n 400,\n );\n }\n return value;\n}\n\nexport function isInstallmentPlan(sub: { totalPayments?: number | null }): boolean {\n return sub.totalPayments != null;\n}\n\n/** After one counted payment — a charge or a waive — the new count and whether the plan is paid in full. */\nexport function countInstallmentPayment(sub: {\n totalPayments?: number | null;\n paymentsMade?: number | null;\n}): { paymentsMade: number; completes: boolean } | null {\n if (sub.totalPayments == null) return null;\n const paymentsMade = (sub.paymentsMade ?? 0) + 1;\n return { paymentsMade, completes: paymentsMade >= sub.totalPayments };\n}\n\nexport interface InstallmentProgress {\n paymentsMade: number;\n totalPayments: number;\n remainingPayments: number;\n remainingCents: number;\n}\n\nexport function installmentProgress(sub: {\n totalPayments?: number | null;\n paymentsMade?: number | null;\n amount: number;\n quantity?: number | null;\n scheduleRows?: Array<{ unitAmount: number; discount: number }> | null;\n}): InstallmentProgress | null {\n if (sub.totalPayments == null) return null;\n const paymentsMade = Math.min(sub.paymentsMade ?? 0, sub.totalPayments);\n const remainingPayments = sub.totalPayments - paymentsMade;\n const quantity = sub.quantity ?? 1;\n const remainingCents =\n sub.scheduleRows && sub.scheduleRows.length > 0\n ? sub.scheduleRows.slice(paymentsMade).reduce((s, r) => s + r.unitAmount * quantity - r.discount, 0)\n : remainingPayments * sub.amount * quantity;\n return { paymentsMade, totalPayments: sub.totalPayments, remainingPayments, remainingCents };\n}\n\nexport function installmentLocked(): InstallmentError {\n return new InstallmentError(\n 'installment_plan_locked',\n \"An installment plan's amount, cadence, quantity and plan are fixed when it is created. Cancel it and create a new one to change the terms.\",\n 409,\n );\n}\n\nexport function merchantOnly(): InstallmentError {\n return new InstallmentError(\n 'installment_plan_merchant_only',\n 'Installment plans can only be paused, resumed or cancelled by the merchant',\n 403,\n );\n}\n\nexport function totalPaymentsImmutable(): InstallmentError {\n return new InstallmentError(\n 'total_payments_immutable',\n 'totalPayments is set when a subscription is created and cannot be changed',\n 400,\n );\n}\n","import { isIntervalUnit, isNamedInterval, type CadenceLike, type NamedInterval } from './cadence';\nimport { isVariedSchedule, scheduleUnitTotal } from './schedule';\n\nconst NAMED_NOUN: Record<NamedInterval, string> = {\n weekly: 'week',\n biweekly: '2 weeks',\n monthly: 'month',\n quarterly: 'quarter',\n yearly: 'year',\n};\n\nconst NAMED_LABEL: Record<NamedInterval, string> = {\n weekly: 'Weekly',\n biweekly: 'Biweekly',\n monthly: 'Monthly',\n quarterly: 'Quarterly',\n yearly: 'Yearly',\n};\n\n/**\n * The period as a noun — what reads correctly after a slash: \"$49.00 / 10\n * days\". This replaces the per-file `INTERVAL_WORD` maps whose drift shipped\n * \"$49.00 / biweekly\" once already. Unknown input falls through unchanged so a\n * value this build doesn't know still renders instead of vanishing.\n */\nexport function cadenceNoun(cadence: CadenceLike): string {\n if (isNamedInterval(cadence.interval)) return NAMED_NOUN[cadence.interval];\n if (isIntervalUnit(cadence.intervalUnit) && cadence.intervalCount != null) {\n return cadence.intervalCount === 1 ? cadence.intervalUnit : `${cadence.intervalCount} ${cadence.intervalUnit}s`;\n }\n return cadence.interval;\n}\n\n/** The cadence as a standalone label, e.g. an Interval column: \"Monthly\", \"Every 10 days\". */\nexport function cadenceLabel(cadence: CadenceLike): string {\n if (isNamedInterval(cadence.interval)) return NAMED_LABEL[cadence.interval];\n if (isIntervalUnit(cadence.intervalUnit) && cadence.intervalCount != null) return `Every ${cadenceNoun(cadence)}`;\n return cadence.interval;\n}\n\n/** Sub-daily cadences show the time of the next charge, not just its date. */\nexport function isSubDaily(cadence: CadenceLike): boolean {\n return cadence.intervalUnit === 'hour';\n}\n\n/** A subscription line's terms, the fields a label needs. */\nexport interface RecurringTermsLike extends CadenceLike {\n totalPayments?: number | null;\n trialDays?: number | null;\n schedule?: Array<{ amount: number }> | null;\n}\n\n/**\n * One short label for a subscription line's terms, for carts and quotes:\n * \"$20.00 / month\", \"$20.00 / 10 days · 3 payments\", \"Free for 14 days, then\n * $20.00 / month\", \"$100.00 total · 4 payments\". The label computes the money\n * itself from the line's unit price and seats — for a schedule, the deal total\n * (every payment × seats), never the line's unit price (payment 1) — and hands\n * it to `money`, so each surface keeps its own money style.\n */\nexport function recurringTermsLabel(\n terms: RecurringTermsLike,\n price: { unitPrice: number; quantity?: number | null },\n money: (minor: number) => string,\n): string {\n const quantity = price.quantity ?? 1;\n if (terms.schedule && terms.schedule.length > 0) {\n return `${money(scheduleUnitTotal(terms.schedule) * quantity)} total · ${terms.schedule.length} payments`;\n }\n const each = money(price.unitPrice * quantity);\n const noun = cadenceNoun(terms);\n const trialDays = terms.trialDays ?? 0;\n if (trialDays > 0) return `Free for ${trialDays} day${trialDays === 1 ? '' : 's'}, then ${each} / ${noun}`;\n return `${each} / ${noun}` + (terms.totalPayments != null ? ` · ${terms.totalPayments} payments` : '');\n}\n\n/** A subscription's public schedule fields, as the API returns them. */\nexport interface ScheduleDealLike {\n paymentSchedule?: Array<{ amount: number; discount: number; intervalUnit: string | null; intervalCount: number | null }> | null;\n scheduleTotal?: number | null;\n}\n\n/**\n * A varied schedule's deal — its total (every row's net: seats in, discounts\n * out) and its payment count — or null for an equal plan, whose synthesized\n * schedule keeps the per-period label, and for an ongoing plan. A varied\n * schedule has no per-period price: `amount` and the cadence columns hold only\n * the next row's (ruling R-FR4). Every surface gates both its price and its\n * cadence cells on this one answer.\n */\nexport function scheduleDeal(sub: ScheduleDealLike): { total: number; payments: number } | null {\n if (!sub.paymentSchedule || sub.scheduleTotal == null || !isVariedSchedule(sub.paymentSchedule)) return null;\n return { total: sub.scheduleTotal, payments: sub.paymentSchedule.length };\n}\n\n/** \"$100.00 total · 4 payments\" for a varied schedule, else null. `money` keeps each surface's money style. */\nexport function scheduleDealLabel(sub: ScheduleDealLike, money: (minor: number) => string): string | null {\n const deal = scheduleDeal(sub);\n return deal ? `${money(deal.total)} total · ${deal.payments} payments` : null;\n}\n","import {\n cadenceNoun as coreCadenceNoun,\n cadenceLabel as coreCadenceLabel,\n installmentProgress as coreInstallmentProgress,\n} from '@core/cadence';\nimport type { Subscription, SubscriptionPaymentScheduleRow, SubscriptionStatus } from './types';\n\n/** `cancelled` and `completed` (an installment plan paid in full) never bill again. */\nexport function isTerminalSubscription(status: SubscriptionStatus | Subscription): boolean {\n const s = getStatus(status);\n return s === 'cancelled' || s === 'completed';\n}\n\nexport function canPauseSubscription(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'active';\n}\n\nexport function canResumeSubscription(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'paused';\n}\n\nexport function canCancelSubscription(status: SubscriptionStatus | Subscription): boolean {\n return !isTerminalSubscription(status);\n}\n\nexport function isSubscriptionPastDue(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'past_due';\n}\n\nexport function isSubscriptionTrialing(status: SubscriptionStatus | Subscription): boolean {\n return getStatus(status) === 'trialing';\n}\n\nexport function daysUntil(date: string | Date, now = new Date()): number {\n const end = date instanceof Date ? date : new Date(date);\n const ms = end.getTime() - now.getTime();\n if (!Number.isFinite(ms)) return 0;\n return Math.max(0, Math.ceil(ms / 86_400_000));\n}\n\nfunction getStatus(input: SubscriptionStatus | Subscription): SubscriptionStatus {\n return typeof input === 'string' ? input : input.status;\n}\n\n/**\n * Anything that describes a cadence the way the API and the database do.\n * Structurally mirrors the private cadence engine's own shape — kept local\n * so the published `.d.ts` never names that unpublished package.\n */\nexport interface CadenceLike {\n interval: string;\n intervalUnit?: string | null;\n intervalCount?: number | null;\n}\n\nexport interface InstallmentProgress {\n paymentsMade: number;\n totalPayments: number;\n remainingPayments: number;\n remainingCents: number;\n}\n\n/** \"10 days\", \"2 weeks\", \"month\" — the period after a slash: `$49.00 / ${cadenceNoun(sub)}`. */\nexport function cadenceNoun(cadence: CadenceLike): string {\n return coreCadenceNoun(cadence);\n}\n\n/** The cadence as a standalone label, e.g. an Interval column: \"Monthly\", \"Every 10 days\". */\nexport function cadenceLabel(cadence: CadenceLike): string {\n return coreCadenceLabel(cadence);\n}\n\n/**\n * Progress of an installment plan. Pass the subscription itself: when it carries\n * `paymentSchedule` (a custom schedule), `remainingCents` is the sum of the\n * not-yet-paid rows' `net` (already net of discount, quantity applied, before\n * tax). Otherwise it is `remainingPayments × amount × quantity`.\n */\nexport function installmentProgress(sub: {\n totalPayments?: number | null;\n paymentsMade?: number | null;\n amount: number;\n quantity?: number | null;\n paymentSchedule?: Array<Pick<SubscriptionPaymentScheduleRow, 'net' | 'status'>> | null;\n}): InstallmentProgress | null {\n const progress = coreInstallmentProgress(sub);\n if (!progress || !sub.paymentSchedule || sub.paymentSchedule.length === 0) return progress;\n const remainingCents = sub.paymentSchedule.filter((r) => r.status !== 'paid').reduce((sum, r) => sum + r.net, 0);\n return { ...progress, remainingCents };\n}\n","import type { CSSProperties } from 'react';\nimport { daysUntil } from './helpers';\nimport type {\n DunningBannerProps,\n SubscriptionStatus,\n SubscriptionStatusBadgeProps,\n TrialCountdownProps,\n} from './types';\n\nconst STATUS_LABELS: Record<SubscriptionStatus, string> = {\n active: 'Active',\n paused: 'Paused',\n cancelled: 'Cancelled',\n past_due: 'Past due',\n trialing: 'Trialing',\n unpaid: 'Unpaid',\n completed: 'Paid in full',\n};\n\nconst STATUS_STYLES: Record<SubscriptionStatus, CSSProperties> = {\n active: { color: '#035c4f', background: '#dff8ef', borderColor: '#a8e8d2' },\n paused: { color: '#735c00', background: '#fff4ca', borderColor: '#f3d46b' },\n cancelled: { color: '#6c2632', background: '#ffe1e7', borderColor: '#f4a9b8' },\n past_due: { color: '#7a2d00', background: '#ffe8d6', borderColor: '#f5b47f' },\n trialing: { color: '#124c73', background: '#dff1ff', borderColor: '#9ed3fb' },\n unpaid: { color: '#7a2d00', background: '#ffe8d6', borderColor: '#f5b47f' },\n completed: { color: '#035c4f', background: '#dff8ef', borderColor: '#a8e8d2' },\n};\n\nexport function SubscriptionStatusBadge({\n status,\n className,\n style,\n labels,\n}: SubscriptionStatusBadgeProps) {\n return (\n <span\n className={className}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n border: '1px solid',\n borderRadius: 999,\n padding: '2px 8px',\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.6,\n whiteSpace: 'nowrap',\n ...STATUS_STYLES[status],\n ...style,\n }}\n >\n {labels?.[status] ?? STATUS_LABELS[status]}\n </span>\n );\n}\n\nexport function TrialCountdown({\n endDate,\n now,\n className,\n style,\n expiredLabel = 'Trial ended',\n prefix = 'Trial ends in',\n}: TrialCountdownProps) {\n const days = daysUntil(endDate, now);\n const label = days <= 0 ? expiredLabel : `${prefix} ${days} ${days === 1 ? 'day' : 'days'}`;\n\n return (\n <span className={className} style={style}>\n {label}\n </span>\n );\n}\n\nexport function DunningBanner({\n subscription,\n onUpdateCard,\n title = 'Payment needs attention',\n message,\n actionLabel = 'Update card',\n className,\n style,\n}: DunningBannerProps) {\n if (subscription.status !== 'past_due') return null;\n const fallbackMessage =\n subscription.failureCount > 0\n ? `We could not renew this subscription after ${subscription.failureCount} failed payment attempt${\n subscription.failureCount === 1 ? '' : 's'\n }.`\n : 'We could not renew this subscription with the saved payment method.';\n\n return (\n <div\n role=\"status\"\n className={className}\n style={{\n border: '1px solid #f5b47f',\n borderRadius: 8,\n background: '#fff7ed',\n color: '#7a2d00',\n padding: 16,\n display: 'grid',\n gap: 10,\n ...style,\n }}\n >\n <strong>{title}</strong>\n <span>{message ?? fallbackMessage}</span>\n {onUpdateCard ? (\n <button\n type=\"button\"\n onClick={onUpdateCard}\n style={{\n width: 'fit-content',\n border: '1px solid #7a2d00',\n borderRadius: 6,\n background: '#7a2d00',\n color: '#fff',\n padding: '8px 12px',\n fontWeight: 700,\n cursor: 'pointer',\n }}\n >\n {actionLabel}\n </button>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":["value","useCallback","useMemo","useCallback","useMemo","cadenceNoun","cadenceLabel","installmentProgress","jsx"]}
package/dist/server.cjs CHANGED
@@ -54,6 +54,9 @@ function normalizeSubscription(raw) {
54
54
  totalPayments: raw?.totalPayments == null ? null : Number(raw.totalPayments),
55
55
  paymentsMade: Number(raw?.paymentsMade ?? 0),
56
56
  completedAt: optionalIsoDateString(raw?.completedAt),
57
+ paymentSchedule: normalizePaymentSchedule(raw?.paymentSchedule),
58
+ scheduleTotal: raw?.scheduleTotal == null ? null : Number(raw.scheduleTotal),
59
+ nextPaymentAmount: raw?.nextPaymentAmount == null ? null : Number(raw.nextPaymentAmount),
57
60
  amount: Number(raw?.amount ?? 0),
58
61
  quantity: Number(raw?.quantity ?? 1),
59
62
  currency: optionalString(raw?.currency) ?? "USD",
@@ -82,6 +85,19 @@ function normalizeSubscription(raw) {
82
85
  updatedAt: optionalDateString(raw?.updatedAt) ?? void 0
83
86
  };
84
87
  }
88
+ function normalizePaymentSchedule(raw) {
89
+ if (!Array.isArray(raw)) return null;
90
+ return raw.map((r) => ({
91
+ index: Number(r?.index ?? 0),
92
+ amount: Number(r?.amount ?? 0),
93
+ discount: Number(r?.discount ?? 0),
94
+ net: Number(r?.net ?? 0),
95
+ intervalUnit: optionalString(r?.intervalUnit) ?? null,
96
+ intervalCount: r?.intervalCount == null ? null : Number(r.intervalCount),
97
+ status: r?.status,
98
+ dueAt: optionalDateString(r?.dueAt)
99
+ }));
100
+ }
85
101
  function normalizeRenewalIssue(raw) {
86
102
  if (!raw || typeof raw !== "object") return null;
87
103
  return {
@@ -272,6 +288,7 @@ var SubscriptionsClient = class {
272
288
  intervalCount: input.intervalCount,
273
289
  totalPayments: input.totalPayments,
274
290
  amount: input.amount,
291
+ schedule: input.schedule,
275
292
  currency: input.currency,
276
293
  currentPeriodStart: toIso(input.currentPeriodStart),
277
294
  currentPeriodEnd: toIso(input.currentPeriodEnd),
@@ -544,14 +561,18 @@ async function handleSubscriptionProxyRequest(request, pathParts, options) {
544
561
  throw err;
545
562
  });
546
563
  if (!subscription) return jsonError(404, "not_found", "Subscription not found");
547
- const defaultAuthorized = await defaultMutationAuthorization(
548
- client,
549
- subscription,
550
- externalCustomerId
551
- );
552
- const customAuthorized = options.authorizeMutation ? await options.authorizeMutation({ request, subscription, externalCustomerId }) : true;
553
- if (!defaultAuthorized || !customAuthorized) {
554
- return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
564
+ const owned = await defaultMutationAuthorization(client, subscription, externalCustomerId);
565
+ if (!owned) return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
566
+ if (options.authorizeMutation) {
567
+ if (!await options.authorizeMutation({ request, subscription, externalCustomerId })) {
568
+ return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
569
+ }
570
+ } else if (subscription.totalPayments != null) {
571
+ return jsonError(
572
+ 403,
573
+ "installment_plan_merchant_only",
574
+ "Installment plans can only be paused, resumed or cancelled by the merchant"
575
+ );
555
576
  }
556
577
  }
557
578
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/errors.ts","../src/normalize.ts","../src/client.ts"],"sourcesContent":["import { SubscriptionsClient, createSubscriptionsClient } from './client';\nimport { ThrottleSubscriptionsError } from './errors';\nimport type { Subscription, SubscriptionsClientOptions } from './types';\n\nexport { SubscriptionsClient, createSubscriptionsClient, ThrottleSubscriptionsError };\nexport type {\n CheckoutSession,\n CreateCheckoutSessionInput,\n Customer,\n CustomerPaymentMethod,\n ListSubscriptionsParams,\n PaginatedSubscriptions,\n Subscription,\n SubscriptionsClientContract,\n SubscriptionsClientOptions,\n TrialEligibilityInput,\n TrialEligibilityResult,\n} from './types';\n\nexport interface SubscriptionProxyOptions {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof globalThis.fetch;\n getExternalCustomerId: (request: Request) => string | null | Promise<string | null>;\n authorizeMutation?: (args: {\n request: Request;\n subscription: Subscription;\n externalCustomerId: string;\n }) => boolean | Promise<boolean>;\n}\n\nexport interface SubscriptionProxyContext {\n params?: {\n path?: string[] | Promise<string[]>;\n };\n path?: string[] | string;\n}\n\nconst ALLOWED_PATHS = [\n /^\\/api\\/v1\\/subscriptions(?:\\/[\\w.-]+)?(?:\\/(?:pause|resume|cancel))?$/,\n /^\\/api\\/v1\\/subscriptions\\/eligibility-check$/,\n /^\\/api\\/v1\\/customers\\/by-external\\/[\\w.:%-]+$/,\n /^\\/api\\/v1\\/customers\\/[\\w.-]+\\/payment-methods$/,\n /^\\/api\\/v1\\/checkout\\/sessions$/,\n];\n\nexport function createSubscriptionProxyHandler(options: SubscriptionProxyOptions) {\n return async function subscriptionProxyHandler(\n request: Request,\n context: SubscriptionProxyContext = {},\n ): Promise<Response> {\n const path = await pathFromContext(context);\n return handleSubscriptionProxyRequest(request, path, options);\n };\n}\n\nexport async function handleSubscriptionProxyRequest(\n request: Request,\n pathParts: string[] | string,\n options: SubscriptionProxyOptions,\n): Promise<Response> {\n const externalCustomerId = await options.getExternalCustomerId(request);\n if (!externalCustomerId) return jsonError(401, 'unauthorized', 'Unauthorized');\n\n const path = normalizePath(pathParts);\n if (!ALLOWED_PATHS.some((pattern) => pattern.test(path))) {\n return jsonError(403, 'forbidden', 'Path is not allowed by the subscription proxy');\n }\n\n const method = request.method.toUpperCase();\n if (!['GET', 'POST', 'PATCH'].includes(method)) {\n return jsonError(405, 'method_not_allowed', 'Method not allowed');\n }\n\n const client = new SubscriptionsClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n fetch: options.fetch,\n } satisfies SubscriptionsClientOptions);\n\n if (isSubscriptionMutation(path, method)) {\n const subId = subscriptionIdFromPath(path);\n if (subId) {\n const subscription = await client.get(subId).catch((err) => {\n if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;\n throw err;\n });\n if (!subscription) return jsonError(404, 'not_found', 'Subscription not found');\n\n const defaultAuthorized = await defaultMutationAuthorization(\n client,\n subscription,\n externalCustomerId,\n );\n const customAuthorized = options.authorizeMutation\n ? await options.authorizeMutation({ request, subscription, externalCustomerId })\n : true;\n if (!defaultAuthorized || !customAuthorized) {\n return jsonError(403, 'forbidden', 'Subscription does not belong to this buyer');\n }\n }\n }\n\n const targetPath = await buildPinnedPathAndBody(request, path, externalCustomerId, client);\n if (targetPath instanceof Response) return targetPath;\n const upstream = await fetchWithApiKey(request, targetPath.path, {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n fetch: options.fetch,\n body: targetPath.body,\n });\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: filterHeaders(upstream.headers),\n });\n}\n\nasync function buildPinnedPathAndBody(\n request: Request,\n path: string,\n externalCustomerId: string,\n client: SubscriptionsClient,\n): Promise<{ path: string; body?: string } | Response> {\n const url = new URL(request.url);\n const query = new URLSearchParams(url.search);\n\n if (request.method.toUpperCase() === 'GET' && path === '/api/v1/subscriptions') {\n query.set('externalCustomerId', externalCustomerId);\n query.delete('customerId');\n }\n\n if (request.method.toUpperCase() === 'GET' && isCustomerByExternalPath(path)) {\n return {\n path: withQuery(\n `/api/v1/customers/by-external/${encodeURIComponent(externalCustomerId)}`,\n query,\n ),\n };\n }\n\n if (request.method.toUpperCase() === 'GET' && isCustomerPaymentMethodsPath(path)) {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n if (!customer) return jsonError(404, 'not_found', 'Customer not found');\n return {\n path: withQuery(\n `/api/v1/customers/${encodeURIComponent(customer.id)}/payment-methods`,\n query,\n ),\n };\n }\n\n if (request.method.toUpperCase() !== 'GET') {\n const raw = await request.text();\n if (path === '/api/v1/subscriptions' && request.method.toUpperCase() === 'POST') {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n if (!customer) return jsonError(404, 'not_found', 'Customer not found');\n\n const body = raw ? (JSON.parse(raw) as Record<string, any>) : {};\n body.customerId = customer.id;\n delete body.externalCustomerId;\n return { path: withQuery(path, query), body: JSON.stringify(body) };\n }\n\n if (path === '/api/v1/checkout/sessions' && raw) {\n const body = JSON.parse(raw) as Record<string, any>;\n body.customer = {\n ...(body.customer ?? {}),\n externalCustomerId,\n };\n delete body.customer.customerId;\n return { path: withQuery(path, query), body: JSON.stringify(body) };\n }\n return { path: withQuery(path, query), body: raw || undefined };\n }\n\n return { path: withQuery(path, query) };\n}\n\nasync function fetchWithApiKey(\n request: Request,\n path: string,\n opts: {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof globalThis.fetch;\n body?: string;\n },\n) {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!fetchImpl) throw new Error('fetch is required');\n const baseUrl = (opts.baseUrl ?? 'https://api.usethrottle.dev').replace(/\\/+$/, '');\n return fetchImpl(`${baseUrl}${path}`, {\n method: request.method,\n headers: {\n 'x-api-key': opts.apiKey,\n 'content-type': request.headers.get('content-type') ?? 'application/json',\n },\n body: request.method.toUpperCase() === 'GET' ? undefined : opts.body,\n });\n}\n\nasync function defaultMutationAuthorization(\n client: SubscriptionsClient,\n subscription: Subscription,\n externalCustomerId: string,\n): Promise<boolean> {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n return Boolean(customer?.id && customer.id === subscription.customerId);\n}\n\nasync function pathFromContext(context: SubscriptionProxyContext): Promise<string[] | string> {\n if (context.path) return context.path;\n const path = context.params?.path;\n if (!path) return [];\n return await path;\n}\n\nfunction normalizePath(pathParts: string[] | string): string {\n if (typeof pathParts === 'string') {\n return pathParts.startsWith('/') ? pathParts : `/${pathParts}`;\n }\n return `/${pathParts.map((part) => part.replace(/^\\/+|\\/+$/g, '')).join('/')}`;\n}\n\nfunction withQuery(path: string, query: URLSearchParams): string {\n const qs = query.toString();\n return qs ? `${path}?${qs}` : path;\n}\n\nfunction isSubscriptionMutation(path: string, method: string): boolean {\n if (path === '/api/v1/subscriptions/eligibility-check') return false;\n return method !== 'GET' && /^\\/api\\/v1\\/subscriptions\\/[\\w.-]+/.test(path);\n}\n\nfunction isCustomerByExternalPath(path: string): boolean {\n return /^\\/api\\/v1\\/customers\\/by-external\\/[\\w.:%-]+$/.test(path);\n}\n\nfunction isCustomerPaymentMethodsPath(path: string): boolean {\n return /^\\/api\\/v1\\/customers\\/[\\w.-]+\\/payment-methods$/.test(path);\n}\n\nfunction subscriptionIdFromPath(path: string): string | null {\n return path.match(/^\\/api\\/v1\\/subscriptions\\/([\\w.-]+)/)?.[1] ?? null;\n}\n\nfunction filterHeaders(headers: Headers): Headers {\n const next = new Headers(headers);\n next.delete('content-encoding');\n next.delete('content-length');\n next.delete('transfer-encoding');\n return next;\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return Response.json({ error: { code, message } }, { status });\n}\n","import { ThrottleError, type ThrottleErrorArgs } from '@usethrottle/errors';\n\n/**\n * Thrown on a non-2xx response from the Throttle Subscriptions API.\n *\n * Extends the shared {@link ThrottleError} so a single\n * `catch (e) { if (e instanceof ThrottleError) … }` covers errors from every\n * Throttle SDK. `instanceof ThrottleSubscriptionsError` keeps working.\n */\nexport class ThrottleSubscriptionsError extends ThrottleError {\n constructor(args: {\n code: string;\n message: string;\n statusCode: number;\n details?: unknown;\n responseHeaders?: ThrottleErrorArgs['responseHeaders'];\n }) {\n super(args);\n this.name = 'ThrottleSubscriptionsError';\n }\n}\n\nexport { ThrottleError, isThrottleError } from '@usethrottle/errors';\n","import type {\n CheckoutSession,\n Customer,\n CustomerPaymentMethod,\n Pagination,\n Subscription,\n SubscriptionInterval,\n SubscriptionRenewalIssue,\n SubscriptionStatus,\n SubscriptionUsageItem,\n TrialEligibilityResult,\n} from './types';\n\nexport function normalizeSubscription(raw: any): Subscription {\n return {\n id: requiredString(raw?.id, 'subscription.id'),\n workspaceId: optionalString(raw?.workspaceId),\n applicationId: optionalString(raw?.applicationId),\n customerId: requiredString(raw?.customerId, 'subscription.customerId'),\n status: (raw?.status ?? 'active') as SubscriptionStatus,\n planReference: requiredString(raw?.planReference, 'subscription.planReference'),\n planName: optionalString(raw?.planName),\n interval: (raw?.interval ?? 'monthly') as SubscriptionInterval,\n intervalUnit: (optionalString(raw?.intervalUnit) as Subscription['intervalUnit']) ?? null,\n intervalCount: raw?.intervalCount == null ? null : Number(raw.intervalCount),\n totalPayments: raw?.totalPayments == null ? null : Number(raw.totalPayments),\n paymentsMade: Number(raw?.paymentsMade ?? 0),\n completedAt: optionalIsoDateString(raw?.completedAt),\n amount: Number(raw?.amount ?? 0),\n quantity: Number(raw?.quantity ?? 1),\n currency: optionalString(raw?.currency) ?? 'USD',\n currentPeriodStart: requiredDateString(\n raw?.currentPeriodStart,\n 'subscription.currentPeriodStart',\n ),\n currentPeriodEnd: requiredDateString(\n raw?.currentPeriodEnd,\n 'subscription.currentPeriodEnd',\n ),\n trialEnd: optionalDateString(raw?.trialEnd),\n cancelAtPeriodEnd: Boolean(raw?.cancelAtPeriodEnd ?? false),\n failureCount: Number(raw?.failureCount ?? 0),\n lastPaymentAt: optionalDateString(raw?.lastPaymentAt),\n pausedAt: optionalDateString(raw?.pausedAt),\n cancelledAt: optionalDateString(raw?.cancelledAt),\n metadata: objectOrEmpty(raw?.metadata),\n renewalIssue: normalizeRenewalIssue(raw?.renewalIssue),\n proration: raw?.proration\n ? {\n creditCents: raw.proration.creditCents,\n chargedCents: raw.proration.chargedCents,\n fullAmount: raw.proration.fullAmount,\n }\n : undefined,\n createdAt: optionalDateString(raw?.createdAt) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt) ?? undefined,\n };\n}\n\nfunction normalizeRenewalIssue(raw: any): SubscriptionRenewalIssue | null {\n if (!raw || typeof raw !== 'object') return null;\n return {\n kind: raw.kind,\n reason: raw.reason,\n count: Number(raw.count ?? 0),\n firstAt: optionalString(raw.firstAt) ?? undefined,\n lastAt: requiredString(raw.lastAt, 'subscription.renewalIssue.lastAt'),\n message: raw.message ?? '',\n alertedAt: optionalDateString(raw.alertedAt),\n charge: raw.charge\n ? {\n transactionId: optionalString(raw.charge.transactionId),\n amount: Number(raw.charge.amount ?? 0),\n currency: optionalString(raw.charge.currency) ?? 'USD',\n periodStart: optionalString(raw.charge.periodStart) ?? undefined,\n }\n : null,\n };\n}\n\nexport function normalizeUsageItem(raw: any): SubscriptionUsageItem {\n return {\n id: requiredString(raw?.id, 'usageItem.id'),\n subscriptionId: requiredString(raw?.subscriptionId, 'usageItem.subscriptionId'),\n kind: raw?.kind ?? '',\n unitCount: Number(raw?.unitCount ?? 0),\n unitPriceCents: Number(raw?.unitPriceCents ?? 0),\n totalCents: Number(raw?.totalCents ?? 0),\n periodStart: requiredDateString(raw?.periodStart, 'usageItem.periodStart'),\n periodEnd: requiredDateString(raw?.periodEnd, 'usageItem.periodEnd'),\n createdAt: requiredDateString(raw?.createdAt, 'usageItem.createdAt'),\n };\n}\n\nexport function normalizeCustomer(raw: any): Customer | null {\n if (!raw) return null;\n return {\n id: requiredString(raw?.id, 'customer.id'),\n externalId: optionalString(raw?.externalId),\n email: optionalString(raw?.email),\n firstName: optionalString(raw?.firstName),\n lastName: optionalString(raw?.lastName),\n phone: optionalString(raw?.phone),\n company: optionalString(raw?.company),\n metadata: objectOrEmpty(raw?.metadata),\n createdAt: optionalDateString(raw?.createdAt) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt) ?? undefined,\n };\n}\n\nexport function normalizeCustomerPaymentMethod(raw: any): CustomerPaymentMethod {\n return {\n id: requiredString(raw?.id, 'paymentMethod.id'),\n processor: optionalString(raw?.processor),\n methodType: optionalString(raw?.methodType),\n cardBrand: optionalString(raw?.cardBrand),\n cardLastFour: optionalString(raw?.cardLastFour),\n isDefault: Boolean(raw?.isDefault ?? false),\n isActive: Boolean(raw?.isActive ?? true),\n createdAt: optionalDateString(raw?.createdAt) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt) ?? undefined,\n };\n}\n\nexport function normalizeCheckoutSession(raw: any): CheckoutSession {\n const sessionId = raw?.sessionId ?? raw?.id;\n return {\n ...withoutUnderscoreKeys(raw),\n id: optionalString(raw?.id) ?? undefined,\n sessionId: requiredString(sessionId, 'checkoutSession.sessionId'),\n checkoutUrl: optionalString(raw?.checkoutUrl) ?? undefined,\n embedToken: optionalString(raw?.embedToken) ?? undefined,\n expiresAt: optionalDateString(raw?.expiresAt) ?? undefined,\n };\n}\n\nexport function normalizeTrialEligibility(raw: any): TrialEligibilityResult {\n return {\n eligible: Boolean(raw?.eligible),\n reason: raw?.reason,\n };\n}\n\nexport function normalizePagination(raw: any): Pagination | undefined {\n if (!raw) return undefined;\n return {\n cursor: raw.cursor ?? null,\n hasMore: Boolean(raw.hasMore ?? false),\n };\n}\n\nexport function toIso(value: string | Date | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value === 'string' && value.length > 0) return value;\n throw new Error(`Invalid Throttle response: missing ${label}`);\n}\n\nfunction optionalString(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\nfunction requiredDateString(value: unknown, label: string): string {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n throw new Error(`Invalid Throttle response: missing ${label}`);\n}\n\nfunction optionalDateString(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n return null;\n}\n\n/**\n * Like `optionalDateString`, but always reserializes to a full ISO string\n * (with milliseconds) rather than passing a raw string through unchanged.\n * `completedAt` is a fresh field with no legacy raw-string callers to stay\n * compatible with, so it normalizes cleanly instead of echoing whatever the\n * API sent.\n */\nfunction optionalIsoDateString(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? value : d.toISOString();\n }\n return null;\n}\n\nfunction objectOrEmpty(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction withoutUnderscoreKeys(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return {};\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).filter(([key]) => !key.includes('_')),\n );\n}\n","import { ThrottleSubscriptionsError } from './errors';\nimport {\n normalizeCheckoutSession,\n normalizeCustomer,\n normalizeCustomerPaymentMethod,\n normalizePagination,\n normalizeSubscription,\n normalizeTrialEligibility,\n normalizeUsageItem,\n toIso,\n} from './normalize';\nimport type {\n ChangePlanInput,\n ChangeQuantityInput,\n CheckoutSession,\n CreateCheckoutSessionInput,\n CreateSubscriptionInput,\n Customer,\n CustomerPaymentMethod,\n ListSubscriptionsParams,\n PaginatedSubscriptions,\n RecordUsageInput,\n Subscription,\n SubscriptionInvoiceRefund,\n SubscriptionUsageItem,\n SubscriptionsClientContract,\n SubscriptionsClientOptions,\n ThrottleEnvelope,\n TrialEligibilityInput,\n TrialEligibilityResult,\n UpdateSubscriptionInput,\n WaivePeriodInput,\n} from './types';\n\nexport class SubscriptionsClient implements SubscriptionsClientContract {\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly fetcher?: SubscriptionsClientOptions['fetcher'];\n private readonly timeoutMs: number;\n\n constructor(opts: SubscriptionsClientOptions = {}) {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!fetchImpl) throw new Error('SubscriptionsClient: fetch is required');\n if (!opts.apiKey && !opts.fetcher) {\n throw new Error('SubscriptionsClient: apiKey or fetcher is required');\n }\n\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? 'https://api.usethrottle.dev').replace(/\\/+$/, '');\n this.fetchImpl = fetchImpl;\n this.fetcher = opts.fetcher;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n async request<T = unknown>(method: string, path: string, body?: unknown): Promise<T> {\n const envelope = await this.requestEnvelope<T>(method, path, body);\n if (Object.prototype.hasOwnProperty.call(envelope, 'data')) {\n return envelope.data as T;\n }\n return envelope as T;\n }\n\n async requestEnvelope<T = unknown>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<ThrottleEnvelope<T>> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n\n try {\n const init: RequestInit = {\n method,\n headers: {\n 'content-type': 'application/json',\n ...(this.apiKey ? { 'x-api-key': this.apiKey } : {}),\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: ctrl.signal,\n };\n\n const res = this.fetcher\n ? await this.fetcher(path, init)\n : await this.fetchImpl(toAbsoluteUrl(this.baseUrl, path), init);\n\n if (res.status === 204) return { data: undefined as T };\n\n const json = (await res.json().catch(() => ({}))) as ThrottleEnvelope<T>;\n if (!res.ok) {\n throw new ThrottleSubscriptionsError({\n code: json.error?.code ?? 'unknown_error',\n message: json.error?.message ?? `HTTP ${res.status}`,\n statusCode: res.status,\n details: json.error?.details,\n responseHeaders: res.headers,\n });\n }\n return json;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async create(input: CreateSubscriptionInput): Promise<Subscription> {\n const customerId = await this.resolveCustomerId(input.customerId, input.externalCustomerId);\n const data = await this.request<unknown>('POST', '/api/v1/subscriptions', {\n applicationId: input.applicationId,\n customerId,\n planReference: input.planReference,\n planName: input.planName,\n interval: input.interval,\n intervalUnit: input.intervalUnit,\n intervalCount: input.intervalCount,\n totalPayments: input.totalPayments,\n amount: input.amount,\n currency: input.currency,\n currentPeriodStart: toIso(input.currentPeriodStart),\n currentPeriodEnd: toIso(input.currentPeriodEnd),\n trialEnd: toIso(input.trialEnd),\n metadata: input.metadata ?? {},\n orderId: input.orderId,\n });\n return normalizeSubscription(data);\n }\n\n async list(params: ListSubscriptionsParams = {}): Promise<PaginatedSubscriptions> {\n const query = new URLSearchParams();\n setQuery(query, 'customerId', params.customerId);\n setQuery(query, 'externalCustomerId', params.externalCustomerId);\n setQuery(query, 'status', params.status);\n setQuery(query, 'interval', params.interval);\n setQuery(query, 'installment', params.installment);\n setQuery(query, 'q', params.q);\n setQuery(query, 'cursor', params.cursor);\n setQuery(query, 'limit', params.limit);\n setQuery(query, 'sort', params.sort);\n setQuery(query, 'createdAfter', toIso(params.createdAfter));\n setQuery(query, 'createdBefore', toIso(params.createdBefore));\n\n const suffix = query.toString() ? `?${query}` : '';\n const envelope = await this.requestEnvelope<unknown[]>('GET', `/api/v1/subscriptions${suffix}`);\n return {\n data: (envelope.data ?? []).map(normalizeSubscription),\n pagination: normalizePagination(envelope.meta?.pagination),\n };\n }\n\n async get(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'GET',\n `/api/v1/subscriptions/${encodeURIComponent(id)}`,\n );\n return normalizeSubscription(data);\n }\n\n /**\n * A term-only edit: rename the plan, correct the cadence or the amount on\n * the row — no proration, no charge, no period reset. Mirrors\n * `PATCH /api/v1/subscriptions/{id}`. This is what `changePlan()` did\n * before 3.0.0; use `changePlan()` now for an actual plan change.\n */\n async update(id: string, input: UpdateSubscriptionInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'PATCH',\n `/api/v1/subscriptions/${encodeURIComponent(id)}`,\n input,\n );\n return normalizeSubscription(data);\n }\n\n /**\n * A real plan change: prorated and charged immediately when\n * `effective: 'now'`, or scheduled for period end with no charge today.\n * Mirrors `POST /api/v1/subscriptions/{id}/change-plan`. The resolved\n * subscription carries `proration` when the server computed one.\n *\n * This is NOT the old `changePlan()` (a PATCH-style term edit with no\n * proration and no charge) — use `update()` for that.\n */\n async changePlan(input: ChangePlanInput): Promise<Subscription> {\n const { id, ...body } = input;\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/change-plan`,\n {\n planReference: body.planReference,\n planName: body.planName,\n interval: body.interval,\n intervalUnit: body.intervalUnit,\n intervalCount: body.intervalCount,\n amount: body.amount,\n effective: body.effective,\n },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Change the seat quantity: prorated and charged immediately when\n * `effective: 'now'`, or scheduled for period end with no charge today.\n * Mirrors `POST /api/v1/subscriptions/{id}/change-quantity`.\n */\n async changeQuantity(id: string, input: ChangeQuantityInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/change-quantity`,\n { quantity: input.quantity, effective: input.effective },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Charge a past-due subscription's card again now, instead of waiting for\n * the next scheduled dunning retry. Mirrors\n * `POST /api/v1/subscriptions/{id}/retry-charge`.\n */\n async retryCharge(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/retry-charge`,\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Forgive the current past-due period — the merchant is not collecting it.\n * Mirrors `POST /api/v1/subscriptions/{id}/waive-period`.\n */\n async waivePeriod(id: string, input: WaivePeriodInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/waive-period`,\n { reason: input.reason },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Record (upsert) a usage item for the subscription's current billing\n * period. Mirrors `POST /api/v1/subscriptions/{id}/usage`.\n */\n async recordUsage(id: string, input: RecordUsageInput): Promise<SubscriptionUsageItem> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/usage`,\n { kind: input.kind, unitCount: input.unitCount, unitPriceCents: input.unitPriceCents },\n );\n return normalizeUsageItem(data);\n }\n\n /**\n * List usage items recorded for the subscription's current billing period.\n * Mirrors `GET /api/v1/subscriptions/{id}/usage`.\n */\n async listUsage(id: string): Promise<SubscriptionUsageItem[]> {\n const envelope = await this.requestEnvelope<{ items?: unknown[] }>(\n 'GET',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/usage`,\n );\n // This route answers `{ items: [...] }`, not the usual `{ data, meta }`\n // envelope, so `request()`'s data-unwrap would just hand back the whole\n // body — read `items` off the raw envelope instead.\n const items = (envelope as unknown as { items?: unknown[] }).items ?? [];\n return items.map(normalizeUsageItem);\n }\n\n async pause(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/pause`,\n );\n return normalizeSubscription(data);\n }\n\n async resume(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/resume`,\n );\n return normalizeSubscription(data);\n }\n\n async cancel(\n id: string,\n input: { atPeriodEnd?: boolean; refund?: 'none' | 'prorated' | 'last_cycle' } = {},\n ): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`,\n { atPeriodEnd: input.atPeriodEnd ?? false, refund: input.refund ?? 'none' },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Refund one billing cycle.\n *\n * `intent` has no default: refunding money and ending the subscription are\n * separate decisions, and guessing either way is wrong often enough that the\n * API requires the caller to say which one they mean.\n */\n async refundInvoice(\n id: string,\n invoiceId: string,\n input: { intent: 'money_only' | 'refund_and_cancel'; amount?: number; reason?: string },\n ): Promise<SubscriptionInvoiceRefund> {\n return this.request<SubscriptionInvoiceRefund>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/invoices/${encodeURIComponent(invoiceId)}/refund`,\n input,\n );\n }\n\n async createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession> {\n const data = await this.request<unknown>('POST', '/api/v1/checkout/sessions', input);\n return normalizeCheckoutSession(data);\n }\n\n async checkTrialEligibility(input: TrialEligibilityInput): Promise<TrialEligibilityResult> {\n const data = await this.request<unknown>('POST', '/api/v1/subscriptions/eligibility-check', {\n paymentMethodId: input.paymentMethodId,\n });\n return normalizeTrialEligibility(data);\n }\n\n async getCustomerByExternalId(externalId: string): Promise<Customer | null> {\n try {\n const data = await this.request<unknown>(\n 'GET',\n `/api/v1/customers/by-external/${encodeURIComponent(externalId)}`,\n );\n return normalizeCustomer(data);\n } catch (err) {\n if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;\n throw err;\n }\n }\n\n async listCustomerPaymentMethods(customerId: string): Promise<CustomerPaymentMethod[]> {\n const data = await this.request<unknown[]>(\n 'GET',\n `/api/v1/customers/${encodeURIComponent(customerId)}/payment-methods`,\n );\n return (data ?? []).map(normalizeCustomerPaymentMethod);\n }\n\n private async resolveCustomerId(\n customerId?: string,\n externalCustomerId?: string,\n ): Promise<string> {\n if (customerId) return customerId;\n if (!externalCustomerId) {\n throw new ThrottleSubscriptionsError({\n code: 'invalid_request',\n message: 'customerId or externalCustomerId is required',\n statusCode: 400,\n });\n }\n\n const customer = await this.getCustomerByExternalId(externalCustomerId);\n if (!customer) {\n throw new ThrottleSubscriptionsError({\n code: 'customer_not_found',\n message: 'Customer not found',\n statusCode: 404,\n });\n }\n return customer.id;\n }\n}\n\nexport function createSubscriptionsClient(opts: SubscriptionsClientOptions): SubscriptionsClient {\n return new SubscriptionsClient(opts);\n}\n\nfunction toAbsoluteUrl(baseUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path;\n return `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n}\n\nfunction setQuery(query: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null || value === '') return;\n query.set(key, String(value));\n}\n"],"mappings":"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","names":["import_errors"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/errors.ts","../src/normalize.ts","../src/client.ts"],"sourcesContent":["import { SubscriptionsClient, createSubscriptionsClient } from './client';\nimport { ThrottleSubscriptionsError } from './errors';\nimport type { Subscription, SubscriptionsClientOptions } from './types';\n\nexport { SubscriptionsClient, createSubscriptionsClient, ThrottleSubscriptionsError };\nexport type {\n CheckoutSession,\n CreateCheckoutSessionInput,\n Customer,\n CustomerPaymentMethod,\n ListSubscriptionsParams,\n PaginatedSubscriptions,\n Subscription,\n SubscriptionsClientContract,\n SubscriptionsClientOptions,\n TrialEligibilityInput,\n TrialEligibilityResult,\n} from './types';\n\nexport interface SubscriptionProxyOptions {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof globalThis.fetch;\n getExternalCustomerId: (request: Request) => string | null | Promise<string | null>;\n /** When supplied it replaces the default installment rule; ownership is always checked. */\n authorizeMutation?: (args: {\n request: Request;\n subscription: Subscription;\n externalCustomerId: string;\n }) => boolean | Promise<boolean>;\n}\n\nexport interface SubscriptionProxyContext {\n params?: {\n path?: string[] | Promise<string[]>;\n };\n path?: string[] | string;\n}\n\nconst ALLOWED_PATHS = [\n /^\\/api\\/v1\\/subscriptions(?:\\/[\\w.-]+)?(?:\\/(?:pause|resume|cancel))?$/,\n /^\\/api\\/v1\\/subscriptions\\/eligibility-check$/,\n /^\\/api\\/v1\\/customers\\/by-external\\/[\\w.:%-]+$/,\n /^\\/api\\/v1\\/customers\\/[\\w.-]+\\/payment-methods$/,\n /^\\/api\\/v1\\/checkout\\/sessions$/,\n];\n\nexport function createSubscriptionProxyHandler(options: SubscriptionProxyOptions) {\n return async function subscriptionProxyHandler(\n request: Request,\n context: SubscriptionProxyContext = {},\n ): Promise<Response> {\n const path = await pathFromContext(context);\n return handleSubscriptionProxyRequest(request, path, options);\n };\n}\n\nexport async function handleSubscriptionProxyRequest(\n request: Request,\n pathParts: string[] | string,\n options: SubscriptionProxyOptions,\n): Promise<Response> {\n const externalCustomerId = await options.getExternalCustomerId(request);\n if (!externalCustomerId) return jsonError(401, 'unauthorized', 'Unauthorized');\n\n const path = normalizePath(pathParts);\n if (!ALLOWED_PATHS.some((pattern) => pattern.test(path))) {\n return jsonError(403, 'forbidden', 'Path is not allowed by the subscription proxy');\n }\n\n const method = request.method.toUpperCase();\n if (!['GET', 'POST', 'PATCH'].includes(method)) {\n return jsonError(405, 'method_not_allowed', 'Method not allowed');\n }\n\n const client = new SubscriptionsClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n fetch: options.fetch,\n } satisfies SubscriptionsClientOptions);\n\n if (isSubscriptionMutation(path, method)) {\n const subId = subscriptionIdFromPath(path);\n if (subId) {\n const subscription = await client.get(subId).catch((err) => {\n if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;\n throw err;\n });\n if (!subscription) return jsonError(404, 'not_found', 'Subscription not found');\n\n const owned = await defaultMutationAuthorization(client, subscription, externalCustomerId);\n if (!owned) return jsonError(403, 'forbidden', 'Subscription does not belong to this buyer');\n if (options.authorizeMutation) {\n if (!(await options.authorizeMutation({ request, subscription, externalCustomerId }))) {\n return jsonError(403, 'forbidden', 'Subscription does not belong to this buyer');\n }\n } else if (subscription.totalPayments != null) {\n // Same rule as the storefront buyer API (D33/D36): installment plans are merchant-only.\n return jsonError(\n 403,\n 'installment_plan_merchant_only',\n 'Installment plans can only be paused, resumed or cancelled by the merchant',\n );\n }\n }\n }\n\n const targetPath = await buildPinnedPathAndBody(request, path, externalCustomerId, client);\n if (targetPath instanceof Response) return targetPath;\n const upstream = await fetchWithApiKey(request, targetPath.path, {\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n fetch: options.fetch,\n body: targetPath.body,\n });\n\n return new Response(upstream.body, {\n status: upstream.status,\n headers: filterHeaders(upstream.headers),\n });\n}\n\nasync function buildPinnedPathAndBody(\n request: Request,\n path: string,\n externalCustomerId: string,\n client: SubscriptionsClient,\n): Promise<{ path: string; body?: string } | Response> {\n const url = new URL(request.url);\n const query = new URLSearchParams(url.search);\n\n if (request.method.toUpperCase() === 'GET' && path === '/api/v1/subscriptions') {\n query.set('externalCustomerId', externalCustomerId);\n query.delete('customerId');\n }\n\n if (request.method.toUpperCase() === 'GET' && isCustomerByExternalPath(path)) {\n return {\n path: withQuery(\n `/api/v1/customers/by-external/${encodeURIComponent(externalCustomerId)}`,\n query,\n ),\n };\n }\n\n if (request.method.toUpperCase() === 'GET' && isCustomerPaymentMethodsPath(path)) {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n if (!customer) return jsonError(404, 'not_found', 'Customer not found');\n return {\n path: withQuery(\n `/api/v1/customers/${encodeURIComponent(customer.id)}/payment-methods`,\n query,\n ),\n };\n }\n\n if (request.method.toUpperCase() !== 'GET') {\n const raw = await request.text();\n if (path === '/api/v1/subscriptions' && request.method.toUpperCase() === 'POST') {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n if (!customer) return jsonError(404, 'not_found', 'Customer not found');\n\n const body = raw ? (JSON.parse(raw) as Record<string, any>) : {};\n body.customerId = customer.id;\n delete body.externalCustomerId;\n return { path: withQuery(path, query), body: JSON.stringify(body) };\n }\n\n if (path === '/api/v1/checkout/sessions' && raw) {\n const body = JSON.parse(raw) as Record<string, any>;\n body.customer = {\n ...(body.customer ?? {}),\n externalCustomerId,\n };\n delete body.customer.customerId;\n return { path: withQuery(path, query), body: JSON.stringify(body) };\n }\n return { path: withQuery(path, query), body: raw || undefined };\n }\n\n return { path: withQuery(path, query) };\n}\n\nasync function fetchWithApiKey(\n request: Request,\n path: string,\n opts: {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof globalThis.fetch;\n body?: string;\n },\n) {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!fetchImpl) throw new Error('fetch is required');\n const baseUrl = (opts.baseUrl ?? 'https://api.usethrottle.dev').replace(/\\/+$/, '');\n return fetchImpl(`${baseUrl}${path}`, {\n method: request.method,\n headers: {\n 'x-api-key': opts.apiKey,\n 'content-type': request.headers.get('content-type') ?? 'application/json',\n },\n body: request.method.toUpperCase() === 'GET' ? undefined : opts.body,\n });\n}\n\nasync function defaultMutationAuthorization(\n client: SubscriptionsClient,\n subscription: Subscription,\n externalCustomerId: string,\n): Promise<boolean> {\n const customer = await client.getCustomerByExternalId(externalCustomerId);\n return Boolean(customer?.id && customer.id === subscription.customerId);\n}\n\nasync function pathFromContext(context: SubscriptionProxyContext): Promise<string[] | string> {\n if (context.path) return context.path;\n const path = context.params?.path;\n if (!path) return [];\n return await path;\n}\n\nfunction normalizePath(pathParts: string[] | string): string {\n if (typeof pathParts === 'string') {\n return pathParts.startsWith('/') ? pathParts : `/${pathParts}`;\n }\n return `/${pathParts.map((part) => part.replace(/^\\/+|\\/+$/g, '')).join('/')}`;\n}\n\nfunction withQuery(path: string, query: URLSearchParams): string {\n const qs = query.toString();\n return qs ? `${path}?${qs}` : path;\n}\n\nfunction isSubscriptionMutation(path: string, method: string): boolean {\n if (path === '/api/v1/subscriptions/eligibility-check') return false;\n return method !== 'GET' && /^\\/api\\/v1\\/subscriptions\\/[\\w.-]+/.test(path);\n}\n\nfunction isCustomerByExternalPath(path: string): boolean {\n return /^\\/api\\/v1\\/customers\\/by-external\\/[\\w.:%-]+$/.test(path);\n}\n\nfunction isCustomerPaymentMethodsPath(path: string): boolean {\n return /^\\/api\\/v1\\/customers\\/[\\w.-]+\\/payment-methods$/.test(path);\n}\n\nfunction subscriptionIdFromPath(path: string): string | null {\n return path.match(/^\\/api\\/v1\\/subscriptions\\/([\\w.-]+)/)?.[1] ?? null;\n}\n\nfunction filterHeaders(headers: Headers): Headers {\n const next = new Headers(headers);\n next.delete('content-encoding');\n next.delete('content-length');\n next.delete('transfer-encoding');\n return next;\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return Response.json({ error: { code, message } }, { status });\n}\n","import { ThrottleError, type ThrottleErrorArgs } from '@usethrottle/errors';\n\n/**\n * Thrown on a non-2xx response from the Throttle Subscriptions API.\n *\n * Extends the shared {@link ThrottleError} so a single\n * `catch (e) { if (e instanceof ThrottleError) … }` covers errors from every\n * Throttle SDK. `instanceof ThrottleSubscriptionsError` keeps working.\n */\nexport class ThrottleSubscriptionsError extends ThrottleError {\n constructor(args: {\n code: string;\n message: string;\n statusCode: number;\n details?: unknown;\n responseHeaders?: ThrottleErrorArgs['responseHeaders'];\n }) {\n super(args);\n this.name = 'ThrottleSubscriptionsError';\n }\n}\n\nexport { ThrottleError, isThrottleError } from '@usethrottle/errors';\n","import type {\n CheckoutSession,\n Customer,\n CustomerPaymentMethod,\n Pagination,\n Subscription,\n SubscriptionInterval,\n SubscriptionIntervalUnit,\n SubscriptionRenewalIssue,\n SubscriptionStatus,\n SubscriptionUsageItem,\n TrialEligibilityResult,\n} from './types';\n\nexport function normalizeSubscription(raw: any): Subscription {\n return {\n id: requiredString(raw?.id, 'subscription.id'),\n workspaceId: optionalString(raw?.workspaceId),\n applicationId: optionalString(raw?.applicationId),\n customerId: requiredString(raw?.customerId, 'subscription.customerId'),\n status: (raw?.status ?? 'active') as SubscriptionStatus,\n planReference: requiredString(raw?.planReference, 'subscription.planReference'),\n planName: optionalString(raw?.planName),\n interval: (raw?.interval ?? 'monthly') as SubscriptionInterval,\n intervalUnit: (optionalString(raw?.intervalUnit) as Subscription['intervalUnit']) ?? null,\n intervalCount: raw?.intervalCount == null ? null : Number(raw.intervalCount),\n totalPayments: raw?.totalPayments == null ? null : Number(raw.totalPayments),\n paymentsMade: Number(raw?.paymentsMade ?? 0),\n completedAt: optionalIsoDateString(raw?.completedAt),\n paymentSchedule: normalizePaymentSchedule(raw?.paymentSchedule),\n scheduleTotal: raw?.scheduleTotal == null ? null : Number(raw.scheduleTotal),\n nextPaymentAmount: raw?.nextPaymentAmount == null ? null : Number(raw.nextPaymentAmount),\n amount: Number(raw?.amount ?? 0),\n quantity: Number(raw?.quantity ?? 1),\n currency: optionalString(raw?.currency) ?? 'USD',\n currentPeriodStart: requiredDateString(\n raw?.currentPeriodStart,\n 'subscription.currentPeriodStart',\n ),\n currentPeriodEnd: requiredDateString(\n raw?.currentPeriodEnd,\n 'subscription.currentPeriodEnd',\n ),\n trialEnd: optionalDateString(raw?.trialEnd),\n cancelAtPeriodEnd: Boolean(raw?.cancelAtPeriodEnd ?? false),\n failureCount: Number(raw?.failureCount ?? 0),\n lastPaymentAt: optionalDateString(raw?.lastPaymentAt),\n pausedAt: optionalDateString(raw?.pausedAt),\n cancelledAt: optionalDateString(raw?.cancelledAt),\n metadata: objectOrEmpty(raw?.metadata),\n renewalIssue: normalizeRenewalIssue(raw?.renewalIssue),\n proration: raw?.proration\n ? {\n creditCents: raw.proration.creditCents,\n chargedCents: raw.proration.chargedCents,\n fullAmount: raw.proration.fullAmount,\n }\n : undefined,\n createdAt: optionalDateString(raw?.createdAt) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt) ?? undefined,\n };\n}\n\nfunction normalizePaymentSchedule(raw: any): Subscription['paymentSchedule'] {\n if (!Array.isArray(raw)) return null;\n return raw.map((r: any) => ({\n index: Number(r?.index ?? 0),\n amount: Number(r?.amount ?? 0),\n discount: Number(r?.discount ?? 0),\n net: Number(r?.net ?? 0),\n intervalUnit: (optionalString(r?.intervalUnit) as SubscriptionIntervalUnit | null) ?? null,\n intervalCount: r?.intervalCount == null ? null : Number(r.intervalCount),\n status: r?.status as 'paid' | 'due' | 'upcoming',\n dueAt: optionalDateString(r?.dueAt),\n }));\n}\n\nfunction normalizeRenewalIssue(raw: any): SubscriptionRenewalIssue | null {\n if (!raw || typeof raw !== 'object') return null;\n return {\n kind: raw.kind,\n reason: raw.reason,\n count: Number(raw.count ?? 0),\n firstAt: optionalString(raw.firstAt) ?? undefined,\n lastAt: requiredString(raw.lastAt, 'subscription.renewalIssue.lastAt'),\n message: raw.message ?? '',\n alertedAt: optionalDateString(raw.alertedAt),\n charge: raw.charge\n ? {\n transactionId: optionalString(raw.charge.transactionId),\n amount: Number(raw.charge.amount ?? 0),\n currency: optionalString(raw.charge.currency) ?? 'USD',\n periodStart: optionalString(raw.charge.periodStart) ?? undefined,\n }\n : null,\n };\n}\n\nexport function normalizeUsageItem(raw: any): SubscriptionUsageItem {\n return {\n id: requiredString(raw?.id, 'usageItem.id'),\n subscriptionId: requiredString(raw?.subscriptionId, 'usageItem.subscriptionId'),\n kind: raw?.kind ?? '',\n unitCount: Number(raw?.unitCount ?? 0),\n unitPriceCents: Number(raw?.unitPriceCents ?? 0),\n totalCents: Number(raw?.totalCents ?? 0),\n periodStart: requiredDateString(raw?.periodStart, 'usageItem.periodStart'),\n periodEnd: requiredDateString(raw?.periodEnd, 'usageItem.periodEnd'),\n createdAt: requiredDateString(raw?.createdAt, 'usageItem.createdAt'),\n };\n}\n\nexport function normalizeCustomer(raw: any): Customer | null {\n if (!raw) return null;\n return {\n id: requiredString(raw?.id, 'customer.id'),\n externalId: optionalString(raw?.externalId),\n email: optionalString(raw?.email),\n firstName: optionalString(raw?.firstName),\n lastName: optionalString(raw?.lastName),\n phone: optionalString(raw?.phone),\n company: optionalString(raw?.company),\n metadata: objectOrEmpty(raw?.metadata),\n createdAt: optionalDateString(raw?.createdAt) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt) ?? undefined,\n };\n}\n\nexport function normalizeCustomerPaymentMethod(raw: any): CustomerPaymentMethod {\n return {\n id: requiredString(raw?.id, 'paymentMethod.id'),\n processor: optionalString(raw?.processor),\n methodType: optionalString(raw?.methodType),\n cardBrand: optionalString(raw?.cardBrand),\n cardLastFour: optionalString(raw?.cardLastFour),\n isDefault: Boolean(raw?.isDefault ?? false),\n isActive: Boolean(raw?.isActive ?? true),\n createdAt: optionalDateString(raw?.createdAt) ?? undefined,\n updatedAt: optionalDateString(raw?.updatedAt) ?? undefined,\n };\n}\n\nexport function normalizeCheckoutSession(raw: any): CheckoutSession {\n const sessionId = raw?.sessionId ?? raw?.id;\n return {\n ...withoutUnderscoreKeys(raw),\n id: optionalString(raw?.id) ?? undefined,\n sessionId: requiredString(sessionId, 'checkoutSession.sessionId'),\n checkoutUrl: optionalString(raw?.checkoutUrl) ?? undefined,\n embedToken: optionalString(raw?.embedToken) ?? undefined,\n expiresAt: optionalDateString(raw?.expiresAt) ?? undefined,\n };\n}\n\nexport function normalizeTrialEligibility(raw: any): TrialEligibilityResult {\n return {\n eligible: Boolean(raw?.eligible),\n reason: raw?.reason,\n };\n}\n\nexport function normalizePagination(raw: any): Pagination | undefined {\n if (!raw) return undefined;\n return {\n cursor: raw.cursor ?? null,\n hasMore: Boolean(raw.hasMore ?? false),\n };\n}\n\nexport function toIso(value: string | Date | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction requiredString(value: unknown, label: string): string {\n if (typeof value === 'string' && value.length > 0) return value;\n throw new Error(`Invalid Throttle response: missing ${label}`);\n}\n\nfunction optionalString(value: unknown): string | null {\n return typeof value === 'string' ? value : null;\n}\n\nfunction requiredDateString(value: unknown, label: string): string {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n throw new Error(`Invalid Throttle response: missing ${label}`);\n}\n\nfunction optionalDateString(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n return null;\n}\n\n/**\n * Like `optionalDateString`, but always reserializes to a full ISO string\n * (with milliseconds) rather than passing a raw string through unchanged.\n * `completedAt` is a fresh field with no legacy raw-string callers to stay\n * compatible with, so it normalizes cleanly instead of echoing whatever the\n * API sent.\n */\nfunction optionalIsoDateString(value: unknown): string | null {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? value : d.toISOString();\n }\n return null;\n}\n\nfunction objectOrEmpty(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction withoutUnderscoreKeys(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return {};\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).filter(([key]) => !key.includes('_')),\n );\n}\n","import { ThrottleSubscriptionsError } from './errors';\nimport {\n normalizeCheckoutSession,\n normalizeCustomer,\n normalizeCustomerPaymentMethod,\n normalizePagination,\n normalizeSubscription,\n normalizeTrialEligibility,\n normalizeUsageItem,\n toIso,\n} from './normalize';\nimport type {\n ChangePlanInput,\n ChangeQuantityInput,\n CheckoutSession,\n CreateCheckoutSessionInput,\n CreateSubscriptionInput,\n Customer,\n CustomerPaymentMethod,\n ListSubscriptionsParams,\n PaginatedSubscriptions,\n RecordUsageInput,\n Subscription,\n SubscriptionInvoiceRefund,\n SubscriptionUsageItem,\n SubscriptionsClientContract,\n SubscriptionsClientOptions,\n ThrottleEnvelope,\n TrialEligibilityInput,\n TrialEligibilityResult,\n UpdateSubscriptionInput,\n WaivePeriodInput,\n} from './types';\n\nexport class SubscriptionsClient implements SubscriptionsClientContract {\n private readonly apiKey?: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly fetcher?: SubscriptionsClientOptions['fetcher'];\n private readonly timeoutMs: number;\n\n constructor(opts: SubscriptionsClientOptions = {}) {\n const fetchImpl = opts.fetch ?? globalThis.fetch;\n if (!fetchImpl) throw new Error('SubscriptionsClient: fetch is required');\n if (!opts.apiKey && !opts.fetcher) {\n throw new Error('SubscriptionsClient: apiKey or fetcher is required');\n }\n\n this.apiKey = opts.apiKey;\n this.baseUrl = (opts.baseUrl ?? 'https://api.usethrottle.dev').replace(/\\/+$/, '');\n this.fetchImpl = fetchImpl;\n this.fetcher = opts.fetcher;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n async request<T = unknown>(method: string, path: string, body?: unknown): Promise<T> {\n const envelope = await this.requestEnvelope<T>(method, path, body);\n if (Object.prototype.hasOwnProperty.call(envelope, 'data')) {\n return envelope.data as T;\n }\n return envelope as T;\n }\n\n async requestEnvelope<T = unknown>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<ThrottleEnvelope<T>> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n\n try {\n const init: RequestInit = {\n method,\n headers: {\n 'content-type': 'application/json',\n ...(this.apiKey ? { 'x-api-key': this.apiKey } : {}),\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: ctrl.signal,\n };\n\n const res = this.fetcher\n ? await this.fetcher(path, init)\n : await this.fetchImpl(toAbsoluteUrl(this.baseUrl, path), init);\n\n if (res.status === 204) return { data: undefined as T };\n\n const json = (await res.json().catch(() => ({}))) as ThrottleEnvelope<T>;\n if (!res.ok) {\n throw new ThrottleSubscriptionsError({\n code: json.error?.code ?? 'unknown_error',\n message: json.error?.message ?? `HTTP ${res.status}`,\n statusCode: res.status,\n details: json.error?.details,\n responseHeaders: res.headers,\n });\n }\n return json;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async create(input: CreateSubscriptionInput): Promise<Subscription> {\n const customerId = await this.resolveCustomerId(input.customerId, input.externalCustomerId);\n const data = await this.request<unknown>('POST', '/api/v1/subscriptions', {\n applicationId: input.applicationId,\n customerId,\n planReference: input.planReference,\n planName: input.planName,\n interval: input.interval,\n intervalUnit: input.intervalUnit,\n intervalCount: input.intervalCount,\n totalPayments: input.totalPayments,\n amount: input.amount,\n schedule: input.schedule,\n currency: input.currency,\n currentPeriodStart: toIso(input.currentPeriodStart),\n currentPeriodEnd: toIso(input.currentPeriodEnd),\n trialEnd: toIso(input.trialEnd),\n metadata: input.metadata ?? {},\n orderId: input.orderId,\n });\n return normalizeSubscription(data);\n }\n\n async list(params: ListSubscriptionsParams = {}): Promise<PaginatedSubscriptions> {\n const query = new URLSearchParams();\n setQuery(query, 'customerId', params.customerId);\n setQuery(query, 'externalCustomerId', params.externalCustomerId);\n setQuery(query, 'status', params.status);\n setQuery(query, 'interval', params.interval);\n setQuery(query, 'installment', params.installment);\n setQuery(query, 'q', params.q);\n setQuery(query, 'cursor', params.cursor);\n setQuery(query, 'limit', params.limit);\n setQuery(query, 'sort', params.sort);\n setQuery(query, 'createdAfter', toIso(params.createdAfter));\n setQuery(query, 'createdBefore', toIso(params.createdBefore));\n\n const suffix = query.toString() ? `?${query}` : '';\n const envelope = await this.requestEnvelope<unknown[]>('GET', `/api/v1/subscriptions${suffix}`);\n return {\n data: (envelope.data ?? []).map(normalizeSubscription),\n pagination: normalizePagination(envelope.meta?.pagination),\n };\n }\n\n async get(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'GET',\n `/api/v1/subscriptions/${encodeURIComponent(id)}`,\n );\n return normalizeSubscription(data);\n }\n\n /**\n * A term-only edit: rename the plan, correct the cadence or the amount on\n * the row — no proration, no charge, no period reset. Mirrors\n * `PATCH /api/v1/subscriptions/{id}`. This is what `changePlan()` did\n * before 3.0.0; use `changePlan()` now for an actual plan change.\n */\n async update(id: string, input: UpdateSubscriptionInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'PATCH',\n `/api/v1/subscriptions/${encodeURIComponent(id)}`,\n input,\n );\n return normalizeSubscription(data);\n }\n\n /**\n * A real plan change: prorated and charged immediately when\n * `effective: 'now'`, or scheduled for period end with no charge today.\n * Mirrors `POST /api/v1/subscriptions/{id}/change-plan`. The resolved\n * subscription carries `proration` when the server computed one.\n *\n * This is NOT the old `changePlan()` (a PATCH-style term edit with no\n * proration and no charge) — use `update()` for that.\n */\n async changePlan(input: ChangePlanInput): Promise<Subscription> {\n const { id, ...body } = input;\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/change-plan`,\n {\n planReference: body.planReference,\n planName: body.planName,\n interval: body.interval,\n intervalUnit: body.intervalUnit,\n intervalCount: body.intervalCount,\n amount: body.amount,\n effective: body.effective,\n },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Change the seat quantity: prorated and charged immediately when\n * `effective: 'now'`, or scheduled for period end with no charge today.\n * Mirrors `POST /api/v1/subscriptions/{id}/change-quantity`.\n */\n async changeQuantity(id: string, input: ChangeQuantityInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/change-quantity`,\n { quantity: input.quantity, effective: input.effective },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Charge a past-due subscription's card again now, instead of waiting for\n * the next scheduled dunning retry. Mirrors\n * `POST /api/v1/subscriptions/{id}/retry-charge`.\n */\n async retryCharge(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/retry-charge`,\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Forgive the current past-due period — the merchant is not collecting it.\n * Mirrors `POST /api/v1/subscriptions/{id}/waive-period`.\n */\n async waivePeriod(id: string, input: WaivePeriodInput): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/waive-period`,\n { reason: input.reason },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Record (upsert) a usage item for the subscription's current billing\n * period. Mirrors `POST /api/v1/subscriptions/{id}/usage`.\n */\n async recordUsage(id: string, input: RecordUsageInput): Promise<SubscriptionUsageItem> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/usage`,\n { kind: input.kind, unitCount: input.unitCount, unitPriceCents: input.unitPriceCents },\n );\n return normalizeUsageItem(data);\n }\n\n /**\n * List usage items recorded for the subscription's current billing period.\n * Mirrors `GET /api/v1/subscriptions/{id}/usage`.\n */\n async listUsage(id: string): Promise<SubscriptionUsageItem[]> {\n const envelope = await this.requestEnvelope<{ items?: unknown[] }>(\n 'GET',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/usage`,\n );\n // This route answers `{ items: [...] }`, not the usual `{ data, meta }`\n // envelope, so `request()`'s data-unwrap would just hand back the whole\n // body — read `items` off the raw envelope instead.\n const items = (envelope as unknown as { items?: unknown[] }).items ?? [];\n return items.map(normalizeUsageItem);\n }\n\n async pause(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/pause`,\n );\n return normalizeSubscription(data);\n }\n\n async resume(id: string): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/resume`,\n );\n return normalizeSubscription(data);\n }\n\n async cancel(\n id: string,\n input: { atPeriodEnd?: boolean; refund?: 'none' | 'prorated' | 'last_cycle' } = {},\n ): Promise<Subscription> {\n const data = await this.request<unknown>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/cancel`,\n { atPeriodEnd: input.atPeriodEnd ?? false, refund: input.refund ?? 'none' },\n );\n return normalizeSubscription(data);\n }\n\n /**\n * Refund one billing cycle.\n *\n * `intent` has no default: refunding money and ending the subscription are\n * separate decisions, and guessing either way is wrong often enough that the\n * API requires the caller to say which one they mean.\n */\n async refundInvoice(\n id: string,\n invoiceId: string,\n input: { intent: 'money_only' | 'refund_and_cancel'; amount?: number; reason?: string },\n ): Promise<SubscriptionInvoiceRefund> {\n return this.request<SubscriptionInvoiceRefund>(\n 'POST',\n `/api/v1/subscriptions/${encodeURIComponent(id)}/invoices/${encodeURIComponent(invoiceId)}/refund`,\n input,\n );\n }\n\n async createCheckoutSession(input: CreateCheckoutSessionInput): Promise<CheckoutSession> {\n const data = await this.request<unknown>('POST', '/api/v1/checkout/sessions', input);\n return normalizeCheckoutSession(data);\n }\n\n async checkTrialEligibility(input: TrialEligibilityInput): Promise<TrialEligibilityResult> {\n const data = await this.request<unknown>('POST', '/api/v1/subscriptions/eligibility-check', {\n paymentMethodId: input.paymentMethodId,\n });\n return normalizeTrialEligibility(data);\n }\n\n async getCustomerByExternalId(externalId: string): Promise<Customer | null> {\n try {\n const data = await this.request<unknown>(\n 'GET',\n `/api/v1/customers/by-external/${encodeURIComponent(externalId)}`,\n );\n return normalizeCustomer(data);\n } catch (err) {\n if (err instanceof ThrottleSubscriptionsError && err.statusCode === 404) return null;\n throw err;\n }\n }\n\n async listCustomerPaymentMethods(customerId: string): Promise<CustomerPaymentMethod[]> {\n const data = await this.request<unknown[]>(\n 'GET',\n `/api/v1/customers/${encodeURIComponent(customerId)}/payment-methods`,\n );\n return (data ?? []).map(normalizeCustomerPaymentMethod);\n }\n\n private async resolveCustomerId(\n customerId?: string,\n externalCustomerId?: string,\n ): Promise<string> {\n if (customerId) return customerId;\n if (!externalCustomerId) {\n throw new ThrottleSubscriptionsError({\n code: 'invalid_request',\n message: 'customerId or externalCustomerId is required',\n statusCode: 400,\n });\n }\n\n const customer = await this.getCustomerByExternalId(externalCustomerId);\n if (!customer) {\n throw new ThrottleSubscriptionsError({\n code: 'customer_not_found',\n message: 'Customer not found',\n statusCode: 404,\n });\n }\n return customer.id;\n }\n}\n\nexport function createSubscriptionsClient(opts: SubscriptionsClientOptions): SubscriptionsClient {\n return new SubscriptionsClient(opts);\n}\n\nfunction toAbsoluteUrl(baseUrl: string, path: string): string {\n if (/^https?:\\/\\//i.test(path)) return path;\n return `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;\n}\n\nfunction setQuery(query: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null || value === '') return;\n query.set(key, String(value));\n}\n"],"mappings":"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","names":["import_errors"]}
package/dist/server.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Subscription } from './errors-BCOtLFs2.cjs';
2
- export { e as CheckoutSession, f as CreateCheckoutSessionInput, h as Customer, i as CustomerPaymentMethod, L as ListSubscriptionsParams, P as PaginatedSubscriptions, y as SubscriptionsClient, z as SubscriptionsClientContract, A as SubscriptionsClientOptions, B as ThrottleSubscriptionsError, F as TrialEligibilityInput, H as TrialEligibilityResult, I as createSubscriptionsClient } from './errors-BCOtLFs2.cjs';
1
+ import { S as Subscription } from './errors-BaFuPdr3.cjs';
2
+ export { e as CheckoutSession, f as CreateCheckoutSessionInput, h as Customer, i as CustomerPaymentMethod, L as ListSubscriptionsParams, P as PaginatedSubscriptions, z as SubscriptionsClient, A as SubscriptionsClientContract, B as SubscriptionsClientOptions, E as ThrottleSubscriptionsError, G as TrialEligibilityInput, I as TrialEligibilityResult, J as createSubscriptionsClient } from './errors-BaFuPdr3.cjs';
3
3
  import 'react';
4
4
  import '@usethrottle/errors';
5
5
 
@@ -8,6 +8,7 @@ interface SubscriptionProxyOptions {
8
8
  baseUrl?: string;
9
9
  fetch?: typeof globalThis.fetch;
10
10
  getExternalCustomerId: (request: Request) => string | null | Promise<string | null>;
11
+ /** When supplied it replaces the default installment rule; ownership is always checked. */
11
12
  authorizeMutation?: (args: {
12
13
  request: Request;
13
14
  subscription: Subscription;
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Subscription } from './errors-BCOtLFs2.js';
2
- export { e as CheckoutSession, f as CreateCheckoutSessionInput, h as Customer, i as CustomerPaymentMethod, L as ListSubscriptionsParams, P as PaginatedSubscriptions, y as SubscriptionsClient, z as SubscriptionsClientContract, A as SubscriptionsClientOptions, B as ThrottleSubscriptionsError, F as TrialEligibilityInput, H as TrialEligibilityResult, I as createSubscriptionsClient } from './errors-BCOtLFs2.js';
1
+ import { S as Subscription } from './errors-BaFuPdr3.js';
2
+ export { e as CheckoutSession, f as CreateCheckoutSessionInput, h as Customer, i as CustomerPaymentMethod, L as ListSubscriptionsParams, P as PaginatedSubscriptions, z as SubscriptionsClient, A as SubscriptionsClientContract, B as SubscriptionsClientOptions, E as ThrottleSubscriptionsError, G as TrialEligibilityInput, I as TrialEligibilityResult, J as createSubscriptionsClient } from './errors-BaFuPdr3.js';
3
3
  import 'react';
4
4
  import '@usethrottle/errors';
5
5
 
@@ -8,6 +8,7 @@ interface SubscriptionProxyOptions {
8
8
  baseUrl?: string;
9
9
  fetch?: typeof globalThis.fetch;
10
10
  getExternalCustomerId: (request: Request) => string | null | Promise<string | null>;
11
+ /** When supplied it replaces the default installment rule; ownership is always checked. */
11
12
  authorizeMutation?: (args: {
12
13
  request: Request;
13
14
  subscription: Subscription;
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  SubscriptionsClient,
3
3
  ThrottleSubscriptionsError,
4
4
  createSubscriptionsClient
5
- } from "./chunk-YQLHMNZH.js";
5
+ } from "./chunk-XXR5PITK.js";
6
6
 
7
7
  // src/server.ts
8
8
  var ALLOWED_PATHS = [
@@ -42,14 +42,18 @@ async function handleSubscriptionProxyRequest(request, pathParts, options) {
42
42
  throw err;
43
43
  });
44
44
  if (!subscription) return jsonError(404, "not_found", "Subscription not found");
45
- const defaultAuthorized = await defaultMutationAuthorization(
46
- client,
47
- subscription,
48
- externalCustomerId
49
- );
50
- const customAuthorized = options.authorizeMutation ? await options.authorizeMutation({ request, subscription, externalCustomerId }) : true;
51
- if (!defaultAuthorized || !customAuthorized) {
52
- return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
45
+ const owned = await defaultMutationAuthorization(client, subscription, externalCustomerId);
46
+ if (!owned) return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
47
+ if (options.authorizeMutation) {
48
+ if (!await options.authorizeMutation({ request, subscription, externalCustomerId })) {
49
+ return jsonError(403, "forbidden", "Subscription does not belong to this buyer");
50
+ }
51
+ } else if (subscription.totalPayments != null) {
52
+ return jsonError(
53
+ 403,
54
+ "installment_plan_merchant_only",
55
+ "Installment plans can only be paused, resumed or cancelled by the merchant"
56
+ );
53
57
  }
54
58
  }
55
59
  }