Skip to main content

Release 6.6.0 - Android OpenIAP module

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

Short and sweet — this release brings the Android migration to openiap-google, tighter null-safety, and example stability fixes. We also rolled in the follow up maintenance work that shipped as 6.6.1 so you have a single snapshot of the 6.6 line.

Highlights

  • Android: Switched to openiap-google with coroutine-based connection gating and improved error logging.
  • Dart: More robust product parsing and safer generics to avoid UI stalls.
  • iOS: Added small helper methods (subscription group, App Store country, typed app transaction, histories).
  • Example: Guarded mounted across async paths to prevent setState after dispose.
  • Tooling: Pre-commit formatting matches CI; avoids staging untracked files.

Breaking Changes & Migration

We’re standardizing identifiers and tokens across platforms. Please review and migrate accordingly.

Transaction Identifier

  • Canonical field: purchase.id
  • Deprecated: transactionId (kept for compatibility, will be removed in a future release)
  • Impacted APIs:
    • Finish transaction on iOS now uses purchase.id internally. If you previously passed or stored transactionId, switch to purchase.id.

Before

await FlutterInappPurchase.instance.finishTransaction(
purchase, // relies on purchase.transactionId
);

After

await FlutterInappPurchase.instance.finishTransaction(
purchase, // uses purchase.id (canonical)
);
final txnId = purchase.id; // use this instead of transactionId

Purchase Token

  • Canonical field: purchaseToken
  • Deprecated: purchaseTokenAndroid (use purchaseToken everywhere)
  • Android subscription upgrade/downgrade:
    • When specifying the existing subscription, pass the token under purchaseToken.

Before

await requestPurchase(
'premium_monthly',
replacementModeAndroid: AndroidReplacementMode.withTimeProration.value,
purchaseTokenAndroid: existingSub.purchaseToken,
);

After

await FlutterInappPurchase.instance.requestPurchase(
options: RequestPurchase(
android: AndroidRequestSubscriptionProps(
skus: ['premium_monthly'],
subscriptionOffers: myOffers,
// unified token field
// when calling platform channel beneath, the key is `purchaseToken`
),
),
);
// For server validation, always send `purchase.purchaseToken` (Android)

Receipt Data (iOS)

  • Canonical verification input: purchaseToken (JWS representation from StoreKit 2)
  • Deprecated: transactionReceipt for server-side verification flows
  • App-side verification:
    • Use validateReceiptIOS(sku: ...) for local testing only. It returns a JWS as purchaseToken.
    • For production, send the returned purchaseToken to your server and verify with Apple public keys.

Before

// Legacy (server hits Apple verifyReceipt endpoint with raw receipt)
await http.post(url, body: {'receipt-data': purchase.transactionReceipt});

After

final res = await FlutterInappPurchase.instance.validateReceiptIOS(sku: productId);
if (res.isValid) {
await yourServer.verifyIOS(purchaseToken: res.purchaseToken); // JWS
}

Clarification

  • Use purchase.id as the canonical transaction identifier for logging, idempotency, and finishTransaction on iOS.
  • Receipt verification itself uses purchaseToken (JWS), not purchase.id or transactionId.

What to change in your code

  • Replace purchase.transactionId with purchase.id.
  • Replace purchaseTokenAndroid with purchaseToken.
  • For iOS server verification, stop sending transactionReceipt; switch to the JWS returned as purchaseToken.

Notes on Backward Compatibility

  • This release keeps transactionId and transactionReceipt readable to ease migration, but they are deprecated and will be removed in a future version.
  • Internally, finishing transactions on iOS already uses purchase.id.

6.6.1 follow-up

  • Completed docs/examples migration from PurchasedItemPurchase so the new types are consistent everywhere.
  • Added unified helpers that callers had to bolt on manually:
    • Purchase.quantity (iOS quantity, defaults to 1 on Android)
    • Purchase.isAutoRenewing
  • Hardened product parsing so Product.id is always populated regardless of store quirks.
  • Example UX tweaks: no alert for user-cancelled purchases; avoids returning inside finally blocks.
  • Analyzer clean-up: targeted ignores for legacy symbols, opacity lints, and duplicate library directives.