1// Per-provider capabilities: what IAPKit implements today, not what a store's2// API allows. Consumers branch on this instead of assuming every store behaves3// like Apple, and support can answer "why no renewal event for Meta?" without4// reading provider code.5//6// Keep in sync with the provider modules; `contract.test.ts` pins the claims7// that have a concrete implementation behind them.8 9import type { CommerceStore } from "./contract";10 11export type ProviderCapabilities = {12 /** Server-side receipt/token validation on demand. */13 supportsInitialValidation: boolean;14 /** Store pushes lifecycle notifications to IAPKit. */15 supportsServerNotifications: boolean;16 /** IAPKit keeps a canonical subscription record for this store. */17 supportsSubscriptions: boolean;18 /** Renewal is observable as a distinct lifecycle event. */19 supportsRenewalEvents: boolean;20 /** Refund or revocation is observable as a distinct lifecycle event. */21 supportsRefundEvents: boolean;22 /** Expiration is observable rather than only inferred from a timestamp. */23 supportsExpiration: boolean;24 /** A scheduled pass re-reads authoritative store state. */25 supportsReconciliation: boolean;26 /** Entitlement state is derivable for gating. */27 supportsEntitlements: boolean;28 /** The store asserts an amount IAPKit can attribute to revenue. */29 supportsRevenueAmount: boolean;30 /** Human-readable reason for every false above. */31 notes: string;32};33 34export const PROVIDER_CAPABILITIES: Record<35 CommerceStore,36 ProviderCapabilities37> = {38 apple: {39 supportsInitialValidation: true,40 supportsServerNotifications: true,41 supportsSubscriptions: true,42 supportsRenewalEvents: true,43 supportsRefundEvents: true,44 supportsExpiration: true,45 supportsReconciliation: false,46 supportsEntitlements: true,47 supportsRevenueAmount: true,48 notes:49 "App Store Server Notifications V2 drive the lifecycle. No scheduled " +50 "reconciliation pass exists yet, so a notification lost past Apple's " +51 "retry window is not self-healing. Receipt verification bootstraps a " +52 "token only before its first store event and cannot recreate a missed " +53 "commerce event.",54 },55 google: {56 supportsInitialValidation: true,57 supportsServerNotifications: true,58 supportsSubscriptions: true,59 supportsRenewalEvents: true,60 supportsRefundEvents: true,61 supportsExpiration: true,62 supportsReconciliation: false,63 supportsEntitlements: true,64 supportsRevenueAmount: true,65 notes:66 "RTDN drives the lifecycle. Google reissues purchaseToken across " +67 "upgrade/downgrade. The receiver requires subscriptionsv2 enrichment " +68 "for non-terminal lifecycle events and uses linkedPurchaseToken to move " +69 "the canonical row onto the replacement token. No scheduled " +70 "reconciliation pass exists, and receipt verification cannot recreate " +71 "a missed commerce event.",72 },73 horizon: {74 supportsInitialValidation: true,75 supportsServerNotifications: false,76 supportsSubscriptions: false,77 supportsRenewalEvents: false,78 supportsRefundEvents: false,79 supportsExpiration: false,80 supportsReconciliation: false,81 supportsEntitlements: true,82 supportsRevenueAmount: false,83 notes:84 "Only the Graph verify_entitlement endpoint is integrated: a one-shot " +85 "check that the viewer owns the SKU. Meta publishes subscription and " +86 "order webhook fields and a subscriptions read API; IAPKit uses " +87 "neither, so it sees no renewal or expiration signal and keeps no " +88 "canonical subscription record. Entitlement is answerable only at the " +89 "moment it is asked.",90 },91 amazon: {92 supportsInitialValidation: true,93 supportsServerNotifications: true,94 supportsSubscriptions: true,95 supportsRenewalEvents: true,96 supportsRefundEvents: true,97 supportsExpiration: true,98 supportsReconciliation: true,99 supportsEntitlements: true,100 supportsRevenueAmount: false,101 notes:102 "Amazon subscription lifecycle is in beta. Signed RTN notifications " +103 "trigger fresh RVS verification and " +104 "update canonical subscriptions and commerce events. Rechecks become " +105 "due every 48 hours; a bounded worker runs every five minutes, so " +106 "backlog and retries can extend that interval. renewalDate is not expiry, " +107 "and auto-renew off retains access until RVS removes it. RVS supplies " +108 "no authoritative charge amount. Live SNS delivery and LAT renewal, " +109 "cancellation, elapsed expiry and server entitlement revocation remain " +110 "unverified; App Tester does not certify them. See " +111 "https://kit.openiap.dev/docs/verification/amazon for beta limits.",112 },113};114 115/** Stores that can produce normalized subscription lifecycle events today. */116export function storesWithLifecycleEvents(): CommerceStore[] {117 return (Object.keys(PROVIDER_CAPABILITIES) as CommerceStore[]).filter(118 (store) => PROVIDER_CAPABILITIES[store].supportsSubscriptions,119 );120}121