[In_app_purchases] migrate to Play Billing Library 2.0. (#2287)

diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md
index ffdb367..1287dea 100644
--- a/packages/in_app_purchase/CHANGELOG.md
+++ b/packages/in_app_purchase/CHANGELOG.md
@@ -1,3 +1,24 @@
+## 0.3.0
+
+* Migrate the `Google Play Library` to 2.0.3.
+     * Introduce a new class `BillingResultWrapper` which contains a detailed result of a BillingClient operation.
+          * **[Breaking Change]:**  All the BillingClient methods that previously return a `BillingResponse` now return a `BillingResultWrapper`, including: `launchBillingFlow`, `startConnection` and `consumeAsync`.
+          * **[Breaking Change]:**  The `SkuDetailsResponseWrapper` now contains a `billingResult` field in place of `billingResponse` field.
+          * A `billingResult` field is added to the `PurchasesResultWrapper`.
+     * Other Updates to the "billing_client_wrappers":
+          * Updates to the `PurchaseWrapper`: Add `developerPayload`, `purchaseState` and `isAcknowledged` fields.
+          * Updates to the `SkuDetailsWrapper`: Add `originalPrice` and `originalPriceAmountMicros` fields.
+          * **[Breaking Change]:** The `BillingClient.queryPurchaseHistory` is updated to return a `PurchasesHistoryResult`, which contains a list of `PurchaseHistoryRecordWrapper` instead of `PurchaseWrapper`. A `PurchaseHistoryRecordWrapper` object has the same fields and values as A `PurchaseWrapper` object, except that a `PurchaseHistoryRecordWrapper` object does not contain `isAutoRenewing`, `orderId` and `packageName`.
+          * Add a new `BillingClient.acknowledgePurchase` API. Starting from this version, the developer has to acknowledge any purchase on Android using this API within 3 days of purchase, or the user will be refunded. Note that if a product is "consumed" via `BillingClient.consumeAsync`, it is implicitly acknowledged.
+          * **[Breaking Change]:**  Added `enablePendingPurchases` in `BillingClientWrapper`. The application has to call this method before calling `BillingClientWrapper.startConnection`. See [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases) for more information.
+     * Updates to the "InAppPurchaseConnection":
+          * **[Breaking Change]:** `InAppPurchaseConnection.completePurchase` now returns a `Future<BillingResultWrapper>` instead of `Future<void>`. A new optional parameter `{String developerPayload}` has also been added to the API. On Android, this API does not throw an exception anymore, it instead acknowledge the purchase. If a purchase is not completed within 3 days on Android, the user will be refunded.
+          * **[Breaking Change]:** `InAppPurchaseConnection.consumePurchase` now returns a `Future<BillingResultWrapper>` instead of `Future<BillingResponse>`. A new optional parameter `{String developerPayload}` has also been added to the API.
+          * A new boolean field `pendingCompletePurchase` has been added to the `PurchaseDetails` class. Which can be used as an indicator of whether to call `InAppPurchaseConnection.completePurchase` on the purchase.
+          * **[Breaking Change]:**  Added `enablePendingPurchases` in `InAppPurchaseConnection`. The application has to call this method when initializing the `InAppPurchaseConnection` on Android. See [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases) for more information.
+     * Misc: Some documentation updates reflecting the `BillingClient` migration and some documentation fixes.
+     * Refer to [Google Play Billing Library Release Note](https://developer.android.com/google/play/billing/billing_library_releases_notes#release-2_0) for a detailed information on the update.
+
 ## 0.2.2+6
 
 * Correct a comment.
diff --git a/packages/in_app_purchase/README.md b/packages/in_app_purchase/README.md
index ce564d1..c366e14 100644
--- a/packages/in_app_purchase/README.md
+++ b/packages/in_app_purchase/README.md
@@ -114,15 +114,15 @@
 }
 ```
 
-Note that the App Store does not have any APIs for querying consummable
-products, and Google Play considers consummable products to no longer be owned
+Note that the App Store does not have any APIs for querying consumable
+products, and Google Play considers consumable products to no longer be owned
 once they're marked as consumed and fails to return them here. For restoring
 these across devices you'll need to persist them on your own server and query
 that as well.
 
 ### Making a purchase
 
-Both storefronts handle consummable and non-consummable products differently. If
+Both storefronts handle consumable and non-consumable products differently. If
 you're using `InAppPurchaseConnection`, you need to make a distinction here and
 call the right purchase method for each type.
 
diff --git a/packages/in_app_purchase/android/build.gradle b/packages/in_app_purchase/android/build.gradle
index 54bd5e1..5d577c2 100644
--- a/packages/in_app_purchase/android/build.gradle
+++ b/packages/in_app_purchase/android/build.gradle
@@ -35,7 +35,7 @@
 
 dependencies {
     implementation 'androidx.annotation:annotation:1.0.0'
-    implementation 'com.android.billingclient:billing:1.2'
+    implementation 'com.android.billingclient:billing:2.0.3'
     testImplementation 'junit:junit:4.12'
     testImplementation 'org.mockito:mockito-core:2.17.0'
     androidTestImplementation 'androidx.test:runner:1.1.1'
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java
index b9ec9f6..b320c17 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java
@@ -17,7 +17,10 @@
    *
    * @param context The context used to create the {@link BillingClient}.
    * @param channel The method channel used to create the {@link BillingClient}.
+   * @param enablePendingPurchases Whether to enable pending purchases. Throws an exception if it is
+   *     false.
    * @return The {@link BillingClient} object that is created.
    */
-  BillingClient createBillingClient(@NonNull Context context, @NonNull MethodChannel channel);
+  BillingClient createBillingClient(
+      @NonNull Context context, @NonNull MethodChannel channel, boolean enablePendingPurchases);
 }
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java
index 383fcab..9bfddaf 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java
@@ -12,9 +12,12 @@
 final class BillingClientFactoryImpl implements BillingClientFactory {
 
   @Override
-  public BillingClient createBillingClient(Context context, MethodChannel channel) {
-    return BillingClient.newBuilder(context)
-        .setListener(new PluginPurchaseListener(channel))
-        .build();
+  public BillingClient createBillingClient(
+      Context context, MethodChannel channel, boolean enablePendingPurchases) {
+    BillingClient.Builder builder = BillingClient.newBuilder(context);
+    if (enablePendingPurchases) {
+      builder.enablePendingPurchases();
+    }
+    return builder.setListener(new PluginPurchaseListener(channel)).build();
   }
 }
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
index 33910ee..a9302d1 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
@@ -36,6 +36,8 @@
         "BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)";
     static final String CONSUME_PURCHASE_ASYNC =
         "BillingClient#consumeAsync(String, ConsumeResponseListener)";
+    static final String ACKNOWLEDGE_PURCHASE =
+        "BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)";
 
     private MethodNames() {};
   }
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
index 2abcc4b..9108ab3 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
@@ -4,7 +4,7 @@
 
 package io.flutter.plugins.inapppurchase;
 
-import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
+import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList;
 import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesResult;
 import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList;
 
@@ -13,11 +13,15 @@
 import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import com.android.billingclient.api.AcknowledgePurchaseParams;
+import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
 import com.android.billingclient.api.BillingClient;
 import com.android.billingclient.api.BillingClientStateListener;
 import com.android.billingclient.api.BillingFlowParams;
+import com.android.billingclient.api.BillingResult;
+import com.android.billingclient.api.ConsumeParams;
 import com.android.billingclient.api.ConsumeResponseListener;
-import com.android.billingclient.api.Purchase;
+import com.android.billingclient.api.PurchaseHistoryRecord;
 import com.android.billingclient.api.PurchaseHistoryResponseListener;
 import com.android.billingclient.api.SkuDetails;
 import com.android.billingclient.api.SkuDetailsParams;
@@ -69,7 +73,10 @@
         isReady(result);
         break;
       case InAppPurchasePlugin.MethodNames.START_CONNECTION:
-        startConnection((int) call.argument("handle"), result);
+        startConnection(
+            (int) call.argument("handle"),
+            (boolean) call.argument("enablePendingPurchases"),
+            result);
         break;
       case InAppPurchasePlugin.MethodNames.END_CONNECTION:
         endConnection(result);
@@ -89,7 +96,16 @@
         queryPurchaseHistoryAsync((String) call.argument("skuType"), result);
         break;
       case InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC:
-        consumeAsync((String) call.argument("purchaseToken"), result);
+        consumeAsync(
+            (String) call.argument("purchaseToken"),
+            (String) call.argument("developerPayload"),
+            result);
+        break;
+      case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE:
+        acknowledgePurchase(
+            (String) call.argument("purchaseToken"),
+            (String) call.argument("developerPayload"),
+            result);
         break;
       default:
         result.notImplemented();
@@ -123,11 +139,12 @@
     billingClient.querySkuDetailsAsync(
         params,
         new SkuDetailsResponseListener() {
+          @Override
           public void onSkuDetailsResponse(
-              int responseCode, @Nullable List<SkuDetails> skuDetailsList) {
+              BillingResult billingResult, List<SkuDetails> skuDetailsList) {
             updateCachedSkus(skuDetailsList);
             final Map<String, Object> skuDetailsResponse = new HashMap<>();
-            skuDetailsResponse.put("responseCode", responseCode);
+            skuDetailsResponse.put("billingResult", Translator.fromBillingResult(billingResult));
             skuDetailsResponse.put("skuDetailsList", fromSkuDetailsList(skuDetailsList));
             result.success(skuDetailsResponse);
           }
@@ -164,10 +181,13 @@
     if (accountId != null && !accountId.isEmpty()) {
       paramsBuilder.setAccountId(accountId);
     }
-    result.success(billingClient.launchBillingFlow(activity, paramsBuilder.build()));
+    result.success(
+        Translator.fromBillingResult(
+            billingClient.launchBillingFlow(activity, paramsBuilder.build())));
   }
 
-  private void consumeAsync(String purchaseToken, final MethodChannel.Result result) {
+  private void consumeAsync(
+      String purchaseToken, String developerPayload, final MethodChannel.Result result) {
     if (billingClientError(result)) {
       return;
     }
@@ -175,12 +195,19 @@
     ConsumeResponseListener listener =
         new ConsumeResponseListener() {
           @Override
-          public void onConsumeResponse(
-              @BillingClient.BillingResponse int responseCode, String outToken) {
-            result.success(responseCode);
+          public void onConsumeResponse(BillingResult billingResult, String outToken) {
+            result.success(Translator.fromBillingResult(billingResult));
           }
         };
-    billingClient.consumeAsync(purchaseToken, listener);
+    ConsumeParams.Builder paramsBuilder =
+        ConsumeParams.newBuilder().setPurchaseToken(purchaseToken);
+
+    if (developerPayload != null) {
+      paramsBuilder.setDeveloperPayload(developerPayload);
+    }
+    ConsumeParams params = paramsBuilder.build();
+
+    billingClient.consumeAsync(params, listener);
   }
 
   private void queryPurchases(String skuType, MethodChannel.Result result) {
@@ -201,18 +228,23 @@
         skuType,
         new PurchaseHistoryResponseListener() {
           @Override
-          public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
+          public void onPurchaseHistoryResponse(
+              BillingResult billingResult, List<PurchaseHistoryRecord> purchasesList) {
             final Map<String, Object> serialized = new HashMap<>();
-            serialized.put("responseCode", responseCode);
-            serialized.put("purchasesList", fromPurchasesList(purchasesList));
+            serialized.put("billingResult", Translator.fromBillingResult(billingResult));
+            serialized.put(
+                "purchaseHistoryRecordList", fromPurchaseHistoryRecordList(purchasesList));
             result.success(serialized);
           }
         });
   }
 
-  private void startConnection(final int handle, final MethodChannel.Result result) {
+  private void startConnection(
+      final int handle, final boolean enablePendingPurchases, final MethodChannel.Result result) {
     if (billingClient == null) {
-      billingClient = billingClientFactory.createBillingClient(applicationContext, methodChannel);
+      billingClient =
+          billingClientFactory.createBillingClient(
+              applicationContext, methodChannel, enablePendingPurchases);
     }
 
     billingClient.startConnection(
@@ -220,14 +252,14 @@
           private boolean alreadyFinished = false;
 
           @Override
-          public void onBillingSetupFinished(int responseCode) {
+          public void onBillingSetupFinished(BillingResult billingResult) {
             if (alreadyFinished) {
               Log.d(TAG, "Tried to call onBilllingSetupFinished multiple times.");
               return;
             }
             alreadyFinished = true;
             // Consider the fact that we've finished a success, leave it to the Dart side to validate the responseCode.
-            result.success(responseCode);
+            result.success(Translator.fromBillingResult(billingResult));
           }
 
           @Override
@@ -239,6 +271,26 @@
         });
   }
 
+  private void acknowledgePurchase(
+      String purchaseToken, @Nullable String developerPayload, final MethodChannel.Result result) {
+    if (billingClientError(result)) {
+      return;
+    }
+    AcknowledgePurchaseParams params =
+        AcknowledgePurchaseParams.newBuilder()
+            .setDeveloperPayload(developerPayload)
+            .setPurchaseToken(purchaseToken)
+            .build();
+    billingClient.acknowledgePurchase(
+        params,
+        new AcknowledgePurchaseResponseListener() {
+          @Override
+          public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
+            result.success(Translator.fromBillingResult(billingResult));
+          }
+        });
+  }
+
   private void updateCachedSkus(@Nullable List<SkuDetails> skuDetailsList) {
     if (skuDetailsList == null) {
       return;
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java
index db3260c..20ab8ad 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java
@@ -4,9 +4,11 @@
 
 package io.flutter.plugins.inapppurchase;
 
+import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult;
 import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
 
 import androidx.annotation.Nullable;
+import com.android.billingclient.api.BillingResult;
 import com.android.billingclient.api.Purchase;
 import com.android.billingclient.api.PurchasesUpdatedListener;
 import io.flutter.plugin.common.MethodChannel;
@@ -22,9 +24,10 @@
   }
 
   @Override
-  public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {
+  public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
     final Map<String, Object> callbackArgs = new HashMap<>();
-    callbackArgs.put("responseCode", responseCode);
+    callbackArgs.put("billingResult", fromBillingResult(billingResult));
+    callbackArgs.put("responseCode", billingResult.getResponseCode());
     callbackArgs.put("purchasesList", fromPurchasesList(purchases));
     channel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED, callbackArgs);
   }
diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java
index 4502b7d..80b6f13 100644
--- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java
+++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java
@@ -5,8 +5,10 @@
 package io.flutter.plugins.inapppurchase;
 
 import androidx.annotation.Nullable;
+import com.android.billingclient.api.BillingResult;
 import com.android.billingclient.api.Purchase;
 import com.android.billingclient.api.Purchase.PurchasesResult;
+import com.android.billingclient.api.PurchaseHistoryRecord;
 import com.android.billingclient.api.SkuDetails;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -31,6 +33,8 @@
     info.put("type", detail.getType());
     info.put("isRewarded", detail.isRewarded());
     info.put("subscriptionPeriod", detail.getSubscriptionPeriod());
+    info.put("originalPrice", detail.getOriginalPrice());
+    info.put("originalPriceAmountMicros", detail.getOriginalPriceAmountMicros());
     return info;
   }
 
@@ -57,6 +61,21 @@
     info.put("sku", purchase.getSku());
     info.put("isAutoRenewing", purchase.isAutoRenewing());
     info.put("originalJson", purchase.getOriginalJson());
+    info.put("developerPayload", purchase.getDeveloperPayload());
+    info.put("isAcknowledged", purchase.isAcknowledged());
+    info.put("purchaseState", purchase.getPurchaseState());
+    return info;
+  }
+
+  static HashMap<String, Object> fromPurchaseHistoryRecord(
+      PurchaseHistoryRecord purchaseHistoryRecord) {
+    HashMap<String, Object> info = new HashMap<>();
+    info.put("purchaseTime", purchaseHistoryRecord.getPurchaseTime());
+    info.put("purchaseToken", purchaseHistoryRecord.getPurchaseToken());
+    info.put("signature", purchaseHistoryRecord.getSignature());
+    info.put("sku", purchaseHistoryRecord.getSku());
+    info.put("developerPayload", purchaseHistoryRecord.getDeveloperPayload());
+    info.put("originalJson", purchaseHistoryRecord.getOriginalJson());
     return info;
   }
 
@@ -72,10 +91,31 @@
     return serialized;
   }
 
+  static List<HashMap<String, Object>> fromPurchaseHistoryRecordList(
+      @Nullable List<PurchaseHistoryRecord> purchaseHistoryRecords) {
+    if (purchaseHistoryRecords == null) {
+      return Collections.emptyList();
+    }
+
+    List<HashMap<String, Object>> serialized = new ArrayList<>();
+    for (PurchaseHistoryRecord purchaseHistoryRecord : purchaseHistoryRecords) {
+      serialized.add(fromPurchaseHistoryRecord(purchaseHistoryRecord));
+    }
+    return serialized;
+  }
+
   static HashMap<String, Object> fromPurchasesResult(PurchasesResult purchasesResult) {
     HashMap<String, Object> info = new HashMap<>();
     info.put("responseCode", purchasesResult.getResponseCode());
+    info.put("billingResult", fromBillingResult(purchasesResult.getBillingResult()));
     info.put("purchasesList", fromPurchasesList(purchasesResult.getPurchasesList()));
     return info;
   }
+
+  static HashMap<String, Object> fromBillingResult(BillingResult billingResult) {
+    HashMap<String, Object> info = new HashMap<>();
+    info.put("responseCode", billingResult.getResponseCode());
+    info.put("debugMessage", billingResult.getDebugMessage());
+    return info;
+  }
 }
diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
index 47bfc11..be00ac4 100644
--- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
+++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
@@ -1,5 +1,6 @@
 package io.flutter.plugins.inapppurchase;
 
+import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE;
 import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC;
 import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.END_CONNECTION;
 import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_READY;
@@ -10,6 +11,8 @@
 import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC;
 import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_SKU_DETAILS;
 import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.START_CONNECTION;
+import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult;
+import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList;
 import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
 import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesResult;
 import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList;
@@ -21,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.contains;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.refEq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -30,15 +34,20 @@
 
 import android.app.Activity;
 import android.content.Context;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import com.android.billingclient.api.AcknowledgePurchaseParams;
+import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
 import com.android.billingclient.api.BillingClient;
-import com.android.billingclient.api.BillingClient.BillingResponse;
 import com.android.billingclient.api.BillingClient.SkuType;
 import com.android.billingclient.api.BillingClientStateListener;
 import com.android.billingclient.api.BillingFlowParams;
+import com.android.billingclient.api.BillingResult;
+import com.android.billingclient.api.ConsumeParams;
 import com.android.billingclient.api.ConsumeResponseListener;
 import com.android.billingclient.api.Purchase;
 import com.android.billingclient.api.Purchase.PurchasesResult;
+import com.android.billingclient.api.PurchaseHistoryRecord;
 import com.android.billingclient.api.PurchaseHistoryResponseListener;
 import com.android.billingclient.api.SkuDetails;
 import com.android.billingclient.api.SkuDetailsParams;
@@ -68,8 +77,10 @@
   @Before
   public void setUp() {
     MockitoAnnotations.initMocks(this);
-
-    factory = (context, channel) -> mockBillingClient;
+    factory =
+        (@NonNull Context context,
+            @NonNull MethodChannel channel,
+            boolean enablePendingPurchases) -> mockBillingClient;
     methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockMethodChannel, factory);
   }
 
@@ -114,15 +125,21 @@
   public void startConnection() {
     ArgumentCaptor<BillingClientStateListener> captor = mockStartConnection();
     verify(result, never()).success(any());
-    captor.getValue().onBillingSetupFinished(100);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    captor.getValue().onBillingSetupFinished(billingResult);
 
-    verify(result, times(1)).success(100);
+    verify(result, times(1)).success(fromBillingResult(billingResult));
   }
 
   @Test
   public void startConnection_multipleCalls() {
-    Map<String, Integer> arguments = new HashMap<>();
+    Map<String, Object> arguments = new HashMap<>();
     arguments.put("handle", 1);
+    arguments.put("enablePendingPurchases", true);
     MethodCall call = new MethodCall(START_CONNECTION, arguments);
     ArgumentCaptor<BillingClientStateListener> captor =
         ArgumentCaptor.forClass(BillingClientStateListener.class);
@@ -130,11 +147,27 @@
 
     methodChannelHandler.onMethodCall(call, result);
     verify(result, never()).success(any());
-    captor.getValue().onBillingSetupFinished(100);
-    captor.getValue().onBillingSetupFinished(200);
-    captor.getValue().onBillingSetupFinished(300);
+    BillingResult billingResult1 =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    BillingResult billingResult2 =
+        BillingResult.newBuilder()
+            .setResponseCode(200)
+            .setDebugMessage("dummy debug message")
+            .build();
+    BillingResult billingResult3 =
+        BillingResult.newBuilder()
+            .setResponseCode(300)
+            .setDebugMessage("dummy debug message")
+            .build();
 
-    verify(result, times(1)).success(100);
+    captor.getValue().onBillingSetupFinished(billingResult1);
+    captor.getValue().onBillingSetupFinished(billingResult2);
+    captor.getValue().onBillingSetupFinished(billingResult3);
+
+    verify(result, times(1)).success(fromBillingResult(billingResult1));
     verify(result, times(1)).success(any());
   }
 
@@ -142,8 +175,9 @@
   public void endConnection() {
     // Set up a connected BillingClient instance
     final int disconnectCallbackHandle = 22;
-    Map<String, Integer> arguments = new HashMap<>();
+    Map<String, Object> arguments = new HashMap<>();
     arguments.put("handle", disconnectCallbackHandle);
+    arguments.put("enablePendingPurchases", true);
     MethodCall connectCall = new MethodCall(START_CONNECTION, arguments);
     ArgumentCaptor<BillingClientStateListener> captor =
         ArgumentCaptor.forClass(BillingClientStateListener.class);
@@ -190,11 +224,16 @@
     // Assert that we handed result BillingClient's response
     int responseCode = 200;
     List<SkuDetails> skuDetailsResponse = asList(buildSkuDetails("foo"));
-    listenerCaptor.getValue().onSkuDetailsResponse(responseCode, skuDetailsResponse);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    listenerCaptor.getValue().onSkuDetailsResponse(billingResult, skuDetailsResponse);
     ArgumentCaptor<HashMap<String, Object>> resultCaptor = ArgumentCaptor.forClass(HashMap.class);
     verify(result).success(resultCaptor.capture());
     HashMap<String, Object> resultData = resultCaptor.getValue();
-    assertEquals(resultData.get("responseCode"), responseCode);
+    assertEquals(resultData.get("billingResult"), fromBillingResult(billingResult));
     assertEquals(resultData.get("skuDetailsList"), fromSkuDetailsList(skuDetailsResponse));
   }
 
@@ -229,8 +268,12 @@
     MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
 
     // Launch the billing flow
-    int responseCode = BillingResponse.OK;
-    when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(responseCode);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult);
     methodChannelHandler.onMethodCall(launchCall, result);
 
     // Verify we pass the arguments to the billing flow
@@ -243,7 +286,7 @@
 
     // Verify we pass the response code to result
     verify(result, never()).error(any(), any(), any());
-    verify(result, times(1)).success(responseCode);
+    verify(result, times(1)).success(fromBillingResult(billingResult));
   }
 
   @Test
@@ -277,8 +320,12 @@
     MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
 
     // Launch the billing flow
-    int responseCode = BillingResponse.OK;
-    when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(responseCode);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult);
     methodChannelHandler.onMethodCall(launchCall, result);
 
     // Verify we pass the arguments to the billing flow
@@ -291,7 +338,7 @@
 
     // Verify we pass the response code to result
     verify(result, never()).error(any(), any(), any());
-    verify(result, times(1)).success(responseCode);
+    verify(result, times(1)).success(fromBillingResult(billingResult));
   }
 
   @Test
@@ -335,9 +382,14 @@
   public void queryPurchases() {
     establishConnectedBillingClient(null, null);
     PurchasesResult purchasesResult = mock(PurchasesResult.class);
-    when(purchasesResult.getResponseCode()).thenReturn(BillingResponse.OK);
     Purchase purchase = buildPurchase("foo");
     when(purchasesResult.getPurchasesList()).thenReturn(asList(purchase));
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    when(purchasesResult.getBillingResult()).thenReturn(billingResult);
     when(mockBillingClient.queryPurchases(SkuType.INAPP)).thenReturn(purchasesResult);
 
     HashMap<String, Object> arguments = new HashMap<>();
@@ -370,8 +422,12 @@
     // Set up an established billing client and all our mocked responses
     establishConnectedBillingClient(null, null);
     ArgumentCaptor<HashMap<String, Object>> resultCaptor = ArgumentCaptor.forClass(HashMap.class);
-    int responseCode = BillingResponse.OK;
-    List<Purchase> purchasesList = asList(buildPurchase("foo"));
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    List<PurchaseHistoryRecord> purchasesList = asList(buildPurchaseHistoryRecord("foo"));
     HashMap<String, Object> arguments = new HashMap<>();
     arguments.put("skuType", SkuType.INAPP);
     ArgumentCaptor<PurchaseHistoryResponseListener> listenerCaptor =
@@ -383,11 +439,12 @@
     // Verify we pass the data to result
     verify(mockBillingClient)
         .queryPurchaseHistoryAsync(eq(SkuType.INAPP), listenerCaptor.capture());
-    listenerCaptor.getValue().onPurchaseHistoryResponse(responseCode, purchasesList);
+    listenerCaptor.getValue().onPurchaseHistoryResponse(billingResult, purchasesList);
     verify(result).success(resultCaptor.capture());
     HashMap<String, Object> resultData = resultCaptor.getValue();
-    assertEquals(responseCode, resultData.get("responseCode"));
-    assertEquals(fromPurchasesList(purchasesList), resultData.get("purchasesList"));
+    assertEquals(fromBillingResult(billingResult), resultData.get("billingResult"));
+    assertEquals(
+        fromPurchaseHistoryRecordList(purchasesList), resultData.get("purchaseHistoryRecordList"));
   }
 
   @Test
@@ -409,45 +466,95 @@
   public void onPurchasesUpdatedListener() {
     PluginPurchaseListener listener = new PluginPurchaseListener(mockMethodChannel);
 
-    int responseCode = BillingResponse.OK;
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
     List<Purchase> purchasesList = asList(buildPurchase("foo"));
     ArgumentCaptor<HashMap<String, Object>> resultCaptor = ArgumentCaptor.forClass(HashMap.class);
     doNothing()
         .when(mockMethodChannel)
         .invokeMethod(eq(ON_PURCHASES_UPDATED), resultCaptor.capture());
-    listener.onPurchasesUpdated(responseCode, purchasesList);
+    listener.onPurchasesUpdated(billingResult, purchasesList);
 
     HashMap<String, Object> resultData = resultCaptor.getValue();
-    assertEquals(responseCode, resultData.get("responseCode"));
+    assertEquals(fromBillingResult(billingResult), resultData.get("billingResult"));
     assertEquals(fromPurchasesList(purchasesList), resultData.get("purchasesList"));
   }
 
   @Test
   public void consumeAsync() {
     establishConnectedBillingClient(null, null);
-    ArgumentCaptor<BillingResponse> resultCaptor = ArgumentCaptor.forClass(BillingResponse.class);
-    int responseCode = BillingResponse.OK;
+    ArgumentCaptor<BillingResult> resultCaptor = ArgumentCaptor.forClass(BillingResult.class);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
     HashMap<String, Object> arguments = new HashMap<>();
     arguments.put("purchaseToken", "mockToken");
+    arguments.put("developerPayload", "mockPayload");
     ArgumentCaptor<ConsumeResponseListener> listenerCaptor =
         ArgumentCaptor.forClass(ConsumeResponseListener.class);
 
     methodChannelHandler.onMethodCall(new MethodCall(CONSUME_PURCHASE_ASYNC, arguments), result);
 
-    // Verify we pass the data to result
-    verify(mockBillingClient).consumeAsync(eq("mockToken"), listenerCaptor.capture());
+    ConsumeParams params =
+        ConsumeParams.newBuilder()
+            .setDeveloperPayload("mockPayload")
+            .setPurchaseToken("mockToken")
+            .build();
 
-    listenerCaptor.getValue().onConsumeResponse(responseCode, "mockToken");
+    // Verify we pass the data to result
+    verify(mockBillingClient).consumeAsync(refEq(params), listenerCaptor.capture());
+
+    listenerCaptor.getValue().onConsumeResponse(billingResult, "mockToken");
     verify(result).success(resultCaptor.capture());
 
     // Verify we pass the response code to result
     verify(result, never()).error(any(), any(), any());
-    verify(result, times(1)).success(responseCode);
+    verify(result, times(1)).success(fromBillingResult(billingResult));
+  }
+
+  @Test
+  public void acknowledgePurchase() {
+    establishConnectedBillingClient(null, null);
+    ArgumentCaptor<BillingResult> resultCaptor = ArgumentCaptor.forClass(BillingResult.class);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    HashMap<String, Object> arguments = new HashMap<>();
+    arguments.put("purchaseToken", "mockToken");
+    arguments.put("developerPayload", "mockPayload");
+    ArgumentCaptor<AcknowledgePurchaseResponseListener> listenerCaptor =
+        ArgumentCaptor.forClass(AcknowledgePurchaseResponseListener.class);
+
+    methodChannelHandler.onMethodCall(new MethodCall(ACKNOWLEDGE_PURCHASE, arguments), result);
+
+    AcknowledgePurchaseParams params =
+        AcknowledgePurchaseParams.newBuilder()
+            .setDeveloperPayload("mockPayload")
+            .setPurchaseToken("mockToken")
+            .build();
+
+    // Verify we pass the data to result
+    verify(mockBillingClient).acknowledgePurchase(refEq(params), listenerCaptor.capture());
+
+    listenerCaptor.getValue().onAcknowledgePurchaseResponse(billingResult);
+    verify(result).success(resultCaptor.capture());
+
+    // Verify we pass the response code to result
+    verify(result, never()).error(any(), any(), any());
+    verify(result, times(1)).success(fromBillingResult(billingResult));
   }
 
   private ArgumentCaptor<BillingClientStateListener> mockStartConnection() {
-    Map<String, Integer> arguments = new HashMap<>();
+    Map<String, Object> arguments = new HashMap<>();
     arguments.put("handle", 1);
+    arguments.put("enablePendingPurchases", true);
     MethodCall call = new MethodCall(START_CONNECTION, arguments);
     ArgumentCaptor<BillingClientStateListener> captor =
         ArgumentCaptor.forClass(BillingClientStateListener.class);
@@ -458,10 +565,11 @@
   }
 
   private void establishConnectedBillingClient(
-      @Nullable Map<String, Integer> arguments, @Nullable Result result) {
+      @Nullable Map<String, Object> arguments, @Nullable Result result) {
     if (arguments == null) {
       arguments = new HashMap<>();
       arguments.put("handle", 1);
+      arguments.put("enablePendingPurchases", true);
     }
     if (result == null) {
       result = mock(Result.class);
@@ -489,7 +597,12 @@
     verify(mockBillingClient).querySkuDetailsAsync(any(), listenerCaptor.capture());
     List<SkuDetails> skuDetailsResponse =
         skusList.stream().map(this::buildSkuDetails).collect(toList());
-    listenerCaptor.getValue().onSkuDetailsResponse(BillingResponse.OK, skuDetailsResponse);
+    BillingResult billingResult =
+        BillingResult.newBuilder()
+            .setResponseCode(100)
+            .setDebugMessage("dummy debug message")
+            .build();
+    listenerCaptor.getValue().onSkuDetailsResponse(billingResult, skuDetailsResponse);
   }
 
   private SkuDetails buildSkuDetails(String id) {
@@ -503,4 +616,10 @@
     when(purchase.getOrderId()).thenReturn(orderId);
     return purchase;
   }
+
+  private PurchaseHistoryRecord buildPurchaseHistoryRecord(String purchaseToken) {
+    PurchaseHistoryRecord purchase = mock(PurchaseHistoryRecord.class);
+    when(purchase.getPurchaseToken()).thenReturn(purchaseToken);
+    return purchase;
+  }
 }
diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java
index 639af24..2ee1044 100644
--- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java
+++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java
@@ -8,9 +8,11 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import com.android.billingclient.api.BillingClient.BillingResponse;
+import com.android.billingclient.api.BillingClient;
+import com.android.billingclient.api.BillingResult;
 import com.android.billingclient.api.Purchase;
 import com.android.billingclient.api.Purchase.PurchasesResult;
+import com.android.billingclient.api.PurchaseHistoryRecord;
 import com.android.billingclient.api.SkuDetails;
 import java.util.Arrays;
 import java.util.Collections;
@@ -22,9 +24,9 @@
 
 public class TranslatorTest {
   private static final String SKU_DETAIL_EXAMPLE_JSON =
-      "{\"productId\":\"example\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\"}";
+      "{\"productId\":\"example\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\",\"original_price\":\"$0.99\",\"original_price_micros\":990000}";
   private static final String PURCHASE_EXAMPLE_JSON =
-      "{\"orderId\":\"foo\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\"}";
+      "{\"orderId\":\"foo\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\"}";
 
   @Test
   public void fromSkuDetail() throws JSONException {
@@ -38,7 +40,7 @@
   @Test
   public void fromSkuDetailsList() throws JSONException {
     final String SKU_DETAIL_EXAMPLE_2_JSON =
-        "{\"productId\":\"example2\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\"}";
+        "{\"productId\":\"example2\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\",\"original_price\":\"$0.99\",\"original_price_micros\":990000}";
     final List<SkuDetails> expected =
         Arrays.asList(
             new SkuDetails(SKU_DETAIL_EXAMPLE_JSON), new SkuDetails(SKU_DETAIL_EXAMPLE_2_JSON));
@@ -58,14 +60,43 @@
   @Test
   public void fromPurchase() throws JSONException {
     final Purchase expected = new Purchase(PURCHASE_EXAMPLE_JSON, "signature");
-
     assertSerialized(expected, Translator.fromPurchase(expected));
   }
 
   @Test
+  public void fromPurchaseHistoryRecord() throws JSONException {
+    final PurchaseHistoryRecord expected =
+        new PurchaseHistoryRecord(PURCHASE_EXAMPLE_JSON, "signature");
+    assertSerialized(expected, Translator.fromPurchaseHistoryRecord(expected));
+  }
+
+  @Test
+  public void fromPurchasesHistoryRecordList() throws JSONException {
+    final String purchase2Json =
+        "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\"}";
+    final String signature = "signature";
+    final List<PurchaseHistoryRecord> expected =
+        Arrays.asList(
+            new PurchaseHistoryRecord(PURCHASE_EXAMPLE_JSON, signature),
+            new PurchaseHistoryRecord(purchase2Json, signature));
+
+    final List<HashMap<String, Object>> serialized =
+        Translator.fromPurchaseHistoryRecordList(expected);
+
+    assertEquals(expected.size(), serialized.size());
+    assertSerialized(expected.get(0), serialized.get(0));
+    assertSerialized(expected.get(1), serialized.get(1));
+  }
+
+  @Test
+  public void fromPurchasesHistoryRecordList_null() {
+    assertEquals(Collections.emptyList(), Translator.fromPurchaseHistoryRecordList(null));
+  }
+
+  @Test
   public void fromPurchasesList() throws JSONException {
     final String purchase2Json =
-        "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\"}";
+        "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\"}";
     final String signature = "signature";
     final List<Purchase> expected =
         Arrays.asList(
@@ -87,33 +118,54 @@
   public void fromPurchasesResult() throws JSONException {
     PurchasesResult result = mock(PurchasesResult.class);
     final String purchase2Json =
-        "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\"}";
+        "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\"}";
     final String signature = "signature";
     final List<Purchase> expectedPurchases =
         Arrays.asList(
             new Purchase(PURCHASE_EXAMPLE_JSON, signature), new Purchase(purchase2Json, signature));
     when(result.getPurchasesList()).thenReturn(expectedPurchases);
-    when(result.getResponseCode()).thenReturn(BillingResponse.OK);
-
+    when(result.getResponseCode()).thenReturn(BillingClient.BillingResponseCode.OK);
+    BillingResult newBillingResult =
+        BillingResult.newBuilder()
+            .setDebugMessage("dummy debug message")
+            .setResponseCode(BillingClient.BillingResponseCode.OK)
+            .build();
+    when(result.getBillingResult()).thenReturn(newBillingResult);
     final HashMap<String, Object> serialized = Translator.fromPurchasesResult(result);
 
-    assertEquals(BillingResponse.OK, serialized.get("responseCode"));
+    assertEquals(BillingClient.BillingResponseCode.OK, serialized.get("responseCode"));
     List<Map<String, Object>> serializedPurchases =
         (List<Map<String, Object>>) serialized.get("purchasesList");
     assertEquals(expectedPurchases.size(), serializedPurchases.size());
     assertSerialized(expectedPurchases.get(0), serializedPurchases.get(0));
     assertSerialized(expectedPurchases.get(1), serializedPurchases.get(1));
+
+    Map<String, Object> billingResultMap = (Map<String, Object>) serialized.get("billingResult");
+    assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode());
+    assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage());
   }
 
   @Test
-  public void fromPurchasesResult_null() throws JSONException {
-    PurchasesResult result = mock(PurchasesResult.class);
-    when(result.getResponseCode()).thenReturn(BillingResponse.ERROR);
+  public void fromBillingResult() throws JSONException {
+    BillingResult newBillingResult =
+        BillingResult.newBuilder()
+            .setDebugMessage("dummy debug message")
+            .setResponseCode(BillingClient.BillingResponseCode.OK)
+            .build();
+    Map<String, Object> billingResultMap = Translator.fromBillingResult(newBillingResult);
 
-    final HashMap<String, Object> serialized = Translator.fromPurchasesResult(result);
+    assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode());
+    assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage());
+  }
 
-    assertEquals(BillingResponse.ERROR, serialized.get("responseCode"));
-    assertEquals(Collections.emptyList(), serialized.get("purchasesList"));
+  @Test
+  public void fromBillingResult_debugMessageNull() throws JSONException {
+    BillingResult newBillingResult =
+        BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build();
+    Map<String, Object> billingResultMap = Translator.fromBillingResult(newBillingResult);
+
+    assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode());
+    assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage());
   }
 
   private void assertSerialized(SkuDetails expected, Map<String, Object> serialized) {
@@ -132,6 +184,9 @@
     assertEquals(expected.getSubscriptionPeriod(), serialized.get("subscriptionPeriod"));
     assertEquals(expected.getTitle(), serialized.get("title"));
     assertEquals(expected.getType(), serialized.get("type"));
+    assertEquals(expected.getOriginalPrice(), serialized.get("originalPrice"));
+    assertEquals(
+        expected.getOriginalPriceAmountMicros(), serialized.get("originalPriceAmountMicros"));
   }
 
   private void assertSerialized(Purchase expected, Map<String, Object> serialized) {
@@ -142,5 +197,17 @@
     assertEquals(expected.getSignature(), serialized.get("signature"));
     assertEquals(expected.getOriginalJson(), serialized.get("originalJson"));
     assertEquals(expected.getSku(), serialized.get("sku"));
+    assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload"));
+    assertEquals(expected.isAcknowledged(), serialized.get("isAcknowledged"));
+    assertEquals(expected.getPurchaseState(), serialized.get("purchaseState"));
+  }
+
+  private void assertSerialized(PurchaseHistoryRecord expected, Map<String, Object> serialized) {
+    assertEquals(expected.getPurchaseTime(), serialized.get("purchaseTime"));
+    assertEquals(expected.getPurchaseToken(), serialized.get("purchaseToken"));
+    assertEquals(expected.getSignature(), serialized.get("signature"));
+    assertEquals(expected.getOriginalJson(), serialized.get("originalJson"));
+    assertEquals(expected.getSku(), serialized.get("sku"));
+    assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload"));
   }
 }
diff --git a/packages/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/example/lib/main.dart
index 8d142c8..826d2a1 100644
--- a/packages/in_app_purchase/example/lib/main.dart
+++ b/packages/in_app_purchase/example/lib/main.dart
@@ -9,6 +9,10 @@
 import 'consumable_store.dart';
 
 void main() {
+  // For play billing library 2.0 on Android, it is mandatory to call
+  // [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases)
+  // as part of initializing the app.
+  InAppPurchaseConnection.enablePendingPurchases();
   runApp(MyApp());
 }
 
@@ -226,7 +230,7 @@
     // We recommend that you use your own server to verity the purchase data.
     Map<String, PurchaseDetails> purchases =
         Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
-      if (Platform.isIOS) {
+      if (purchase.pendingCompletePurchase) {
         InAppPurchaseConnection.instance.completePurchase(purchase);
       }
       return MapEntry<String, PurchaseDetails>(purchase.productID, purchase);
@@ -361,28 +365,32 @@
 
   void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
     purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
+      await InAppPurchaseConnection.instance.consumePurchase(purchaseDetails);
       if (purchaseDetails.status == PurchaseStatus.pending) {
         showPendingUI();
       } else {
         if (purchaseDetails.status == PurchaseStatus.error) {
           handleError(purchaseDetails.error);
+          return;
         } else if (purchaseDetails.status == PurchaseStatus.purchased) {
           bool valid = await _verifyPurchase(purchaseDetails);
           if (valid) {
             deliverProduct(purchaseDetails);
           } else {
             _handleInvalidPurchase(purchaseDetails);
+            return;
           }
         }
-        if (Platform.isIOS) {
-          await InAppPurchaseConnection.instance
-              .completePurchase(purchaseDetails);
-        } else if (Platform.isAndroid) {
+        if (Platform.isAndroid) {
           if (!kAutoConsume && purchaseDetails.productID == _kConsumableId) {
             await InAppPurchaseConnection.instance
                 .consumePurchase(purchaseDetails);
           }
         }
+        if (purchaseDetails.pendingCompletePurchase) {
+          await InAppPurchaseConnection.instance
+              .completePurchase(purchaseDetails);
+        }
       }
     });
   }
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart
index 6d7cd83..ebbd90a 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/billing_client_wrapper.dart
@@ -6,6 +6,7 @@
 import 'package:flutter/services.dart';
 import 'package:flutter/foundation.dart';
 import 'package:json_annotation/json_annotation.dart';
+import '../../billing_client_wrappers.dart';
 import '../channel.dart';
 import 'purchase_wrapper.dart';
 import 'sku_details_wrapper.dart';
@@ -48,6 +49,8 @@
 /// some minor changes to account for language differences. Callbacks have been
 /// converted to futures where appropriate.
 class BillingClient {
+  bool _enablePendingPurchases = false;
+
   BillingClient(PurchasesUpdatedListener onPurchasesUpdated) {
     assert(onPurchasesUpdated != null);
     channel.setMethodCallHandler(callHandler);
@@ -70,25 +73,43 @@
   Future<bool> isReady() async =>
       await channel.invokeMethod<bool>('BillingClient#isReady()');
 
+  /// Enable the [BillingClientWrapper] to handle pending purchases.
+  ///
+  /// Play requires that you call this method when initializing your application.
+  /// It is to acknowledge your application has been updated to support pending purchases.
+  /// See [Support pending transactions](https://developer.android.com/google/play/billing/billing_library_overview#pending)
+  /// for more details.
+  ///
+  /// Failure to call this method before any other method in the [startConnection] will throw an exception.
+  void enablePendingPurchases() {
+    _enablePendingPurchases = true;
+  }
+
   /// Calls
   /// [`BillingClient#startConnection(BillingClientStateListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#startconnection)
   /// to create and connect a `BillingClient` instance.
   ///
   /// [onBillingServiceConnected] has been converted from a callback parameter
   /// to the Future result returned by this function. This returns the
-  /// `BillingClient.BillingResponse` `responseCode` of the connection result.
+  /// `BillingClient.BillingResultWrapper` describing the connection result.
   ///
   /// This triggers the creation of a new `BillingClient` instance in Java if
   /// one doesn't already exist.
-  Future<BillingResponse> startConnection(
+  Future<BillingResultWrapper> startConnection(
       {@required
           OnBillingServiceDisconnected onBillingServiceDisconnected}) async {
+    assert(_enablePendingPurchases,
+        'enablePendingPurchases() must be called before calling startConnection');
     List<Function> disconnectCallbacks =
         _callbacks[_kOnBillingServiceDisconnected] ??= [];
     disconnectCallbacks.add(onBillingServiceDisconnected);
-    return BillingResponseConverter().fromJson(await channel.invokeMethod<int>(
-        "BillingClient#startConnection(BillingClientStateListener)",
-        <String, dynamic>{'handle': disconnectCallbacks.length - 1}));
+    return BillingResultWrapper.fromJson(await channel
+        .invokeMapMethod<String, dynamic>(
+            "BillingClient#startConnection(BillingClientStateListener)",
+            <String, dynamic>{
+          'handle': disconnectCallbacks.length - 1,
+          'enablePendingPurchases': _enablePendingPurchases
+        }));
   }
 
   /// Calls
@@ -134,7 +155,7 @@
   /// Calling this attemps to show the Google Play purchase UI. The user is free
   /// to complete the transaction there.
   ///
-  /// This method returns a [BillingResponse] representing the initial attempt
+  /// This method returns a [BillingResultWrapper] representing the initial attempt
   /// to show the Google Play billing flow. Actual purchase updates are
   /// delivered via the [PurchasesUpdatedListener].
   ///
@@ -146,16 +167,17 @@
   /// skuDetails](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder.html#setskudetails)
   /// and [the given
   /// accountId](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder.html#setAccountId(java.lang.String)).
-  Future<BillingResponse> launchBillingFlow(
+  Future<BillingResultWrapper> launchBillingFlow(
       {@required String sku, String accountId}) async {
     assert(sku != null);
     final Map<String, dynamic> arguments = <String, dynamic>{
       'sku': sku,
       'accountId': accountId,
     };
-    return BillingResponseConverter().fromJson(await channel.invokeMethod<int>(
-        'BillingClient#launchBillingFlow(Activity, BillingFlowParams)',
-        arguments));
+    return BillingResultWrapper.fromJson(
+        await channel.invokeMapMethod<String, dynamic>(
+            'BillingClient#launchBillingFlow(Activity, BillingFlowParams)',
+            arguments));
   }
 
   /// Fetches recent purchases for the given [SkuType].
@@ -190,9 +212,9 @@
   /// This wraps [`BillingClient#queryPurchaseHistoryAsync(String skuType,
   /// PurchaseHistoryResponseListener
   /// listener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchasehistoryasync).
-  Future<PurchasesResultWrapper> queryPurchaseHistory(SkuType skuType) async {
+  Future<PurchasesHistoryResult> queryPurchaseHistory(SkuType skuType) async {
     assert(skuType != null);
-    return PurchasesResultWrapper.fromJson(await channel.invokeMapMethod<String,
+    return PurchasesHistoryResult.fromJson(await channel.invokeMapMethod<String,
             dynamic>(
         'BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)',
         <String, dynamic>{'skuType': SkuTypeConverter().toJson(skuType)}));
@@ -201,15 +223,54 @@
   /// Consumes a given in-app product.
   ///
   /// Consuming can only be done on an item that's owned, and as a result of consumption, the user will no longer own it.
-  /// Consumption is done asynchronously. The method returns a Future containing a [BillingResponse].
+  /// Consumption is done asynchronously. The method returns a Future containing a [BillingResultWrapper].
+  ///
+  /// The `purchaseToken` must not be null.
+  /// The `developerPayload` is the developer data associated with the purchase to be consumed, it defaults to null.
   ///
   /// This wraps [`BillingClient#consumeAsync(String, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener))
-  Future<BillingResponse> consumeAsync(String purchaseToken) async {
+  Future<BillingResultWrapper> consumeAsync(String purchaseToken,
+      {String developerPayload}) async {
     assert(purchaseToken != null);
-    return BillingResponseConverter().fromJson(await channel.invokeMethod<int>(
-      'BillingClient#consumeAsync(String, ConsumeResponseListener)',
-      <String, String>{'purchaseToken': purchaseToken},
-    ));
+    return BillingResultWrapper.fromJson(await channel
+        .invokeMapMethod<String, dynamic>(
+            'BillingClient#consumeAsync(String, ConsumeResponseListener)',
+            <String, String>{
+          'purchaseToken': purchaseToken,
+          'developerPayload': developerPayload,
+        }));
+  }
+
+  /// Acknowledge an in-app purchase.
+  ///
+  /// The developer must acknowledge all in-app purchases after they have been granted to the user.
+  /// If this doesn't happen within three days of the purchase, the purchase will be refunded.
+  ///
+  /// Consumables are already implicitly acknowledged by calls to [consumeAsync] and
+  /// do not need to be explicitly acknowledged by using this method.
+  /// However this method can be called for them in order to explicitly acknowledge them if desired.
+  ///
+  /// Be sure to only acknowledge a purchase after it has been granted to the user.
+  /// [PurchaseWrapper.purchaseState] should be [PurchaseStateWrapper.purchased] and
+  /// the purchase should be validated. See [Verify a purchase](https://developer.android.com/google/play/billing/billing_library_overview#Verify) on verifying purchases.
+  ///
+  /// Please refer to [acknowledge](https://developer.android.com/google/play/billing/billing_library_overview#acknowledge) for more
+  /// details.
+  ///
+  /// The `purchaseToken` must not be null.
+  /// The `developerPayload` is the developer data associated with the purchase to be consumed, it defaults to null.
+  ///
+  /// This wraps [`BillingClient#acknowledgePurchase(String, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener))
+  Future<BillingResultWrapper> acknowledgePurchase(String purchaseToken,
+      {String developerPayload}) async {
+    assert(purchaseToken != null);
+    return BillingResultWrapper.fromJson(await channel.invokeMapMethod<String,
+            dynamic>(
+        'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)',
+        <String, String>{
+          'purchaseToken': purchaseToken,
+          'developerPayload': developerPayload,
+        }));
   }
 
   @visibleForTesting
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart
index 5d05221..1e81895 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:in_app_purchase/billing_client_wrappers.dart';
+import 'package:in_app_purchase/in_app_purchase.dart';
 import 'package:json_annotation/json_annotation.dart';
 
 part 'enum_converters.g.dart';
@@ -42,4 +43,36 @@
 class _SerializedEnums {
   BillingResponse response;
   SkuType type;
+  PurchaseStateWrapper purchaseState;
+}
+
+/// Serializer for [PurchaseStateWrapper].
+///
+/// Use these in `@JsonSerializable()` classes by annotating them with
+/// `@PurchaseStateConverter()`.
+class PurchaseStateConverter
+    implements JsonConverter<PurchaseStateWrapper, int> {
+  const PurchaseStateConverter();
+
+  @override
+  PurchaseStateWrapper fromJson(int json) => _$enumDecode<PurchaseStateWrapper>(
+      _$PurchaseStateWrapperEnumMap.cast<PurchaseStateWrapper, dynamic>(),
+      json);
+
+  @override
+  int toJson(PurchaseStateWrapper object) =>
+      _$PurchaseStateWrapperEnumMap[object];
+
+  PurchaseStatus toPurchaseStatus(PurchaseStateWrapper object) {
+    switch (object) {
+      case PurchaseStateWrapper.pending:
+        return PurchaseStatus.pending;
+      case PurchaseStateWrapper.purchased:
+        return PurchaseStatus.purchased;
+      case PurchaseStateWrapper.unspecified_state:
+        return PurchaseStatus.error;
+    }
+
+    throw ArgumentError('$object isn\'t mapped to PurchaseStatus');
+  }
 }
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.g.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.g.dart
index 0f54909..899304b 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.g.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/enum_converters.g.dart
@@ -9,13 +9,16 @@
 _SerializedEnums _$_SerializedEnumsFromJson(Map json) {
   return _SerializedEnums()
     ..response = _$enumDecode(_$BillingResponseEnumMap, json['response'])
-    ..type = _$enumDecode(_$SkuTypeEnumMap, json['type']);
+    ..type = _$enumDecode(_$SkuTypeEnumMap, json['type'])
+    ..purchaseState =
+        _$enumDecode(_$PurchaseStateWrapperEnumMap, json['purchaseState']);
 }
 
 Map<String, dynamic> _$_SerializedEnumsToJson(_SerializedEnums instance) =>
     <String, dynamic>{
       'response': _$BillingResponseEnumMap[instance.response],
       'type': _$SkuTypeEnumMap[instance.type],
+      'purchaseState': _$PurchaseStateWrapperEnumMap[instance.purchaseState],
     };
 
 T _$enumDecode<T>(
@@ -57,3 +60,9 @@
   SkuType.inapp: 'inapp',
   SkuType.subs: 'subs',
 };
+
+const _$PurchaseStateWrapperEnumMap = {
+  PurchaseStateWrapper.unspecified_state: 0,
+  PurchaseStateWrapper.purchased: 1,
+  PurchaseStateWrapper.pending: 2,
+};
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart
index 30f8732..0d4b74f 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.dart
@@ -7,13 +7,14 @@
 import 'package:json_annotation/json_annotation.dart';
 import 'enum_converters.dart';
 import 'billing_client_wrapper.dart';
+import 'sku_details_wrapper.dart';
 
 // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the
 // below generated file. Run `flutter packages pub run build_runner watch` to
 // rebuild and watch for further changes.
 part 'purchase_wrapper.g.dart';
 
-/// Data structure reprenting a succesful purchase.
+/// Data structure representing a successful purchase.
 ///
 /// All purchase information should also be verified manually, with your
 /// server if at all possible. See ["Verify a
@@ -21,18 +22,21 @@
 ///
 /// This wraps [`com.android.billlingclient.api.Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase)
 @JsonSerializable()
+@PurchaseStateConverter()
 class PurchaseWrapper {
   @visibleForTesting
-  PurchaseWrapper({
-    @required this.orderId,
-    @required this.packageName,
-    @required this.purchaseTime,
-    @required this.purchaseToken,
-    @required this.signature,
-    @required this.sku,
-    @required this.isAutoRenewing,
-    @required this.originalJson,
-  });
+  PurchaseWrapper(
+      {@required this.orderId,
+      @required this.packageName,
+      @required this.purchaseTime,
+      @required this.purchaseToken,
+      @required this.signature,
+      @required this.sku,
+      @required this.isAutoRenewing,
+      @required this.originalJson,
+      @required this.developerPayload,
+      @required this.isAcknowledged,
+      @required this.purchaseState});
 
   factory PurchaseWrapper.fromJson(Map map) => _$PurchaseWrapperFromJson(map);
 
@@ -48,12 +52,23 @@
         typedOther.signature == signature &&
         typedOther.sku == sku &&
         typedOther.isAutoRenewing == isAutoRenewing &&
-        typedOther.originalJson == originalJson;
+        typedOther.originalJson == originalJson &&
+        typedOther.isAcknowledged == isAcknowledged &&
+        typedOther.purchaseState == purchaseState;
   }
 
   @override
-  int get hashCode => hashValues(orderId, packageName, purchaseTime,
-      purchaseToken, signature, sku, isAutoRenewing, originalJson);
+  int get hashCode => hashValues(
+      orderId,
+      packageName,
+      purchaseTime,
+      purchaseToken,
+      signature,
+      sku,
+      isAutoRenewing,
+      originalJson,
+      isAcknowledged,
+      purchaseState);
 
   /// The unique ID for this purchase. Corresponds to the Google Payments order
   /// ID.
@@ -89,11 +104,93 @@
   /// Note though that verifying a purchase locally is inherently insecure (see
   /// the article for more details).
   final String originalJson;
+
+  /// The payload specified by the developer when the purchase was acknowledged or consumed.
+  final String developerPayload;
+
+  /// Whether the purchase has been acknowledged.
+  ///
+  /// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase].
+  /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
+  final bool isAcknowledged;
+
+  /// Determines the current state of the purchase.
+  ///
+  /// [BillingClient.acknowledgePurchase] should only be called when the `purchaseState` is [PurchaseStateWrapper.purchased].
+  /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
+  final PurchaseStateWrapper purchaseState;
+}
+
+/// Data structure representing a purchase history record.
+///
+/// This class includes a subset of fields in [PurchaseWrapper].
+///
+/// This wraps [`com.android.billlingclient.api.PurchaseHistoryRecord`](https://developer.android.com/reference/com/android/billingclient/api/PurchaseHistoryRecord)
+///
+/// * See also: [BillingClient.queryPurchaseHistory] for obtaining a [PurchaseHistoryRecordWrapper].
+// We can optionally make [PurchaseWrapper] extend or implement [PurchaseHistoryRecordWrapper].
+// For now, we keep them separated classes to be consistent with Android's BillingClient implementation.
+@JsonSerializable()
+class PurchaseHistoryRecordWrapper {
+  @visibleForTesting
+  PurchaseHistoryRecordWrapper({
+    @required this.purchaseTime,
+    @required this.purchaseToken,
+    @required this.signature,
+    @required this.sku,
+    @required this.originalJson,
+    @required this.developerPayload,
+  });
+
+  factory PurchaseHistoryRecordWrapper.fromJson(Map map) =>
+      _$PurchaseHistoryRecordWrapperFromJson(map);
+
+  /// When the purchase was made, as an epoch timestamp.
+  final int purchaseTime;
+
+  /// A unique ID for a given [SkuDetailsWrapper], user, and purchase.
+  final String purchaseToken;
+
+  /// Signature of purchase data, signed with the developer's private key. Uses
+  /// RSASSA-PKCS1-v1_5.
+  final String signature;
+
+  /// The product ID of this purchase.
+  final String sku;
+
+  /// Details about this purchase, in JSON.
+  ///
+  /// This can be used verify a purchase. See ["Verify a purchase on a
+  /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device).
+  /// Note though that verifying a purchase locally is inherently insecure (see
+  /// the article for more details).
+  final String originalJson;
+
+  /// The payload specified by the developer when the purchase was acknowledged or consumed.
+  final String developerPayload;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) return true;
+    if (other.runtimeType != runtimeType) return false;
+    final PurchaseHistoryRecordWrapper typedOther = other;
+    return typedOther.purchaseTime == purchaseTime &&
+        typedOther.purchaseToken == purchaseToken &&
+        typedOther.signature == signature &&
+        typedOther.sku == sku &&
+        typedOther.originalJson == originalJson &&
+        typedOther.developerPayload == developerPayload;
+  }
+
+  @override
+  int get hashCode => hashValues(purchaseTime, purchaseToken, signature, sku,
+      originalJson, developerPayload);
 }
 
 /// A data struct representing the result of a transaction.
 ///
-/// Contains a potentially empty list of [PurchaseWrapper]s and a
+/// Contains a potentially empty list of [PurchaseWrapper]s, a [BillingResultWrapper]
+/// that contains a detailed description of the status and a
 /// [BillingResponse] to signify the overall state of the transaction.
 ///
 /// Wraps [`com.android.billingclient.api.Purchase.PurchasesResult`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult).
@@ -101,7 +198,9 @@
 @BillingResponseConverter()
 class PurchasesResultWrapper {
   PurchasesResultWrapper(
-      {@required this.responseCode, @required this.purchasesList});
+      {@required this.responseCode,
+      @required this.billingResult,
+      @required this.purchasesList});
 
   factory PurchasesResultWrapper.fromJson(Map<String, dynamic> map) =>
       _$PurchasesResultWrapperFromJson(map);
@@ -112,11 +211,15 @@
     if (other.runtimeType != runtimeType) return false;
     final PurchasesResultWrapper typedOther = other;
     return typedOther.responseCode == responseCode &&
-        typedOther.purchasesList == purchasesList;
+        typedOther.purchasesList == purchasesList &&
+        typedOther.billingResult == billingResult;
   }
 
   @override
-  int get hashCode => hashValues(responseCode, purchasesList);
+  int get hashCode => hashValues(billingResult, responseCode, purchasesList);
+
+  /// The detailed description of the status of the operation.
+  final BillingResultWrapper billingResult;
 
   /// The status of the operation.
   ///
@@ -124,8 +227,73 @@
   /// of the operation and the "user made purchases" transaction itself.
   final BillingResponse responseCode;
 
-  /// The list of succesful purchases made in this transaction.
+  /// The list of successful purchases made in this transaction.
   ///
   /// May be empty, especially if [responseCode] is not [BillingResponse.ok].
   final List<PurchaseWrapper> purchasesList;
 }
+
+/// A data struct representing the result of a purchase history.
+///
+/// Contains a potentially empty list of [PurchaseHistoryRecordWrapper]s and a [BillingResultWrapper]
+/// that contains a detailed description of the status.
+@JsonSerializable()
+@BillingResponseConverter()
+class PurchasesHistoryResult {
+  PurchasesHistoryResult(
+      {@required this.billingResult, @required this.purchaseHistoryRecordList});
+
+  factory PurchasesHistoryResult.fromJson(Map<String, dynamic> map) =>
+      _$PurchasesHistoryResultFromJson(map);
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) return true;
+    if (other.runtimeType != runtimeType) return false;
+    final PurchasesHistoryResult typedOther = other;
+    return typedOther.purchaseHistoryRecordList == purchaseHistoryRecordList &&
+        typedOther.billingResult == billingResult;
+  }
+
+  @override
+  int get hashCode => hashValues(billingResult, purchaseHistoryRecordList);
+
+  /// The detailed description of the status of the [BillingClient.queryPurchaseHistory].
+  final BillingResultWrapper billingResult;
+
+  /// The list of queried purchase history records.
+  ///
+  /// May be empty, especially if [billingResult.responseCode] is not [BillingResponse.ok].
+  final List<PurchaseHistoryRecordWrapper> purchaseHistoryRecordList;
+}
+
+/// Possible state of a [PurchaseWrapper].
+///
+/// Wraps
+/// [`BillingClient.api.Purchase.PurchaseState`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState.html).
+/// * See also: [PurchaseWrapper].
+enum PurchaseStateWrapper {
+  /// The state is unspecified.
+  ///
+  /// No actions on the [PurchaseWrapper] should be performed on this state.
+  /// This is a catch-all. It should never be returned by the Play Billing Library.
+  @JsonValue(0)
+  unspecified_state,
+
+  /// The user has completed the purchase process.
+  ///
+  /// The production should be delivered and then the purchase should be acknowledged.
+  /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
+  @JsonValue(1)
+  purchased,
+
+  /// The user has started the purchase process.
+  ///
+  /// The user should follow the instructions that were given to them by the Play
+  /// Billing Library to complete the purchase.
+  ///
+  /// You can also choose to remind the user to complete the purchase if you detected a
+  /// [PurchaseWrapper] is still in the `pending` state in the future while calling [BillingClient.queryPurchases].
+  @JsonValue(2)
+  pending,
+}
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart
index 899e600..3d55589 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/purchase_wrapper.g.dart
@@ -16,6 +16,10 @@
     sku: json['sku'] as String,
     isAutoRenewing: json['isAutoRenewing'] as bool,
     originalJson: json['originalJson'] as String,
