# React Native and TypeScript

The TypeScript package is `@appvisionos/iap-client` 0.5.0. It uses a Fetch-compatible transport and structurally accepts React Native IAP 15/OpenIAP purchase objects without adding the storefront library as a runtime dependency. It can also perform public runtime operations in other modern Fetch environments.

## Integrate the source package

The package is private in the current preview and is not published to npm. Obtain the approved source checkout, run `npm ci` and `npm run build` inside `sdk/typescript`, then use your project's local-file/workspace package integration with that directory.

Keep the source build's generated `dist` available to the consuming application; the package's exports point there. Add and configure your chosen storefront library separately.

## Configure the client and token cache

```ts
import { IAPClient, InMemoryCustomerTokenProvider } from '@appvisionos/iap-client';

const tokens = new InMemoryCustomerTokenProvider({
  fetchToken: async (appUserId) => {
    const response = await sessionApi.createIAPToken({ appUserId });
    return { token: response.token, expiresAt: new Date(response.expires_at) };
  },
});
const iap = new IAPClient({
  baseUrl: 'https://api-staging.cherrysub.com',
  publishableKey: 'iap_pk_REPLACE_WITH_YOUR_KEY',
  customerTokenProvider: tokens.provider,
});
```

`sessionApi` is your application's authenticated backend client. Its backend must derive the App User ID from verified session identity even if your client helper accepts an argument. See [authentication](/docs/authentication).

## Read the current presentation

```ts
const productSet = await iap.getCurrentProductSet(user.id);
```

Handle `null`, filter packages to the device's store, preserve order, and load the storefront's product details and localized prices. CherrySub does not render a paywall or choose a Google offer for you.

## Submit and complete an OpenIAP purchase

```ts
await iap.submitAndFinishOpenIAPPurchase(
  user.id,
  purchase,
  () => finishTransaction({ purchase, isConsumable: false }),
  'subscription', // Required for Google; omit for Apple.
);
const subscriber = await iap.getSubscriber(user.id);
const hasPro = subscriber.accessLevels.pro?.active === true;
```

`purchase`, `finishTransaction`, and `user` come from your application/storefront integration. Choose `isConsumable` and Google's product type for the actual product; this example uses a non-consumable subscription completion policy.

The adapter accepts only `purchaseState: 'purchased'`, uses the unified `purchaseToken` (StoreKit JWS or Google token), and rejects unsupported stores. It invokes your finish callback only after backend acceptance. Use `submitOpenIAPPurchase` for submit-only handling, or `submitAppleTransaction` / `submitGooglePurchase` with an application-owned bridge.

## Errors and lifecycle

A 401 forces token refresh and one retry. Other statuses are not automatically replayed. Handle configuration, validation, transport, and response errors without showing premium access as a fallback.

The in-memory provider coalesces simultaneous refreshes and isolates caches by user. On logout, call `tokens.invalidate(user.id)`, clear your own access cache, and revoke the token on the backend if required. Keep event IDs as decimal strings; they can exceed JavaScript's exact integer range.

Hook both the immediate purchase response and storefront update/recovery events into your verified completion flow. If the app exits before submission finishes, recover the unfinished store transaction on the next session rather than assuming it failed to charge.

## Test

The package accepts an injected Fetch implementation for controlled transport tests. Run `npm test` in `sdk/typescript` for the source package's conformance checks, then perform real store-backed sandbox tests in the app. Verify pending/cancelled purchases, network interruption before completion, 401 refresh, invalid product types, restore ownership, and store refunds. [Testing](/docs/testing).
