@baukit/purchases-core
A store-neutral purchase flow for Baukit mobile apps. It defines the PurchasesPort that a store adapter implements, a no-op port for builds without store keys, and a PurchaseFlow that gates features on the entitlement the product backend verified. It never reads store entitlements. Schlüsselbund owns entitlements, so a store receipt grants nothing until the refreshed ticket says so.
The package has no Expo, React Native or RevenueCat imports. @baukit/purchases-revenuecat is the RevenueCat adapter.
import {
PurchaseFlow,
createNoopPurchasesPort,
createAccountPlanBackend,
} from '@baukit/purchases-core';
const flow = new PurchaseFlow({
port: createNoopPurchasesPort(),
backend: createAccountPlanBackend({ baseUrl: `${apiOrigin}/api/v1`, fetch, headers: authHeaders }),
});
await flow.identify(suiteSubject);
const { current } = await flow.offerings();
const outcome = await flow.purchase(current!.packages[0]!);
identify(suiteSubject)must finish beforepurchase,restoreorpurchaseCreditPack. Without it they reject withnot_identified.signOut()clears the state and calls the port'sreset.purchasereturnsgrantedonce the backend serves an entitlement with a newer revision, orpendingafterrefreshAttemptsreads (default 5, 2 seconds apart).restorereturnsgrantedorunchanged.purchaseCreditPackaccepts onlyone_timepackages and returnscreditedonce the credit balance revision advances, orpending.- Only one purchase runs at a time. A second one rejects with
busy. - Failures are
PurchaseErrorwithcodecancelled,pending,network,store,unavailable,not_identified,identity_changed,subject_mismatch,invalid_subject,not_credit_packorbusy.
createAccountPlanBackend reads the GET /me/entitlement and GET /me/credits routes that baukit-suite's account_plan_router serves. It treats suite_identity_missing as no entitlement and raises AccountPlanError with code and retryAfterSeconds otherwise. A client that verifies tickets itself uses entitlementFromTicket(claims, plansUrl).
@baukit/purchases-core/react has PurchasesProvider (with flow and productId), usePurchaseFlow, usePurchaseState, useEntitlement, useFeature(key) and LockedFeature. useFeature returns loading, unavailable, granted, or locked with a paywall. Loading and unavailable show neither the feature nor a purchase, and a failed refresh keeps the last verified entitlement. paywallFor turns the ticket's billing claim into purchase, manage (web and store plans, never a second purchase), covered (family and organization) or none (standalone). plansLink builds the Schlüsselbund C17 link /plans?from=<product id>&feature=<feature key> and drops feature without from.
Read Baukit's docs/platform/mobile-purchases.md for the full wiring.