React to Purchased Products

This guide explains how your app can react and modify its state based on which in-app purchase products are active.

Once you have Nami set up and integrated the SDK in your application, you'll also want to know how you can detect and react to purchases being made.

There are two main ways to have your app take additional actions based on a purchase being completed:

  • Manually check to see if a SKU has been purchased
  • Register a callback to take an action when a purchase is made

Manually check to see if a purchase has been made

You can ask Nami if it knows if a product SKU ID or a set of product SKU IDs is purchased.

The code below can be used to check if a single SKU has been purchased. Be sure to use the correct sku_id from the Control Center.

let purchased = NamiPurchaseManager.isSKUIDPurchased("productMonthly")
BOOL purchased = [NamiPurchaseManager isSKUIDPurchased: @"productMonthly"];
NativeModules.NamiPurchaseManagerBridge.isSKUIDPurchased("productMonthly",
  (purchased) => {
    // callback that receives the boolean return value
  }
);
val isPurchased = NamiPurchaseManager.isSKUIDPurchased(skuID = "productMonthly")
boolean isPurchased = NamiPurchaseManager.isSKUIDPurchased("productMonthly");
bool isPurchased = await NamiPurchaseManager.isSKUIDPurchased("productMonthly");

If you have multiple product SKUs that you wish to check at the same time to see if any have been purchased, you can use the anySKUIDPurchased method.

let purchased = NamiPurchaseManager.anySKUIDPurchased(["productMonthly", "productYearly"])
BOOL purchased = [NamiPurchaseManager anySKUIDPurchased: @[@"productMonthly", @"productYearly"]];
NativeModules.NamiPurchaseManagerBridge.anySKUIDPurchased(["productMonthly", "productYearly"],
  (purchased) => {
    // callback that receives boolean return value
  }
);
val arePurchased = NamiPurchaseManager.anySKUIDPurchased(listOf("productMonthly", "productYearly"))
boolean arePurchased = NamiPurchaseManager.anySKUIDPurchased(Arrays.asList("productMonthly", "productYearly"));
bool arePurchased = await NamiPurchaseManager.anySKUIDPurchased(["productMonthly", "productYearly"]);

Using a callback when the SDK detects a change to purchased products

It can be handy to check in specific spots to see if a product has been purchased to activate functionality, but your application may also want to have a solution that is aware of any purchase changes and can more automatically take action.

For this, Nami offers a callback that can be used that will be activated whenever purchases or purchase state change. This could mean either a purchase had been made, or a purchase has expired, such as when a subscription lapses.

🚧

Purchase State

Not all stores and SDKs have the same purchase states available. Be sure to check out the list of supported purchase states by platform to see what to expect for your app.

In Swift and Objective-C this is done by registering a handler. In React Native, simply add a listener for the correct event.

When dealing with the purchase change event, it is important to check the state of the purchase as there are multiple states that can result in this event getting triggered.

  • PURCHASED - This means the product SKU has been successfully purchased by the user.
  • PENDING - This state occurs when a user has started the system purchase flow but it has not completed.
  • FAILED - The user tried to make a purchase but it failed for some reason.
  • CANCELLED - the user started a purchase attempt and then cancelled it.
NamiPurchaseManager.registerPurchasesChangedHandler { (purchases: [NamiPurchase], purchaseState: NamiPurchaseState, error: Error) in
  // retrieve active entitlements if you want to use them
  let activeEntitlements = NamiEntitlementManager.activeEntitlements()
                              
  if purchaseState == .purchased {
    // react to a successful purchase
  } else if purchaseState == .pending {
    // react to a pending purchase
  } else if purchaseState == .cancelled {
    // react to a cancelled purchase
  } else if purchaseState == .failed {
    // react to a failed purchase
  }
}
[NamiPurchaseManager registerPurchasesChangedHandler:^(NSArray<NamiPurchase *> * _Nonnull purchases, enum NamiPurchaseState purchaseState, NSError * _Nullable error) {
     if ( purchaseState == NamiPurchaseStatePurchased ) {
       // react to a successful purchase
     } else if ( purchaseState == NamiPurchaseStatePending ) {
       // react to a pending purchase 
     } else if ( purchaseState == NamiPurchaseStateCancelled ) {
       // react to a cancelled purchase
     } else if ( purchaseState == NamiPurchaseStateFailed ) {
       // react to a failed purchase
     }
  }];
NamiPurchaseManager.registerPurchasesChangedListener { namiPurchases, namiPurchaseState, error ->
    // retrieve active entitlements if you want to use them
    val activeEntitlements = NamiEntitlementManager.activeEntitlements()
    when (namiPurchaseState) {
        NamiPurchaseState.PURCHASED -> {
            // react to a successful purchase
        }
        NamiPurchaseState.FAILED -> {
            // react to a failed purchase
        }
        NamiPurchaseState.CANCELLED -> {
            // react to a cancelled purchase
        }
        NamiPurchaseState.UNKNOWN -> {
            // react to a unknown purchase
        }
    }
}
NamiPurchaseManager.registerPurchasesChangedListener((namiPurchases, namiPurchaseState, error) -> {
    // retrieve active entitlements if you want to use them
    List<NamiEntitlement> activeEntitlements = NamiEntitlementManager.activeEntitlements();
    switch (namiPurchaseState) {
        case PURCHASED:
             // react to a successful purchase
             break;
        case FAILED:
             // react to a failed purchase
             break;
        case CANCELLED:
             // react to a cancelled purchase
             break;
        case UNKNOWN:
             // react to a unknown purchase
             break;
    }
    return Unit.INSTANCE;
});
useEffect(() => {
  eventEmitter.addListener('PurchasesChanged', onSessionConnect);
}
          
const onSessionConnect = (event) => {  
  if (event.purchaseState == "PURCHASED") {
    // react to a successful purchase, including pulling in
    // all active entitlements and all the active product SKUs
    NativeModules.NamiEntitlementManagerBridge.activeEntitlements( (entitlements) => {
      console.log("Active entitlements: ", entitlements);
      console.log("SKUs: ", event.skuIDs);
    });
  } else if (event.purchaseState == "PENDING") {
    // react to a pending purchase
  } else if (event.purchaseState == "CANCELLED") {
    // react to a cancelled purchase
  } else if (event.purchaseState == "FAILED") {
    // react to a failed purchase 
  }
}
NamiPurchaseManager.purchaseChangeEvents().listen((purchaseChangeEventData) {
    // retrieve active entitlements if you want to use them
    var activeEntitlements = NamiEntitlementManager.activeEntitlements();

    switch(purchaseChangeEventData.purchaseState) {
      case NamiPurchaseState.purchased:
        // react to a successful purchase
        break;
      case NamiPurchaseState.failed:
        // react to a failed purchase
        break;
      case NamiPurchaseState.cancelled:
        // react to a cancelled purchase
        break;
      case NamiPurchaseState.unknown:
        // react to a unknown purchase
        break;
    }
});

You can use this callback in a few different ways:

  • This callback will be called for a variety of purchase activity, such as purchases made, cancelled, or purchase errors. When this method is called, you can check the NamiPurchaseManager or NamiEntitlementManager to see if purchases or entitlements you care about are active or removed.

  • You can specifically examine the purchaseState passed in, looking for a state of "purchased" to detect when purchases occur and activate features of your application.