+    developerPayload: json['developerPayload'] as String,
+    isAcknowledged: json['isAcknowledged'] as bool,
+    purchaseState:
+        const PurchaseStateConverter().fromJson(json['purchaseState'] as int),
   );
 }
 
@@ -29,11 +33,39 @@
       'sku': instance.sku,
       'isAutoRenewing': instance.isAutoRenewing,
       'originalJson': instance.originalJson,
+      'developerPayload': instance.developerPayload,
+      'isAcknowledged': instance.isAcknowledged,
+      'purchaseState':
+          const PurchaseStateConverter().toJson(instance.purchaseState),
+    };
+
+PurchaseHistoryRecordWrapper _$PurchaseHistoryRecordWrapperFromJson(Map json) {
+  return PurchaseHistoryRecordWrapper(
+    purchaseTime: json['purchaseTime'] as int,
+    purchaseToken: json['purchaseToken'] as String,
+    signature: json['signature'] as String,
+    sku: json['sku'] as String,
+    originalJson: json['originalJson'] as String,
+    developerPayload: json['developerPayload'] as String,
+  );
+}
+
+Map<String, dynamic> _$PurchaseHistoryRecordWrapperToJson(
+        PurchaseHistoryRecordWrapper instance) =>
+    <String, dynamic>{
+      'purchaseTime': instance.purchaseTime,
+      'purchaseToken': instance.purchaseToken,
+      'signature': instance.signature,
+      'sku': instance.sku,
+      'originalJson': instance.originalJson,
+      'developerPayload': instance.developerPayload,
     };
 
 PurchasesResultWrapper _$PurchasesResultWrapperFromJson(Map json) {
   return PurchasesResultWrapper(
-    responseCode: _$enumDecode(_$BillingResponseEnumMap, json['responseCode']),
+    responseCode:
+        const BillingResponseConverter().fromJson(json['responseCode'] as int),
+    billingResult: BillingResultWrapper.fromJson(json['billingResult'] as Map),
     purchasesList: (json['purchasesList'] as List)
         .map((e) => PurchaseWrapper.fromJson(e as Map))
         .toList(),
@@ -43,41 +75,24 @@
 Map<String, dynamic> _$PurchasesResultWrapperToJson(
         PurchasesResultWrapper instance) =>
     <String, dynamic>{
-      'responseCode': _$BillingResponseEnumMap[instance.responseCode],
+      'billingResult': instance.billingResult,
+      'responseCode':
+          const BillingResponseConverter().toJson(instance.responseCode),
       'purchasesList': instance.purchasesList,
     };
 
-T _$enumDecode<T>(
-  Map<T, dynamic> enumValues,
-  dynamic source, {
-  T unknownValue,
-}) {
-  if (source == null) {
-    throw ArgumentError('A value must be provided. Supported values: '
-        '${enumValues.values.join(', ')}');
-  }
-
-  final value = enumValues.entries
-      .singleWhere((e) => e.value == source, orElse: () => null)
-      ?.key;
-
-  if (value == null && unknownValue == null) {
-    throw ArgumentError('`$source` is not one of the supported values: '
-        '${enumValues.values.join(', ')}');
-  }
-  return value ?? unknownValue;
+PurchasesHistoryResult _$PurchasesHistoryResultFromJson(Map json) {
+  return PurchasesHistoryResult(
+    billingResult: BillingResultWrapper.fromJson(json['billingResult'] as Map),
+    purchaseHistoryRecordList: (json['purchaseHistoryRecordList'] as List)
+        .map((e) => PurchaseHistoryRecordWrapper.fromJson(e as Map))
+        .toList(),
+  );
 }
 
-const _$BillingResponseEnumMap = {
-  BillingResponse.featureNotSupported: -2,
-  BillingResponse.serviceDisconnected: -1,
-  BillingResponse.ok: 0,
-  BillingResponse.userCanceled: 1,
-  BillingResponse.serviceUnavailable: 2,
-  BillingResponse.billingUnavailable: 3,
-  BillingResponse.itemUnavailable: 4,
-  BillingResponse.developerError: 5,
-  BillingResponse.error: 6,
-  BillingResponse.itemAlreadyOwned: 7,
-  BillingResponse.itemNotOwned: 8,
-};
+Map<String, dynamic> _$PurchasesHistoryResultToJson(
+        PurchasesHistoryResult instance) =>
+    <String, dynamic>{
+      'billingResult': instance.billingResult,
+      'purchaseHistoryRecordList': instance.purchaseHistoryRecordList,
+    };
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart
index 670bf51..4d6a930 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.dart
@@ -35,6 +35,8 @@
     @required this.title,
     @required this.type,
     @required this.isRewarded,
+    @required this.originalPrice,
+    @required this.originalPriceAmountMicros,
   });
 
   /// Constructs an instance of this from a key value map of data.
@@ -84,6 +86,12 @@
   /// False if the product is paid.
   final bool isRewarded;
 
+  /// The original price that the user purchased this product for.
+  final String originalPrice;
+
+  /// [originalPrice] in micro-units ("990000").
+  final int originalPriceAmountMicros;
+
   @override
   bool operator ==(dynamic other) {
     if (other.runtimeType != runtimeType) {
@@ -104,7 +112,9 @@
         typedOther.subscriptionPeriod == subscriptionPeriod &&
         typedOther.title == title &&
         typedOther.type == type &&
-        typedOther.isRewarded == isRewarded;
+        typedOther.isRewarded == isRewarded &&
+        typedOther.originalPrice == originalPrice &&
+        typedOther.originalPriceAmountMicros == originalPriceAmountMicros;
   }
 
   @override
@@ -122,7 +132,9 @@
         subscriptionPeriod.hashCode,
         title.hashCode,
         type.hashCode,
-        isRewarded.hashCode);
+        isRewarded.hashCode,
+        originalPrice,
+        originalPriceAmountMicros);
   }
 }
 
