8.2.2 - Win-Back Offers, Product Status, and Suspended Subscriptions
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 purchasesSubscriptionOfferTypeIOS.WinBack- New offer type for win-back offers
Classes
WinBackOfferInputIOS- Win-back offer configurationPromotionalOfferJWSInputIOS- JWS promotional offer inputBillingResultAndroid- 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:
winBackOfferpromotionalOfferJWSintroductoryOfferEligibility
This is a type-safety improvement - these fields only apply to subscription purchases.
Dependencies
| Package | Previous | Current |
|---|---|---|
| openiap-gql | 1.3.13 | 1.3.14 |
| openiap-google | 1.3.23 | 1.3.25 |
| openiap-apple | 1.3.10 | 1.3.12 |
