@akira-io/billing-js 0.1.0 → 0.1.6

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/vue.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pricing.ts","../src/downloads.ts","../src/helpers.ts","../src/vue.ts"],"names":[],"mappings":"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","file":"vue.js","sourcesContent":["import type {\n BillingInterval,\n PricingFeature,\n PricingPayload,\n PricingTier,\n TierMeta,\n} from './types';\n\nexport type FetchPricingConfig = {\n baseUrl: string;\n productKey: string;\n tierMeta?: Record<string, TierMeta>;\n yearlyMonthsFree?: number;\n /** Override the global fetch (e.g. Node 18+ or custom retry). */\n fetcher?: typeof fetch;\n};\n\ntype ApiFeature = {\n key: string;\n name: string;\n description: string | null;\n};\n\ntype ApiPlan = {\n key: string;\n name: string;\n description: string | null;\n amount: number | null;\n currency: string | null;\n billing_interval: BillingInterval | null;\n trial_period_days: number;\n is_coming_soon?: boolean;\n features: ApiFeature[];\n};\n\ntype ApiPayload = {\n product: string;\n name: string;\n description: string | null;\n beta_active: boolean;\n plans: ApiPlan[];\n};\n\nconst INTERVAL_SUFFIXES = ['_monthly', '_yearly', '_month', '_year', '_one_time'];\n\nfunction tierKeyFromPlanKey(planKey: string): string {\n for (const suf of INTERVAL_SUFFIXES) {\n if (planKey.endsWith(suf)) {\n return planKey.slice(0, -suf.length);\n }\n }\n return planKey;\n}\n\nfunction titleCase(s: string): string {\n return s.replace(/[_-]+/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction emptyPayload(productKey: string): PricingPayload {\n return { product: productKey, betaActive: false, tiers: [] };\n}\n\nfunction shapeFromApi(payload: ApiPayload, config: FetchPricingConfig): PricingPayload {\n const meta = config.tierMeta ?? {};\n const monthsFree = config.yearlyMonthsFree ?? 2;\n const tiersMap = new Map<string, PricingTier>();\n\n for (const plan of payload.plans) {\n const tierKey = tierKeyFromPlanKey(plan.key);\n const m = meta[tierKey];\n\n if (!tiersMap.has(tierKey)) {\n tiersMap.set(tierKey, {\n key: tierKey,\n name: m?.label ?? titleCase(tierKey),\n tagline: m?.tagline ?? plan.description ?? '',\n highlighted: m?.highlighted ?? false,\n monthly: null,\n yearly: null,\n oneTime: null,\n isComingSoon: false,\n features: plan.features.map(\n (f): PricingFeature => ({ key: f.key, name: f.name, description: f.description }),\n ),\n });\n }\n\n const tier = tiersMap.get(tierKey)!;\n\n if (plan.is_coming_soon === true) {\n tier.isComingSoon = true;\n }\n\n if (tier.features.length === 0 && plan.features.length > 0) {\n tier.features = plan.features.map((f) => ({ key: f.key, name: f.name, description: f.description }));\n }\n\n if (plan.billing_interval === 'month' && plan.amount !== null && plan.currency !== null) {\n tier.monthly = { amount: plan.amount, currency: plan.currency, planKey: plan.key };\n continue;\n }\n\n if (plan.billing_interval === 'year' && plan.amount !== null && plan.currency !== null) {\n tier.yearly = { amount: plan.amount, currency: plan.currency, monthsFree, planKey: plan.key };\n continue;\n }\n\n if (plan.billing_interval === null) {\n const amount = plan.amount ?? 0;\n const currency = plan.currency ?? 'eur';\n if (amount === 0) {\n tier.monthly = { amount: 0, currency, planKey: plan.key };\n } else {\n tier.oneTime = { amount, currency, planKey: plan.key };\n }\n }\n }\n\n const tiers = Array.from(tiersMap.values()).sort((a, b) => {\n const oa = meta[a.key]?.order ?? 999;\n const ob = meta[b.key]?.order ?? 999;\n return oa - ob;\n });\n\n return {\n product: payload.product,\n betaActive: payload.beta_active,\n tiers,\n };\n}\n\nexport async function fetchPricing(config: FetchPricingConfig): Promise<PricingPayload> {\n const base = config.baseUrl?.replace(/\\/$/, '');\n if (!base) return emptyPayload(config.productKey);\n\n const f = config.fetcher ?? globalThis.fetch;\n if (!f) {\n throw new Error('No fetch implementation available. Pass a fetcher in config or use Node 18+.');\n }\n\n try {\n const res = await f(`${base}/api/v1/products/${config.productKey}/plans`, {\n headers: { Accept: 'application/json' },\n });\n if (!res.ok) return emptyPayload(config.productKey);\n const data = (await res.json()) as ApiPayload;\n return shapeFromApi(data, config);\n } catch {\n return emptyPayload(config.productKey);\n }\n}\n\nexport function formatPrice(amountInCents: number, currency: string): string {\n const symbol = currency.toLowerCase() === 'eur' ? '€' : currency.toUpperCase() + ' ';\n const major = (amountInCents / 100).toFixed(amountInCents % 100 === 0 ? 0 : 2);\n return `${symbol}${major}`;\n}\n\nexport type { PricingFeature, PricingPayload, PricingTier, TierMeta };\n","import type { AssetPlatform, IssuedDownload, ReleaseChannel } from './types';\n\nexport type DownloadConfig = {\n baseUrl: string;\n product: string;\n channel: ReleaseChannel;\n platform: AssetPlatform;\n /** Optional UTM + landing tracking. */\n query?: Record<string, string | undefined>;\n /** Delay before firing the completion beacon, ms. Default 1500. */\n beaconDelayMs?: number;\n fetcher?: typeof fetch;\n};\n\nexport function downloadUrl(config: Pick<DownloadConfig, 'baseUrl' | 'product' | 'channel' | 'platform' | 'query'>): string {\n const base = config.baseUrl.replace(/\\/$/, '');\n const path = `/api/v1/downloads/${config.product}/${config.channel}/${config.platform}`;\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(config.query ?? {})) {\n if (v !== undefined && v !== '') params.set(k, v);\n }\n const qs = params.toString();\n return qs ? `${base}${path}?${qs}` : `${base}${path}`;\n}\n\n/**\n * Issues a download via the billing API and returns the signed asset URL +\n * beacon URL without redirecting. Useful when you want full control of the\n * UX (e.g. fetch then trigger your own `<a download>` flow).\n */\nexport async function issueDownload(config: DownloadConfig): Promise<IssuedDownload> {\n const f = config.fetcher ?? globalThis.fetch;\n if (!f) {\n throw new Error('No fetch implementation available. Pass a fetcher in config or use Node 18+.');\n }\n\n const url = downloadUrl(config);\n const res = await f(url, { headers: { Accept: 'application/json' } });\n\n if (!res.ok) {\n throw new Error(`download issue failed: HTTP ${res.status}`);\n }\n\n return (await res.json()) as IssuedDownload;\n}\n\n/**\n * Fires the completion beacon for an issued download. Uses\n * `navigator.sendBeacon` when available (survives page navigation), falls\n * back to `fetch` with `keepalive: true`. Safe to call at unload time.\n */\nexport function sendCompletionBeacon(beaconUrl: string): void {\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(beaconUrl);\n return;\n }\n\n if (typeof fetch !== 'undefined') {\n void fetch(beaconUrl, { method: 'POST', keepalive: true }).catch(() => {});\n }\n}\n\n/**\n * One-shot helper for landing-page download CTAs: fetches a signed URL,\n * navigates the current tab to the asset, then schedules the completion\n * beacon. The function resolves once the navigation has been triggered.\n */\nexport async function triggerDownload(config: DownloadConfig): Promise<IssuedDownload> {\n const issued = await issueDownload(config);\n const delay = config.beaconDelayMs ?? 1500;\n\n if (typeof window !== 'undefined') {\n window.location.href = issued.signedUrl;\n setTimeout(() => sendCompletionBeacon(issued.beaconUrl), delay);\n }\n\n return issued;\n}\n","import { checkoutUrl } from './checkout';\nimport { formatPrice } from './pricing';\nimport type { PricingTier, TierMeta } from './types';\n\nexport type IntervalKey = 'monthly' | 'yearly' | 'oneTime';\n\nexport type CtaProps = {\n label: string;\n href: string | null;\n disabled: boolean;\n monthlyHref: string | null;\n yearlyHref: string | null;\n oneTimeHref: string | null;\n};\n\nexport type CtaOptions = {\n billingBaseUrl: string;\n productKey: string;\n tierMeta?: TierMeta;\n interval?: IntervalKey;\n freeLabel?: string;\n subscribeLabel?: string;\n buyLabel?: string;\n comingSoonLabel?: string;\n};\n\nexport function isFreeTier(tier: PricingTier): boolean {\n return tier.monthly?.amount === 0 && tier.yearly === null && tier.oneTime === null;\n}\n\nexport function isOneTimeTier(tier: PricingTier): boolean {\n return tier.oneTime !== null && tier.monthly === null;\n}\n\nexport function hasYearly(tier: PricingTier): boolean {\n return tier.yearly !== null && tier.monthly !== null;\n}\n\n/**\n * Resolves all CTA fields for a tier — label, primary href, per-interval\n * hrefs (so a UI toggle can hot-swap without re-deriving), and disabled\n * state for coming-soon plans. Replaces hand-rolled if/else trees in\n * landing pages.\n */\nexport function getCtaProps(tier: PricingTier, opts: CtaOptions): CtaProps {\n const meta = opts.tierMeta;\n const monthlyHref = tier.monthly\n ? checkoutUrl(opts.billingBaseUrl, opts.productKey, tier.monthly.planKey)\n : null;\n const yearlyHref = tier.yearly\n ? checkoutUrl(opts.billingBaseUrl, opts.productKey, tier.yearly.planKey)\n : null;\n const oneTimeHref = tier.oneTime\n ? checkoutUrl(opts.billingBaseUrl, opts.productKey, tier.oneTime.planKey)\n : null;\n\n if (tier.isComingSoon) {\n return {\n label: opts.comingSoonLabel ?? 'Coming soon',\n href: null,\n disabled: true,\n monthlyHref,\n yearlyHref,\n oneTimeHref,\n };\n }\n\n if (meta?.ctaHref) {\n return {\n label: meta.ctaLabel ?? 'Get started',\n href: meta.ctaHref,\n disabled: false,\n monthlyHref,\n yearlyHref,\n oneTimeHref,\n };\n }\n\n let primary: string | null;\n let defaultLabel: string;\n\n if (isFreeTier(tier)) {\n primary = null;\n defaultLabel = opts.freeLabel ?? 'Get started';\n } else if (isOneTimeTier(tier)) {\n primary = oneTimeHref;\n defaultLabel = opts.buyLabel ?? 'Buy';\n } else {\n primary = opts.interval === 'yearly' ? yearlyHref ?? monthlyHref : monthlyHref;\n defaultLabel = opts.subscribeLabel ?? 'Subscribe';\n }\n\n return {\n label: meta?.ctaLabel ?? defaultLabel,\n href: primary,\n disabled: false,\n monthlyHref,\n yearlyHref,\n oneTimeHref,\n };\n}\n\nexport type FormattedPrice = {\n amount: string;\n suffix: string;\n raw: { amount: number; currency: string } | null;\n note?: string;\n};\n\n/**\n * Returns the price + suffix to render for a tier given the active\n * interval. Falls back gracefully: a tier with only monthly always\n * shows monthly; a free tier shows €0; a one-time tier shows the amount\n * with a 'one-time' suffix.\n */\nexport function getActivePrice(tier: PricingTier, interval: IntervalKey): FormattedPrice {\n if (interval === 'yearly' && tier.yearly) {\n return {\n amount: formatPrice(tier.yearly.amount, tier.yearly.currency),\n suffix: '/year',\n raw: { amount: tier.yearly.amount, currency: tier.yearly.currency },\n note: tier.yearly.monthsFree > 0 ? `${tier.yearly.monthsFree} months free` : undefined,\n };\n }\n\n if (interval === 'oneTime' && tier.oneTime) {\n return {\n amount: formatPrice(tier.oneTime.amount, tier.oneTime.currency),\n suffix: ' one-time',\n raw: { amount: tier.oneTime.amount, currency: tier.oneTime.currency },\n };\n }\n\n if (tier.monthly) {\n return {\n amount: formatPrice(tier.monthly.amount, tier.monthly.currency),\n suffix: '/month',\n raw: { amount: tier.monthly.amount, currency: tier.monthly.currency },\n };\n }\n\n if (tier.oneTime) {\n return {\n amount: formatPrice(tier.oneTime.amount, tier.oneTime.currency),\n suffix: ' one-time',\n raw: { amount: tier.oneTime.amount, currency: tier.oneTime.currency },\n };\n }\n\n return { amount: '—', suffix: '', raw: null };\n}\n\n/**\n * Picks the natural default interval for a list of tiers: 'yearly' if\n * any tier has a yearly option, else 'monthly'. Useful as the initial\n * state for a billing-interval toggle.\n */\nexport function defaultInterval(tiers: PricingTier[]): IntervalKey {\n if (tiers.some(hasYearly)) return 'monthly';\n if (tiers.every((t) => t.monthly === null && t.oneTime !== null)) return 'oneTime';\n return 'monthly';\n}\n","import { computed, onUnmounted, ref, watch, watchEffect, type ComputedRef, type Ref } from 'vue';\nimport { fetchPricing, type FetchPricingConfig } from './pricing';\nimport { triggerDownload, type DownloadConfig } from './downloads';\nimport { defaultInterval, type IntervalKey } from './helpers';\nimport type { IssuedDownload, PricingPayload, PricingTier } from './types';\n\nexport type UsePricingComposable = {\n data: Ref<PricingPayload | null>;\n error: Ref<Error | null>;\n isLoading: Ref<boolean>;\n refresh: () => void;\n};\n\n/**\n * Reactive pricing fetcher. Re-runs when the source config changes; expose\n * loading/error refs and a manual refresh trigger.\n */\nexport function usePricing(getConfig: () => FetchPricingConfig): UsePricingComposable {\n const data = ref<PricingPayload | null>(null);\n const error = ref<Error | null>(null);\n const isLoading = ref(true);\n let alive = true;\n let cycle = 0;\n\n function run() {\n const config = getConfig();\n const current = ++cycle;\n isLoading.value = true;\n error.value = null;\n\n fetchPricing(config)\n .then((p) => {\n if (alive && current === cycle) data.value = p;\n })\n .catch((e: unknown) => {\n if (alive && current === cycle) error.value = e instanceof Error ? e : new Error(String(e));\n })\n .finally(() => {\n if (alive && current === cycle) isLoading.value = false;\n });\n }\n\n watchEffect(run);\n\n onUnmounted(() => {\n alive = false;\n });\n\n return { data, error, isLoading, refresh: run };\n}\n\nexport type UseBillingIntervalComposable = {\n interval: Ref<IntervalKey>;\n toggle: () => void;\n set: (next: IntervalKey) => void;\n};\n\nexport function useBillingInterval(\n tiersRef: Ref<PricingTier[] | null | undefined> | ComputedRef<PricingTier[] | null | undefined>,\n): UseBillingIntervalComposable {\n const interval = ref<IntervalKey>(defaultInterval(tiersRef.value ?? []));\n\n watch(\n () => tiersRef.value,\n (next) => {\n interval.value = defaultInterval(next ?? []);\n },\n );\n\n function toggle() {\n interval.value = interval.value === 'monthly' ? 'yearly' : 'monthly';\n }\n\n function set(next: IntervalKey) {\n interval.value = next;\n }\n\n return { interval, toggle, set };\n}\n\nexport type UseDownloadComposable = {\n trigger: () => Promise<void>;\n isPending: Ref<boolean>;\n error: Ref<Error | null>;\n lastIssued: Ref<IssuedDownload | null>;\n};\n\nexport function useDownload(getConfig: () => DownloadConfig): UseDownloadComposable {\n const isPending = ref(false);\n const error = ref<Error | null>(null);\n const lastIssued = ref<IssuedDownload | null>(null);\n\n async function trigger() {\n isPending.value = true;\n error.value = null;\n try {\n const issued = await triggerDownload(getConfig());\n lastIssued.value = issued;\n } catch (e: unknown) {\n error.value = e instanceof Error ? e : new Error(String(e));\n } finally {\n isPending.value = false;\n }\n }\n\n return { trigger, isPending, error, lastIssued };\n}\n"]}
1
+ {"version":3,"sources":["../src/pricing.ts","../src/downloads.ts","../src/helpers.ts","../src/vue.ts"],"names":[],"mappings":"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","file":"vue.js","sourcesContent":["import type {\n BillingInterval,\n PricingFeature,\n PricingPayload,\n PricingTier,\n TierMeta,\n} from './types';\n\nexport type FetchPricingConfig = {\n baseUrl: string;\n productKey: string;\n tierMeta?: Record<string, TierMeta>;\n yearlyMonthsFree?: number;\n /** Override the global fetch (e.g. Node 18+ or custom retry). */\n fetcher?: typeof fetch;\n};\n\ntype ApiFeature = {\n key: string;\n name: string;\n description: string | null;\n};\n\ntype ApiPlan = {\n key: string;\n name: string;\n description: string | null;\n amount: number | null;\n currency: string | null;\n billing_interval: BillingInterval | null;\n trial_period_days: number;\n is_coming_soon?: boolean;\n features: ApiFeature[];\n};\n\ntype ApiPayload = {\n product: string;\n name: string;\n description: string | null;\n beta_active: boolean;\n plans: ApiPlan[];\n};\n\nconst INTERVAL_SUFFIXES = ['_monthly', '_yearly', '_month', '_year', '_one_time'];\n\nfunction tierKeyFromPlanKey(planKey: string): string {\n for (const suf of INTERVAL_SUFFIXES) {\n if (planKey.endsWith(suf)) {\n return planKey.slice(0, -suf.length);\n }\n }\n return planKey;\n}\n\nfunction titleCase(s: string): string {\n return s.replace(/[_-]+/g, ' ').replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nfunction emptyPayload(productKey: string): PricingPayload {\n return { product: productKey, betaActive: false, tiers: [] };\n}\n\nfunction shapeFromApi(payload: ApiPayload, config: FetchPricingConfig): PricingPayload {\n const meta = config.tierMeta ?? {};\n const monthsFree = config.yearlyMonthsFree ?? 2;\n const tiersMap = new Map<string, PricingTier>();\n\n for (const plan of payload.plans) {\n const tierKey = tierKeyFromPlanKey(plan.key);\n const m = meta[tierKey];\n\n if (!tiersMap.has(tierKey)) {\n tiersMap.set(tierKey, {\n key: tierKey,\n name: m?.label ?? titleCase(tierKey),\n tagline: m?.tagline ?? plan.description ?? '',\n highlighted: m?.highlighted ?? false,\n monthly: null,\n yearly: null,\n oneTime: null,\n isComingSoon: false,\n features: plan.features.map(\n (f): PricingFeature => ({ key: f.key, name: f.name, description: f.description }),\n ),\n });\n }\n\n const tier = tiersMap.get(tierKey)!;\n\n if (plan.is_coming_soon === true) {\n tier.isComingSoon = true;\n }\n\n if (tier.features.length === 0 && plan.features.length > 0) {\n tier.features = plan.features.map((f) => ({ key: f.key, name: f.name, description: f.description }));\n }\n\n if (plan.billing_interval === 'month' && plan.amount !== null && plan.currency !== null) {\n tier.monthly = { amount: plan.amount, currency: plan.currency, planKey: plan.key };\n continue;\n }\n\n if (plan.billing_interval === 'year' && plan.amount !== null && plan.currency !== null) {\n tier.yearly = { amount: plan.amount, currency: plan.currency, monthsFree, planKey: plan.key };\n continue;\n }\n\n if (plan.billing_interval === null) {\n const amount = plan.amount ?? 0;\n const currency = plan.currency ?? 'eur';\n if (amount === 0) {\n tier.monthly = { amount: 0, currency, planKey: plan.key };\n } else {\n tier.oneTime = { amount, currency, planKey: plan.key };\n }\n }\n }\n\n const tiers = Array.from(tiersMap.values()).sort((a, b) => {\n const oa = meta[a.key]?.order ?? 999;\n const ob = meta[b.key]?.order ?? 999;\n return oa - ob;\n });\n\n return {\n product: payload.product,\n betaActive: payload.beta_active,\n tiers,\n };\n}\n\nexport async function fetchPricing(config: FetchPricingConfig): Promise<PricingPayload> {\n const base = config.baseUrl?.replace(/\\/$/, '');\n if (!base) return emptyPayload(config.productKey);\n\n const f = config.fetcher ?? globalThis.fetch;\n if (!f) {\n throw new Error('No fetch implementation available. Pass a fetcher in config or use Node 18+.');\n }\n\n try {\n const res = await f(`${base}/api/v1/products/${config.productKey}/plans`, {\n headers: { Accept: 'application/json' },\n });\n if (!res.ok) return emptyPayload(config.productKey);\n const data = (await res.json()) as ApiPayload;\n return shapeFromApi(data, config);\n } catch {\n return emptyPayload(config.productKey);\n }\n}\n\nexport function formatPrice(amountInCents: number, currency: string): string {\n const symbol = currency.toLowerCase() === 'eur' ? '€' : currency.toUpperCase() + ' ';\n const major = (amountInCents / 100).toFixed(amountInCents % 100 === 0 ? 0 : 2);\n return `${symbol}${major}`;\n}\n\nexport type { PricingFeature, PricingPayload, PricingTier, TierMeta };\n","import type { AssetPlatform, IssuedDownload, ReleaseChannel } from './types';\n\nexport type DownloadConfig = {\n baseUrl: string;\n product: string;\n channel: ReleaseChannel;\n platform: AssetPlatform;\n /** Optional UTM + landing tracking. */\n query?: Record<string, string | undefined>;\n /** Delay before firing the completion beacon, ms. Default 1500. */\n beaconDelayMs?: number;\n fetcher?: typeof fetch;\n};\n\nexport function downloadUrl(config: Pick<DownloadConfig, 'baseUrl' | 'product' | 'channel' | 'platform' | 'query'>): string {\n const base = config.baseUrl.replace(/\\/$/, '');\n const path = `/api/v1/downloads/${config.product}/${config.channel}/${config.platform}`;\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(config.query ?? {})) {\n if (v !== undefined && v !== '') params.set(k, v);\n }\n const qs = params.toString();\n return qs ? `${base}${path}?${qs}` : `${base}${path}`;\n}\n\n/**\n * Issues a download via the billing API and returns the signed asset URL +\n * beacon URL without redirecting. Useful when you want full control of the\n * UX (e.g. fetch then trigger your own `<a download>` flow).\n */\nexport async function issueDownload(config: DownloadConfig): Promise<IssuedDownload> {\n const f = config.fetcher ?? globalThis.fetch;\n if (!f) {\n throw new Error('No fetch implementation available. Pass a fetcher in config or use Node 18+.');\n }\n\n const url = downloadUrl(config);\n const res = await f(url, { headers: { Accept: 'application/json' } });\n\n if (!res.ok) {\n throw new Error(`download issue failed: HTTP ${res.status}`);\n }\n\n return (await res.json()) as IssuedDownload;\n}\n\n/**\n * Fires the completion beacon for an issued download. Uses\n * `navigator.sendBeacon` when available (survives page navigation), falls\n * back to `fetch` with `keepalive: true`. Safe to call at unload time.\n */\nexport function sendCompletionBeacon(beaconUrl: string): void {\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(beaconUrl);\n return;\n }\n\n if (typeof fetch !== 'undefined') {\n void fetch(beaconUrl, { method: 'POST', keepalive: true }).catch(() => {});\n }\n}\n\n/**\n * One-shot helper for landing-page download CTAs: fetches a signed URL,\n * navigates the current tab to the asset, then schedules the completion\n * beacon. The function resolves once the navigation has been triggered.\n */\nexport async function triggerDownload(config: DownloadConfig): Promise<IssuedDownload> {\n const issued = await issueDownload(config);\n const delay = config.beaconDelayMs ?? 1500;\n\n if (typeof window !== 'undefined') {\n window.location.href = issued.signedUrl;\n setTimeout(() => sendCompletionBeacon(issued.beaconUrl), delay);\n }\n\n return issued;\n}\n","import { checkoutUrl } from './checkout';\nimport { formatPrice } from './pricing';\nimport type { PricingTier, TierMeta } from './types';\n\nexport type IntervalKey = 'monthly' | 'yearly' | 'oneTime';\n\nexport type CtaProps = {\n label: string;\n href: string | null;\n disabled: boolean;\n monthlyHref: string | null;\n yearlyHref: string | null;\n oneTimeHref: string | null;\n};\n\nexport type CtaOptions = {\n billingBaseUrl: string;\n productKey: string;\n tierMeta?: TierMeta;\n interval?: IntervalKey;\n freeLabel?: string;\n subscribeLabel?: string;\n buyLabel?: string;\n comingSoonLabel?: string;\n};\n\nexport function isFreeTier(tier: PricingTier): boolean {\n return tier.monthly?.amount === 0 && tier.yearly === null && tier.oneTime === null;\n}\n\nexport function isOneTimeTier(tier: PricingTier): boolean {\n return tier.oneTime !== null && tier.monthly === null;\n}\n\nexport function hasYearly(tier: PricingTier): boolean {\n return tier.yearly !== null && tier.monthly !== null;\n}\n\n/**\n * Resolves all CTA fields for a tier — label, primary href, per-interval\n * hrefs (so a UI toggle can hot-swap without re-deriving), and disabled\n * state for coming-soon plans. Replaces hand-rolled if/else trees in\n * landing pages.\n */\nexport function getCtaProps(tier: PricingTier, opts: CtaOptions): CtaProps {\n const meta = opts.tierMeta;\n const monthlyHref = tier.monthly\n ? checkoutUrl(opts.billingBaseUrl, opts.productKey, tier.monthly.planKey)\n : null;\n const yearlyHref = tier.yearly\n ? checkoutUrl(opts.billingBaseUrl, opts.productKey, tier.yearly.planKey)\n : null;\n const oneTimeHref = tier.oneTime\n ? checkoutUrl(opts.billingBaseUrl, opts.productKey, tier.oneTime.planKey)\n : null;\n\n if (tier.isComingSoon) {\n return {\n label: opts.comingSoonLabel ?? 'Coming soon',\n href: null,\n disabled: true,\n monthlyHref,\n yearlyHref,\n oneTimeHref,\n };\n }\n\n if (meta?.ctaHref) {\n return {\n label: meta.ctaLabel ?? 'Get started',\n href: meta.ctaHref,\n disabled: false,\n monthlyHref,\n yearlyHref,\n oneTimeHref,\n };\n }\n\n let primary: string | null;\n let defaultLabel: string;\n\n if (isFreeTier(tier)) {\n primary = null;\n defaultLabel = opts.freeLabel ?? 'Get started';\n } else if (isOneTimeTier(tier)) {\n primary = oneTimeHref;\n defaultLabel = opts.buyLabel ?? 'Buy';\n } else {\n primary = opts.interval === 'yearly' ? yearlyHref ?? monthlyHref : monthlyHref;\n defaultLabel = opts.subscribeLabel ?? 'Subscribe';\n }\n\n return {\n label: meta?.ctaLabel ?? defaultLabel,\n href: primary,\n disabled: false,\n monthlyHref,\n yearlyHref,\n oneTimeHref,\n };\n}\n\nexport type FormattedPrice = {\n amount: string;\n suffix: string;\n raw: { amount: number; currency: string } | null;\n note?: string;\n};\n\n/**\n * Returns the price + suffix to render for a tier given the active\n * interval. Falls back gracefully: a tier with only monthly always\n * shows monthly; a free tier shows €0; a one-time tier shows the amount\n * with a 'one-time' suffix.\n */\nexport function getActivePrice(tier: PricingTier, interval: IntervalKey): FormattedPrice {\n if (interval === 'yearly' && tier.yearly) {\n return {\n amount: formatPrice(tier.yearly.amount, tier.yearly.currency),\n suffix: '/year',\n raw: { amount: tier.yearly.amount, currency: tier.yearly.currency },\n note: tier.yearly.monthsFree > 0 ? `${tier.yearly.monthsFree} months free` : undefined,\n };\n }\n\n if (interval === 'oneTime' && tier.oneTime) {\n return {\n amount: formatPrice(tier.oneTime.amount, tier.oneTime.currency),\n suffix: ' one-time',\n raw: { amount: tier.oneTime.amount, currency: tier.oneTime.currency },\n };\n }\n\n if (tier.monthly) {\n return {\n amount: formatPrice(tier.monthly.amount, tier.monthly.currency),\n suffix: '/month',\n raw: { amount: tier.monthly.amount, currency: tier.monthly.currency },\n };\n }\n\n if (tier.oneTime) {\n return {\n amount: formatPrice(tier.oneTime.amount, tier.oneTime.currency),\n suffix: ' one-time',\n raw: { amount: tier.oneTime.amount, currency: tier.oneTime.currency },\n };\n }\n\n return { amount: '—', suffix: '', raw: null };\n}\n\n/**\n * Picks the natural default interval for a list of tiers: 'yearly' if\n * any tier has a yearly option, else 'monthly'. Useful as the initial\n * state for a billing-interval toggle.\n */\nexport function defaultInterval(tiers: PricingTier[]): IntervalKey {\n if (tiers.some(hasYearly)) return 'monthly';\n if (tiers.every((t) => t.monthly === null && t.oneTime !== null)) return 'oneTime';\n return 'monthly';\n}\n","import { onUnmounted, ref, watch, watchEffect, type ComputedRef, type Ref } from 'vue';\nimport { fetchPricing, type FetchPricingConfig } from './pricing';\nimport { triggerDownload, type DownloadConfig } from './downloads';\nimport { defaultInterval, type IntervalKey } from './helpers';\nimport type { IssuedDownload, PricingPayload, PricingTier } from './types';\n\nexport type UsePricingComposable = {\n data: Ref<PricingPayload | null>;\n error: Ref<Error | null>;\n isLoading: Ref<boolean>;\n refresh: () => void;\n};\n\n/**\n * Reactive pricing fetcher. Re-runs when the source config changes; expose\n * loading/error refs and a manual refresh trigger.\n */\nexport function usePricing(getConfig: () => FetchPricingConfig): UsePricingComposable {\n const data = ref<PricingPayload | null>(null);\n const error = ref<Error | null>(null);\n const isLoading = ref(true);\n let alive = true;\n let cycle = 0;\n\n function run() {\n const config = getConfig();\n const current = ++cycle;\n isLoading.value = true;\n error.value = null;\n\n fetchPricing(config)\n .then((p) => {\n if (alive && current === cycle) data.value = p;\n })\n .catch((e: unknown) => {\n if (alive && current === cycle) error.value = e instanceof Error ? e : new Error(String(e));\n })\n .finally(() => {\n if (alive && current === cycle) isLoading.value = false;\n });\n }\n\n watchEffect(run);\n\n onUnmounted(() => {\n alive = false;\n });\n\n return { data, error, isLoading, refresh: run };\n}\n\nexport type UseBillingIntervalComposable = {\n interval: Ref<IntervalKey>;\n toggle: () => void;\n set: (next: IntervalKey) => void;\n};\n\nexport function useBillingInterval(\n tiersRef: Ref<PricingTier[] | null | undefined> | ComputedRef<PricingTier[] | null | undefined>,\n): UseBillingIntervalComposable {\n const interval = ref<IntervalKey>(defaultInterval(tiersRef.value ?? []));\n\n watch(\n () => tiersRef.value,\n (next) => {\n interval.value = defaultInterval(next ?? []);\n },\n );\n\n function toggle() {\n interval.value = interval.value === 'monthly' ? 'yearly' : 'monthly';\n }\n\n function set(next: IntervalKey) {\n interval.value = next;\n }\n\n return { interval, toggle, set };\n}\n\nexport type UseDownloadComposable = {\n trigger: () => Promise<void>;\n isPending: Ref<boolean>;\n error: Ref<Error | null>;\n lastIssued: Ref<IssuedDownload | null>;\n};\n\nexport function useDownload(getConfig: () => DownloadConfig): UseDownloadComposable {\n const isPending = ref(false);\n const error = ref<Error | null>(null);\n const lastIssued = ref<IssuedDownload | null>(null);\n\n async function trigger() {\n isPending.value = true;\n error.value = null;\n try {\n const issued = await triggerDownload(getConfig());\n lastIssued.value = issued;\n } catch (e: unknown) {\n error.value = e instanceof Error ? e : new Error(String(e));\n } finally {\n isPending.value = false;\n }\n }\n\n return { trigger, isPending, error, lastIssued };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akira-io/billing-js",
3
- "version": "0.1.0",
3
+ "version": "0.1.6",
4
4
  "description": "TypeScript client for the Akira Billing API. Pricing, downloads, checkout helpers for landing pages and web apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,6 +38,11 @@
38
38
  "import": "./dist/helpers.js",
39
39
  "require": "./dist/helpers.cjs"
40
40
  },
41
+ "./license": {
42
+ "types": "./dist/license.d.ts",
43
+ "import": "./dist/license.js",
44
+ "require": "./dist/license.cjs"
45
+ },
41
46
  "./react": {
42
47
  "types": "./dist/react.d.ts",
43
48
  "import": "./dist/react.js",
@@ -54,8 +59,12 @@
54
59
  "vue": ">=3"
55
60
  },
56
61
  "peerDependenciesMeta": {
57
- "react": { "optional": true },
58
- "vue": { "optional": true }
62
+ "react": {
63
+ "optional": true
64
+ },
65
+ "vue": {
66
+ "optional": true
67
+ }
59
68
  },
60
69
  "files": [
61
70
  "dist",
@@ -79,15 +88,18 @@
79
88
  "prepublishOnly": "pnpm build && pnpm test && pnpm typecheck"
80
89
  },
81
90
  "devDependencies": {
91
+ "@eslint/js": "^10.0.1",
82
92
  "@testing-library/react": "^16.1.0",
83
93
  "@types/node": "^22.10.2",
84
94
  "@types/react": "^19.0.2",
85
95
  "@vue/test-utils": "^2.4.6",
96
+ "eslint": "^10.4.0",
86
97
  "happy-dom": "^15.11.7",
87
98
  "react": "^19.0.0",
88
99
  "react-dom": "^19.0.0",
89
100
  "tsup": "^8.3.5",
90
101
  "typescript": "^5.7.2",
102
+ "typescript-eslint": "^8.59.3",
91
103
  "vitest": "^2.1.8",
92
104
  "vue": "^3.5.13"
93
105
  },