REACT NATIVE IAP · StoreKit & StoreKit 2 – The Definitive iOS Guide

4 min read
Share:

Why This Matters for React Native IAP Developers

If you have shipped iOS purchases with react-native-iap, you know the ritual: Base64 receipt blobs, global listeners scattered across files, flaky Sandbox testers, and a backend endpoint Apple is actively shutting down. StoreKit 2 is not a patch — it is a clean architectural rewrite. Apple rebuilt iOS payments around async/await, replacing every delegate callback with a promise and every opaque receipt with a cryptographically signed JWS (JSON Web Signature) token you can verify on-device without a network call. In react-native-iap v12+ you unlock the entire new model with one config line: storekitMode: “STOREKIT2_MODE”.

na

SK1 (left): delegate/observer cascade ending at the deprecated /verifyReceipt endpoint. SK2 (right): async/await chain with self-verifiable JWS tokens and the current App Store Server API.

Key Architectural Changes

    • Transactions are now first-class citizens. Instead of a single monolithic receipt, SK2 gives you individual Transaction objects — signed in JSON Web Signature (JWS) format. Each Transaction carries its own payload: product ID, purchase date, expiry date, quantity, and crucially, a cryptographic signature you can verify on-device without a network call. This alone eliminates the biggest operational risk in SK1.
    • Async/await throughout. Every SK2 API uses Swift concurrency. Product.products(for:), Product.purchase(), Transaction.currentEntitlements — all return with await. React Native bridges this naturally through the react-native-iap promise layer, giving you clean async/await in JavaScript.
    • Transaction.currentEntitlements. This async sequence delivers every currently-active entitlement for the user — all active subscriptions, all unconsumed consumables, all non-consumables. One call, correct state, no receipt parsing.

JWS vs Receipt — Why Your Backend Changes Too

  • SK1 backend: You POST a Base64 receipt blob to /verifyReceipt, parse a deeply nested JSON response, find latest_receipt_info, check expires_date_ms, and grant access. This endpoint is deprecated — Apple will remove it.
  • SK2 backend: Each transaction delivers a jwsRepresentation string — a standard ES256-signed JWT. Verify the signature against Apple’s public key at appleid.apple.com/auth/keys. The payload is plain typed JSON: productId, expiresDate, type. No parsing gymnastics. Verifiable on-device or on your server.

Code-level changes in react-native-iap when migrating from SK1 to SK2

  • Initialisation & Setup
    StoreKit 1
    import { setup } from ‘react-native-iap’;
    setup({ storekitMode: ‘STOREKIT1_MODE’ });
    StoreKit 2
    import { setup } from ‘react-native-iap’;
    setup({ storekitMode: ‘STOREKIT1_MODE’ });
  • Fetching Products

    StoreKit 1

    // getProducts for consumables /

    // non-consumables

    const products = await getProducts({

      skus: [‘com.app.coins’],

    });

    // getSubscriptions for subs

    const subs = await getSubscriptions({

      skus: [‘com.app.monthly’],

    });

    StoreKit 2 // Same API — no change needed

    // SK2 resolves product metadata

    const products = await getProducts({

    skus: [‘com.app.coins’],

    });

     

    const subs = await getSubscriptions({

    skus: [‘com.app.monthly’],

    });

     

    // ✓ No code change required here

  • Purchase Flow  ← Biggest Change
    StoreKit 1  —  Listener / Callback

    // Must register GLOBALLY

    // before any purchase attempt

    const sub = purchaseUpdatedListener(

    async (purchase) => {

    if (purchase.transactionReceipt){

    await sendToServer(

    purchase.transactionReceipt

    // ↑ raw Base64 blob

    );

    await finishTransaction(

    { purchase });

    }

    });

     

    // Trigger purchase separately

    await requestPurchase({ sku });

     

    // ⚠ Must remove — leaks if missed

    sub.remove();

    StoreKit 2  —  Async / Await

    // No listener needed at all

    // requestPurchase returns a promise

     

    try {

    const purchase =

    await requestPurchase({

    sku,

    andDangerously

    FinishTransaction

    AutomaticallyIOS: false,

    });

     

    // transactionId = JWS token

    await verifyJWS(

    purchase.transactionId

    // ↑ signed JWT, not Base64

    );

    await finishTransaction(

    { purchase });

    } catch (e) { /* handle */ }

Summary

StoreKit 1 served the iOS developer community for fifteen years and deserves respect for enabling an entire economy of premium apps and subscriptions. But its delegate-based, receipt-centric model was always a leaky abstraction — one that required significant server infrastructure and constant maintenance just to answer a simple question: “does this user have an active subscription?”

StoreKit 2 answers that question in three lines of Swift. It eliminates receipt parsing, replaces observer spaghetti with clean async/await, provides on-device cryptographic verification, and ships with an Xcode-integrated test harness that makes TDD for in-app purchases genuinely possible for the first time.

For React Native developers, react-native-iap v12+ exposes all of this through a promise-based API that feels natural. The migration from SK1 to SK2 is non-trivial — especially if you have an existing backend validation layer — but the operational benefits are immediate and compounding. Every new Apple feature from here forward will be SK2 only. The time to migrate is now.

Leave a Reply

Your email address will not be published. Required fields are marked *