Google应用内购买72小时自动退款问题及代码排查咨询
Hey there! The issue you're facing is a super common gotcha with Google Play Billing, and it’s directly tied to a missing critical step in your code: acknowledging purchases.
The Core Problem
Google Play requires that you explicitly acknowledge every non-consumable in-app purchase (like your remove_ads item) within 72 hours of the purchase being made. If you skip this step, the system automatically refunds the user—exactly what you’re seeing right now.
Looking at your onPurchasesUpdated method, you’re only showing a toast when a purchase succeeds, but you never tell Google Play that you’ve delivered the product (or applied the ad-free status) to the user. That’s why the platform is reversing the payment.
Fixing the Code
Let’s update your code to add purchase acknowledgment, plus handle edge cases like missed purchases if the app crashed right after payment.
Step 1: Update onPurchasesUpdated to Acknowledge Purchases
First, add a helper method to handle purchase confirmation, then integrate it into your update handler:
@Override public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> list) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) { for (Purchase purchase : list) { handlePurchase(purchase); } } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) { Toast.makeText(this, "İptal edildi", Toast.LENGTH_SHORT).show(); } else if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) { Toast.makeText(this, "already owned", Toast.LENGTH_LONG).show(); // Don't forget to acknowledge existing owned purchases too! List<Purchase> existingPurchases = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP).getPurchasesList(); for (Purchase p : existingPurchases) { if (!p.isAcknowledged()) { handlePurchase(p); } } } else { Toast.makeText(this, "Error: " + billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show(); } } // Helper method to handle purchase acknowledgment and benefit delivery private void handlePurchase(Purchase purchase) { if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { if (!purchase.isAcknowledged()) { AcknowledgePurchaseParams acknowledgeParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); mBillingClient.acknowledgePurchase(acknowledgeParams, new AcknowledgePurchaseResponseListener() { @Override public void onAcknowledgePurchaseResponse(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { // Here's where you should apply the user's purchase (e.g., disable ads) Toast.makeText(MainActivity.this, purchase.getSku() + " successfully activated!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, "Failed to confirm purchase: " + billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show(); } } }); } else { // Purchase already acknowledged, just apply the benefit Toast.makeText(MainActivity.this, purchase.getSku() + " is already active!", Toast.LENGTH_SHORT).show(); } } }
Step 2: Check for Unacknowledged Purchases on App Start
If the app crashes or closes right after a purchase, you’ll need to catch those unconfirmed orders when the app restarts. Add this logic to your onBillingSetupFinished method:
@Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { // First, check for any existing unacknowledged purchases QueryPurchasesParams queryParams = QueryPurchasesParams.newBuilder() .setProductType(BillingClient.SkuType.INAPP) .build(); mBillingClient.queryPurchasesAsync(queryParams, (billingResult1, purchases) -> { if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) { for (Purchase purchase : purchases) { handlePurchase(purchase); } } }); // Then continue with your existing SKU query logic final List<String> skuList = new ArrayList<>(); skuList.add("remove_ads"); final SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP); mBillingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(BillingResult billingResult2, List<SkuDetails> list) { if (billingResult2.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) { BillingFlowParams flowParams = BillingFlowParams.newBuilder() .setSkuDetails(list.get(0)) .build(); mBillingClient.launchBillingFlow(MainActivity.this, flowParams); } } }); } else { Toast.makeText(MainActivity.this, "Billing setup error: " + billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show(); } }
Key Takeaways
- Always acknowledge non-consumable purchases: This tells Google Play you’ve delivered the product, preventing automatic refunds.
- Handle edge cases: Check for unacknowledged purchases on app startup to cover scenarios where the app closed unexpectedly.
- Log errors: Include debug messages to troubleshoot any acknowledgment failures.
Once you implement these changes, your purchases should stop getting automatically refunded after 72 hours.
内容的提问来源于stack exchange,提问作者Serhat Hacıoğlu

