Skip to main content

Myket In-App Purchase Guide for React Native

React Native
Sample project

For a complete, runnable Myket React Native and Expo in-app billing implementation, see the official sample project on GitHub.

The iab-myket-reactnative library is the official React Native SDK for integrating Myket In-App Billing (IAB) into React Native and Expo applications. It supports functional components (via the useMyket hook) and class components (via the myket singleton).

Note: This library supports Android only. In Expo projects, using development builds or production prebuilds is required.

Features​

  • Promise-based API: Supports async/await syntax.
  • React Hook Support: Provides useMyket for managing billing lifecycle in functional components.
  • Expo & Bare React Native: Compatible with Managed Expo (via Config Plugin) and standard React Native projects.
  • In-App Product Support: Supports one-time purchases, including consumable items (such as coins or credits) and non-consumable entitlements (such as premium upgrades or ad removal).
  • Inventory & Purchase Recovery: Query user-owned purchases and recover unconsumed purchases after connection drops.
  • Type Definitions: Includes TypeScript declarations.
  • Native Logging: Enable Android Logcat output using enableDebugging().

Prerequisites​

Before integrating the SDK, ensure you have:

  1. A Myket Developer Account.
  2. An application registered in your Myket developer panel with a reserved applicationId (package name).
  3. Products (SKUs) defined in your application console on Myket.
  4. Your app's Base64-encoded RSA Public Key copied from the Myket developer console.
  5. The Myket App Store installed on the test device or emulator.
Match Package Name

The Android applicationId in your project must strictly match the package name registered in the Myket developer panel.

Installation​

Install the package using your preferred package manager:

# npm
npm install iab-myket-reactnative

# yarn
yarn add iab-myket-reactnative
Rebuild Required

After installing the library, rebuild the native Android application (npx react-native run-android or npx expo run:android). Reloading JavaScript or restarting Metro alone will not link the native module.

Android Configuration​

Myket uses Android Intent services and permissions to handle secure billing. Configure the manifest placeholders according to your project setup:

Bare React Native​

1. Groovy DSL (android/app/build.gradle)​

Add manifestPlaceholders inside android { defaultConfig { ... } }:

android {
...
defaultConfig {
...
def marketApplicationId = "ir.mservices.market"
def marketBindAddress = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders = [
marketApplicationId: "${marketApplicationId}",
marketBindAddress : "${marketBindAddress}",
marketPermission : "${marketApplicationId}.BILLING"
]
}
}

2. Kotlin DSL (android/app/build.gradle.kts)​

android {
...
defaultConfig {
...
manifestPlaceholders["marketApplicationId"] = "ir.mservices.market"
manifestPlaceholders["marketBindAddress"] = "ir.mservices.market.InAppBillingService.BIND"
manifestPlaceholders["marketPermission"] = "ir.mservices.market.BILLING"
}
}

Expo (Managed / EAS Build)​

For Expo projects using Continuous Native Generation (CNG) or npx expo prebuild, configure a config plugin to inject the manifest placeholders into build.gradle:

1. Create plugins/withMarketPlace.js​

Create a plugins directory in your project root and add withMarketPlace.js:

const { withAppBuildGradle } = require('@expo/config-plugins');