@@ -130,10 +142,10 @@
 ///
 /// Returned by [BillingClient.querySkuDetails].
 @JsonSerializable()
-@BillingResponseConverter()
 class SkuDetailsResponseWrapper {
   @visibleForTesting
-  SkuDetailsResponseWrapper({@required this.responseCode, this.skuDetailsList});
+  SkuDetailsResponseWrapper(
+      {@required this.billingResult, this.skuDetailsList});
 
   /// Constructs an instance of this from a key value map of data.
   ///
@@ -142,8 +154,8 @@
   factory SkuDetailsResponseWrapper.fromJson(Map<String, dynamic> map) =>
       _$SkuDetailsResponseWrapperFromJson(map);
 
-  /// The final status of the [BillingClient.querySkuDetails] call.
-  final BillingResponse responseCode;
+  /// The final result of the [BillingClient.querySkuDetails] call.
+  final BillingResultWrapper billingResult;
 
   /// A list of [SkuDetailsWrapper] matching the query to [BillingClient.querySkuDetails].
   final List<SkuDetailsWrapper> skuDetailsList;
@@ -156,10 +168,48 @@
 
     final SkuDetailsResponseWrapper typedOther = other;
     return typedOther is SkuDetailsResponseWrapper &&
-        typedOther.responseCode == responseCode &&
+        typedOther.billingResult == billingResult &&
         typedOther.skuDetailsList == skuDetailsList;
   }
 
   @override
