Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 0de4191

Browse files
author
Chris Yang
committed
migrate to billing client 2.0
1 parent 1b5eae5 commit 0de4191

26 files changed

Lines changed: 1280 additions & 255 deletions

packages/in_app_purchase/CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## 0.3.2
2+
3+
* Migrate the `BillingClient` to 2.0.
4+
* Introduce a new class `BillingResultWrapper` which contains a detailed result of a BillingClient operation.
5+
* **[Breaking Change]:** All the BillingClient methods that previously return a `BillingResponse` now return a `BillingResultWrapper`, including: `launchBillingFlow`, `startConnection` and `consumeAsync`.
6+
* **[Breaking Change]:** The `SkuDetailsResponseWrapper` now contains a `billingResult` field in place of `billingResponse` field.
7+
* A `billingResult` field is added to the `PurchasesResultWrapper`.
8+
* Other Updates to the "billing_client_wrappers":
9+
* Updates to the `PurchaseWrapper`: Add `developerPayload`, `purchaseState` and `isAcknowledged` fields.
10+
* Updates to the `SkuDetailsWrapper`: Add `originalPrice` and `originalPriceAmountMicros` fields.
11+
* 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`.
12+
* 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.
13+
* Updates to the "InAppPurchaseConnection":
14+
* **[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.
15+
* **[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.
16+
* 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.
17+
* Misc: Some documentation updates reflecting the `BillingClient` migration and some documentation fixes.
18+
* 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.
19+
120
## 0.2.2+2
221

322
* Include lifecycle dependency as a compileOnly one on Android to resolve

packages/in_app_purchase/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ android {
3535

3636
dependencies {
3737
implementation 'androidx.annotation:annotation:1.0.0'
38-
implementation 'com.android.billingclient:billing:1.2'
38+
implementation 'com.android.billingclient:billing:2.0.3'
3939
testImplementation 'junit:junit:4.12'
4040
testImplementation 'org.mockito:mockito-core:2.17.0'
4141
androidTestImplementation 'androidx.test:runner:1.1.1'

packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class BillingClientFactoryImpl implements BillingClientFactory {
1414
@Override
1515
public BillingClient createBillingClient(Context context, MethodChannel channel) {
1616
return BillingClient.newBuilder(context)
17+
.enablePendingPurchases()
1718
.setListener(new PluginPurchaseListener(channel))
1819
.build();
1920
}

packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ static final class MethodNames {
3636
"BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)";
3737
static final String CONSUME_PURCHASE_ASYNC =
3838
"BillingClient#consumeAsync(String, ConsumeResponseListener)";
39+
static final String ACKNOWLEDGE_PURCHASE =
40+
"BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)";
3941

4042
private MethodNames() {};
4143
}

packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
package io.flutter.plugins.inapppurchase;
66

7-
import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
7+
import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList;
88
import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesResult;
99
import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList;
1010

@@ -13,11 +13,15 @@
1313
import android.util.Log;
1414
import androidx.annotation.NonNull;
1515
import androidx.annotation.Nullable;
16+
import com.android.billingclient.api.AcknowledgePurchaseParams;
17+
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
1618
import com.android.billingclient.api.BillingClient;
1719
import com.android.billingclient.api.BillingClientStateListener;
1820
import com.android.billingclient.api.BillingFlowParams;
21+
import com.android.billingclient.api.BillingResult;
22+
import com.android.billingclient.api.ConsumeParams;
1923
import com.android.billingclient.api.ConsumeResponseListener;
20-
import com.android.billingclient.api.Purchase;
24+
import com.android.billingclient.api.PurchaseHistoryRecord;
2125
import com.android.billingclient.api.PurchaseHistoryResponseListener;
2226
import com.android.billingclient.api.SkuDetails;
2327
import com.android.billingclient.api.SkuDetailsParams;
@@ -89,7 +93,16 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
8993
queryPurchaseHistoryAsync((String) call.argument("skuType"), result);
9094
break;
9195
case InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC:
92-
consumeAsync((String) call.argument("purchaseToken"), result);
96+
consumeAsync(
97+
(String) call.argument("purchaseToken"),
98+
(String) call.argument("developerPayload"),
99+
result);
100+
break;
101+
case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE:
102+
acknowledgePurchase(
103+
(String) call.argument("purchaseToken"),
104+
(String) call.argument("developerPayload"),
105+
result);
93106
break;
94107
default:
95108
result.notImplemented();
@@ -123,11 +136,12 @@ private void querySkuDetailsAsync(
123136
billingClient.querySkuDetailsAsync(
124137
params,
125138
new SkuDetailsResponseListener() {
139+
@Override
126140
public void onSkuDetailsResponse(
127-
int responseCode, @Nullable List<SkuDetails> skuDetailsList) {
141+
BillingResult billingResult, List<SkuDetails> skuDetailsList) {
128142
updateCachedSkus(skuDetailsList);
129143
final Map<String, Object> skuDetailsResponse = new HashMap<>();
130-
skuDetailsResponse.put("responseCode", responseCode);
144+
skuDetailsResponse.put("billingResult", Translator.fromBillingResult(billingResult));
131145
skuDetailsResponse.put("skuDetailsList", fromSkuDetailsList(skuDetailsList));
132146
result.success(skuDetailsResponse);
133147
}
@@ -164,23 +178,33 @@ private void launchBillingFlow(
164178
if (accountId != null && !accountId.isEmpty()) {
165179
paramsBuilder.setAccountId(accountId);
166180
}
167-
result.success(billingClient.launchBillingFlow(activity, paramsBuilder.build()));
181+
result.success(
182+
Translator.fromBillingResult(
183+
billingClient.launchBillingFlow(activity, paramsBuilder.build())));
168184
}
169185

170-
private void consumeAsync(String purchaseToken, final MethodChannel.Result result) {
186+
private void consumeAsync(
187+
String purchaseToken, String developerPayload, final MethodChannel.Result result) {
171188
if (billingClientError(result)) {
172189
return;
173190
}
174191

175192
ConsumeResponseListener listener =
176193
new ConsumeResponseListener() {
177194
@Override
178-
public void onConsumeResponse(
179-
@BillingClient.BillingResponse int responseCode, String outToken) {
180-
result.success(responseCode);
195+
public void onConsumeResponse(BillingResult billingResult, String outToken) {
196+
result.success(Translator.fromBillingResult(billingResult));
181197
}
182198
};
183-
billingClient.consumeAsync(purchaseToken, listener);
199+
ConsumeParams.Builder paramsBuilder =
200+
ConsumeParams.newBuilder().setPurchaseToken(purchaseToken);
201+
202+
if (developerPayload != null) {
203+
paramsBuilder.setDeveloperPayload(developerPayload);
204+
}
205+
ConsumeParams params = paramsBuilder.build();
206+
207+
billingClient.consumeAsync(params, listener);
184208
}
185209

186210
private void queryPurchases(String skuType, MethodChannel.Result result) {
@@ -201,10 +225,12 @@ private void queryPurchaseHistoryAsync(String skuType, final MethodChannel.Resul
201225
skuType,
202226
new PurchaseHistoryResponseListener() {
203227
@Override
204-
public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
228+
public void onPurchaseHistoryResponse(
229+
BillingResult billingResult, List<PurchaseHistoryRecord> purchasesList) {
205230
final Map<String, Object> serialized = new HashMap<>();
206-
serialized.put("responseCode", responseCode);
207-
serialized.put("purchasesList", fromPurchasesList(purchasesList));
231+
serialized.put("billingResult", Translator.fromBillingResult(billingResult));
232+
serialized.put(
233+
"purchaseHistoryRecordList", fromPurchaseHistoryRecordList(purchasesList));
208234
result.success(serialized);
209235
}
210236
});
@@ -220,14 +246,14 @@ private void startConnection(final int handle, final MethodChannel.Result result
220246
private boolean alreadyFinished = false;
221247

222248
@Override
223-
public void onBillingSetupFinished(int responseCode) {
249+
public void onBillingSetupFinished(BillingResult billingResult) {
224250
if (alreadyFinished) {
225251
Log.d(TAG, "Tried to call onBilllingSetupFinished multiple times.");
226252
return;
227253
}
228254
alreadyFinished = true;
229255
// Consider the fact that we've finished a success, leave it to the Dart side to validate the responseCode.
230-
result.success(responseCode);
256+
result.success(Translator.fromBillingResult(billingResult));
231257
}
232258

233259
@Override
@@ -239,6 +265,26 @@ public void onBillingServiceDisconnected() {
239265
});
240266
}
241267

268+
private void acknowledgePurchase(
269+
String purchaseToken, @Nullable String developerPayload, final MethodChannel.Result result) {
270+
if (billingClientError(result)) {
271+
return;
272+
}
273+
AcknowledgePurchaseParams params =
274+
AcknowledgePurchaseParams.newBuilder()
275+
.setDeveloperPayload(developerPayload)
276+
.setPurchaseToken(purchaseToken)
277+
.build();
278+
billingClient.acknowledgePurchase(
279+
params,
280+
new AcknowledgePurchaseResponseListener() {
281+
@Override
282+
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
283+
result.success(Translator.fromBillingResult(billingResult));
284+
}
285+
});
286+
}
287+
242288
private void updateCachedSkus(@Nullable List<SkuDetails> skuDetailsList) {
243289
if (skuDetailsList == null) {
244290
return;

packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
package io.flutter.plugins.inapppurchase;
66

7+
import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult;
78
import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList;
89

910
import androidx.annotation.Nullable;
11+
import com.android.billingclient.api.BillingResult;
1012
import com.android.billingclient.api.Purchase;
1113
import com.android.billingclient.api.PurchasesUpdatedListener;
1214
import io.flutter.plugin.common.MethodChannel;
@@ -22,9 +24,11 @@ class PluginPurchaseListener implements PurchasesUpdatedListener {
2224
}
2325

2426
@Override
25-
public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {
27+
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
2628
final Map<String, Object> callbackArgs = new HashMap<>();
27-
callbackArgs.put("responseCode", responseCode);
29+
//TODO(cyanglaz): 2.0 return the BillingResult object to dart side.
30+
callbackArgs.put("billingResult", fromBillingResult(billingResult));
31+
callbackArgs.put("responseCode", billingResult.getResponseCode());
2832
callbackArgs.put("purchasesList", fromPurchasesList(purchases));
2933
channel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED, callbackArgs);
3034
}

packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package io.flutter.plugins.inapppurchase;
66

77
import androidx.annotation.Nullable;
8+
import com.android.billingclient.api.BillingResult;
89
import com.android.billingclient.api.Purchase;
910
import com.android.billingclient.api.Purchase.PurchasesResult;
11+
import com.android.billingclient.api.PurchaseHistoryRecord;
1012
import com.android.billingclient.api.SkuDetails;
1113
import java.util.ArrayList;
1214
import java.util.Collections;
@@ -31,6 +33,8 @@ static HashMap<String, Object> fromSkuDetail(SkuDetails detail) {
3133
info.put("type", detail.getType());
3234
info.put("isRewarded", detail.isRewarded());
3335
info.put("subscriptionPeriod", detail.getSubscriptionPeriod());
36+
info.put("originalPrice", detail.getOriginalPrice());
37+
info.put("originalPriceAmountMicros", detail.getOriginalPriceAmountMicros());
3438
return info;
3539
}
3640

@@ -57,6 +61,21 @@ static HashMap<String, Object> fromPurchase(Purchase purchase) {
5761
info.put("sku", purchase.getSku());
5862
info.put("isAutoRenewing", purchase.isAutoRenewing());
5963
info.put("originalJson", purchase.getOriginalJson());
64+
info.put("developerPayload", purchase.getDeveloperPayload());
65+
info.put("isAcknowledged", purchase.isAcknowledged());
66+
info.put("purchaseState", purchase.getPurchaseState());
67+
return info;
68+
}
69+
70+
static HashMap<String, Object> fromPurchaseHistoryRecord(
71+
PurchaseHistoryRecord purchaseHistoryRecord) {
72+
HashMap<String, Object> info = new HashMap<>();
73+
info.put("purchaseTime", purchaseHistoryRecord.getPurchaseTime());
74+
info.put("purchaseToken", purchaseHistoryRecord.getPurchaseToken());
75+
info.put("signature", purchaseHistoryRecord.getSignature());
76+
info.put("sku", purchaseHistoryRecord.getSku());
77+
info.put("developerPayload", purchaseHistoryRecord.getDeveloperPayload());
78+
info.put("originalJson", purchaseHistoryRecord.getOriginalJson());
6079
return info;
6180
}
6281

@@ -72,10 +91,31 @@ static List<HashMap<String, Object>> fromPurchasesList(@Nullable List<Purchase>
7291
return serialized;
7392
}
7493

94+
static List<HashMap<String, Object>> fromPurchaseHistoryRecordList(
95+
@Nullable List<PurchaseHistoryRecord> purchaseHistoryRecords) {
96+
if (purchaseHistoryRecords == null) {
97+
return Collections.emptyList();
98+
}
99+
100+
List<HashMap<String, Object>> serialized = new ArrayList<>();
101+
for (PurchaseHistoryRecord purchaseHistoryRecord : purchaseHistoryRecords) {
102+
serialized.add(fromPurchaseHistoryRecord(purchaseHistoryRecord));
103+
}
104+
return serialized;
105+
}
106+
75107
static HashMap<String, Object> fromPurchasesResult(PurchasesResult purchasesResult) {
76108
HashMap<String, Object> info = new HashMap<>();
77109
info.put("responseCode", purchasesResult.getResponseCode());
110+
info.put("billingResult", fromBillingResult(purchasesResult.getBillingResult()));
78111
info.put("purchasesList", fromPurchasesList(purchasesResult.getPurchasesList()));
79112
return info;
80113
}
114+
115+
static HashMap<String, Object> fromBillingResult(BillingResult billingResult) {
116+
HashMap<String, Object> info = new HashMap<>();
117+
info.put("responseCode", billingResult.getResponseCode());
118+
info.put("debugMessage", billingResult.getDebugMessage());
119+
return info;
120+
}
81121
}

0 commit comments

Comments
 (0)