{"record":{"id":"2fbd18d7b83e82ef","repo":"microg/GmsCore","slug":"acknowledgepurchaseresult-purchase-item-count-1","errorCode":null,"errorMessage":"AcknowledgePurchaseResult purchase item count != 1","messagePattern":"AcknowledgePurchaseResult purchase item count != 1","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"vending-app/src/main/java/org/microg/vending/billing/core/AcknowledgePurchaseResult.kt","lineNumber":32,"sourceCode":"            response: AcknowledgePurchaseResponse?\n        ): AcknowledgePurchaseResult {\n            if (response == null) {\n                throw NullPointerException(\"response is null\")\n            }\n            if (response.failedResponse != null) {\n                return AcknowledgePurchaseResult(\n                    null,\n                    mapOf(\n                        \"RESPONSE_CODE\" to (response.failedResponse?.statusCode ?: 0),\n                        \"DEBUG_MESSAGE\" to (response.failedResponse?.msg ?: \"\")\n                    )\n                )\n            }\n            if (response.purchaseItem == null) {\n                throw NullPointerException(\"AcknowledgePurchaseResponse PurchaseItem is null\")\n            }\n            if (response.purchaseItem?.purchaseItemData?.size != 1)\n                throw IllegalStateException(\"AcknowledgePurchaseResult purchase item count != 1\")\n            return AcknowledgePurchaseResult(parsePurchaseItem(response.purchaseItem!!).getOrNull(0))\n        }\n    }\n}","sourceCodeStart":14,"sourceCodeEnd":36,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/vending-app/src/main/java/org/microg/vending/billing/core/AcknowledgePurchaseResult.kt#L14-L36","documentation":"parseFrom throws IllegalStateException when the response's purchaseItemData list does not contain exactly one item. The library's contract is that one acknowledge/purchase response corresponds to exactly one purchase item; any other count (0 or >1) is treated as an unexpected server shape and is not recoverable.","triggerScenarios":"AcknowledgePurchaseResponse.purchaseItem.purchaseItemData.size != 1 — e.g. an empty purchaseItemData list (token not found) or multiple entries from an unexpected server response.","commonSituations":"Acknowledging an already-consumed or unknown purchase token; backend changes returning multiple purchase items; mismatched app package/signature causing the server to return no items.","solutions":["Verify the purchaseToken being acknowledged exists and is in 'purchased' state (query purchases first)","Check that the app's package name and signing key match what the Play Store knows","Log purchaseItemData.size to see whether you got 0 or multiple items and handle each case","Catch IllegalStateException and map it to a billing error code instead of crashing"],"exampleFix":"// before\niapCore.acknowledgePurchase(AcknowledgePurchaseParams(purchaseToken = token))\n// after\nval purchases = billingClient.queryPurchases(\"inapp\")\nif (purchases.any { it.purchaseToken == token && it.purchaseState == PURCHASED }) {\n    iapCore.acknowledgePurchase(AcknowledgePurchaseParams(purchaseToken = token))\n}","handlingStrategy":"validation","validationCode":"val count = ack.purchaseItem?.purchaseItemData?.size\nif (count != 1) throw IllegalStateException(\"Expected 1 purchase item, got $count\")","typeGuard":"fun AcknowledgePurchaseResponse.hasSingleItem(): Boolean =\n    this.purchaseItem?.purchaseItemData?.size == 1","tryCatchPattern":"try {\n    AcknowledgePurchaseResult.parseFrom(ack)\n} catch (e: IllegalStateException) {\n    // log purchaseItemData.size and surface billing error\n}","preventionTips":["Query purchases and confirm state == PURCHASED before acknowledging","Verify app package name and signing key match Play Store records","Log item count to distinguish empty vs multi-item responses"],"tags":["kotlin","illegalstate","billing","invariant"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}