-  int get hashCode => hashValues(responseCode, skuDetailsList);
+  int get hashCode => hashValues(billingResult, skuDetailsList);
+}
+
+/// Params containing the response code and the debug message from the Play Billing API response.
+@JsonSerializable()
+@BillingResponseConverter()
+class BillingResultWrapper {
+  /// Constructs the object with [responseCode] and [debugMessage].
+  BillingResultWrapper({@required this.responseCode, this.debugMessage});
+
+  /// Constructs an instance of this from a key value map of data.
+  ///
+  /// The map needs to have named string keys with values matching the names and
+  /// types of all of the members on this class.
+  factory BillingResultWrapper.fromJson(Map map) =>
+      _$BillingResultWrapperFromJson(map);
+
+  /// Response code returned in the Play Billing API calls.
+  final BillingResponse responseCode;
+
+  /// Debug message returned in the Play Billing API calls.
+  ///
+  /// This message uses an en-US locale and should not be shown to users.
+  final String debugMessage;
+
+  @override
+  bool operator ==(dynamic other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+
+    final BillingResultWrapper typedOther = other;
+    return typedOther is BillingResultWrapper &&
+        typedOther.responseCode == responseCode &&
+        typedOther.debugMessage == debugMessage;
+  }
+
+  @override
+  int get hashCode => hashValues(responseCode, debugMessage);
 }
