Skip to main content

8.2.2 - Win-Back Offers, Product Status, and Suspended Subscriptions

· 3 min read
Hyo
Maintainer of flutter_inapp_purchase & expo-iap

v8.2.2 syncs with OpenIAP v1.3.14 bringing Win-Back offers for iOS 18+, product-level status codes for Android 8.0+, and suspended subscription support.

New Features

Win-Back Offers (iOS 18+)

Win-back offers help re-engage churned subscribers with discounts or free trials:

// Request subscription with win-back offer
final props = RequestSubscriptionIosProps(
sku: 'premium_monthly',
winBackOffer: WinBackOfferInputIOS(offerId: 'winback_50_off'),
);

JWS Promotional Offers (iOS 15+, WWDC 2025)

New simplified JWS format for promotional offers, back-deployed to iOS 15:

final props = RequestSubscriptionIosProps(
sku: 'premium_monthly',
promotionalOfferJWS: PromotionalOfferJWSInputIOS(
jws: 'header.payload.signature',
offerId: 'promo_offer_id',
),
);

Introductory Offer Eligibility Override (iOS 15+)

Override system-determined introductory offer eligibility:

final props = RequestSubscriptionIosProps(
sku: 'premium_monthly',
introductoryOfferEligibility: true, // Force eligible
);

Product Status Codes (Android 8.0+)

Products now include status codes explaining fetch results:

final products = await iap.fetchProducts(skus: ['sku1', 'sku2']);
for (final product in products) {
if (product is ProductAndroid) {
switch (product.productStatusAndroid) {
case ProductStatusAndroid.Ok:
// Product fetched successfully
break;
case ProductStatusAndroid.NotFound:
// SKU doesn't exist in Play Console
break;
case ProductStatusAndroid.NoOffersAvailable:
// User not eligible for any offers
break;
case ProductStatusAndroid.Unknown:
// Unknown error
break;
}
}
}

Suspended Subscriptions (Android 8.1+)

Include suspended subscriptions when restoring purchases:

// Get all purchases including suspended ones
final purchases = await iap.getAvailablePurchases(
includeSuspendedAndroid: true,
);

// Check suspension status
for (final purchase in purchases) {
if (purchase is PurchaseAndroid && purchase.isSuspendedAndroid == true) {
// Subscription is suspended - do NOT grant entitlements
// Direct user to subscription center to resolve payment issues
}
}

Sub-Response Codes (Android 8.0+)

More granular error information for billing operations via BillingResultAndroid:

// BillingResultAndroid contains sub-response codes for detailed error info
// Available when handling billing operation results

// SubResponseCodeAndroid enum values:
// - NoApplicableSubResponseCode: No specific sub-response
// - PaymentDeclinedDueToInsufficientFunds: Payment method has insufficient funds
// - UserIneligible: User doesn't meet offer eligibility requirements

// Example: Checking sub-response code from billing result
void handleBillingResult(BillingResultAndroid result) {
if (result.responseCode != 0) {
// Error occurred
switch (result.subResponseCode) {
case SubResponseCodeAndroid.PaymentDeclinedDueToInsufficientFunds:
// Show user-friendly message about insufficient funds
print('Payment failed: insufficient funds');
break;
case SubResponseCodeAndroid.UserIneligible:
// User doesn't qualify for this offer
print('You are not eligible for this offer');
break;
default:
print('Error: ${result.debugMessage}');
}
}
}

Builder Support for New iOS Features

The RequestSubscriptionIosBuilder now supports all new iOS 18+ features:

final builder = RequestSubscriptionBuilder();
builder.ios
..sku = 'premium_monthly'
..winBackOffer = WinBackOfferInputIOS(offerId: 'winback_50_off')
..promotionalOfferJWS = PromotionalOfferJWSInputIOS(
jws: 'header.payload.signature',
offerId: 'promo_offer_id',
)
..introductoryOfferEligibility = true;

await iap.requestPurchase(builder.build());

New Types

Enums

  • ProductStatusAndroid - Product fetch status (Ok, NotFound, NoOffersAvailable, Unknown)
  • SubResponseCodeAndroid - Detailed error codes for purchases
  • SubscriptionOfferTypeIOS.WinBack - New offer type for win-back offers

Classes

  • WinBackOfferInputIOS - Win-back offer configuration
  • PromotionalOfferJWSInputIOS - JWS promotional offer input
  • BillingResultAndroid - Extended billing result with sub-response code

Breaking Changes

Subscription-Only Fields Moved (iOS)

The following fields have been removed from RequestPurchaseIosProps and are now only available in RequestSubscriptionIosProps:

  • winBackOffer
  • promotionalOfferJWS
  • introductoryOfferEligibility

This is a type-safety improvement - these fields only apply to subscription purchases.

Dependencies

PackagePreviousCurrent
openiap-gql1.3.131.3.14
openiap-google1.3.231.3.25
openiap-apple1.3.101.3.12

References