Skip to content

@lastshotlabs/slingshot-billing

npm install @lastshotlabs/slingshot-billing

Provider-owned contract object for slingshot-billing.

Source: packages/slingshot-billing/src/public.ts

Stable identifier for this package; used as the contract/event owner name.

Source: packages/slingshot-billing/src/plugin.ts

Capability resolving an owner’s current Entitlement. Consumers call ctx.capabilities.require(BillingEntitlementCap)(ownerId) for an on-demand read; the same value is pushed via the billing:entitlement.changed event.

Source: packages/slingshot-billing/src/public.ts

Runtime schema validating createBillingPackage() configuration.

FieldDescription
donationsOne-time donation configuration.
mountPathURL path prefix for billing routes. Must start with ’/’. Default: /billing.
plansSubscription plans mapped to provider prices. Empty ⇒ no paid plans.
providerPayment provider credentials. Omit to run billing dormant (unconfigured).
urlsCheckout / Portal redirect URLs. Required once a provider is configured.
webhookMaxBodyBytesMaximum body size (bytes) accepted on the Stripe webhook route. Defaults to 1 MiB.

Source: packages/slingshot-billing/src/types/config.ts

Create the billing package: provider-abstracted subscriptions, trials, and one-time donations, exposing an app-agnostic entitlement capability + events.

Dormant by default: with no provider configured, the checkout / donate / portal routes return 503 billing_unavailable and the entitlement capability (and GET /billing/entitlement) resolves to FREE_ENTITLEMENT. Adding the package without Stripe keys changes nothing for the host app.

The client-facing routes (checkout, donate, portal, entitlement) mount through the framework’s typed OpenAPI router; the signature-verified Stripe webhook is a plain (non-OpenAPI) POST mounted only when billing is configured — dormant apps expose no webhook surface at all.

function createBillingPackage(rawConfig: Partial<BillingPackageConfig> = {}, internals: BillingPackageInternals = {},): SlingshotPackageDefinition

Source: packages/slingshot-billing/src/plugin.ts

The production BillingStore: a thin, stateless mapping over the three billing entity adapters. Constructed wherever the adapters are reachable (setupPost via maybeEntityAdapter, or onAdapter wiring callbacks).

function createEntityBillingStore(adapters: BillingEntityAdapters): BillingStore

Source: packages/slingshot-billing/src/lib/store.ts

Create the Stripe BillingProvider.

Stateless beyond the lazily-constructed SDK client; persistence and entitlement logic live in the package. Customer dedupe is deliberately NOT here: the routes check billing_customers first and call ensureCustomer only when no mapping exists, so this implementation always creates.

function createStripeProvider(config: StripeProviderConfig): BillingProvider

Source: packages/slingshot-billing/src/lib/providers/stripe.ts

Derive an owner’s Entitlement from their stored subscription rows.

Multi-row rule (binding): rows whose plan is not a configured plan key are discarded (unknown plan ⇒ no entitlement from that row), then the BEST remaining row wins — ranked active > trialing > past_due > canceled, tie-broken by the latest currentPeriodEnd. No qualifying rows ⇒ FREE_ENTITLEMENT.

function deriveEntitlement(rows: readonly BillingSubscriptionRow[], plans: readonly PlanConfig[],): Entitlement

Source: packages/slingshot-billing/src/lib/entitlement.ts

Structural equality for entitlements — used by sync to report changed.

function entitlementEquals(a: Entitlement, b: Entitlement): boolean

Source: packages/slingshot-billing/src/lib/entitlement.ts

Whether billing has a usable provider configured. When false the package is “dormant”: routes short-circuit to 503 and the entitlement capability yields a free/none entitlement. This is the single gate every runtime path consults.

function isBillingConfigured(config: BillingPackageConfig): boolean

Source: packages/slingshot-billing/src/types/config.ts

Map a verified Stripe event onto billing’s normalized ProviderEvent.

Handled types:

  • customer.subscription.created / .updatedsubscription.updated
  • customer.subscription.deletedsubscription.deleted
  • checkout.session.completed (mode subscription) → subscription.updated
  • checkout.session.completed (mode payment) → payment.completed
  • invoice.payment_failedsubscription.updated with status past_due
  • everything else → ignored
function normalizeStripeEvent(event: StripeEventLike): ProviderEvent

Source: packages/slingshot-billing/src/lib/providers/stripe.ts

Map a provider price id onto the configured plan key.

Unknown or absent prices map to 'free' — a webhook for a price the app never configured must not grant an entitlement.

function planKeyForPrice(priceId: string | null | undefined, plans: readonly PlanConfig[],): string

Source: packages/slingshot-billing/src/lib/entitlement.ts

Apply one normalized provider event to storage.

async function syncProviderEvent(event: ProviderEvent, store: BillingStore, plans: readonly PlanConfig[],): Promise<SyncOutcome>

Source: packages/slingshot-billing/src/lib/sync.ts

Source: packages/slingshot-billing/src/entities/customer.ts