diff --git a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart
index 52447cc..70bde93 100644
--- a/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart
+++ b/packages/in_app_purchase/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart
@@ -20,8 +20,10 @@
     sku: json['sku'] as String,
     subscriptionPeriod: json['subscriptionPeriod'] as String,
     title: json['title'] as String,
-    type: _$enumDecode(_$SkuTypeEnumMap, json['type']),
+    type: const SkuTypeConverter().fromJson(json['type'] as String),
     isRewarded: json['isRewarded'] as bool,
+    originalPrice: json['originalPrice'] as String,
+    originalPriceAmountMicros: json['originalPriceAmountMicros'] as int,
   );
 }
 
@@ -39,39 +41,15 @@
       'sku': instance.sku,
       'subscriptionPeriod': instance.subscriptionPeriod,
       'title': instance.title,
-      'type': _$SkuTypeEnumMap[instance.type],
+      'type': const SkuTypeConverter().toJson(instance.type),
       'isRewarded': instance.isRewarded,
+      'originalPrice': instance.originalPrice,
+      'originalPriceAmountMicros': instance.originalPriceAmountMicros,
     };
 
-T _$enumDecode<T>(
-  Map<T, dynamic> enumValues,
-  dynamic source, {
-  T unknownValue,
-}) {
-  if (source == null) {
-    throw ArgumentError('A value must be provided. Supported values: '
-        '${enumValues.values.join(', ')}');
-  }
-
-  final value = enumValues.entries
-      .singleWhere((e) => e.value == source, orElse: () => null)
-      ?.key;
-
-  if (value == null && unknownValue == null) {
-    throw ArgumentError('`$source` is not one of the supported values: '
-        '${enumValues.values.join(', ')}');
-  }
-  return value ?? unknownValue;
-}
-
-const _$SkuTypeEnumMap = {
-  SkuType.inapp: 'inapp',
-  SkuType.subs: 'subs',
-};
-
 SkuDetailsResponseWrapper _$SkuDetailsResponseWrapperFromJson(Map json) {
   return SkuDetailsResponseWrapper(
-    responseCode: _$enumDecode(_$BillingResponseEnumMap, json['responseCode']),
+    billingResult: BillingResultWrapper.fromJson(json['billingResult'] as Map),
     skuDetailsList: (json['skuDetailsList'] as List)
         .map((e) => SkuDetailsWrapper.fromJson(e as Map))
         .toList(),
@@ -81,20 +59,22 @@
 Map<String, dynamic> _$SkuDetailsResponseWrapperToJson(
         SkuDetailsResponseWrapper instance) =>
     <String, dynamic>{
-      'responseCode': _$BillingResponseEnumMap[instance.responseCode],
+      'billingResult': instance.billingResult,
       'skuDetailsList': instance.skuDetailsList,
     };
 
-const _$BillingResponseEnumMap = {
-  BillingResponse.featureNotSupported: -2,
-  BillingResponse.serviceDisconnected: -1,
-  BillingResponse.ok: 0,
-  BillingResponse.userCanceled: 1,
-  BillingResponse.serviceUnavailable: 2,
-  BillingResponse.billingUnavailable: 3,
-  BillingResponse.itemUnavailable: 4,
-  BillingResponse.developerError: 5,
-  BillingResponse.error: 6,
-  BillingResponse.itemAlreadyOwned: 7,
-  BillingResponse.itemNotOwned: 8,
-};
+BillingResultWrapper _$BillingResultWrapperFromJson(Map json) {
+  return BillingResultWrapper(
+    responseCode:
+        const BillingResponseConverter().fromJson(json['responseCode'] as int),
+    debugMessage: json['debugMessage'] as String,
+  );
+}
+
+Map<String, dynamic> _$BillingResultWrapperToJson(
+        BillingResultWrapper instance) =>
+    <String, dynamic>{
+      'responseCode':
+          const BillingResponseConverter().toJson(instance.responseCode),
+      'debugMessage': instance.debugMessage,
+    };
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart
index 956eb09..f5ab95c 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart
@@ -62,13 +62,16 @@
   }
 
   @override
-  Future<void> completePurchase(PurchaseDetails purchase) {
-    return _skPaymentQueueWrapper
+  Future<BillingResultWrapper> completePurchase(PurchaseDetails purchase,
+      {String developerPayload}) async {
+    await _skPaymentQueueWrapper
         .finishTransaction(purchase.skPaymentTransaction);
+    return BillingResultWrapper(responseCode: BillingResponse.ok);
   }
 
   @override
-  Future<BillingResponse> consumePurchase(PurchaseDetails purchase) {
+  Future<BillingResultWrapper> consumePurchase(PurchaseDetails purchase,
+      {String developerPayload}) {
     throw UnsupportedError('consume purchase is not available on Android');
   }
 
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
index 96a3d0c..f2cd87b 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart
@@ -24,6 +24,9 @@
           _purchaseUpdatedController
               .add(await _getPurchaseDetailsFromResult(resultWrapper));
         }) {
+    if (InAppPurchaseConnection.enablePendingPurchase) {
+      billingClient.enablePendingPurchases();
+    }
     _readyFuture = _connect();
     WidgetsBinding.instance.addObserver(this);
     _purchaseUpdatedController = StreamController.broadcast();
@@ -50,10 +53,11 @@
 
   @override
   Future<bool> buyNonConsumable({@required PurchaseParam purchaseParam}) async {
-    BillingResponse response = await billingClient.launchBillingFlow(
-        sku: purchaseParam.productDetails.id,
-        accountId: purchaseParam.applicationUserName);
-    return response == BillingResponse.ok;
+    BillingResultWrapper billingResultWrapper =
+        await billingClient.launchBillingFlow(
+            sku: purchaseParam.productDetails.id,
+            accountId: purchaseParam.applicationUserName);
+    return billingResultWrapper.responseCode == BillingResponse.ok;
   }
 
   @override
@@ -66,14 +70,22 @@
   }
 
   @override
-  Future<void> completePurchase(PurchaseDetails purchase) {
-    throw UnsupportedError('complete purchase is not available on Android');
+  Future<BillingResultWrapper> completePurchase(PurchaseDetails purchase,
+      {String developerPayload}) async {
+    if (purchase.billingClientPurchase.isAcknowledged) {
+      return BillingResultWrapper(responseCode: BillingResponse.ok);
+    }
+    return await billingClient.acknowledgePurchase(
+        purchase.verificationData.serverVerificationData,
+        developerPayload: developerPayload);
   }
 
   @override
-  Future<BillingResponse> consumePurchase(PurchaseDetails purchase) {
-    return billingClient
-        .consumeAsync(purchase.verificationData.serverVerificationData);
+  Future<BillingResultWrapper> consumePurchase(PurchaseDetails purchase,
+      {String developerPayload}) {
+    return billingClient.consumeAsync(
+        purchase.verificationData.serverVerificationData,
+        developerPayload: developerPayload);
   }
 
   @override
@@ -90,9 +102,21 @@
       exception = e;
       responses = [
         PurchasesResultWrapper(
-            responseCode: BillingResponse.error, purchasesList: []),
+          responseCode: BillingResponse.error,
+          purchasesList: [],
+          billingResult: BillingResultWrapper(
+            responseCode: BillingResponse.error,
+            debugMessage: e.details.toString(),
+          ),
+        ),
         PurchasesResultWrapper(
-            responseCode: BillingResponse.error, purchasesList: [])
+          responseCode: BillingResponse.error,
+          purchasesList: [],
+          billingResult: BillingResultWrapper(
+            responseCode: BillingResponse.error,
+            debugMessage: e.details.toString(),
+          ),
+        )
       ];
     }
 
