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 log PurchaseDetails.
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 on restored).
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!