Myket In-App Purchase Guide for React Native
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/awaitsyntax. - React Hook Support: Provides
useMyketfor 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:
- A Myket Developer Account.
- An application registered in your Myket developer panel with a reserved
applicationId(package name). - Products (SKUs) defined in your application console on Myket.
- Your app's Base64-encoded RSA Public Key copied from the Myket developer console.
- The Myket App Store installed on the test device or emulator.
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
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
1. Functional Components (useMyket Hook) (Recommended)
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:trueto fetch detailed SKU information (price, title, description),falseto query only purchases.moreSkus: Array of SKU strings to fetch metadata for.- Returns: Promise resolving to an
Inventoryobject containingallProductsandallPurchases.
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
Purchaseobject on successful payment.
myket.consumePurchase(purchase: Purchase): Promise<void>
Consumes a previously purchased consumable product so it can be purchased again.
purchase: ThePurchaseobject received frompurchaseProductorqueryPurchaseProduct.
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 / Reason | Explanation | Resolution |
|---|---|---|
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
- Verify Developer Payload: Always pass a unique, cryptographically random
developerPayloadwhen callingpurchaseProduct(), and verify thatpurchase.developerPayloadmatches before granting entitlements.
The developerPayload string must not exceed 800 characters.
- Server-Side Signature Verification: For sensitive digital assets, send
purchase.originalJsonandpurchase.signatureto your backend server to verify the cryptographic signature using your RSA public key. - 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. - Prevent Duplicate Delivery: Store each
purchase.tokenin 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:
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.