@@ -188,10 +212,14 @@
       responses = [
         // ignore: invalid_use_of_visible_for_testing_member
         SkuDetailsResponseWrapper(
-            responseCode: BillingResponse.error, skuDetailsList: []),
+            billingResult: BillingResultWrapper(
+                responseCode: BillingResponse.error, debugMessage: e.code),
+            skuDetailsList: []),
         // ignore: invalid_use_of_visible_for_testing_member
         SkuDetailsResponseWrapper(
-            responseCode: BillingResponse.error, skuDetailsList: [])
+            billingResult: BillingResultWrapper(
+                responseCode: BillingResponse.error, debugMessage: e.code),
+            skuDetailsList: [])
       ];
     }
     List<ProductDetails> productDetailsList =
@@ -220,23 +248,18 @@
   static Future<List<PurchaseDetails>> _getPurchaseDetailsFromResult(
       PurchasesResultWrapper resultWrapper) async {
     IAPError error;
-    PurchaseStatus status;
-    if (resultWrapper.responseCode == BillingResponse.ok) {
-      error = null;
-      status = PurchaseStatus.purchased;
-    } else {
+    if (resultWrapper.responseCode != BillingResponse.ok) {
       error = IAPError(
         source: IAPSource.GooglePlay,
         code: kPurchaseErrorCode,
         message: resultWrapper.responseCode.toString(),
+        details: resultWrapper.billingResult.debugMessage,
       );
-      status = PurchaseStatus.error;
     }
     final List<Future<PurchaseDetails>> purchases =
         resultWrapper.purchasesList.map((PurchaseWrapper purchase) {
-      return _maybeAutoConsumePurchase(PurchaseDetails.fromPurchase(purchase)
-        ..status = status
-        ..error = error);
+      return _maybeAutoConsumePurchase(
+          PurchaseDetails.fromPurchase(purchase)..error = error);
     }).toList();
     if (purchases.isNotEmpty) {
       return Future.wait(purchases);
@@ -260,14 +283,16 @@
       return purchaseDetails;
     }
 
-    final BillingResponse consumedResponse =
+    final BillingResultWrapper billingResult =
         await instance.consumePurchase(purchaseDetails);
+    final BillingResponse consumedResponse = billingResult.responseCode;
     if (consumedResponse != BillingResponse.ok) {
       purchaseDetails.status = PurchaseStatus.error;
       purchaseDetails.error = IAPError(
         source: IAPSource.GooglePlay,
         code: kConsumptionFailedErrorCode,
         message: consumedResponse.toString(),
+        details: billingResult.debugMessage,
       );
     }
     _productIdsToConsume.remove(purchaseDetails.productID);
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart
index 73990e8..4c4953d 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart
@@ -11,6 +11,8 @@
 import 'package:in_app_purchase/billing_client_wrappers.dart';
 import './purchase_details.dart';
 
+export 'package:in_app_purchase/billing_client_wrappers.dart';
+
 /// Basic API for making in app purchases across multiple platforms.
 ///
 /// This is a generic abstraction built from `billing_client_wrapers` and
@@ -58,9 +60,28 @@
     return _purchaseUpdatedStream;
   }
 
+  /// Whether pending purchase is enabled.
+  ///
+  /// See also [enablePendingPurchases] for more on pending purchases.
+  static bool get enablePendingPurchase => _enablePendingPurchase;
+  static bool _enablePendingPurchase = false;
+
   /// Returns true if the payment platform is ready and available.
   Future<bool> isAvailable();
 
+  /// Enable the [InAppPurchaseConnection] to handle pending purchases.
+  ///
+  /// Android Only: This method is required to be called when initialize the application.
+  /// It is to acknowledge your application has been updated to support pending purchases.
+  /// See [Support pending transactions](https://developer.android.com/google/play/billing/billing_library_overview#pending)
+  /// for more details.
+  /// Failure to call this method before access [instance] will throw an exception.
+  ///
+  /// It is an no-op on iOS.
+  static void enablePendingPurchases() {
+    _enablePendingPurchase = true;
+  }
+
   /// Query product details for the given set of IDs.
   ///
   /// The [identifiers] need to exactly match existing configured product
@@ -92,7 +113,7 @@
   /// purchasing process.
   ///
   /// This method does return whether or not the purchase request was initially
-  /// sent succesfully.
+  /// sent successfully.
   ///
   /// Consumable items are defined differently by the different underlying
   /// payment platforms, and there's no way to query for whether or not the
@@ -168,16 +189,27 @@
   Future<bool> buyConsumable(
       {@required PurchaseParam purchaseParam, bool autoConsume = true});
 
-  /// (App Store only) Mark that purchased content has been delivered to the
+  /// Mark that purchased content has been delivered to the
   /// user.
   ///
   /// You are responsible for completing every [PurchaseDetails] whose
-  /// [PurchaseDetails.status] is [PurchaseStatus.purchased] or
-  /// [[PurchaseStatus.error]. Completing a [PurchaseStatus.pending] purchase
-  /// will cause an exception.
+  /// [PurchaseDetails.status] is [PurchaseStatus.purchased]. Additionally on iOS,
+  /// the purchase needs to be completed if the [PurchaseDetails.status] is [PurchaseStatus.error].
+  /// Completing a [PurchaseStatus.pending] purchase will cause an exception.
+  /// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a purchase is pending for completion.
   ///
-  /// This throws an [UnsupportedError] on Android.
-  Future<void> completePurchase(PurchaseDetails purchase);
+  /// The method returns a [BillingResultWrapper] to indicate a detailed status of the complete process.
+  /// If the result contains [BillingResponse.error] or [BillingResponse.serviceUnavailable], the developer should try
+  /// to complete the purchase via this method again, or retry the [completePurchase] it at a later time.
+  /// If the result indicates other errors, there might be some issue with
+  /// the app's code. The developer is responsible to fix the issue.
+  ///
+  /// Warning! Failure to call this method and get a successful response within 3 days of the purchase will result a refund on Android.
+  /// The [consumePurchase] acts as an implicit [completePurchase] on Android.
+  ///
+  /// The optional parameter `developerPayload` only works on Android.
+  Future<BillingResultWrapper> completePurchase(PurchaseDetails purchase,
+      {String developerPayload});
 
   /// (Play only) Mark that the user has consumed a product.
   ///
@@ -185,8 +217,11 @@
   /// delivered. The user won't be able to buy the same product again until the
   /// purchase of the product is consumed.
   ///
+  /// The `developerPayload` can be specified to be associated with this consumption.
+  ///
   /// This throws an [UnsupportedError] on iOS.
-  Future<BillingResponse> consumePurchase(PurchaseDetails purchase);
+  Future<BillingResultWrapper> consumePurchase(PurchaseDetails purchase,
+      {String developerPayload});
 
   /// Query all previous purchases.
   ///
diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart b/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart
index 7994209..375b48a 100644
--- a/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart
+++ b/packages/in_app_purchase/lib/src/in_app_purchase/purchase_details.dart
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 import 'package:flutter/foundation.dart';
+import 'package:in_app_purchase/src/billing_client_wrappers/enum_converters.dart';
 import 'package:in_app_purchase/src/billing_client_wrappers/purchase_wrapper.dart';
+import 'package:in_app_purchase/src/store_kit_wrappers/enum_converters.dart';
 import 'package:in_app_purchase/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart';
 import './in_app_purchase_connection.dart';
 import './product_details.dart';
@@ -11,6 +13,8 @@
 final String kPurchaseErrorCode = 'purchase_error';
 final String kRestoredPurchaseErrorCode = 'restore_transactions_failed';
 final String kConsumptionFailedErrorCode = 'consume_purchase_failed';
+final String _kPlatformIOS = 'ios';
+final String _kPlatformAndroid = 'android';
 
 /// Represents the data that is used to verify purchases.
 ///
@@ -122,7 +126,23 @@
   final String transactionDate;
 
   /// The status that this [PurchaseDetails] is currently on.
-  PurchaseStatus status;
+  PurchaseStatus get status => _status;
+  set status(PurchaseStatus status) {
+    if (_platform == _kPlatformIOS) {
+      if (status == PurchaseStatus.purchased ||
+          status == PurchaseStatus.error) {
+        _pendingCompletePurchase = true;
+      }
+    }
+    if (_platform == _kPlatformAndroid) {
+      if (status == PurchaseStatus.purchased) {
+        _pendingCompletePurchase = true;
+      }
+    }
+    _status = status;
+  }
+
+  PurchaseStatus _status;
 
   /// The error is only available when [status] is [PurchaseStatus.error].
   IAPError error;
@@ -137,6 +157,19 @@
   /// This is null on iOS.
   final PurchaseWrapper billingClientPurchase;
 
+  /// The developer has to call [InAppPurchaseConnection.completePurchase] if the value is `true`
+  /// and the product has been delivered to the user.
+  ///
+  /// The initial value is `false`.
+  /// * See also [InAppPurchaseConnection.completePurchase] for more details on completing purchases.
+  bool get pendingCompletePurchase => _pendingCompletePurchase;
+  bool _pendingCompletePurchase = false;
+
+  // The platform that the object is created on.
+  //
+  // The value is either '_kPlatformIOS' or '_kPlatformAndroid'.
+  String _platform;
+
   PurchaseDetails({
     @required this.purchaseID,
     @required this.productID,
@@ -159,7 +192,10 @@
             ? (transaction.transactionTimeStamp * 1000).toInt().toString()
             : null,
         this.skPaymentTransaction = transaction,
-        this.billingClientPurchase = null;
+        this.billingClientPurchase = null,
+        _status = SKTransactionStatusConverter()
+            .toPurchaseStatus(transaction.transactionState),
+        _platform = _kPlatformIOS;
 
   /// Generate a [PurchaseDetails] object based on an Android [Purchase] object.
   PurchaseDetails.fromPurchase(PurchaseWrapper purchase)
@@ -171,7 +207,10 @@
             source: IAPSource.GooglePlay),
         this.transactionDate = purchase.purchaseTime.toString(),
         this.skPaymentTransaction = null,
-        this.billingClientPurchase = purchase;
+        this.billingClientPurchase = purchase,
+        _status =
+            PurchaseStateConverter().toPurchaseStatus(purchase.purchaseState),
+        _platform = _kPlatformAndroid;
 }
 
 /// The response object for fetching the past purchases.
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart
index cb21a01..bc52082 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart
@@ -11,8 +11,8 @@
     payment: json['payment'] == null
         ? null
         : SKPaymentWrapper.fromJson(json['payment'] as Map),
-    transactionState: _$enumDecodeNullable(
-        _$SKPaymentTransactionStateWrapperEnumMap, json['transactionState']),
+    transactionState: const SKTransactionStatusConverter()
+        .fromJson(json['transactionState'] as int),
     originalTransaction: json['originalTransaction'] == null
         ? null
         : SKPaymentTransactionWrapper.fromJson(
@@ -27,51 +27,11 @@
 Map<String, dynamic> _$SKPaymentTransactionWrapperToJson(
         SKPaymentTransactionWrapper instance) =>
     <String, dynamic>{
-      'transactionState':
-          _$SKPaymentTransactionStateWrapperEnumMap[instance.transactionState],
+      'transactionState': const SKTransactionStatusConverter()
+          .toJson(instance.transactionState),
       'payment': instance.payment,
       'originalTransaction': instance.originalTransaction,
       'transactionTimeStamp': instance.transactionTimeStamp,
       'transactionIdentifier': instance.transactionIdentifier,
       'error': instance.error,
     };
-
-T _$enumDecode<T>(
-  Map<T, dynamic> enumValues,
-  dynamic source, {
-  T unknownValue,
-}) {
-  if (source == null) {
-    throw ArgumentError('A value must be provided. Supported values: '
-        '${enumValues.values.join(', ')}');
-  }
-
-  final value = enumValues.entries
-      .singleWhere((e) => e.value == source, orElse: () => null)
-      ?.key;
-
-  if (value == null && unknownValue == null) {
-    throw ArgumentError('`$source` is not one of the supported values: '
-        '${enumValues.values.join(', ')}');
-  }
-  return value ?? unknownValue;
-}
-
-T _$enumDecodeNullable<T>(
-  Map<T, dynamic> enumValues,
-  dynamic source, {
-  T unknownValue,
-}) {
-  if (source == null) {
-    return null;
-  }
-  return _$enumDecode<T>(enumValues, source, unknownValue: unknownValue);
-}
-
-const _$SKPaymentTransactionStateWrapperEnumMap = {
-  SKPaymentTransactionStateWrapper.purchasing: 0,
-  SKPaymentTransactionStateWrapper.purchased: 1,
-  SKPaymentTransactionStateWrapper.failed: 2,
-  SKPaymentTransactionStateWrapper.restored: 3,
-  SKPaymentTransactionStateWrapper.deferred: 4,
-};
diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml
index dd94ffd..f826758 100644
--- a/packages/in_app_purchase/pubspec.yaml
+++ b/packages/in_app_purchase/pubspec.yaml
@@ -1,7 +1,7 @@
 name: in_app_purchase
 description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
 homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
-version: 0.2.2+6
+version: 0.3.0
 
 
 dependencies:
diff --git a/packages/in_app_purchase/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/test/billing_client_wrappers/billing_client_wrapper_test.dart
index 8182506..54f7c3e 100644
--- a/packages/in_app_purchase/test/billing_client_wrappers/billing_client_wrapper_test.dart
+++ b/packages/in_app_purchase/test/billing_client_wrappers/billing_client_wrapper_test.dart
@@ -23,6 +23,7 @@
 
   setUp(() {
     billingClient = BillingClient((PurchasesResultWrapper _) {});
+    billingClient.enablePendingPurchases();
     stubPlatform.reset();
   });
 
@@ -39,25 +40,43 @@
   });
 
   group('startConnection', () {
-    test('returns BillingResponse', () async {
+    final String methodName =
+        'BillingClient#startConnection(BillingClientStateListener)';
+    test('returns BillingResultWrapper', () async {
+      const String debugMessage = 'dummy message';
+      final BillingResponse responseCode = BillingResponse.developerError;
       stubPlatform.addResponse(
-          name: 'BillingClient#startConnection(BillingClientStateListener)',
-          value: BillingResponseConverter().toJson(BillingResponse.ok));
+        name: methodName,
+        value: <String, dynamic>{
+          'responseCode': BillingResponseConverter().toJson(responseCode),
+          'debugMessage': debugMessage,
+        },
+      );
+
+      BillingResultWrapper billingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       expect(
           await billingClient.startConnection(
               onBillingServiceDisconnected: () {}),
-          equals(BillingResponse.ok));
+          equals(billingResult));
     });
 
     test('passes handle to onBillingServiceDisconnected', () async {
-      final String methodName =
-          'BillingClient#startConnection(BillingClientStateListener)';
+      const String debugMessage = 'dummy message';
+      final BillingResponse responseCode = BillingResponse.developerError;
       stubPlatform.addResponse(
-          name: methodName,
-          value: BillingResponseConverter().toJson(BillingResponse.ok));
+        name: methodName,
+        value: <String, dynamic>{
+          'responseCode': BillingResponseConverter().toJson(responseCode),
+          'debugMessage': debugMessage,
+        },
+      );
       await billingClient.startConnection(onBillingServiceDisconnected: () {});
       final MethodCall call = stubPlatform.previousCallMatching(methodName);
-      expect(call.arguments, equals(<dynamic, dynamic>{'handle': 0}));
+      expect(
+          call.arguments,
+          equals(
+              <dynamic, dynamic>{'handle': 0, 'enablePendingPurchases': true}));
     });
   });
 
