Skip to main content

2.7.0 - New Platform API & Google Play Billing v8

ยท 5 min read
Hyo
Expo IAP Maintainer

We're excited to announce expo-iap v2.7.0! This release includes a cleaner platform-specific API for handling purchases and full support for Google Play Billing Library v8.0.0.

๐ŸŽฏ New Platform-Specific APIโ€‹

The Problemโ€‹

Previously, developers had to write conditional logic to handle platform differences:

// Old approach - requires platform checks
if (Platform.OS === 'ios') {
await requestPurchase({
request: {sku: productId},
});
} else {
await requestPurchase({
request: {skus: [productId]},
});
}

This approach had several issues:

  • Required manual platform checks
  • Easy to miss platform-specific parameters
  • TypeScript couldn't provide proper platform-specific type hints

The Solution: Platform-Specific Parametersโ€‹

The new API introduces a cleaner structure with explicit platform parameters:

// New approach - clear platform separation
await requestPurchase({
request: {
ios: {
sku: productId,
appAccountToken: 'user-123',
},
android: {
skus: [productId],
obfuscatedAccountIdAndroid: 'user-123',
},
},
});

Key Benefitsโ€‹

  1. Better Type Safety: TypeScript now provides accurate autocompletion for each platform's specific parameters
  2. Clearer Code Structure: Platform-specific logic is clearly separated
  3. Backward Compatibility: The old API still works, so you can migrate at your own pace

Migration Examplesโ€‹

Basic Product Purchaseโ€‹

Before:

const buyProduct = async (productId: string) => {
if (Platform.OS === 'ios') {
await requestPurchase({
request: {sku: productId},
});
} else {
await requestPurchase({
request: {skus: [productId]},
});
}
};

After:

const buyProduct = async (productId: string) => {
await requestPurchase({
request: {
ios: {sku: productId},
android: {skus: [productId]},
},
});
};

Subscription Purchaseโ€‹

Before:

const buySubscription = async (subId: string) => {
if (Platform.OS === 'ios') {
await requestPurchase({
request: {
sku: subId,
appAccountToken: 'user-123',
},
type: 'subs',
});
} else {
const subscription = subscriptions.find((s) => s.id === subId);
const offer = subscription?.subscriptionOfferDetails?.[0];

await requestPurchase({
request: {
skus: [subId],
subscriptionOffers: [
{
sku: subId,
offerToken: offer?.offerToken || '',
},
],
},
type: 'subs',
});
}
};

After:

const buySubscription = async (subId: string) => {
const subscription = subscriptions.find((s) => s.id === subId);

await requestPurchase({
request: {
ios: {
sku: subId,
appAccountToken: 'user-123',
},
android: {
skus: [subId],
subscriptionOffers:
subscription?.subscriptionOfferDetails?.map((offer) => ({
sku: subId,
offerToken: offer.offerToken,
})) || [],
},
},
type: 'subs',
});
};

Platform-Specific Parametersโ€‹

iOS Parametersโ€‹

{
ios: {
sku: string; // Required: Product SKU
andDangerouslyFinishTransactionAutomatically?: boolean;
appAccountToken?: string; // For server validation
quantity?: number; // For bulk purchases
withOffer?: PaymentDiscount; // For discounts
}
}

Android Parametersโ€‹

{
android: {
skus: string[]; // Required: Product SKUs
obfuscatedAccountIdAndroid?: string; // User identifier
obfuscatedProfileIdAndroid?: string; // Profile identifier
isOfferPersonalized?: boolean; // For personalized pricing

// For subscriptions only:
subscriptionOffers?: Array<{
sku: string;
offerToken: string;
}>;
purchaseTokenAndroid?: string; // For upgrades/downgrades
replacementModeAndroid?: number; // Proration mode
}
}

๐Ÿ”„ Deprecation Notice: requestSubscriptionโ€‹

Starting from v2.7.0, requestSubscription is deprecated in favor of using requestPurchase with type: 'subs'. This unifies our API and makes the codebase cleaner.

