Web and PWA SDK
Use @inappifylabs/web-sdk 0.2.0 for sessions, authentication, offerings, discounts, checkout/returns and Flutter Web bridging.
Web SDK is a headless library for websites/PWAs. It does not add checkout UI/paywall rendering, StoreKit, Bazaar or Myket billing to browsers. This guide targets 0.2.0, independently versioned from Flutter.
Installation and prerequisites
Enable the Web App, approved origin, offerings and checkout. Guest purchases require the corresponding feature. Attribute writes require allowlists. Use a Public Web App Key, not mobile/server keys. Initialize in the browser after browser APIs are ready; don't share one SSR customer session between users.
Create one client
Keep one client in application session scope. Configure creates/restores the session, loads customer state and prefetches offerings. Defaults: IR country, empty appVersion, 15-second timeout, five-minute offering TTL, prefetch enabled. appVersion is the host version; SDK version is embedded in its build.
Access tokens stay in memory; refresh tokens use available IndexedDB storage. Memory-only sessions don't promise restoration after reload. CustomerInfo has no separate persistent cache. Don't rewrite provider storage or copy tokens into localStorage.
Identity and offerings
Use client.logIn({identityTicket}) or client.logIn({challengeId, code}), then client.logOut() for logout. Tickets must be fresh from your authenticated backend; identity examples also cover OTP.
getCurrentOffering(), getCurrentOffering('upgrade'), and getOffering('special_offer') cover three selection modes. getOfferings() returns a collection with array all. Don't reconstruct packages from names. See offerings.
Purchase and return
purchase({package}) redirects by default. The example disables it to return checkout data. Before navigating to nextAction.url, durably save checkout ID and attempt/identity context, and apply the host destination/origin policy. createCheckout(params) never navigates itself.
getCheckoutStatus(id) queries once; handlePaymentReturn(id, options) polls for at most 60 seconds by default, every 1500 ms. Only authoritative server Paid proves payment. Refresh CustomerInfo for access; consumables still need a durable grant. Query parameters, browser closure or poll cancellation don't confirm/cancel payment. Timeout leaves uncertainty unresolved.
CustomerInfo and sync
The following are alternatives; choose one policy in real code, rather than issuing every call:
JS defaults to FETCH_CURRENT. Other modes provide cache-only, fresh-cache-or-server, and immediate cache with stale refresh. Staleness is five minutes visible/25 hours hidden. JS entitlements are arrays; use client access helpers instead of copying Flutter map access.
setAttributes({...}) writes immediately. syncAttributesAndOfferingsIfNeeded() refreshes customer state then offerings, with five admissions per rolling 60 seconds. Failures count and identity changes don't reset quota; excess calls return current-session cache or RATE_LIMITED_NO_CACHE. There is no native offline attribute queue.
Discounts and failures
validateDiscountCode(code) prices current; validateDiscountCode(code, offeringId) prices the explicit offer. For a placement, pass the selected offering ID. Never rewrite blank/invalid IDs to default. Discount examples use server-priced data.
Failed Promises throw/reject; identify structured failures with isInAppifyError(error). An exception/aborted signal does not prove a payment didn't commit. Legacy checkEntitlement/force options remain compatible; prefer CustomerInfo policies and synchronous access helpers for new code.
Flutter Web: bridge before bootstrap
The npm dependency must be bundled by the host build; Flutter doesn't turn bare npm imports into browser assets. Use the Flutter repository's web/inappify_web_sdk_entry.mjs as a bundler input. Its boundary resembles:
Load the self-hosted bundle output, for example inappify-provider.js, and replace the duplicated Flutter bootstrap with this ordered loader:
That file must be bundled output, not raw source with a bare import. The official entry guards duplicate installation; don't use the illustrative boundary to overwrite an existing one. Allow required assets/services in host CSP. Use only the Flutter facade afterward; don't create a parallel JS client.
Guest startup uses InappifySdk.configureResult(apiKey: 'YOUR_PUBLIC_WEB_APP_KEY'); authenticated startup uses the Web helper.
Flutter Web: checkout and gaps
Identifiers come from the valid/discounted offering's Package. createCheckout carries an explicit discountCode, absent from Flutter's InappifyPurchaseParams. purchaseResult(Package) also works, but projects checkout into PurchaseResult and calls JS with redirects disabled; the host navigates.
Without checkoutId, handlePaymentReturnResult() uses the pending checkout in sessionStorage. getPendingCheckoutIdResult() and getCheckoutStatus(checkoutId:) are available. Storage is correlation, not proof of payment; storage failure doesn't establish that no checkout exists.
The Flutter 0.2.1 bridge doesn't implement combined sync/native delivery/history, and does not forward fetchPolicy. getOffering works through the collection helper. Follow the support matrix; don't bypass gaps through another client or private src classes.