@@ -74,9 +93,13 @@
         'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)';
 
     test('handles empty skuDetails', () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse responseCode = BillingResponse.developerError;
       stubPlatform.addResponse(name: queryMethodName, value: <dynamic, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
+        'billingResult': <String, dynamic>{
+          'responseCode': BillingResponseConverter().toJson(responseCode),
+          'debugMessage': debugMessage,
+        },
         'skuDetailsList': <Map<String, dynamic>>[]
       });
 
@@ -84,14 +107,20 @@
           .querySkuDetails(
               skuType: SkuType.inapp, skusList: <String>['invalid']);
 
-      expect(response.responseCode, equals(responseCode));
+      BillingResultWrapper billingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
+      expect(response.billingResult, equals(billingResult));
       expect(response.skuDetailsList, isEmpty);
     });
 
     test('returns SkuDetailsResponseWrapper', () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse responseCode = BillingResponse.ok;
       stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
+        'billingResult': <String, dynamic>{
+          'responseCode': BillingResponseConverter().toJson(responseCode),
+          'debugMessage': debugMessage,
+        },
         'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
       });
 
@@ -99,7 +128,9 @@
           .querySkuDetails(
               skuType: SkuType.inapp, skusList: <String>['invalid']);
 
-      expect(response.responseCode, equals(responseCode));
+      BillingResultWrapper billingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
+      expect(response.billingResult, equals(billingResult));
       expect(response.skuDetailsList, contains(dummySkuDetails));
     });
   });
@@ -109,17 +140,21 @@
         'BillingClient#launchBillingFlow(Activity, BillingFlowParams)';
 
     test('serializes and deserializes data', () async {
-      final BillingResponse sentCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResponse responseCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
-          name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode));
+        name: launchMethodName,
+        value: buildBillingResultMap(expectedBillingResult),
+      );
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
       final String accountId = "hashedAccountId";
 
-      final BillingResponse receivedCode = await billingClient
-          .launchBillingFlow(sku: skuDetails.sku, accountId: accountId);
-
-      expect(receivedCode, equals(sentCode));
+      expect(
+          await billingClient.launchBillingFlow(
+              sku: skuDetails.sku, accountId: accountId),
+          equals(expectedBillingResult));
       Map<dynamic, dynamic> arguments =
           stubPlatform.previousCallMatching(launchMethodName).arguments;
       expect(arguments['sku'], equals(skuDetails.sku));
@@ -127,16 +162,18 @@
     });
 
     test('handles null accountId', () async {
-      final BillingResponse sentCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResponse responseCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
-          name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode));
+        name: launchMethodName,
+        value: buildBillingResultMap(expectedBillingResult),
+      );
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
 
-      final BillingResponse receivedCode =
-          await billingClient.launchBillingFlow(sku: skuDetails.sku);
-
-      expect(receivedCode, equals(sentCode));
+      expect(await billingClient.launchBillingFlow(sku: skuDetails.sku),
+          equals(expectedBillingResult));
       Map<dynamic, dynamic> arguments =
           stubPlatform.previousCallMatching(launchMethodName).arguments;
       expect(arguments['sku'], equals(skuDetails.sku));
@@ -153,8 +190,12 @@
       final List<PurchaseWrapper> expectedList = <PurchaseWrapper>[
         dummyPurchase
       ];
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform
           .addResponse(name: queryPurchasesMethodName, value: <String, dynamic>{
+        'billingResult': buildBillingResultMap(expectedBillingResult),
         'responseCode': BillingResponseConverter().toJson(expectedCode),
         'purchasesList': expectedList
             .map((PurchaseWrapper purchase) => buildPurchaseMap(purchase))
@@ -164,6 +205,7 @@
       final PurchasesResultWrapper response =
           await billingClient.queryPurchases(SkuType.inapp);
 
+      expect(response.billingResult, equals(expectedBillingResult));
       expect(response.responseCode, equals(expectedCode));
       expect(response.purchasesList, equals(expectedList));
     });
@@ -174,8 +216,12 @@
 
     test('handles empty purchases', () async {
       final BillingResponse expectedCode = BillingResponse.userCanceled;
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform
           .addResponse(name: queryPurchasesMethodName, value: <String, dynamic>{
+        'billingResult': buildBillingResultMap(expectedBillingResult),
         'responseCode': BillingResponseConverter().toJson(expectedCode),
         'purchasesList': [],
       });
@@ -183,6 +229,7 @@
       final PurchasesResultWrapper response =
           await billingClient.queryPurchases(SkuType.inapp);
 
+      expect(response.billingResult, equals(expectedBillingResult));
       expect(response.responseCode, equals(expectedCode));
       expect(response.purchasesList, isEmpty);
     });
@@ -194,23 +241,27 @@
 
     test('serializes and deserializes data', () async {
       final BillingResponse expectedCode = BillingResponse.ok;
-      final List<PurchaseWrapper> expectedList = <PurchaseWrapper>[
-        dummyPurchase
+      final List<PurchaseHistoryRecordWrapper> expectedList =
+          <PurchaseHistoryRecordWrapper>[
+        dummyPurchaseHistoryRecord,
       ];
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: queryPurchaseHistoryMethodName,
           value: <String, dynamic>{
-            'responseCode': BillingResponseConverter().toJson(expectedCode),
-            'purchasesList': expectedList
-                .map((PurchaseWrapper purchase) => buildPurchaseMap(purchase))
+            'billingResult': buildBillingResultMap(expectedBillingResult),
+            'purchaseHistoryRecordList': expectedList
+                .map((PurchaseHistoryRecordWrapper purchaseHistoryRecord) =>
+                    buildPurchaseHistoryRecordMap(purchaseHistoryRecord))
                 .toList(),
           });
 
-      final PurchasesResultWrapper response =
+      final PurchasesHistoryResult response =
           await billingClient.queryPurchaseHistory(SkuType.inapp);
-
-      expect(response.responseCode, equals(expectedCode));
-      expect(response.purchasesList, equals(expectedList));
+      expect(response.billingResult, equals(expectedBillingResult));
+      expect(response.purchaseHistoryRecordList, equals(expectedList));
     });
 
     test('checks for null params', () async {
@@ -220,18 +271,19 @@
 
     test('handles empty purchases', () async {
       final BillingResponse expectedCode = BillingResponse.userCanceled;
-      stubPlatform.addResponse(
-          name: queryPurchaseHistoryMethodName,
-          value: <String, dynamic>{
-            'responseCode': BillingResponseConverter().toJson(expectedCode),
-            'purchasesList': [],
-          });
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
+      stubPlatform.addResponse(name: queryPurchaseHistoryMethodName, value: {
+        'billingResult': buildBillingResultMap(expectedBillingResult),
+        'purchaseHistoryRecordList': [],
+      });
 
-      final PurchasesResultWrapper response =
+      final PurchasesHistoryResult response =
           await billingClient.queryPurchaseHistory(SkuType.inapp);
 
-      expect(response.responseCode, equals(expectedCode));
-      expect(response.purchasesList, isEmpty);
+      expect(response.billingResult, equals(expectedBillingResult));
+      expect(response.purchaseHistoryRecordList, isEmpty);
     });
   });
 
@@ -240,14 +292,37 @@
         'BillingClient#consumeAsync(String, ConsumeResponseListener)';
     test('consume purchase async success', () async {
       final BillingResponse expectedCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: consumeMethodName,
-          value: BillingResponseConverter().toJson(expectedCode));
+          value: buildBillingResultMap(expectedBillingResult));
 
-      final BillingResponse responseCode =
-          await billingClient.consumeAsync('dummy token');
+      final BillingResultWrapper billingResult = await billingClient
+          .consumeAsync('dummy token', developerPayload: 'dummy payload');
 
-      expect(responseCode, equals(expectedCode));
+      expect(billingResult, equals(expectedBillingResult));
+    });
+  });
+
+  group('acknowledge purchases', () {
+    const String acknowledgeMethodName =
+        'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
+    test('acknowledge purchase success', () async {
+      final BillingResponse expectedCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
+      stubPlatform.addResponse(
+          name: acknowledgeMethodName,
+          value: buildBillingResultMap(expectedBillingResult));
+
+      final BillingResultWrapper billingResult =
+          await billingClient.acknowledgePurchase('dummy token',
+              developerPayload: 'dummy payload');
+
+      expect(billingResult, equals(expectedBillingResult));
     });
   });
 }
diff --git a/packages/in_app_purchase/test/billing_client_wrappers/purchase_wrapper_test.dart b/packages/in_app_purchase/test/billing_client_wrappers/purchase_wrapper_test.dart
index f1865b4..6f65bdc 100644
--- a/packages/in_app_purchase/test/billing_client_wrappers/purchase_wrapper_test.dart
+++ b/packages/in_app_purchase/test/billing_client_wrappers/purchase_wrapper_test.dart
@@ -17,6 +17,33 @@
   purchaseToken: 'purchaseToken',
   isAutoRenewing: false,
   originalJson: '',
+  developerPayload: 'dummy payload',
+  isAcknowledged: true,
+  purchaseState: PurchaseStateWrapper.purchased,
+);
+
+final PurchaseWrapper dummyUnacknowledgedPurchase = PurchaseWrapper(
+  orderId: 'orderId',
+  packageName: 'packageName',
+  purchaseTime: 0,
+  signature: 'signature',
+  sku: 'sku',
+  purchaseToken: 'purchaseToken',
+  isAutoRenewing: false,
+  originalJson: '',
+  developerPayload: 'dummy payload',
+  isAcknowledged: false,
+  purchaseState: PurchaseStateWrapper.purchased,
+);
+
+final PurchaseHistoryRecordWrapper dummyPurchaseHistoryRecord =
+    PurchaseHistoryRecordWrapper(
+  purchaseTime: 0,
+  signature: 'signature',
+  sku: 'sku',
+  purchaseToken: 'purchaseToken',
+  originalJson: '',
+  developerPayload: 'dummy payload',
 );
 
 void main() {
@@ -45,6 +72,17 @@
     });
   });
 
+  group('PurchaseHistoryRecordWrapper', () {
+    test('converts from map', () {
+      final PurchaseHistoryRecordWrapper expected = dummyPurchaseHistoryRecord;
+      final PurchaseHistoryRecordWrapper parsed =
+          PurchaseHistoryRecordWrapper.fromJson(
+              buildPurchaseHistoryRecordMap(expected));
+
+      expect(parsed, equals(expected));
+    });
+  });
+
   group('PurchasesResultWrapper', () {
     test('parsed from map', () {
       final BillingResponse responseCode = BillingResponse.ok;
@@ -52,22 +90,55 @@
         dummyPurchase,
         dummyPurchase
       ];
+      const String debugMessage = 'dummy Message';
+      final BillingResultWrapper billingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       final PurchasesResultWrapper expected = PurchasesResultWrapper(
-          responseCode: responseCode, purchasesList: purchases);
-
+          billingResult: billingResult,
+          responseCode: responseCode,
+          purchasesList: purchases);
       final PurchasesResultWrapper parsed =
           PurchasesResultWrapper.fromJson(<String, dynamic>{
+        'billingResult': buildBillingResultMap(billingResult),
         'responseCode': BillingResponseConverter().toJson(responseCode),
         'purchasesList': <Map<String, dynamic>>[
           buildPurchaseMap(dummyPurchase),
           buildPurchaseMap(dummyPurchase)
         ]
       });
-
+      expect(parsed.billingResult, equals(expected.billingResult));
       expect(parsed.responseCode, equals(expected.responseCode));
       expect(parsed.purchasesList, containsAll(expected.purchasesList));
     });
   });
+
+  group('PurchasesHistoryResult', () {
+    test('parsed from map', () {
+      final BillingResponse responseCode = BillingResponse.ok;
+      final List<PurchaseHistoryRecordWrapper> purchaseHistoryRecordList =
+          <PurchaseHistoryRecordWrapper>[
+        dummyPurchaseHistoryRecord,
+        dummyPurchaseHistoryRecord
+      ];
+      const String debugMessage = 'dummy Message';
+      final BillingResultWrapper billingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
+      final PurchasesHistoryResult expected = PurchasesHistoryResult(
+          billingResult: billingResult,
+          purchaseHistoryRecordList: purchaseHistoryRecordList);
+      final PurchasesHistoryResult parsed =
+          PurchasesHistoryResult.fromJson(<String, dynamic>{
+        'billingResult': buildBillingResultMap(billingResult),
+        'purchaseHistoryRecordList': <Map<String, dynamic>>[
+          buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord),
+          buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord)
+        ]
+      });
+      expect(parsed.billingResult, equals(billingResult));
+      expect(parsed.purchaseHistoryRecordList,
+          containsAll(expected.purchaseHistoryRecordList));
+    });
+  });
 }
 
 Map<String, dynamic> buildPurchaseMap(PurchaseWrapper original) {
@@ -80,5 +151,27 @@
     'purchaseToken': original.purchaseToken,
     'isAutoRenewing': original.isAutoRenewing,
     'originalJson': original.originalJson,
+    'developerPayload': original.developerPayload,
+    'purchaseState': PurchaseStateConverter().toJson(original.purchaseState),
+    'isAcknowledged': original.isAcknowledged,
+  };
+}
+
+Map<String, dynamic> buildPurchaseHistoryRecordMap(
+    PurchaseHistoryRecordWrapper original) {
+  return <String, dynamic>{
+    'purchaseTime': original.purchaseTime,
+    'signature': original.signature,
+    'sku': original.sku,
+    'purchaseToken': original.purchaseToken,
+    'originalJson': original.originalJson,
+    'developerPayload': original.developerPayload,
+  };
+}
+
+Map<String, dynamic> buildBillingResultMap(BillingResultWrapper original) {
+  return <String, dynamic>{
+    'responseCode': BillingResponseConverter().toJson(original.responseCode),
+    'debugMessage': original.debugMessage,
   };
 }
diff --git a/packages/in_app_purchase/test/billing_client_wrappers/sku_details_wrapper_test.dart b/packages/in_app_purchase/test/billing_client_wrappers/sku_details_wrapper_test.dart
index ace2f41..c305e6d 100644
--- a/packages/in_app_purchase/test/billing_client_wrappers/sku_details_wrapper_test.dart
+++ b/packages/in_app_purchase/test/billing_client_wrappers/sku_details_wrapper_test.dart
@@ -4,8 +4,8 @@
 
 import 'package:test/test.dart';
 import 'package:in_app_purchase/billing_client_wrappers.dart';
-import 'package:in_app_purchase/src/billing_client_wrappers/enum_converters.dart';
 import 'package:in_app_purchase/src/in_app_purchase/product_details.dart';
