Implementation of Android Billing Client v8.0
npm install react-native-billing-sdk

A React Native library that exposes the native Google Play Billing Library functionality, implementing the latest Android Billing Client v8.0 for handling in-app purchases and subscriptions.
> β Latest Version: This library uses Billing Library 8.0, which exceeds Google's requirement for version 7+ by the August 31, 2025 deadline and includes the latest features and security improvements.
- π One-Time Products - Handle consumable and non-consumable in-app purchases
- π Subscriptions - Complete subscription lifecycle with grace periods, hold states, and renewals
- π± Latest Android Billing - Direct access to Google Play Billing Library v8.0 APIs
- π― TypeScript Support - Full type safety with comprehensive TypeScript definitions
- β‘ Real-Time Updates - Purchase state changes and billing service events
- π§ Simple Integration - React Native wrapper around native billing functionality
- β³ Pending Transactions - Support for delayed payment methods and pending states
- π Multi-Quantity - Handle multiple quantities of the same product in one purchase
| Platform | Status |
|----------|--------|
| Android | β
Full support (Billing Client v8.0) |
| iOS | β Not implemented yet |
``bash`
npm install react-native-billing-sdkor
yarn add react-native-billing-sdk
1. Add BILLING permission to your android/app/src/main/AndroidManifest.xml:
`xml`
2. Configure your app in Google Play Console with in-app products/subscriptions.
iOS support is not yet implemented. The library will reject calls on iOS with "Unsupported platform" error.
Following the official Google Play Billing flow, this library supports the complete purchase lifecycle:
1. Show Products - Query and display available products to users
2. Launch Purchase - Initiate the billing flow for user acceptance
3. Verify Purchase - Validate purchases on your server (recommended)
4. Deliver Content - Provide the purchased content to the user
5. Acknowledge Purchase - Confirm delivery for non-consumables or consume for consumables
Subscriptions can transition through various states as defined in the Android Billing documentation:
- Active - User has access and subscription is in good standing
- Cancelled - User cancelled but retains access until expiration
- Grace Period - Payment issue occurred, user retains access while Google retries payment
- On Hold - Payment issue occurred, user loses access while Google retries payment
- Paused - User paused their subscription access
`javascript
import { BillingSdkAndroid, BillingSdkAndroidConstants } from 'react-native-billing-sdk';
// Initialize billing connection
const initializeBilling = async () => {
try {
await BillingSdkAndroid.startConnection();
console.log('Billing client connected');
} catch (error) {
console.error('Failed to connect billing client:', error);
}
};
// Set up purchase listener
const removePurchaseListener = BillingSdkAndroid.setPurchaseUpdatedListener(
({ responseCode, purchases, debugMessage }) => {
if (responseCode === BillingSdkAndroidConstants.ResponseCode.OK && purchases) {
purchases.forEach(purchase => {
console.log('Purchase received:', purchase);
// Handle purchase (acknowledge, consume, etc.)
});
} else {
console.error('Purchase failed:', debugMessage);
}
}
);
// Clean up listeners when component unmounts
return () => {
removePurchaseListener();
};
`
`javascriptProduct: ${product.title}
// Query in-app products
const queryProducts = async () => {
try {
const products = await BillingSdkAndroid.queryProductDetails(
['your_product_id_1', 'your_product_id_2'],
BillingSdkAndroidConstants.ProductType.INAPP
);
products.forEach(product => {
console.log();Price: ${product.oneTimePurchaseOfferDetails?.formattedPrice}
console.log();
});
} catch (error) {
console.error('Failed to query products:', error);
}
};
// Query subscriptions
const querySubscriptions = async () => {
try {
const subscriptions = await BillingSdkAndroid.queryProductDetails(
['your_subscription_id'],
BillingSdkAndroidConstants.ProductType.SUBS
);
subscriptions.forEach(subscription => {
console.log(Subscription: ${subscription.title});Offer: ${offer.pricingPhases[0]?.formattedPrice}
subscription.subscriptionOfferDetails?.forEach(offer => {
console.log();`
});
});
} catch (error) {
console.error('Failed to query subscriptions:', error);
}
};
`javascript
// Purchase an in-app product
const purchaseProduct = async (productId: string) => {
try {
await BillingSdkAndroid.launchBillingFlow(productId);
// Purchase result will be delivered to setPurchaseUpdatedListener
} catch (error) {
console.error('Failed to launch billing flow:', error);
}
};
// Purchase a subscription with specific offer
const purchaseSubscription = async (productId: string, offerToken: string) => {
try {
await BillingSdkAndroid.launchBillingFlow(productId, offerToken);
} catch (error) {
console.error('Failed to launch billing flow:', error);
}
};
// Upgrade/downgrade subscription
const changeSubscription = async (
newProductId: string,
newOfferToken: string,
oldPurchaseToken: string
) => {
try {
await BillingSdkAndroid.launchBillingFlow(
newProductId,
newOfferToken,
oldPurchaseToken,
BillingSdkAndroidConstants.SubscriptionReplacementMode.CHARGE_PRORATED_PRICE
);
} catch (error) {
console.error('Failed to change subscription:', error);
}
};
`
`javascript
// Acknowledge a purchase (required for non-consumable products)
const acknowledgePurchase = async (purchaseToken: string) => {
try {
await BillingSdkAndroid.acknowledgePurchase(purchaseToken);
console.log('Purchase acknowledged');
} catch (error) {
console.error('Failed to acknowledge purchase:', error);
}
};
// Consume a purchase (for consumable products)
const consumePurchase = async (purchaseToken: string) => {
try {
await BillingSdkAndroid.consume(purchaseToken);
console.log('Purchase consumed');
} catch (error) {
console.error('Failed to consume purchase:', error);
}
};
// Query active purchases
const queryActivePurchases = async () => {
try {
const purchases = await BillingSdkAndroid.queryPurchases(
BillingSdkAndroidConstants.ProductType.INAPP
);
purchases.forEach(purchase => {
console.log(Active purchase: ${purchase.productId});
if (!purchase.isAcknowledged) {
// Acknowledge if needed
acknowledgePurchase(purchase.purchaseToken);
}
});
} catch (error) {
console.error('Failed to query purchases:', error);
}
};
// Query purchase history
const queryPurchaseHistory = async () => {
try {
const history = await BillingSdkAndroid.queryPurchaseHistory(
BillingSdkAndroidConstants.ProductType.INAPP
);
history?.forEach(record => {
console.log(Past purchase: ${record.productId} at ${record.purchaseTime});`
});
} catch (error) {
console.error('Failed to query purchase history:', error);
}
};
As per Google's documentation, some payment methods may result in pending transactions that complete asynchronously:
`javascript`
// Handle pending purchases in your purchase listener
const removePurchaseListener = BillingSdkAndroid.setPurchaseUpdatedListener(
({ responseCode, purchases, debugMessage }) => {
if (responseCode === BillingSdkAndroidConstants.ResponseCode.OK && purchases) {
purchases.forEach(purchase => {
switch (purchase.purchaseState) {
case BillingSdkAndroidConstants.PurchaseState.PURCHASED:
// Purchase completed - deliver content and acknowledge
console.log('Purchase completed:', purchase.productId);
deliverContent(purchase);
if (!purchase.isAcknowledged) {
BillingSdkAndroid.acknowledgePurchase(purchase.purchaseToken);
}
break;
case BillingSdkAndroidConstants.PurchaseState.PENDING:
// Payment is pending - inform user and wait
console.log('Purchase pending:', purchase.productId);
showPendingPaymentUI(purchase);
// Do NOT acknowledge pending purchases
break;
case BillingSdkAndroidConstants.PurchaseState.UNSPECIFIED_STATE:
console.log('Unknown purchase state:', purchase.productId);
break;
}
});
}
}
);
> Important: Only acknowledge purchases when purchaseState is PURCHASED. The 3-day acknowledgment window begins only when the purchase transitions from PENDING to PURCHASED.
`javascript
// Check connection state
const checkConnection = async () => {
try {
const state = await BillingSdkAndroid.getConnectionState();
console.log('Connection state:', state);
if (state !== BillingSdkAndroidConstants.ConnectionState.CONNECTED) {
await BillingSdkAndroid.startConnection();
}
} catch (error) {
console.error('Connection check failed:', error);
}
};
// Handle disconnection
const removeDisconnectionListener = BillingSdkAndroid.setBillingServiceDisconnectedListener(() => {
console.log('Billing service disconnected, attempting to reconnect...');
BillingSdkAndroid.startConnection();
});
// End connection when done
const cleanup = async () => {
await BillingSdkAndroid.endConnection();
removeDisconnectionListener();
};
`
| Method | Description | Parameters | Returns |
|--------|-------------|------------|---------|
| startConnection() | Establishes connection to Google Play Billing | None | Promise |endConnection()
| | Terminates the billing connection | None | Promise |getConnectionState()
| | Gets current connection state | None | Promise |queryProductDetails()
| | Retrieves product/subscription details | productIds: string[], productType: ProductType | Promise |launchBillingFlow()
| | Initiates purchase flow | productId: string, offerToken?: string, oldPurchaseToken?: string, replacementMode?: SubscriptionReplacementMode | Promise |acknowledgePurchase()
| | Acknowledges a purchase | purchaseToken: string | Promise |consume()
| | Consumes a purchase | purchaseToken: string | Promise |queryPurchases()
| | Gets active purchases | productType: ProductType | Promise |queryPurchaseHistory()
| | Gets purchase history | productType: ProductType | Promise |
| Listener | Description | Callback Parameters |
|----------|-------------|-------------------|
| setPurchaseUpdatedListener() | Listens for purchase updates | { responseCode, purchases, debugMessage } |setBillingServiceDisconnectedListener()
| | Listens for service disconnection | None |
#### ProductType
- INAPP - In-app productsSUBS
- - Subscriptions
#### PurchaseState
- PURCHASED - Purchase completedPENDING
- - Purchase pendingUNSPECIFIED_STATE
- - Unknown state
#### ConnectionState
- DISCONNECTED - Not connectedCONNECTING
- - ConnectingCONNECTED
- - ConnectedCLOSED
- - Connection closed
#### ResponseCode
- OK - SuccessUSER_CANCELLED
- - User cancelledSERVICE_UNAVAILABLE
- - Service unavailableBILLING_UNAVAILABLE
- - Billing unavailableITEM_UNAVAILABLE
- - Item unavailableDEVELOPER_ERROR
- - Developer errorERROR
- - General error
- And more...
This library exposes the native Google Play Billing response codes. All API calls should be wrapped in try-catch blocks to handle various error conditions:
`javascript``
try {
await BillingSdkAndroid.startConnection();
} catch (error) {
switch (error.code) {
case BillingSdkAndroidConstants.ResponseCode.BILLING_UNAVAILABLE:
console.log('Billing not available on this device');
break;
case BillingSdkAndroidConstants.ResponseCode.SERVICE_UNAVAILABLE:
console.log('Google Play Store service is unavailable');
break;
case BillingSdkAndroidConstants.ResponseCode.USER_CANCELLED:
console.log('User cancelled the purchase');
break;
case BillingSdkAndroidConstants.ResponseCode.ITEM_ALREADY_OWNED:
console.log('User already owns this item');
break;
case BillingSdkAndroidConstants.ResponseCode.DEVELOPER_ERROR:
console.error('Developer error - check your configuration');
break;
default:
console.error('Billing error:', error.message);
}
}
- BILLING_UNAVAILABLE - Device doesn't support billing (e.g., emulator without Google Play)
- SERVICE_UNAVAILABLE - Google Play Store is not available or outdated
- ITEM_UNAVAILABLE - Product ID not found in Google Play Console
- DEVELOPER_ERROR - App not properly configured in Google Play Console
Follow the official testing guidelines from Google:
1. Google Play Console Test Tracks - Use internal/closed testing tracks
2. Test Accounts - Add test accounts in Google Play Console
3. License Testing - Test with special license testing accounts
4. Test Products - Create test products that won't charge real money
You can test pending transactions using license testing with special test payment methods that simulate delayed payment completion or cancellation.
- Use test credit cards provided by Google for different scenarios
- Test various payment methods including those that result in pending states
- Verify proper handling of declined payments and cancellations
> Important: Always test your complete purchase flow including server-side verification before releasing to production.
- React Native >= 0.60
- Android API level 21+
- Google Play Billing Library 8.0+
- Node.js >= 16.0.0
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
This library is a React Native wrapper around the native Google Play Billing Library. For comprehensive information about billing concepts, best practices, and advanced features, refer to the official documentation:
- π Google Play Billing Library Integration
- β οΈ Billing Error Codes and Handling
- π§ͺ Testing In-App Purchases
- π Subscription Lifecycle Management
- ποΈ Server-Side Verification
- π Play Console Product Setup
- π Report bugs
- π‘ Request features
- π View documentation
- π Google Play Billing Docs
Kaan ΓembertaΕ - @kaancembertas
---
Made with β€οΈ for the React Native community