3.3.0 - Billing Programs API & One-Time Product Discounts
Expo IAP 3.3.0 brings Google Play Billing Library 8.2.0 features including the new Billing Programs API for external billing and one-time product discount support from Billing Library 7.0+.
New Features
1. Billing Programs API (Android 8.2.0+)
New methods for handling external billing programs:
isBillingProgramAvailableAndroid(program)- Check if a billing program is available for the usercreateBillingProgramReportingDetailsAndroid(program)- Get external transaction token for reportinglaunchExternalLinkAndroid(params)- Launch external link for billing programs
Supported Programs:
external-offer- External offer programsexternal-content-link- External content link programs
import {
isBillingProgramAvailableAndroid,
createBillingProgramReportingDetailsAndroid,
launchExternalLinkAndroid,
} from 'expo-iap';
// Step 1: Check availability
const result = await isBillingProgramAvailableAndroid('external-offer');
if (!result.isAvailable) return;
// Step 2: Launch external link
await launchExternalLinkAndroid({
billingProgram: 'external-offer',
launchMode: 'launch-in-external-browser-or-app',
linkType: 'link-to-digital-content-offer',
linkUri: 'https://your-payment-site.com',
});
// Step 3: Get reporting token
const details = await createBillingProgramReportingDetailsAndroid('external-offer');
// Report token to Google Play within 24 hours
2. One-Time Product Discounts (Android 7.0+)
oneTimePurchaseOfferDetailsAndroid is now an array to support multiple offers with discounts.
New Fields:
offerId- Unique offer identifierfullPriceMicros- Original price before discountdiscountDisplayInfo- Percentage and amount discount infolimitedQuantityInfo- Purchase quantity limitsvalidTimeWindow- Offer validity periodpreorderDetailsAndroid- Preorder release info (8.1.0+)rentalDetailsAndroid- Rental period info
3. Purchase Suspension Status (Android 8.1.0+)
- Added
isSuspendedAndroidfield toPurchaseAndroidtype - Indicates when a subscription is suspended due to payment issues
- Users should be directed to fix payment method when suspended
4. Subscription Replacement Parameters (Android 8.1.0+)
- Added
SubscriptionProductReplacementParamsAndroidfor item-level replacement - Added
SubscriptionReplacementModeAndroidenum withkeep-existingmode - Enables more granular control over subscription upgrades/downgrades
Breaking Changes
oneTimePurchaseOfferDetailsAndroid Type Change
Before (3.2.x):
oneTimePurchaseOfferDetailsAndroid?: ProductAndroidOneTimePurchaseOfferDetail | null;
After (3.3.0):
oneTimePurchaseOfferDetailsAndroid?: ProductAndroidOneTimePurchaseOfferDetail[] | null;
Migration: Update code that accesses this field to handle arrays:
// Before
const price = product.oneTimePurchaseOfferDetailsAndroid?.formattedPrice;
// After
const offers = product.oneTimePurchaseOfferDetailsAndroid;
const price = offers?.[0]?.formattedPrice;
Deprecated Methods
The following methods are deprecated in favor of the new Billing Programs API:
checkAlternativeBillingAvailabilityAndroid()- UseisBillingProgramAvailableAndroid()insteadshowAlternativeBillingDialogAndroid()- UselaunchExternalLinkAndroid()insteadcreateAlternativeBillingTokenAndroid()- UsecreateBillingProgramReportingDetailsAndroid()insteadreplacementModeAndroidparameter - UsesubscriptionProductReplacementParamsinstead
New Types
Billing Programs API Types
// Billing program type
type BillingProgramAndroid = 'unspecified' | 'external-content-link' | 'external-offer';
// Launch mode for external links
type ExternalLinkLaunchModeAndroid =
| 'unspecified'
| 'launch-in-external-browser-or-app'
| 'caller-will-launch-link';
// Link type for external links
type ExternalLinkTypeAndroid =
| 'unspecified'
| 'link-to-digital-content-offer'
| 'link-to-app-download';
// Parameters for launching external links
interface LaunchExternalLinkParamsAndroid {
billingProgram: BillingProgramAndroid;
launchMode: ExternalLinkLaunchModeAndroid;
linkType: ExternalLinkTypeAndroid;
linkUri: string;
}
// Result of checking billing program availability
interface BillingProgramAvailabilityResultAndroid {
billingProgram: BillingProgramAndroid;
isAvailable: boolean;
}
// Reporting details for external transactions
interface BillingProgramReportingDetailsAndroid {
billingProgram: BillingProgramAndroid;
externalTransactionToken: string;
}
One-Time Product Discount Types
// Discount amount details
interface DiscountAmountAndroid {
discountAmountMicros: string;
formattedDiscountAmount: string;
}
// Discount display information
interface DiscountDisplayInfoAndroid {
percentageDiscount?: number | null;
discountAmount?: DiscountAmountAndroid | null;
}
// Limited quantity information
interface LimitedQuantityInfoAndroid {
maximumQuantity: number;
remainingQuantity: number;
}
// Offer validity period
interface ValidTimeWindowAndroid {
startTimeMillis: string;
endTimeMillis: string;
}
// Pre-order details (8.1.0+)
interface PreorderDetailsAndroid {
preorderReleaseTimeMillis: string;
preorderPresaleEndTimeMillis: string;
}
// Rental details
interface RentalDetailsAndroid {
rentalPeriod: string;
rentalExpirationPeriod?: string | null;
}
Subscription Replacement Types (8.1.0+)
// Product-level replacement parameters
interface SubscriptionProductReplacementParamsAndroid {
oldProductId: string;
replacementMode: SubscriptionReplacementModeAndroid;
}
// Replacement mode options
type SubscriptionReplacementModeAndroid =
| 'unknown-replacement-mode'
| 'with-time-proration'
| 'charge-prorated-price'
| 'charge-full-price'
| 'without-proration'
| 'deferred'
| 'keep-existing';
verifyPurchase API Update
The verifyPurchase() function now uses platform-specific options with apple, google, and horizon fields:
import { verifyPurchase } from 'expo-iap';
// All platform options can be provided - the library handles platform detection internally
const result = await verifyPurchase({
// iOS App Store
apple: { sku: productId },
// Google Play Store
google: {
sku: productId,
packageName: 'com.example.app',
purchaseToken: purchase.purchaseToken!, // Required
accessToken: await getAccessTokenFromServer(), // ⚠️ Must be fetched from backend
isSub: true,
},
// Meta Horizon (Quest)
// horizon: { sku, userId, accessToken }
});
Breaking Change: The previous sku root field and androidOptions have been replaced with apple, google, and horizon fields containing platform-specific options.
Type System Updates
This release also brings important type refinements:
- New
IapStoretype:'unknown' | 'apple' | 'google' | 'horizon' storefield replacesplatformon Product and Purchase types- Request parameters now use
apple/googlekeys (legacyios/androidstill supported but deprecated) VerifyPurchasePropsnow usesapple,google,horizonfields instead of root-levelsku
OpenIAP Updates
- openiap-google: 1.3.10 -> 1.3.14
- openiap-gql: 1.3.0 -> 1.3.4
- openiap-apple: 1.3.0 -> 1.3.2
Installation
bun add expo-iap@3.3.0
# or
npm install expo-iap@3.3.0
# or
yarn add expo-iap@3.3.0
References
- Google Play Billing Library 8.2.0 Release Notes
- Google Play Billing Programs
- Google Play One-Time Product Discounts
Questions or feedback? Reach out via GitHub issues.
