Skip to main content

3.3.0 - Billing Programs API & One-Time Product Discounts

· 4 min read
Hyo
Expo IAP Maintainer

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 user
  • createBillingProgramReportingDetailsAndroid(program) - Get external transaction token for reporting
  • launchExternalLinkAndroid(params) - Launch external link for billing programs

Supported Programs:

  • external-offer - External offer programs
  • external-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 identifier
  • fullPriceMicros - Original price before discount
  • discountDisplayInfo - Percentage and amount discount info
  • limitedQuantityInfo - Purchase quantity limits
  • validTimeWindow - Offer validity period
  • preorderDetailsAndroid - Preorder release info (8.1.0+)
  • rentalDetailsAndroid - Rental period info

3. Purchase Suspension Status (Android 8.1.0+)

  • Added isSuspendedAndroid field to PurchaseAndroid type
  • 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 SubscriptionProductReplacementParamsAndroid for item-level replacement
  • Added SubscriptionReplacementModeAndroid enum with keep-existing mode
  • 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() - Use isBillingProgramAvailableAndroid() instead
  • showAlternativeBillingDialogAndroid() - Use launchExternalLinkAndroid() instead
  • createAlternativeBillingTokenAndroid() - Use createBillingProgramReportingDetailsAndroid() instead
  • replacementModeAndroid parameter - Use subscriptionProductReplacementParams instead

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 IapStore type: 'unknown' | 'apple' | 'google' | 'horizon'
  • store field replaces platform on Product and Purchase types
  • Request parameters now use apple/google keys (legacy ios/android still supported but deprecated)
  • VerifyPurchaseProps now uses apple, google, horizon fields instead of root-level sku

OpenIAP Updates

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

Questions or feedback? Reach out via GitHub issues.