Skip to content

Commit 2d8fb05

Browse files
Adam Fiskclaude
andcommitted
AppPurchase: log underlying error when status is canceled
The in_app_purchase plugin maps several silent Google Play rejections to PurchaseStatus.canceled — offer ineligibility, missing payment method, region mismatch, billing-service hiccups all surface the same way as a real user-initiated cancel. Without the underlying error fields we can't tell those apart, which has been blocking root-cause on a cluster of "I bought Pro but it didn't activate" tickets from v9.1.x Android users in China (e.g. engineering#3463 follow-up investigation of FD #174173, where a renewing Pro user sees 7+ consecutive `canceled` responses across 30 minutes). Capture productID, error.code, error.message, and error.details on the canceled branch. On Android `error.details` typically contains a Map with the raw BillingClient response_code and debug_message, which is the signal we need to differentiate real cancels from silent Play rejections. No behavior change — the user-visible "Purchase canceled" snackbar fires exactly as before. Single info-level log line added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 41ae88c commit 2d8fb05

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

lib/core/services/app_purchase.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,21 @@ class AppPurchase {
323323
return;
324324
}
325325
if (status == PurchaseStatus.canceled) {
326-
/// User has cancelled the purchase
326+
// `canceled` is not necessarily a real user cancel — the
327+
// in_app_purchase plugin also maps several silent Google Play
328+
// rejections (offer ineligibility, missing payment method, region
329+
// mismatch, billing-service hiccups) to the same status. Without
330+
// capturing the underlying error we can't distinguish those from a
331+
// user who actually X'd the dialog, which is the difference between
332+
// "expected" and "a bug to fix." For Android, `error.details`
333+
// typically contains a Map with the raw BillingClient
334+
// `response_code` and `debug_message`.
335+
appLogger.info(
336+
'[AppPurchase] Purchase canceled: productID=${purchaseDetails.productID}'
337+
' errorCode=${purchaseDetails.error?.code}'
338+
' message=${purchaseDetails.error?.message}'
339+
' details=${purchaseDetails.error?.details}',
340+
);
327341
_onError?.call("Purchase canceled");
328342
return;
329343
}

0 commit comments

Comments
 (0)