1// Per-provider capability declarations.2//3// These describe what IAPKit *actually implements today*, not what a store's4// API theoretically allows. Consumers branch on this instead of assuming every5// store behaves like Apple, and support can answer "why is there no renewal6// event for Meta" without reading provider code.7//8// Keep in sync with the provider modules; `contract.test.ts` pins the9// claims that have a concrete implementation behind them.10 11import type { CommerceStore } from "./contract";12 13export type ProviderCapabilities = {14 /** Server-side receipt/token validation on demand. */15 supportsInitialValidation: boolean;16 /** Store pushes lifecycle notifications to IAPKit. */17 supportsServerNotifications: boolean;18 /** IAPKit keeps a canonical subscription record for this store. */19 supportsSubscriptions: boolean;20 /** Renewal is observable as a distinct lifecycle event. */21 supportsRenewalEvents: boolean;22 /** Refund or revocation is observable as a distinct lifecycle event. */23 supportsRefundEvents: boolean;24 /** Expiration is observable rather than only inferred from a timestamp. */25 supportsExpiration: boolean;26 /** A scheduled pass re-reads authoritative store state. */27 supportsReconciliation: boolean;28 /** Entitlement state is derivable for gating. */29 supportsEntitlements: boolean;30 /** The store asserts an amount IAPKit can attribute to revenue. */31 supportsRevenueAmount: boolean;32 /** Human-readable reason for every false above. */33 notes: string;34};35 36export const PROVIDER_CAPABILITIES: Record<37 CommerceStore,38 ProviderCapabilities39> = {40 apple: {41 supportsInitialValidation: true,42 supportsServerNotifications: true,43 supportsSubscriptions: true,44 supportsRenewalEvents: true,45 supportsRefundEvents: true,46 supportsExpiration: true,47 supportsReconciliation: false,48 supportsEntitlements: true,49 supportsRevenueAmount: true,50 notes:51 "App Store Server Notifications V2 drive the lifecycle. No scheduled " +52 "reconciliation pass exists yet, so a notification lost past Apple's " +53 "retry window is not self-healing. Receipt verification bootstraps a " +54 "token only before its first store event and cannot recreate a missed " +55 "commerce event.",56 },57 google: {58 supportsInitialValidation: true,59 supportsServerNotifications: true,60 supportsSubscriptions: true,61 supportsRenewalEvents: true,62 supportsRefundEvents: true,63 supportsExpiration: true,64 supportsReconciliation: false,65 supportsEntitlements: true,66 supportsRevenueAmount: true,67 notes:68 "RTDN drives the lifecycle. Google reissues purchaseToken across " +69 "upgrade/downgrade. The receiver requires subscriptionsv2 enrichment " +70 "for non-terminal lifecycle events and uses linkedPurchaseToken to move " +71 "the canonical row onto the replacement token. No scheduled " +72 "reconciliation pass exists, and receipt verification cannot recreate " +73 "a missed commerce event.",74 },75 horizon: {76 supportsInitialValidation: true,77 supportsServerNotifications: false,78 supportsSubscriptions: false,79 supportsRenewalEvents: false,80 supportsRefundEvents: false,81 supportsExpiration: false,82 supportsReconciliation: false,83 supportsEntitlements: true,84 supportsRevenueAmount: false,85 notes:86 "Only the Graph verify_entitlement endpoint is integrated: a one-shot " +87 "check that the viewer owns the SKU. Meta exposes no server " +88 "notifications to IAPKit, so there is no renewal, expiration or refund " +89 "signal and no canonical subscription record. Entitlement is answerable " +90 "only at the moment it is asked.",91 },92 amazon: {93 supportsInitialValidation: true,94 supportsServerNotifications: false,95 supportsSubscriptions: false,96 supportsRenewalEvents: false,97 supportsRefundEvents: false,98 supportsExpiration: false,99 supportsReconciliation: true,100 supportsEntitlements: true,101 supportsRevenueAmount: false,102 notes:103 "RVS validates receipts. Rows become due every 48 hours and a bounded " +104 "worker checks due work every five minutes, so backlog and retries can " +105 "extend that interval. RVS alone does not carry " +106 "enough lifecycle detail for a canonical subscription record, so no " +107 "subscription rows or lifecycle events are produced. A verification " +108 "still answers point-in-time entitlement.",109 },110};111 112/** Stores that can produce normalized subscription lifecycle events today. */113export function storesWithLifecycleEvents(): CommerceStore[] {114 return (Object.keys(PROVIDER_CAPABILITIES) as CommerceStore[]).filter(115 (store) => PROVIDER_CAPABILITIES[store].supportsSubscriptions,116 );117}118