Migration example:

// Old way (deprecated)
await requestSubscription({
sku: subscriptionId,
skus: [subscriptionId],
});

// New way (recommended)
await requestPurchase({
request: {
ios: {sku: subscriptionId},
android: {skus: [subscriptionId]},
},
type: 'subs',
});

๐Ÿค– Google Play Billing Library v8.0.0 Supportโ€‹

We've updated to Google Play Billing Library v8.0.0 to meet Google Play's latest requirements. For more details on the migration, see Google's official migration guide.

Key Changesโ€‹

1. Updated Dependenciesโ€‹

The Android module now uses the latest Google Play Billing Library:

implementation "com.android.billingclient:billing-ktx:8.0.0"

2. Removed Deprecated Methodsโ€‹

getPurchaseHistory is no longer available on Android

Google Play Billing Library v8 has removed the queryPurchaseHistoryAsync() method. The getPurchaseHistories() function will now return an empty array on Android with a console warning:

// Before v8
const history = await getPurchaseHistories(); // Returns purchase history

// After v8
const history = await getPurchaseHistories(); // Returns [] on Android with warning
// Use getAvailablePurchases() instead for active purchases

3. Automatic Service Reconnectionโ€‹

The library now includes automatic service reconnection support, improving reliability when the billing service disconnects unexpectedly.

4. Sub-Response Codesโ€‹

The library now provides more detailed error information through sub-response codes:

// Error object now includes sub-response codes
{
responseCode: 6, // ERROR
debugMessage: "Error processing purchase",
subResponseCode: 1, // PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS
subResponseMessage: "Payment declined due to insufficient funds"
}

Breaking Changes Summaryโ€‹

  1. getPurchaseHistory() removed - Use getAvailablePurchases() instead
  2. querySkuDetailsAsync() removed - Already migrated to queryProductDetailsAsync()
  3. enablePendingPurchases() signature changed - Now requires PendingPurchasesParams
  4. queryPurchasesAsync(skuType) removed - Use queryPurchasesAsync(QueryPurchasesParams) instead

Migration Guide for getPurchaseHistoryโ€‹

If you're using getPurchaseHistory() or getPurchaseHistories() on Android:

// Old approach
const history = await getPurchaseHistories();

// New approach - use getAvailablePurchases for active purchases
const activePurchases = await getAvailablePurchases();

๐Ÿš€ Best Practicesโ€‹

  1. Handle Platform Availability: Not all parameters need to be set for both platforms

    await requestPurchase({
    request: {
    ios: {sku: productId},
    android: {skus: [productId]},
    },
    });
  2. Use TypeScript: Let TypeScript guide you with autocompletion

    await requestPurchase({
    request: {
    ios: {
    // TypeScript will show iOS-specific options
    },
    android: {
    // TypeScript will show Android-specific options
    },
    },
    });
  3. Gradual Migration: The old API still works, migrate at your own pace

๐Ÿ“ฆ Upgradingโ€‹

To upgrade to version 2.7.0:

npm install expo-iap@2.7.0
# or
yarn add expo-iap@2.7.0
# or
bun add expo-iap@2.7.0

Requirementsโ€‹

  • Android Gradle Plugin 4.0 or higher
  • Kotlin 1.6 or higher
  • JVM target 17 (automatically configured)
  • Google Play's latest billing requirements (deadline: August 31, 2025)

๐ŸŽ‰ Benefitsโ€‹

  • Cleaner Code: No more Platform.OS checks in your purchase logic
  • Better Type Safety: Platform-specific TypeScript hints
  • Future-Proof: Compliance with Google Play's latest requirements
  • Improved Reliability: Automatic service reconnection on Android
  • Enhanced Error Handling: Detailed sub-response codes

๐Ÿ“š Documentationโ€‹

๐Ÿ’ฌ Feedbackโ€‹

If you encounter any issues with this update, please open an issue on our GitHub repository.

Happy coding! ๐Ÿš€