⚠️ Demonstration mock — react-native-encore-mock is not the real Encore SDK and will be deleted after the demo.
Integration Walkthrough

Integration Walkthrough

Five steps using the react-native-encore-mock API. Hotspot Havoc wires it exactly this way.

1. Configuration

Keep your key, the mock switch, and your placement IDs in one place:

src/lib/encoreConfig.js
export const ENCORE_API_KEY = 'pk_your_api_key_here'; // inert in mock mode
export const USE_MOCK = true;
 
export const Placements = {
  CancellationFlow: 'cancellation_flow',
  FeaturePaywall: 'feature_paywall',
};

Create the client once and wrap your app in <MockEncoreProvider>, which runs configure() + registerCallbacks() for you:

App.js
import { createMockEncore, MockEncoreProvider } from 'react-native-encore-mock';
import { GAME_OFFERS } from './src/lib/offers';
 
const Encore = createMockEncore({ offers: GAME_OFFERS });
 
<MockEncoreProvider client={Encore} apiKey={ENCORE_API_KEY} logLevel="debug">
  <App />
</MockEncoreProvider>

Why: placement IDs come from the Encore dashboard. Referencing them through a Placements constant (not bare strings) keeps every call site in sync and turns a typo into an error.

2. User Identity

Tie tracking to a user as soon as you know who they are:

Encore.identify('user_12345', {
  email: 'player@example.com',
  subscriptionTier: 'premium',
  monthsSubscribed: '6',
});

setUserAttributes merges — it never replaces — so you can enrich over time. On logout, call Encore.reset() to clear identity, entitlements, and attributes.

Why: identifying the user lets Encore sync entitlements across devices and target offers based on attributes like tenure or tier.

3. Presenting Offers

Ask Encore to present an offer at a placement; show() resolves with the result:

import Encore, { isGranted } from './src/lib/encore';
 
const result = await Encore.placement(Placements.FeaturePaywall).show();
if (isGranted(result)) {
  unlockPremium(); // reveal the gated content
}

Why: you decide the moment (the cancel tap, the locked feature); Encore decides whether and what to show — a paid offer or a brand-sponsored partner trial, handled identically via isGranted(result).

4. Handling Results

show() resolves with a PlacementResult whose status is your primary signal ('granted' | 'completed' | 'not_granted' | 'dismissed' | 'no_offers'). isGranted(result) covers both “won” outcomes — granted (a paid purchase) and completed (a brand-sponsored trial):

try {
  const result = await Encore.placement(Placements.CancellationFlow).show();
 
  if (isGranted(result)) {
    keepSubscribed();
    setMessage(
      result.status === 'completed'
        ? '🎁 A partner is sponsoring your subscription — thanks for staying!'
        : '🎉 Thanks for staying! Your discount is applied.',
    );
  } else {
    finalizeCancellation(); // dismissed / no_offers → cancel
  }
} catch (error) {
  finalizeCancellation(); // never block the user on an SDK error
}

The dismissal path also flows through onPassthrough (registered via useEncoreCallbacks’s onResume) so you can resume the user’s original action and never trap them.

5. Completing Purchases

Register the callbacks once with useEncoreCallbacks. You supply purchase (your billing); the hook calls completePurchaseRequest on every path for you:

App.js
useEncoreCallbacks(Encore, {
  purchase: (productId) => billing.purchase(productId),
  onResume: (placementId) => resumeOriginalAction(placementId),
  onPurchaseComplete: ({ productId, transactionId }) =>
    console.log('[encore] complete', productId, transactionId),
});

Why — the #1 gotcha: if completePurchaseRequest(true/false) is skipped (especially in the failure path), Encore believes a purchase is still in flight and blocks all future offers. The hook guarantees it runs whether your billing succeeds or throws — that’s the main reason to use it instead of registering onPurchaseRequest by hand.

Sponsored partner trials resolve to status: 'completed' without touching purchase — the brand pays. See Sponsored Offers.