const withMarketPlace = (config, options = {}) => {
const { marketApplicationId, marketBindAddress, marketPermission } = options;

return withAppBuildGradle(config, (modConfig) => {
if (modConfig.modResults.language === 'groovy') {
const placeholders = `
manifestPlaceholders = [
marketApplicationId: "${marketApplicationId}",
marketBindAddress: "${marketBindAddress}",
marketPermission: "${marketPermission}"
]
`;
if (!modConfig.modResults.contents.includes('marketApplicationId')) {
modConfig.modResults.contents = modConfig.modResults.contents.replace(
/defaultConfig\s*\{/,
(match) => `${match}\n${placeholders}`
);
}
}
return modConfig;
});
};

module.exports = withMarketPlace;

2. Register Plugin in app.config.js​

const withMarketPlace = require('./plugins/withMarketPlace');

export default ({ config }) => ({
...config,
plugins: [
...(config.plugins || []),
[
withMarketPlace,
{
marketApplicationId: "ir.mservices.market",
marketBindAddress: "ir.mservices.market.InAppBillingService.BIND",
marketPermission: "ir.mservices.market.BILLING"
}
]
]
});

Then generate and run the native Android build:

npx expo run:android

Usage & Examples​

Use the built-in useMyket hook to automatically handle service connection on mount and cleanup on unmount:

import React, { useState } from 'react';
import { View, Button, Text, Alert } from 'react-native';
import { useMyket } from 'iab-myket-reactnative';

const RSA_PUBLIC_KEY = "YOUR_BASE64_RSA_PUBLIC_KEY_FROM_MYKET";

export default function ShopScreen() {
const myket = useMyket(RSA_PUBLIC_KEY);
const [loading, setLoading] = useState(false);

// Fetch products and pending purchases
const loadStoreItems = async () => {
try {
setLoading(true);
const inventory = await myket.queryPurchaseProduct(true, ['coin_pack_100', 'remove_ads']);
console.log('Available Products:', inventory.allProducts);

// Consume any unconsumed purchases (crash recovery)
for (const purchase of inventory.allPurchases) {
if (purchase.sku === 'coin_pack_100') {
await myket.consumePurchase(purchase);
console.log('Consumed pending purchase:', purchase.orderId);
}
}
} catch (error) {
console.error('Failed to load products:', error);
} finally {
setLoading(false);
}
};

// Launch purchase flow
const handleBuy = async (sku: string) => {
try {
const developerPayload = `payload_${Date.now()}`;
const purchase = await myket.purchaseProduct(sku, developerPayload);

// Verify payload
if (purchase.developerPayload === developerPayload) {
// If it's a consumable item:
if (purchase.sku === 'coin_pack_100') {
await myket.consumePurchase(purchase);
Alert.alert('Success', 'Item purchased and consumed successfully!');
} else {
// Non-consumable product (e.g. ad removal)
Alert.alert('Success', 'Premium feature unlocked successfully!');
}
}
} catch (error) {
Alert.alert('Purchase Error', JSON.stringify(error));
}
};

return (
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Button title="Load Inventory" onPress={loadStoreItems} disabled={loading} />
<Button title="Buy Coins" onPress={() => handleBuy('coin_pack_100')} />
</View>
);
}

2. Class Components (myket Singleton)​

In class components, use the default myket singleton and manage lifecycle manually:

import React, { Component } from 'react';
import { View, Button, Alert } from 'react-native';
import myket, { Inventory, Purchase } from 'iab-myket-reactnative';

const RSA_PUBLIC_KEY = "YOUR_BASE64_RSA_PUBLIC_KEY_FROM_MYKET";

class ShopScreen extends Component {
async componentDidMount() {
try {
// Enable debug logs during development
myket.enableDebugging(__DEV__);

// Connect to Myket Billing service
await myket.connect(RSA_PUBLIC_KEY);
this.fetchInventory();
} catch (error) {
console.error('Connection failed:', error);
}
}

componentWillUnmount() {
// Release native resources
myket.disconnect().catch(console.error);
}

fetchInventory = async () => {
try {
const inventory: Inventory = await myket.queryPurchaseProduct(true, ['premium_upgrade']);
console.log('Products:', inventory.allProducts);
} catch (error) {
console.error('Query failed:', error);
}
};

buyProduct = async (sku: string) => {
try {
const payload = 'my_unique_tracking_payload';
const purchase: Purchase = await myket.purchaseProduct(sku, payload);
Alert.alert('Purchase Successful', `Order ID: ${purchase.orderId}`);
} catch (error) {
Alert.alert('Purchase Failed', JSON.stringify(error));
}
};

render() {
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 16 }}>
<Button title="Upgrade to Premium" onPress={() => this.buyProduct('premium_upgrade')} />
</View>
);
}
}

export default ShopScreen;

API Reference​

myket.connect(rsaKey: string): Promise<string>​

Initializes the billing client and connects to the Myket billing service.

  • rsaKey: Your Base64 RSA public key obtained from the Myket developer console.
  • Returns: Promise resolving with a success message or rejecting if Myket is not installed or initialization fails.

myket.disconnect(): Promise<void>​

