What platform are you targeting? And what version? iOS, testing in Sandbox on a physical device.
What version of Xcode are you using? [Xcode __]
What version of the OS are you testing on? iOS 18 on iPhone 15 pro.
What specific API are you using?
StoreKit 2 via Flutter’s in_app_purchase
plugin (Dart), which uses in_app_purchase_storekit
under the hood.
What are the exact steps you took?
-
In App Store Connect, I created several Consumable IAPs (status “Ready to Submit”). Example product IDs:
USD3.99TenMinuteCoffeePlan
(Consumable)USD24.99OneHourDinnerPlan
(Consumable)USD14.99InviteAFriendAsGenie
(Consumable)
-
Signed in as a Sandbox tester on device (Settings → App Store → Sandbox Account).
-
App queries products with
InAppPurchase.instance.queryProductDetails(ids)
— products load successfully. -
Call
buyConsumable(purchaseParam: PurchaseParam(productDetails: ...))
. -
Listen to
purchaseStream
and logPurchaseDetails
.
If something failed, what are the symptoms?
- The purchase sheet often does not appear.
- The purchase stream reports
PurchaseStatus.restored
, immediately, for SKUs that are marked Consumable. - Example log lines (from Dart):
Products loaded: 6
Product: id=USD3.99TenMinuteCoffeePlan, price=3.99
Product: id=USD24.99OneHourDinnerPlan, price=24.99
Product: id=USD14.99InviteAFriendAsGenie, price=14.99
Purchase update: productID=USD3.99TenMinuteCoffeePlan,
status=PurchaseStatus.restored, pendingComplete=false, purchaseID=2000000991974131
Purchase update: productID=USD24.99OneHourDinnerPlan,
status=PurchaseStatus.restored, pendingComplete=false, purchaseID=2000000992079251
Purchase update: productID=USD14.99InviteAFriendAsGenie,
status=PurchaseStatus.restored, pendingComplete=false, purchaseID=2000000999910991
Purchase update: productID=USD29.99InviteAFriendAsGenie,
status=PurchaseStatus.restored, pendingComplete=false, purchaseID=2000001003571920
If nothing failed, what results did you see? And what were you expecting?
- Actual:
restored
events (no sheet) for items configured as Consumable. - Expected: For Consumables, a purchase sheet followed by
purchased
status. Consumables shouldn’t “restore”.
What else have you tried?
- Verified every SKU shows Type = Consumable and Ready to Submit in App Store Connect; “Cleared for Sale” enabled; pricing/localization filled.
- Created new product IDs (to avoid any prior non-consumable history).
- Verified I’m not calling
restorePurchases
. - In the listener, I only grant benefits on
PurchaseStatus.purchased
(not onrestored
). - Observed that
queryProductDetails
succeeds; some IDs that aren’t fully configured return “not found,” as expected.
Minimal code (core bits):
final _iap = InAppPurchase.instance;
Future<void> init() async {
final resp = await _iap.queryProductDetails({
'USD3.99TenMinuteCoffeePlan',
'USD24.99OneHourDinnerPlan',
'USD14.99InviteAFriendAsGenie',
'USD29.99InviteAFriendAsGenie',
});
_products = resp.productDetails;
_sub = _iap.purchaseStream.listen(_onUpdates);
}
Future<void> buy(ProductDetails p) async {
final param = PurchaseParam(productDetails: p);
await _iap.buyConsumable(purchaseParam: param); // iOS SK2 path
}
void _onUpdates(List<PurchaseDetails> list) async {
for (final pd in list) {
print('status=${pd.status}, id=${pd.productID}, pending=${pd.pendingCompletePurchase}, purchaseID=${pd.purchaseID}');
switch (pd.status) {
case PurchaseStatus.purchased:
// deliver & (if pendingCompletePurchase) completePurchase
break;
case PurchaseStatus.restored:
// for consumables, I do not deliver here
break;
default:
break;
}
}
}
Questions for the community/Apple:
-
Under what conditions would StoreKit 2 return
restored
for a SKU that’s set to Consumable?- Is there any server-side caching of old product type or ownership tied to a product ID that could cause this in Sandbox?
-
Is “Ready to Submit” sufficient for Sandbox testing of IAPs, or must the SKUs be attached to a submitted build before StoreKit treats them as consumable?
-
If a product ID was ever created/purchased as Non-Consumable historically, does creating a new ASC entry with the same string ID as Consumable still cause
restored
for that tester? -
Besides creating brand-new product IDs and/or resetting the Sandbox tester’s purchase history, is there any other recommended way to clear this state?
Happy to provide a device sysdiagnose or a stripped test project if that helps. Thanks!