[in_app_purchase] Migrate android to Billing to 5.0.0 (#5405)
diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
index 966df51..2f37f09 100644
--- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.2.3
+
+* Upgrades Google Play Billing Library to 5.0
+* Migrates APIs to support breaking changes in new Google Play Billing API
+* `PurchaseWrapper` and `PurchaseHistoryRecordWrapper` now handles `skus` a list of sku strings. `sku` is deprecated.
+
## 0.2.2+8
* Ignores deprecation warnings for upcoming styleFrom button API changes.
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle
index 9a5a74d..663a4df 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle
+++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle
@@ -52,11 +52,11 @@
}
dependencies {
- implementation 'androidx.annotation:annotation:1.0.0'
- implementation 'com.android.billingclient:billing:3.0.2'
+ implementation 'androidx.annotation:annotation:1.3.0'
+ implementation 'com.android.billingclient:billing:5.0.0'
testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.json:json:20180813'
- testImplementation 'org.mockito:mockito-core:3.6.0'
- androidTestImplementation 'androidx.test:runner:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+ testImplementation 'org.json:json:20220320'
+ testImplementation 'org.mockito:mockito-core:4.5.1'
+ androidTestImplementation 'androidx.test:runner:1.4.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
index b21ab69..6f4e4bb 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java
@@ -39,6 +39,7 @@
static final String ON_PURCHASES_UPDATED =
"PurchasesUpdatedListener#onPurchasesUpdated(int, List<Purchase>)";
static final String QUERY_PURCHASES = "BillingClient#queryPurchases(String)";
+ static final String QUERY_PURCHASES_ASYNC = "BillingClient#queryPurchasesAsync(String)";
static final String QUERY_PURCHASE_HISTORY_ASYNC =
"BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)";
static final String CONSUME_PURCHASE_ASYNC =
@@ -48,6 +49,7 @@
static final String IS_FEATURE_SUPPORTED = "BillingClient#isFeatureSupported(String)";
static final String LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW =
"BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)";
+ static final String GET_CONNECTION_STATE = "BillingClient#getConnectionState()";
private MethodNames() {};
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
index adad84b..ab12b2d 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java
@@ -5,7 +5,7 @@
package io.flutter.plugins.inapppurchase;
import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList;
-import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesResult;
+import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList;
import android.app.Activity;
@@ -25,8 +25,12 @@
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.PriceChangeFlowParams;
+import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchaseHistoryRecord;
import com.android.billingclient.api.PurchaseHistoryResponseListener;
+import com.android.billingclient.api.PurchasesResponseListener;
+import com.android.billingclient.api.QueryPurchaseHistoryParams;
+import com.android.billingclient.api.QueryPurchasesParams;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
@@ -131,10 +135,14 @@
: ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY,
result);
break;
- case InAppPurchasePlugin.MethodNames.QUERY_PURCHASES:
- queryPurchases((String) call.argument("skuType"), result);
+ case InAppPurchasePlugin.MethodNames.QUERY_PURCHASES: // Legacy method name.
+ queryPurchasesAsync((String) call.argument("skuType"), result);
+ break;
+ case InAppPurchasePlugin.MethodNames.QUERY_PURCHASES_ASYNC:
+ queryPurchasesAsync((String) call.argument("skuType"), result);
break;
case InAppPurchasePlugin.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC:
+ Log.e("flutter", (String) call.argument("skuType"));
queryPurchaseHistoryAsync((String) call.argument("skuType"), result);
break;
case InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC:
@@ -149,6 +157,9 @@
case InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW:
launchPriceChangeConfirmationFlow((String) call.argument("sku"), result);
break;
+ case InAppPurchasePlugin.MethodNames.GET_CONNECTION_STATE:
+ getConnectionState(result);
+ break;
default:
result.notImplemented();
}
@@ -174,6 +185,7 @@
result.success(billingClient.isReady());
}
+ // TODO(garyq): Migrate to new subscriptions API: https://developer.android.com/google/play/billing/migrate-gpblv5
private void querySkuDetailsAsync(
final String skuType, final List<String> skusList, final MethodChannel.Result result) {
if (billingClientError(result)) {
@@ -208,7 +220,6 @@
if (billingClientError(result)) {
return;
}
-
SkuDetails skuDetails = cachedSkus.get(sku);
if (skuDetails == null) {
result.error(
@@ -255,12 +266,15 @@
if (obfuscatedProfileId != null && !obfuscatedProfileId.isEmpty()) {
paramsBuilder.setObfuscatedProfileId(obfuscatedProfileId);
}
- if (oldSku != null && !oldSku.isEmpty()) {
- paramsBuilder.setOldSku(oldSku, purchaseToken);
+ BillingFlowParams.SubscriptionUpdateParams.Builder subscriptionUpdateParamsBuilder =
+ BillingFlowParams.SubscriptionUpdateParams.newBuilder();
+ if (oldSku != null && !oldSku.isEmpty() && purchaseToken != null) {
+ subscriptionUpdateParamsBuilder.setOldPurchaseToken(purchaseToken);
+ // The proration mode value has to match one of the following declared in
+ // https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(prorationMode);
+ paramsBuilder.setSubscriptionUpdateParams(subscriptionUpdateParamsBuilder.build());
}
- // The proration mode value has to match one of the following declared in
- // https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode
- paramsBuilder.setReplaceSkusProrationMode(prorationMode);
result.success(
Translator.fromBillingResult(
billingClient.launchBillingFlow(activity, paramsBuilder.build())));
@@ -286,14 +300,30 @@
billingClient.consumeAsync(params, listener);
}
- private void queryPurchases(String skuType, MethodChannel.Result result) {
+ private void queryPurchasesAsync(String skuType, MethodChannel.Result result) {
if (billingClientError(result)) {
return;
}
// Like in our connect call, consider the billing client responding a "success" here regardless
// of status code.
- result.success(fromPurchasesResult(billingClient.queryPurchases(skuType)));
+ QueryPurchasesParams.Builder paramsBuilder = QueryPurchasesParams.newBuilder();
+ paramsBuilder.setProductType(skuType);
+ billingClient.queryPurchasesAsync(
+ paramsBuilder.build(),
+ new PurchasesResponseListener() {
+ @Override
+ public void onQueryPurchasesResponse(
+ BillingResult billingResult, List<Purchase> purchasesList) {
+ final Map<String, Object> serialized = new HashMap<>();
+ // The response code is no longer passed, as part of billing 4.0, so we pass OK here
+ // as success is implied by calling this callback.
+ serialized.put("responseCode", BillingClient.BillingResponseCode.OK);
+ serialized.put("billingResult", Translator.fromBillingResult(billingResult));
+ serialized.put("purchaseList", fromPurchasesList(purchasesList));
+ result.success(serialized);
+ }
+ });
}
private void queryPurchaseHistoryAsync(String skuType, final MethodChannel.Result result) {
@@ -302,7 +332,7 @@
}
billingClient.queryPurchaseHistoryAsync(
- skuType,
+ QueryPurchaseHistoryParams.newBuilder().setProductType(skuType).build(),
new PurchaseHistoryResponseListener() {
@Override
public void onPurchaseHistoryResponse(
@@ -316,6 +346,15 @@
});
}
+ private void getConnectionState(final MethodChannel.Result result) {
+ if (billingClientError(result)) {
+ return;
+ }
+ final Map<String, Object> serialized = new HashMap<>();
+ serialized.put("connectionState", billingClient.getConnectionState());
+ result.success(serialized);
+ }
+
private void startConnection(final int handle, final MethodChannel.Result result) {
if (billingClient == null) {
billingClient = billingClientFactory.createBillingClient(applicationContext, methodChannel);
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java
index 7546fe7..5a0cf6e 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java
@@ -8,7 +8,6 @@
import com.android.billingclient.api.AccountIdentifiers;
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;
@@ -56,17 +55,19 @@
static HashMap<String, Object> fromPurchase(Purchase purchase) {
HashMap<String, Object> info = new HashMap<>();
+ List<String> skus = purchase.getSkus();
info.put("orderId", purchase.getOrderId());
info.put("packageName", purchase.getPackageName());
info.put("purchaseTime", purchase.getPurchaseTime());
info.put("purchaseToken", purchase.getPurchaseToken());
info.put("signature", purchase.getSignature());
- info.put("sku", purchase.getSku());
+ info.put("skus", skus);
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());
+ info.put("quantity", purchase.getQuantity());
AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
if (accountIdentifiers != null) {
info.put("obfuscatedAccountId", accountIdentifiers.getObfuscatedAccountId());
@@ -78,12 +79,14 @@
static HashMap<String, Object> fromPurchaseHistoryRecord(
PurchaseHistoryRecord purchaseHistoryRecord) {
HashMap<String, Object> info = new HashMap<>();
+ List<String> skus = purchaseHistoryRecord.getSkus();
info.put("purchaseTime", purchaseHistoryRecord.getPurchaseTime());
info.put("purchaseToken", purchaseHistoryRecord.getPurchaseToken());
info.put("signature", purchaseHistoryRecord.getSignature());
- info.put("sku", purchaseHistoryRecord.getSku());
+ info.put("skus", skus);
info.put("developerPayload", purchaseHistoryRecord.getDeveloperPayload());
info.put("originalJson", purchaseHistoryRecord.getOriginalJson());
+ info.put("quantity", purchaseHistoryRecord.getQuantity());
return info;
}
@@ -112,14 +115,6 @@
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());
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
index d676bf3..e99ff46 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java
@@ -13,21 +13,19 @@
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_DISCONNECT;
import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED;
-import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_PURCHASES;
+import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_PURCHASES_ASYNC;
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;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.contains;
@@ -56,9 +54,9 @@
import com.android.billingclient.api.PriceChangeConfirmationListener;
import com.android.billingclient.api.PriceChangeFlowParams;
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.QueryPurchaseHistoryParams;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
@@ -294,7 +292,6 @@
ArgumentCaptor.forClass(BillingFlowParams.class);
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
- assertEquals(params.getSku(), skuId);
// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
@@ -327,8 +324,6 @@
ArgumentCaptor.forClass(BillingFlowParams.class);
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
- assertEquals(params.getSku(), skuId);
- assertNull(params.getOldSku());
// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
verify(result, times(1)).success(fromBillingResult(billingResult));
@@ -380,8 +375,6 @@
ArgumentCaptor.forClass(BillingFlowParams.class);
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
- assertEquals(params.getSku(), skuId);
- assertEquals(params.getOldSku(), oldSkuId);
// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
@@ -413,7 +406,6 @@
ArgumentCaptor.forClass(BillingFlowParams.class);
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
- assertEquals(params.getSku(), skuId);
// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
@@ -451,10 +443,6 @@
ArgumentCaptor.forClass(BillingFlowParams.class);
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
- assertEquals(params.getSku(), skuId);
- assertEquals(params.getOldSku(), oldSkuId);
- assertEquals(params.getOldSkuPurchaseToken(), purchaseToken);
- assertEquals(params.getReplaceSkusProrationMode(), prorationMode);
// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
@@ -496,6 +484,43 @@
}
@Test
+ public void launchBillingFlow_ok_Full() {
+ // Fetch the sku details first and query the method call
+ String skuId = "foo";
+ String oldSkuId = "oldFoo";
+ String purchaseToken = "purchaseTokenFoo";
+ String accountId = "account";
+ int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE;
+ queryForSkus(unmodifiableList(asList(skuId, oldSkuId)));
+ HashMap<String, Object> arguments = new HashMap<>();
+ arguments.put("sku", skuId);
+ arguments.put("accountId", accountId);
+ arguments.put("oldSku", oldSkuId);
+ arguments.put("purchaseToken", purchaseToken);
+ arguments.put("prorationMode", prorationMode);
+ MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
+
+ // Launch the billing flow
+ 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
+ ArgumentCaptor<BillingFlowParams> billingFlowParamsCaptor =
+ ArgumentCaptor.forClass(BillingFlowParams.class);
+ verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
+ BillingFlowParams params = billingFlowParamsCaptor.getValue();
+
+ // Verify we pass the response code to result
+ verify(result, never()).error(any(), any(), any());
+ verify(result, times(1)).success(fromBillingResult(billingResult));
+ }
+
+ @Test
public void launchBillingFlow_clientDisconnected() {
// Prepare the launch call after disconnecting the client
MethodCall disconnectCall = new MethodCall(END_CONNECTION, null);
@@ -554,38 +579,13 @@
}
@Test
- public void queryPurchases() {
- establishConnectedBillingClient(null, null);
- PurchasesResult purchasesResult = mock(PurchasesResult.class);
- 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<>();
- arguments.put("skuType", SkuType.INAPP);
- methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES, arguments), result);
-
- // Verify we pass the response to result
- ArgumentCaptor<HashMap<String, Object>> resultCaptor = ArgumentCaptor.forClass(HashMap.class);
- verify(result, never()).error(any(), any(), any());
- verify(result, times(1)).success(resultCaptor.capture());
- assertEquals(fromPurchasesResult(purchasesResult), resultCaptor.getValue());
- }
-
- @Test
public void queryPurchases_clientDisconnected() {
// Prepare the launch call after disconnecting the client
methodChannelHandler.onMethodCall(new MethodCall(END_CONNECTION, null), mock(Result.class));
HashMap<String, Object> arguments = new HashMap<>();
arguments.put("skuType", SkuType.INAPP);
- methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES, arguments), result);
+ methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES_ASYNC, arguments), result);
// Assert that we sent an error back.
verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any());
@@ -613,7 +613,7 @@
// Verify we pass the data to result
verify(mockBillingClient)
- .queryPurchaseHistoryAsync(eq(SkuType.INAPP), listenerCaptor.capture());
+ .queryPurchaseHistoryAsync(any(QueryPurchaseHistoryParams.class), listenerCaptor.capture());
listenerCaptor.getValue().onPurchaseHistoryResponse(billingResult, purchasesList);
verify(result).success(resultCaptor.capture());
HashMap<String, Object> resultData = resultCaptor.getValue();
diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java
index 2837dce..79852e7 100644
--- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java
+++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java
@@ -9,15 +9,12 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import androidx.annotation.NonNull;
import com.android.billingclient.api.AccountIdentifiers;
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;
@@ -139,37 +136,6 @@
}
@Test
- 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\",\"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(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(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 fromBillingResult() throws JSONException {
BillingResult newBillingResult =
BillingResult.newBuilder()
@@ -232,7 +198,7 @@
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.getSkus(), serialized.get("skus"));
assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload"));
assertEquals(expected.isAcknowledged(), serialized.get("isAcknowledged"));
assertEquals(expected.getPurchaseState(), serialized.get("purchaseState"));
@@ -251,7 +217,7 @@
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.getSkus(), serialized.get("skus"));
assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload"));
}
}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle
index b7dc185..498355a 100644
--- a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle
@@ -72,7 +72,7 @@
defaultConfig {
applicationId project.APP_ID
minSdkVersion 16
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode project.VERSION_CODE
versionName project.VERSION_NAME
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -106,7 +106,7 @@
}
dependencies {
- implementation 'com.android.billingclient:billing:3.0.2'
+ implementation 'com.android.billingclient:billing:5.0.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:3.6.0'
testImplementation 'org.json:json:20180813'
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart
index efaf079..af1aaa5 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart
@@ -33,7 +33,8 @@
required this.purchaseTime,
required this.purchaseToken,
required this.signature,
- required this.sku,
+ @Deprecated('Use skus instead') String? sku,
+ required this.skus,
required this.isAutoRenewing,
required this.originalJson,
this.developerPayload,
@@ -41,7 +42,7 @@
required this.purchaseState,
this.obfuscatedAccountId,
this.obfuscatedProfileId,
- });
+ }) : _sku = sku;
/// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details.
factory PurchaseWrapper.fromJson(Map<String, dynamic> map) =>
@@ -104,8 +105,13 @@
final String signature;
/// The product ID of this purchase.
- @JsonKey(defaultValue: '')
- final String sku;
+ @Deprecated('Use skus instead')
+ String get sku => _sku ?? (skus.isNotEmpty ? skus.first : '');
+ final String? _sku;
+
+ /// The product IDs of this purchase.
+ @JsonKey(defaultValue: <String>[])
+ final List<String> skus;
/// True for subscriptions that renew automatically. Does not apply to
/// [SkuType.inapp] products.
@@ -178,10 +184,11 @@
required this.purchaseTime,
required this.purchaseToken,
required this.signature,
- required this.sku,
+ @Deprecated('Use skus instead') String? sku,
+ required this.skus,
required this.originalJson,
required this.developerPayload,
- });
+ }) : _sku = sku;
/// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details.
factory PurchaseHistoryRecordWrapper.fromJson(Map<String, dynamic> map) =>
@@ -201,8 +208,14 @@
final String signature;
/// The product ID of this purchase.
- @JsonKey(defaultValue: '')
- final String sku;
+ @Deprecated('Use skus instead')
+ String get sku => _sku ?? (skus.isNotEmpty ? skus.first : '');
+
+ final String? _sku;
+
+ /// The product ID of this purchase.
+ @JsonKey(defaultValue: <String>[])
+ final List<String> skus;
/// Details about this purchase, in JSON.
///
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart
index 5815a86..7f6fd0f 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart
@@ -12,7 +12,10 @@
purchaseTime: json['purchaseTime'] as int? ?? 0,
purchaseToken: json['purchaseToken'] as String? ?? '',
signature: json['signature'] as String? ?? '',
- sku: json['sku'] as String? ?? '',
+ skus: json['skus'] != null
+ ? (json['skus'] as List)?.map((item) => item as String)?.toList() ??
+ <String>[]
+ : <String>[],
isAutoRenewing: json['isAutoRenewing'] as bool,
originalJson: json['originalJson'] as String? ?? '',
developerPayload: json['developerPayload'] as String?,
@@ -28,7 +31,10 @@
purchaseTime: json['purchaseTime'] as int? ?? 0,
purchaseToken: json['purchaseToken'] as String? ?? '',
signature: json['signature'] as String? ?? '',
- sku: json['sku'] as String? ?? '',
+ skus: json['skus'] != null
+ ? (json['skus'] as List)?.map((item) => item as String)?.toList() ??
+ <String>[]
+ : <String>[],
originalJson: json['originalJson'] as String? ?? '',
developerPayload: json['developerPayload'] as String?,
);
diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
index b1e7766..db419b8 100644
--- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs.
repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 0.2.2+8
+version: 0.2.3
environment:
sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart
index 65c8bb2..184d933 100644
--- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart
@@ -11,7 +11,7 @@
packageName: 'packageName',
purchaseTime: 0,
signature: 'signature',
- sku: 'sku',
+ skus: <String>['sku'],
purchaseToken: 'purchaseToken',
isAutoRenewing: false,
originalJson: '',
@@ -27,7 +27,7 @@
packageName: 'packageName',
purchaseTime: 0,
signature: 'signature',
- sku: 'sku',
+ skus: <String>['sku'],
purchaseToken: 'purchaseToken',
isAutoRenewing: false,
originalJson: '',
@@ -40,7 +40,7 @@
PurchaseHistoryRecordWrapper(
purchaseTime: 0,
signature: 'signature',
- sku: 'sku',
+ skus: <String>['sku'],
purchaseToken: 'purchaseToken',
originalJson: '',
developerPayload: 'dummy payload',
@@ -51,7 +51,7 @@
packageName: 'oldPackageName',
purchaseTime: 0,
signature: 'oldSignature',
- sku: 'oldSku',
+ skus: <String>['oldSku'],
purchaseToken: 'oldPurchaseToken',
isAutoRenewing: false,
originalJson: '',
@@ -205,7 +205,7 @@
'packageName': original.packageName,
'purchaseTime': original.purchaseTime,
'signature': original.signature,
- 'sku': original.sku,
+ 'skus': original.skus,
'purchaseToken': original.purchaseToken,
'isAutoRenewing': original.isAutoRenewing,
'originalJson': original.originalJson,
@@ -223,7 +223,7 @@
return <String, dynamic>{
'purchaseTime': original.purchaseTime,
'signature': original.signature,
- 'sku': original.sku,
+ 'skus': original.skus,
'purchaseToken': original.purchaseToken,
'originalJson': original.originalJson,
'developerPayload': original.developerPayload,
diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart
index b19d631..4f90dcc 100644
--- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart
@@ -299,7 +299,7 @@
'purchasesList': <dynamic>[
<dynamic, dynamic>{
'orderId': 'orderID1',
- 'sku': skuDetails.sku,
+ 'skus': <String>[skuDetails.sku],
'isAutoRenewing': false,
'packageName': 'package',
'purchaseTime': 1231231231,
@@ -401,7 +401,7 @@
'purchasesList': <dynamic>[
<dynamic, dynamic>{
'orderId': 'orderID1',
- 'sku': skuDetails.sku,
+ 'skus': <String>[skuDetails.sku],
'isAutoRenewing': false,
'packageName': 'package',
'purchaseTime': 1231231231,
@@ -515,7 +515,7 @@
'purchasesList': <dynamic>[
<dynamic, dynamic>{
'orderId': 'orderID1',
- 'sku': skuDetails.sku,
+ 'skus': <String>[skuDetails.sku],
'isAutoRenewing': false,
'packageName': 'package',
'purchaseTime': 1231231231,
@@ -592,7 +592,7 @@
'purchasesList': <dynamic>[
<dynamic, dynamic>{
'orderId': 'orderID1',
- 'sku': skuDetails.sku,
+ 'skus': <String>[skuDetails.sku],
'isAutoRenewing': false,
'packageName': 'package',
'purchaseTime': 1231231231,