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

Commit 088bdee

Browse files
author
Chris Yang
authored
[in_app_purchase] migrate playing billing library to v3 (#3636)
1 parent 98a90d6 commit 088bdee

File tree

16 files changed

+160
-115
lines changed

16 files changed

+160
-115
lines changed

packages/in_app_purchase/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 0.5.0
2+
3+
* Migrate to Google Billing Library 3.0
4+
* Add `obfuscatedProfileId`, `purchaseToken` in [BillingClientWrapper.launchBillingFlow].
5+
* **Breaking Change**
6+
* Removed `developerPayload` in [BillingClientWrapper.acknowledgePurchase], [BillingClientWrapper.consumeAsync], [InAppPurchaseConnection.completePurchase], [InAppPurchaseConnection.consumePurchase].
7+
* Removed `isRewarded` from [SkuDetailsWrapper].
8+
* [SkuDetailsWrapper.introductoryPriceCycles] now returns `int` instead of `String`.
9+
* Above breaking changes are inline with the breaking changes introduced in [Google Play Billing 3.0 release](https://developer.android.com/google/play/billing/release-notes#3-0).
10+
* Additional information on some the changes:
11+
* [Dropping reward SKU support](https://support.google.com/googleplay/android-developer/answer/9155268?hl=en)
12+
* [Developer payload](https://developer.android.com/google/play/billing/developer-payload)
13+
114
## 0.4.1
215

316
* Support InApp subscription upgrade/downgrade.

packages/in_app_purchase/android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ android {
3535

3636
dependencies {
3737
implementation 'androidx.annotation:annotation:1.0.0'
38-
implementation 'com.android.billingclient:billing:2.0.3'
38+
implementation 'com.android.billingclient:billing:3.0.2'
3939
testImplementation 'junit:junit:4.12'
40-
testImplementation 'org.mockito:mockito-core:2.17.0'
40+
testImplementation 'org.mockito:mockito-core:3.6.0'
4141
androidTestImplementation 'androidx.test:runner:1.1.1'
4242
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
4343
}

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

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
125125
launchBillingFlow(
126126
(String) call.argument("sku"),
127127
(String) call.argument("accountId"),
128+
(String) call.argument("obfuscatedProfileId"),
128129
(String) call.argument("oldSku"),
130+
(String) call.argument("purchaseToken"),
129131
call.hasArgument("prorationMode")
130132
? (int) call.argument("prorationMode")
131133
: ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY,
@@ -138,16 +140,10 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
138140
queryPurchaseHistoryAsync((String) call.argument("skuType"), result);
139141
break;
140142
case InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC:
141-
consumeAsync(
142-
(String) call.argument("purchaseToken"),
143-
(String) call.argument("developerPayload"),
144-
result);
143+
consumeAsync((String) call.argument("purchaseToken"), result);
145144
break;
146145
case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE:
147-
acknowledgePurchase(
148-
(String) call.argument("purchaseToken"),
149-
(String) call.argument("developerPayload"),
150-
result);
146+
acknowledgePurchase((String) call.argument("purchaseToken"), result);
151147
break;
152148
default:
153149
result.notImplemented();
@@ -200,7 +196,9 @@ public void onSkuDetailsResponse(
200196
private void launchBillingFlow(
201197
String sku,
202198
@Nullable String accountId,
199+
@Nullable String obfuscatedProfileId,
203200
@Nullable String oldSku,
201+
@Nullable String purchaseToken,
204202
int prorationMode,
205203
MethodChannel.Result result) {
206204
if (billingClientError(result)) {
@@ -248,10 +246,13 @@ private void launchBillingFlow(
248246
BillingFlowParams.Builder paramsBuilder =
249247
BillingFlowParams.newBuilder().setSkuDetails(skuDetails);
250248
if (accountId != null && !accountId.isEmpty()) {
251-
paramsBuilder.setAccountId(accountId);
249+
paramsBuilder.setObfuscatedAccountId(accountId);
250+
}
251+
if (obfuscatedProfileId != null && !obfuscatedProfileId.isEmpty()) {
252+
paramsBuilder.setObfuscatedProfileId(obfuscatedProfileId);
252253
}
253254
if (oldSku != null && !oldSku.isEmpty()) {
254-
paramsBuilder.setOldSku(oldSku);
255+
paramsBuilder.setOldSku(oldSku, purchaseToken);
255256
}
256257
// The proration mode value has to match one of the following declared in
257258
// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode
@@ -261,8 +262,7 @@ private void launchBillingFlow(
261262
billingClient.launchBillingFlow(activity, paramsBuilder.build())));
262263
}
263264

264-
private void consumeAsync(
265-
String purchaseToken, String developerPayload, final MethodChannel.Result result) {
265+
private void consumeAsync(String purchaseToken, final MethodChannel.Result result) {
266266
if (billingClientError(result)) {
267267
return;
268268
}
@@ -277,9 +277,6 @@ public void onConsumeResponse(BillingResult billingResult, String outToken) {
277277
ConsumeParams.Builder paramsBuilder =
278278
ConsumeParams.newBuilder().setPurchaseToken(purchaseToken);
279279

280-
if (developerPayload != null) {
281-
paramsBuilder.setDeveloperPayload(developerPayload);
282-
}
283280
ConsumeParams params = paramsBuilder.build();
284281

285282
billingClient.consumeAsync(params, listener);
@@ -348,16 +345,12 @@ public void onBillingServiceDisconnected() {
348345
});
349346
}
350347

351-
private void acknowledgePurchase(
352-
String purchaseToken, @Nullable String developerPayload, final MethodChannel.Result result) {
348+
private void acknowledgePurchase(String purchaseToken, final MethodChannel.Result result) {
353349
if (billingClientError(result)) {
354350
return;
355351
}
356352
AcknowledgePurchaseParams params =
357-
AcknowledgePurchaseParams.newBuilder()
358-
.setDeveloperPayload(developerPayload)
359-
.setPurchaseToken(purchaseToken)
360-
.build();
353+
AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build();
361354
billingClient.acknowledgePurchase(
362355
params,
363356
new AcknowledgePurchaseResponseListener() {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ static HashMap<String, Object> fromSkuDetail(SkuDetails detail) {
3131
info.put("priceCurrencyCode", detail.getPriceCurrencyCode());
3232
info.put("sku", detail.getSku());
3333
info.put("type", detail.getType());
34-
info.put("isRewarded", detail.isRewarded());
3534
info.put("subscriptionPeriod", detail.getSubscriptionPeriod());
3635
info.put("originalPrice", detail.getOriginalPrice());
3736
info.put("originalPriceAmountMicros", detail.getOriginalPriceAmountMicros());

packages/in_app_purchase/example/android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,9 @@ flutter {
106106
}
107107

108108
dependencies {
109-
implementation 'com.android.billingclient:billing:1.2'
109+
implementation 'com.android.billingclient:billing:3.0.2'
110110
testImplementation 'junit:junit:4.12'
111-
testImplementation 'org.mockito:mockito-core:2.17.0'
111+
testImplementation 'org.mockito:mockito-core:3.6.0'
112112
testImplementation 'org.json:json:20180813'
113113
androidTestImplementation 'androidx.test:runner:1.1.1'
114114
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static java.util.stream.Collectors.toList;
2323
import static org.junit.Assert.assertEquals;
2424
import static org.junit.Assert.assertNull;
25+
import static org.junit.Assert.fail;
2526
import static org.mockito.ArgumentMatchers.any;
2627
import static org.mockito.ArgumentMatchers.contains;
2728
import static org.mockito.ArgumentMatchers.eq;
@@ -60,6 +61,7 @@
6061
import java.util.HashMap;
6162
import java.util.List;
6263
import java.util.Map;
64+
import org.json.JSONException;
6365
import org.junit.Before;
6466
import org.junit.Test;
6567
import org.mockito.ArgumentCaptor;
@@ -79,7 +81,7 @@ public class MethodCallHandlerTest {
7981

8082
@Before
8183
public void setUp() {
82-
MockitoAnnotations.initMocks(this);
84+
MockitoAnnotations.openMocks(this);
8385
factory =
8486
(@NonNull Context context,
8587
@NonNull MethodChannel channel,
@@ -261,14 +263,18 @@ public void querySkuDetailsAsync_clientDisconnected() {
261263
verify(result, never()).success(any());
262264
}
263265

266+
// Test launchBillingFlow not crash if `accountId` is `null`
267+
// Ideally, we should check if the `accountId` is null in the parameter; however,
268+
// since PBL 3.0, the `accountId` variable is not public.
264269
@Test
265-
public void launchBillingFlow_ok_null_AccountId() {
270+
public void launchBillingFlow_null_AccountId_do_not_crash() {
266271
// Fetch the sku details first and then prepare the launch billing flow call
267272
String skuId = "foo";
268273
queryForSkus(singletonList(skuId));
269274
HashMap<String, Object> arguments = new HashMap<>();
270275
arguments.put("sku", skuId);
271276
arguments.put("accountId", null);
277+
arguments.put("obfuscatedProfileId", null);
272278
MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
273279

274280
// Launch the billing flow
@@ -286,7 +292,6 @@ public void launchBillingFlow_ok_null_AccountId() {
286292
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
287293
BillingFlowParams params = billingFlowParamsCaptor.getValue();
288294
assertEquals(params.getSku(), skuId);
289-
assertNull(params.getAccountId());
290295

291296
// Verify we pass the response code to result
292297
verify(result, never()).error(any(), any(), any());
@@ -320,7 +325,6 @@ public void launchBillingFlow_ok_null_OldSku() {
320325
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
321326
BillingFlowParams params = billingFlowParamsCaptor.getValue();
322327
assertEquals(params.getSku(), skuId);
323-
assertEquals(params.getAccountId(), accountId);
324328
assertNull(params.getOldSku());
325329
// Verify we pass the response code to result
326330
verify(result, never()).error(any(), any(), any());
@@ -374,7 +378,6 @@ public void launchBillingFlow_ok_oldSku() {
374378
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
375379
BillingFlowParams params = billingFlowParamsCaptor.getValue();
376380
assertEquals(params.getSku(), skuId);
377-
assertEquals(params.getAccountId(), accountId);
378381
assertEquals(params.getOldSku(), oldSkuId);
379382

380383
// Verify we pass the response code to result
@@ -408,7 +411,6 @@ public void launchBillingFlow_ok_AccountId() {
408411
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
409412
BillingFlowParams params = billingFlowParamsCaptor.getValue();
410413
assertEquals(params.getSku(), skuId);
411-
assertEquals(params.getAccountId(), accountId);
412414

413415
// Verify we pass the response code to result
414416
verify(result, never()).error(any(), any(), any());
@@ -420,13 +422,15 @@ public void launchBillingFlow_ok_Proration() {
420422
// Fetch the sku details first and query the method call
421423
String skuId = "foo";
422424
String oldSkuId = "oldFoo";
425+
String purchaseToken = "purchaseTokenFoo";
423426
String accountId = "account";
424427
int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE;
425428
queryForSkus(unmodifiableList(asList(skuId, oldSkuId)));
426429
HashMap<String, Object> arguments = new HashMap<>();
427430
arguments.put("sku", skuId);
428431
arguments.put("accountId", accountId);
429432
arguments.put("oldSku", oldSkuId);
433+
arguments.put("purchaseToken", purchaseToken);
430434
arguments.put("prorationMode", prorationMode);
431435
MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);
432436

@@ -445,8 +449,8 @@ public void launchBillingFlow_ok_Proration() {
445449
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
446450
BillingFlowParams params = billingFlowParamsCaptor.getValue();
447451
assertEquals(params.getSku(), skuId);
448-
assertEquals(params.getAccountId(), accountId);
449452
assertEquals(params.getOldSku(), oldSkuId);
453+
assertEquals(params.getOldSkuPurchaseToken(), purchaseToken);
450454
assertEquals(params.getReplaceSkusProrationMode(), prorationMode);
451455

452456
// Verify we pass the response code to result
@@ -668,11 +672,7 @@ public void consumeAsync() {
668672

669673
methodChannelHandler.onMethodCall(new MethodCall(CONSUME_PURCHASE_ASYNC, arguments), result);
670674

671-
ConsumeParams params =
672-
ConsumeParams.newBuilder()
673-
.setDeveloperPayload("mockPayload")
674-
.setPurchaseToken("mockToken")
675-
.build();
675+
ConsumeParams params = ConsumeParams.newBuilder().setPurchaseToken("mockToken").build();
676676

677677
// Verify we pass the data to result
678678
verify(mockBillingClient).consumeAsync(refEq(params), listenerCaptor.capture());
@@ -703,10 +703,7 @@ public void acknowledgePurchase() {
703703
methodChannelHandler.onMethodCall(new MethodCall(ACKNOWLEDGE_PURCHASE, arguments), result);
704704

705705
AcknowledgePurchaseParams params =
706-
AcknowledgePurchaseParams.newBuilder()
707-
.setDeveloperPayload("mockPayload")
708-
.setPurchaseToken("mockToken")
709-
.build();
706+
AcknowledgePurchaseParams.newBuilder().setPurchaseToken("mockToken").build();
710707

711708
// Verify we pass the data to result
712709
verify(mockBillingClient).acknowledgePurchase(refEq(params), listenerCaptor.capture());
@@ -774,6 +771,7 @@ private void queryForSkus(List<String> skusList) {
774771
verify(mockBillingClient).querySkuDetailsAsync(any(), listenerCaptor.capture());
775772
List<SkuDetails> skuDetailsResponse =
776773
skusList.stream().map(this::buildSkuDetails).collect(toList());
774+
777775
BillingResult billingResult =
778776
BillingResult.newBuilder()
779777
.setResponseCode(100)
@@ -783,8 +781,16 @@ private void queryForSkus(List<String> skusList) {
783781
}
784782

785783
private SkuDetails buildSkuDetails(String id) {
786-
SkuDetails details = mock(SkuDetails.class);
787-
when(details.getSku()).thenReturn(id);
784+
String json =
785+
String.format(
786+
"{\"packageName\": \"dummyPackageName\",\"productId\":\"%s\",\"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}",
787+
id);
788+
SkuDetails details = null;
789+
try {
790+
details = new SkuDetails(json);
791+
} catch (JSONException e) {
792+
fail("buildSkuDetails failed with JSONException " + e.toString());
793+
}
788794
return details;
789795
}
790796

0 commit comments

Comments
 (0)