Unbinds from the billing service and releases native Android resources.

myket.enableDebugging(enable: boolean): void​

Enables or disables verbose billing debug logs in Android Logcat. Helpful during development.

myket.queryPurchaseProduct(querySkuDetails: boolean, moreSkus?: string[]): Promise<Inventory>​

Queries product details and existing purchases.

  • querySkuDetails: true to fetch detailed SKU information (price, title, description), false to query only purchases.
  • moreSkus: Array of SKU strings to fetch metadata for.
  • Returns: Promise resolving to an Inventory object containing allProducts and allPurchases.
Startup Recovery

Always query inventory at app launch or store entrance to detect and recover any purchases that were completed but not yet consumed due to network dropouts or app termination.

myket.purchaseProduct(sku: string, developerPayload?: string): Promise<Purchase>​

Launches the Myket payment screen for the specified product.

  • sku: The unique product identifier configured in the Myket panel.
  • developerPayload: An optional string to bind to the purchase for verification and replay-attack prevention.
  • Returns: Promise resolving to a Purchase object on successful payment.

myket.consumePurchase(purchase: Purchase): Promise<void>​

Consumes a previously purchased consumable product so it can be purchased again.

  • purchase: The Purchase object received from purchaseProduct or queryPurchaseProduct.
danger

Never consume non-consumable products. Consuming an item permanently removes it from the user's active inventory on Myket, destroying ownership.

useMyket(rsaKey: string): typeof myket​

React Hook that connects to Myket upon mounting and automatically disconnects on unmount.

TypeScript Definitions​

export interface Inventory {
allProducts: SkuDetails[];
allPurchases: Purchase[];
}

export interface SkuDetails {
itemType: string; // e.g. "inapp"
sku: string; // Product ID
type: string; // Product type
price: string; // Formatted price with currency
title: string; // Product title
description: string; // Product description
json: string; // Raw JSON string
}

export interface Purchase {
itemType: string; // e.g. "inapp"
orderId: string; // Unique Myket order ID
packageName: string; // Application package name
sku: string; // Product ID
purchaseTime: string; // Epoch timestamp in milliseconds
purchaseState: number; // 0 = Purchased
developerPayload: string; // Custom payload passed during purchase
token: string; // Purchase verification token
originalJson: string; // Raw purchase JSON
signature: string; // Cryptographic signature
}

Error Handling​

When an operation fails, the promise rejects with a JSON error string or an error object conforming to the schema below:

{
"message": "Error description message",
"responseCode": -1008
}

Common Error Scenarios​

Error / ReasonExplanationResolution
Myket application NotInstalled!The Myket store app is not installed on the user's device.Guide the user to install Myket before completing a purchase.
Public key is null or empty!Invalid or missing RSA public key passed to connect().Ensure you pass your valid Base64 public key from the Myket console.
Another async operation in progress!An ongoing billing operation has not finished yet (race condition).Await prior billing calls before initiating another purchase or query.
parameter 'sku' is null or empty!SKU was not provided to purchaseProduct().Ensure a non-empty SKU string is passed.
FetchError (responseCode)Specific billing response failure from the native billing helper.Inspect responseCode to handle specific market errors.

Security Recommendations​

  1. Verify Developer Payload: Always pass a unique, cryptographically random developerPayload when calling purchaseProduct(), and verify that purchase.developerPayload matches before granting entitlements.
tip

The developerPayload string must not exceed 800 characters.

  1. Server-Side Signature Verification: For sensitive digital assets, send purchase.originalJson and purchase.signature to your backend server to verify the cryptographic signature using your RSA public key.
  2. Consume Promptly: Immediately consume consumable items after crediting the user. If the app crashes between purchase and consumption, use queryPurchaseProduct() on next startup to detect and finish pending consumptions.
  3. Prevent Duplicate Delivery: Store each purchase.token in your server database to prevent replay attacks and duplicate fulfillment.

Sample Application​

A complete working sample application demonstrating in-app billing in React Native and Expo is available in the official GitHub repository:

Subscription support

The subscription flow in the sample does not mean Myket currently supports subscriptions. Myket does not currently support subscription products; do not use the subscription example for a real product.