+import 'package:in_app_purchase/src/billing_client_wrappers/enum_converters.dart';
 
 final SkuDetailsWrapper dummySkuDetails = SkuDetailsWrapper(
   description: 'description',
@@ -22,6 +22,8 @@
   title: 'title',
   type: SkuType.inapp,
   isRewarded: true,
+  originalPrice: 'originalPrice',
+  originalPriceAmountMicros: 1000,
 );
 
 void main() {
@@ -38,23 +40,29 @@
   group('SkuDetailsResponseWrapper', () {
     test('parsed from map', () {
       final BillingResponse responseCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
       final List<SkuDetailsWrapper> skusDetails = <SkuDetailsWrapper>[
         dummySkuDetails,
         dummySkuDetails
       ];
+      BillingResultWrapper result = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       final SkuDetailsResponseWrapper expected = SkuDetailsResponseWrapper(
-          responseCode: responseCode, skuDetailsList: skusDetails);
+          billingResult: result, skuDetailsList: skusDetails);
 
       final SkuDetailsResponseWrapper parsed =
           SkuDetailsResponseWrapper.fromJson(<String, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
+        'billingResult': <String, dynamic>{
+          'responseCode': BillingResponseConverter().toJson(responseCode),
+          'debugMessage': debugMessage,
+        },
         'skuDetailsList': <Map<String, dynamic>>[
           buildSkuMap(dummySkuDetails),
           buildSkuMap(dummySkuDetails)
         ]
       });
 
-      expect(parsed.responseCode, equals(expected.responseCode));
+      expect(parsed.billingResult, equals(expected.billingResult));
       expect(parsed.skuDetailsList, containsAll(expected.skuDetailsList));
     });
 
@@ -72,17 +80,23 @@
 
     test('handles empty list of skuDetails', () {
       final BillingResponse responseCode = BillingResponse.error;
+      const String debugMessage = 'dummy message';
       final List<SkuDetailsWrapper> skusDetails = <SkuDetailsWrapper>[];
+      BillingResultWrapper billingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       final SkuDetailsResponseWrapper expected = SkuDetailsResponseWrapper(
-          responseCode: responseCode, skuDetailsList: skusDetails);
+          billingResult: billingResult, skuDetailsList: skusDetails);
 
       final SkuDetailsResponseWrapper parsed =
           SkuDetailsResponseWrapper.fromJson(<String, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
+        'billingResult': <String, dynamic>{
+          'responseCode': BillingResponseConverter().toJson(responseCode),
+          'debugMessage': debugMessage,
+        },
         'skuDetailsList': <Map<String, dynamic>>[]
       });
 
-      expect(parsed.responseCode, equals(expected.responseCode));
+      expect(parsed.billingResult, equals(expected.billingResult));
       expect(parsed.skuDetailsList, containsAll(expected.skuDetailsList));
     });
   });
@@ -104,5 +118,7 @@
     'title': original.title,
     'type': original.type.toString().substring(8),
     'isRewarded': original.isRewarded,
+    'originalPrice': original.originalPrice,
+    'originalPriceAmountMicros': original.originalPriceAmountMicros,
   };
 }
diff --git a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart
index ae24167..9f963c4 100644
--- a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart
+++ b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart
@@ -245,7 +245,7 @@
       subscription = stream.listen((purchaseDetailsList) {
         details.addAll(purchaseDetailsList);
         purchaseDetailsList.forEach((purchaseDetails) {
-          if (purchaseDetails.status == PurchaseStatus.purchased) {
+          if (purchaseDetails.pendingCompletePurchase) {
             AppStoreConnection.instance.completePurchase(purchaseDetails);
             completer.complete(details);
             subscription.cancel();
diff --git a/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart b/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart
index 512664a..dfad32d 100644
--- a/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart
+++ b/packages/in_app_purchase/test/in_app_purchase_connection/google_play_connection_test.dart
@@ -35,10 +35,15 @@
 
   setUp(() {
     WidgetsFlutterBinding.ensureInitialized();
+    const String debugMessage = 'dummy message';
+    final BillingResponse responseCode = BillingResponse.ok;
+    final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+        responseCode: responseCode, debugMessage: debugMessage);
     stubPlatform.addResponse(
         name: startConnectionCall,
-        value: BillingResponseConverter().toJson(BillingResponse.ok));
+        value: buildBillingResultMap(expectedBillingResult));
     stubPlatform.addResponse(name: endConnectionCall, value: null);
+    InAppPurchaseConnection.enablePendingPurchases();
     connection = GooglePlayConnection.instance;
   });
 
@@ -82,10 +87,13 @@
         'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)';
 
     test('handles empty skuDetails', () async {
-      final BillingResponse responseCode = BillingResponse.developerError;
-      stubPlatform.addResponse(name: queryMethodName, value: <dynamic, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
-        'skuDetailsList': <Map<String, dynamic>>[]
+      const String debugMessage = 'dummy message';
+      final BillingResponse responseCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
+      stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
+        'billingResult': buildBillingResultMap(expectedBillingResult),
+        'skuDetailsList': [],
       });
 
       final ProductDetailsResponse response =
@@ -94,9 +102,12 @@
     });
 
     test('should get correct product details', () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse responseCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
+        'billingResult': buildBillingResultMap(expectedBillingResult),
         'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
       });
       // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
@@ -110,9 +121,12 @@
     });
 
     test('should get the correct notFoundIDs', () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse responseCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
-        'responseCode': BillingResponseConverter().toJson(responseCode),
+        'billingResult': buildBillingResultMap(expectedBillingResult),
         'skuDetailsList': <Map<String, dynamic>>[buildSkuMap(dummySkuDetails)]
       });
       // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead
@@ -157,8 +171,13 @@
   group('queryPurchaseDetails', () {
     const String queryMethodName = 'BillingClient#queryPurchases(String)';
     test('handles error', () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse responseCode = BillingResponse.developerError;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
+
       stubPlatform.addResponse(name: queryMethodName, value: <dynamic, dynamic>{
+        'billingResult': buildBillingResultMap(expectedBillingResult),
         'responseCode': BillingResponseConverter().toJson(responseCode),
         'purchasesList': <Map<String, dynamic>>[]
       });
@@ -170,8 +189,13 @@
     });
 
     test('returns SkuDetailsResponseWrapper', () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse responseCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
+
       stubPlatform.addResponse(name: queryMethodName, value: <String, dynamic>{
+        'billingResult': buildBillingResultMap(expectedBillingResult),
         'responseCode': BillingResponseConverter().toJson(responseCode),
         'purchasesList': <Map<String, dynamic>>[
           buildPurchaseMap(dummyPurchase),
@@ -187,11 +211,16 @@
     });
 
     test('should store platform exception in the response', () async {
+      const String debugMessage = 'dummy message';
+
       final BillingResponse responseCode = BillingResponse.developerError;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: responseCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: queryMethodName,
           value: <dynamic, dynamic>{
             'responseCode': BillingResponseConverter().toJson(responseCode),
+            'billingResult': buildBillingResultMap(expectedBillingResult),
             'purchasesList': <Map<String, dynamic>>[]
           },
           additionalStepBeforeReturn: (_) {
@@ -225,13 +254,18 @@
     test('buy non consumable, serializes and deserializes data', () async {
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
       final String accountId = "hashedAccountId";
+      const String debugMessage = 'dummy message';
       final BillingResponse sentCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
+
       stubPlatform.addResponse(
           name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode),
+          value: buildBillingResultMap(expectedBillingResult),
           additionalStepBeforeReturn: (_) {
             // Mock java update purchase callback.
             MethodCall call = MethodCall(kOnPurchasesUpdated, {
+              'billingResult': buildBillingResultMap(expectedBillingResult),
               'responseCode': BillingResponseConverter().toJson(sentCode),
               'purchasesList': [
                 {
@@ -242,7 +276,10 @@
                   'purchaseTime': 1231231231,
                   'purchaseToken': "token",
                   'signature': 'sign',
-                  'originalJson': 'json'
+                  'originalJson': 'json',
+                  'developerPayload': 'dummy payload',
+                  'isAcknowledged': true,
+                  'purchaseState': 1,
                 }
               ]
             });
@@ -274,13 +311,18 @@
     test('handles an error with an empty purchases list', () async {
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
       final String accountId = "hashedAccountId";
+      const String debugMessage = 'dummy message';
       final BillingResponse sentCode = BillingResponse.error;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
+
       stubPlatform.addResponse(
           name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode),
+          value: buildBillingResultMap(expectedBillingResult),
           additionalStepBeforeReturn: (_) {
             // Mock java update purchase callback.
             MethodCall call = MethodCall(kOnPurchasesUpdated, {
+              'billingResult': buildBillingResultMap(expectedBillingResult),
               'responseCode': BillingResponseConverter().toJson(sentCode),
               'purchasesList': []
             });
@@ -313,13 +355,18 @@
         () async {
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
       final String accountId = "hashedAccountId";
+      const String debugMessage = 'dummy message';
       final BillingResponse sentCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
+
       stubPlatform.addResponse(
           name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode),
+          value: buildBillingResultMap(expectedBillingResult),
           additionalStepBeforeReturn: (_) {
             // Mock java update purchase callback.
             MethodCall call = MethodCall(kOnPurchasesUpdated, {
+              'billingResult': buildBillingResultMap(expectedBillingResult),
               'responseCode': BillingResponseConverter().toJson(sentCode),
               'purchasesList': [
                 {
@@ -330,7 +377,10 @@
                   'purchaseTime': 1231231231,
                   'purchaseToken': "token",
                   'signature': 'sign',
-                  'originalJson': 'json'
+                  'originalJson': 'json',
+                  'developerPayload': 'dummy payload',
+                  'isAcknowledged': true,
+                  'purchaseState': 1,
                 }
               ]
             });
@@ -339,9 +389,12 @@
       Completer consumeCompleter = Completer();
       // adding call back for consume purchase
       final BillingResponse expectedCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResultForConsume =
+          BillingResultWrapper(
+              responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: consumeMethodName,
-          value: BillingResponseConverter().toJson(expectedCode),
+          value: buildBillingResultMap(expectedBillingResultForConsume),
           additionalStepBeforeReturn: (dynamic args) {
             String purchaseToken = args['purchaseToken'];
             consumeCompleter.complete((purchaseToken));
@@ -374,10 +427,13 @@
 
     test('buyNonConsumable propagates failures to launch the billing flow',
         () async {
+      const String debugMessage = 'dummy message';
       final BillingResponse sentCode = BillingResponse.error;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode));
+          value: buildBillingResultMap(expectedBillingResult));
 
       final bool result = await GooglePlayConnection.instance.buyNonConsumable(
           purchaseParam: PurchaseParam(
@@ -389,10 +445,14 @@
 
     test('buyConsumable propagates failures to launch the billing flow',
         () async {
-      final BillingResponse sentCode = BillingResponse.error;
+      const String debugMessage = 'dummy message';
+      final BillingResponse sentCode = BillingResponse.developerError;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
-          name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode));
+        name: launchMethodName,
+        value: buildBillingResultMap(expectedBillingResult),
+      );
 
       final bool result = await GooglePlayConnection.instance.buyConsumable(
           purchaseParam: PurchaseParam(
@@ -405,13 +465,17 @@
     test('adds consumption failures to PurchaseDetails objects', () async {
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
       final String accountId = "hashedAccountId";
+      const String debugMessage = 'dummy message';
       final BillingResponse sentCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode),
+          value: buildBillingResultMap(expectedBillingResult),
           additionalStepBeforeReturn: (_) {
             // Mock java update purchase callback.
             MethodCall call = MethodCall(kOnPurchasesUpdated, {
+              'billingResult': buildBillingResultMap(expectedBillingResult),
               'responseCode': BillingResponseConverter().toJson(sentCode),
               'purchasesList': [
                 {
@@ -422,7 +486,10 @@
                   'purchaseTime': 1231231231,
                   'purchaseToken': "token",
                   'signature': 'sign',
-                  'originalJson': 'json'
+                  'originalJson': 'json',
+                  'developerPayload': 'dummy payload',
+                  'isAcknowledged': true,
+                  'purchaseState': 1,
                 }
               ]
             });
@@ -431,12 +498,15 @@
       Completer consumeCompleter = Completer();
       // adding call back for consume purchase
       final BillingResponse expectedCode = BillingResponse.error;
+      final BillingResultWrapper expectedBillingResultForConsume =
+          BillingResultWrapper(
+              responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: consumeMethodName,
-          value: BillingResponseConverter().toJson(expectedCode),
+          value: buildBillingResultMap(expectedBillingResultForConsume),
           additionalStepBeforeReturn: (dynamic args) {
             String purchaseToken = args['purchaseToken'];
-            consumeCompleter.complete((purchaseToken));
+            consumeCompleter.complete(purchaseToken);
           });
 
       Completer completer = Completer();
@@ -469,13 +539,18 @@
         () async {
       final SkuDetailsWrapper skuDetails = dummySkuDetails;
       final String accountId = "hashedAccountId";
-      final BillingResponse sentCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResponse sentCode = BillingResponse.developerError;
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: sentCode, debugMessage: debugMessage);
+
       stubPlatform.addResponse(
           name: launchMethodName,
-          value: BillingResponseConverter().toJson(sentCode),
+          value: buildBillingResultMap(expectedBillingResult),
           additionalStepBeforeReturn: (_) {
             // Mock java update purchase callback.
             MethodCall call = MethodCall(kOnPurchasesUpdated, {
+              'billingResult': buildBillingResultMap(expectedBillingResult),
               'responseCode': BillingResponseConverter().toJson(sentCode),
               'purchasesList': [
                 {
@@ -486,7 +561,10 @@
                   'purchaseTime': 1231231231,
                   'purchaseToken': "token",
                   'signature': 'sign',
-                  'originalJson': 'json'
+                  'originalJson': 'json',
+                  'developerPayload': 'dummy payload',
+                  'isAcknowledged': true,
+                  'purchaseState': 1,
                 }
               ]
             });
@@ -495,9 +573,12 @@
       Completer consumeCompleter = Completer();
       // adding call back for consume purchase
       final BillingResponse expectedCode = BillingResponse.ok;
+      final BillingResultWrapper expectedBillingResultForConsume =
+          BillingResultWrapper(
+              responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
           name: consumeMethodName,
-          value: BillingResponseConverter().toJson(expectedCode),
+          value: buildBillingResultMap(expectedBillingResultForConsume),
           additionalStepBeforeReturn: (dynamic args) {
             String purchaseToken = args['purchaseToken'];
             consumeCompleter.complete((purchaseToken));
@@ -524,20 +605,50 @@
         'BillingClient#consumeAsync(String, ConsumeResponseListener)';
     test('consume purchase async success', () async {
       final BillingResponse expectedCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
       stubPlatform.addResponse(
-          name: consumeMethodName,
-          value: BillingResponseConverter().toJson(expectedCode));
+        name: consumeMethodName,
+        value: buildBillingResultMap(expectedBillingResult),
+      );
+      final BillingResultWrapper billingResultWrapper =
+          await GooglePlayConnection.instance
+              .consumePurchase(PurchaseDetails.fromPurchase(dummyPurchase));
 
-      final BillingResponse responseCode = await GooglePlayConnection.instance
-          .consumePurchase(PurchaseDetails.fromPurchase(dummyPurchase));
-
-      expect(responseCode, equals(expectedCode));
+      expect(billingResultWrapper, equals(expectedBillingResult));
     });
   });
 
   group('complete purchase', () {
-    test('calling complete purchase on android should throw', () async {
-      expect(() => connection.completePurchase(null), throwsUnsupportedError);
+    const String completeMethodName =
+        'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)';
+    test('complete purchase success', () async {
+      final BillingResponse expectedCode = BillingResponse.ok;
+      const String debugMessage = 'dummy message';
+      final BillingResultWrapper expectedBillingResult = BillingResultWrapper(
+          responseCode: expectedCode, debugMessage: debugMessage);
+      stubPlatform.addResponse(
+        name: completeMethodName,
+        value: buildBillingResultMap(expectedBillingResult),
+      );
+      PurchaseDetails purchaseDetails =
+          PurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase);
+      Completer completer = Completer();
+      purchaseDetails.status = PurchaseStatus.purchased;
+      if (purchaseDetails.pendingCompletePurchase) {
+        final BillingResultWrapper billingResultWrapper =
+            await GooglePlayConnection.instance.completePurchase(
+                purchaseDetails,
+                developerPayload: 'dummy payload');
+        print('pending ${billingResultWrapper.responseCode}');
+        print('expectedBillingResult ${expectedBillingResult.responseCode}');
+        print('pending ${billingResultWrapper.debugMessage}');
+        print('expectedBillingResult ${expectedBillingResult.debugMessage}');
+        expect(billingResultWrapper, equals(expectedBillingResult));
+        completer.complete(billingResultWrapper);
+      }
+      expect(await completer.future, equals(expectedBillingResult));
     });
   });
 }