Source: packages/slingshot-billing/src/entities/payment.ts

Source: packages/slingshot-billing/src/entities/subscription.ts

The entitlement returned when billing is dormant or the owner has no subscription.

Source: packages/slingshot-billing/src/public.ts

A billing_customers row as billing’s domain logic sees it.

Source: packages/slingshot-billing/src/lib/store.ts

Payload for billing:entitlement.changed — an owner’s derived entitlement actually changed after a verified webhook event was synced.

Delivery is fire-and-forget (the bus swallows and logs handler throws), so consumers treat this as a cache-invalidation hint and reconcile via BillingEntitlementCap on read.

Source: packages/slingshot-billing/src/events.ts

The structural slice of a framework entity adapter this store needs (BareEntityAdapterCrud in slingshot-entity satisfies it). Kept local so the store compiles against a stable, minimal shape.

Source: packages/slingshot-billing/src/lib/store.ts

The three entity adapters createEntityBillingStore maps over.

Source: packages/slingshot-billing/src/lib/store.ts

Internal construction seam for createBillingPackage.

Not part of the app-facing configuration surface: tests inject a FakeBillingProvider here so the full route/lifecycle stack runs without the Stripe SDK. Production apps never pass this.

Source: packages/slingshot-billing/src/plugin.ts

Payload for billing:payment.completed — a one-time payment/donation settled.

Source: packages/slingshot-billing/src/events.ts

A billing_payments row as billing’s domain logic sees it.

Source: packages/slingshot-billing/src/lib/store.ts

The provider contract. Implementations are stateless wrappers over a payment SDK; persistence, entitlement derivation, and event emission live in the package, not here.

Source: packages/slingshot-billing/src/lib/provider.ts

Everything lib/sync.ts needs from persistence — nothing more. Implemented over entity adapters in production (createEntityBillingStore) and over a Map in tests.

Source: packages/slingshot-billing/src/lib/store.ts

A billing_subscriptions row as billing’s domain logic sees it.

Source: packages/slingshot-billing/src/lib/store.ts

Redirect URLs a hosted checkout / portal session returns to.

Source: packages/slingshot-billing/src/lib/provider.ts

Input to start a one-time donation checkout.

Source: packages/slingshot-billing/src/lib/provider.ts

The single app-agnostic answer billing provides: what an owner has paid for right now. Apps map plan onto their own domain (a spend tier, a perk, …). When nothing is active this is { plan: 'free', status: 'none', ... }.

Source: packages/slingshot-billing/src/public.ts

A hosted-page URL the caller redirects the user to.

Source: packages/slingshot-billing/src/lib/provider.ts

Who a customer/subscription belongs to. Defaults to a user id; may later be an org.

Source: packages/slingshot-billing/src/lib/provider.ts

Input to open a self-service billing portal session.

Source: packages/slingshot-billing/src/lib/provider.ts

A provider-side customer handle, persisted as billing_customers.

Source: packages/slingshot-billing/src/lib/provider.ts

The structural slice of a Stripe webhook event normalizeStripeEvent reads. Deliberately NOT the SDK’s Stripe.Event: tests build these as plain objects, and the mapping stays honest about which fields it depends on.

Source: packages/slingshot-billing/src/lib/providers/stripe.ts

Input to start a subscription checkout for a configured plan.

Source: packages/slingshot-billing/src/lib/provider.ts

Input to create a customer row (id is adapter-generated).

Source: packages/slingshot-billing/src/lib/store.ts

Validated, inferred configuration shape accepted by createBillingPackage().

Source: packages/slingshot-billing/src/types/config.ts

Input to create a payment row (id is adapter-generated).

Source: packages/slingshot-billing/src/lib/store.ts

Input to create a subscription row (id is adapter-generated).

Source: packages/slingshot-billing/src/lib/store.ts

Patch applied to an existing subscription row.

Source: packages/slingshot-billing/src/lib/store.ts

Source: packages/slingshot-billing/src/types/config.ts

Normalized subscription status surfaced to consuming apps.

Source: packages/slingshot-billing/src/public.ts

Source: packages/slingshot-billing/src/types/config.ts

A provider webhook event normalized to billing’s domain. verifyAndParseWebhook returns one of these after a successful signature check; lib/sync.ts (Phase 2) switches on kind without any provider-specific knowledge.

Source: packages/slingshot-billing/src/lib/provider.ts

Source: packages/slingshot-billing/src/types/config.ts

Normalized subscription lifecycle status, provider-agnostic.

Source: packages/slingshot-billing/src/lib/provider.ts

Why a sync run produced no emittable result.

Source: packages/slingshot-billing/src/lib/sync.ts

What a sync run did. The webhook route switches on kind:

  • 'entitlement' + changed: true → emit billing:entitlement.changed.
  • 'payment' → emit billing:payment.completed with payload.
  • 'noop' → acknowledge (200) and emit nothing.

Source: packages/slingshot-billing/src/lib/sync.ts