Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
migrate to play library 2.0
  • Loading branch information
Chris Yang committed Nov 19, 2019
commit e4c979655ece3b573985565279355b524c7db908
19 changes: 19 additions & 0 deletions packages/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
## 0.3.2

* 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", it is implicitly acknowledged.
* 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.
* **[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.
* 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+2

* Include lifecycle dependency as a compileOnly one on Android to resolve
Expand Down
2 changes: 1 addition & 1 deletion packages/in_app_purchase/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ android {

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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class BillingClientFactoryImpl implements BillingClientFactory {
@Override
public BillingClient createBillingClient(Context context, MethodChannel channel) {
return BillingClient.newBuilder(context)
.enablePendingPurchases()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell this API exists on the PBL2 SDK just to make sure that the developer has really read the new API docs and is aware of the need to register pending purchase changes. I think we should also gate this breaking change behind something similar, though I'm not totally sure as to what. Some people use package: any dependencies in their pubspec and will just automatically get this. I think it may be good to have some kind of obvious hard failure that makes it basically impossible to miss the new changes. I'm not sure if requiring people to effectively set a param or call this is the right way to go here, but I'm considering it. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can guard this by asking BillingClient on dart to call an API with the same name enablePendingPurchases (), otherwise do an assertion failure. Although we can fail it on dart side, I think we should always call it on the JAVA side.
On the unified layer, we can either do the same, or hide it from the user. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to reach out offline to get some second opinions on this.

.setListener(new PluginPurchaseListener(channel))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ static final class MethodNames {
"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() {};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -89,7 +93,16 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
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();
Expand Down Expand Up @@ -123,11 +136,12 @@ private void querySkuDetailsAsync(
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);
}
Expand Down Expand Up @@ -164,23 +178,33 @@ private void launchBillingFlow(
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;
}

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) {
Expand All @@ -201,10 +225,12 @@ private void queryPurchaseHistoryAsync(String skuType, final MethodChannel.Resul
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);
}
});
Expand All @@ -220,14 +246,14 @@ private void startConnection(final int handle, final MethodChannel.Result result
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
Expand All @@ -239,6 +265,26 @@ public void onBillingServiceDisconnected() {
});
}

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,9 +24,10 @@ class PluginPurchaseListener implements PurchasesUpdatedListener {
}

@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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +33,8 @@ static HashMap<String, Object> fromSkuDetail(SkuDetails detail) {
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;
}

Expand All @@ -57,6 +61,21 @@ static HashMap<String, Object> fromPurchase(Purchase purchase) {
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;
}

Expand All @@ -72,10 +91,31 @@ static List<HashMap<String, Object>> fromPurchasesList(@Nullable List<Purchase>
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;
}
}
Loading