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

[in_app_purchase] migrate playing billing library to v3 #3636

Merged
merged 12 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 13 additions & 0 deletions packages/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## 0.5.0

* Migrate to Google Billing Library 3.0
* Add `obfuscatedProfileId`, `purchaseToken` in [BillingClientWrapper.launchBillingFlow].
* **Breaking Change**
* Removed `developerPayload` in [BillingClientWrapper.acknowledgePurchase], [BillingClientWrapper.consumeAsync], [InAppPurchaseConnection.completePurchase], [InAppPurchaseConnection.consumePurchase].
* Removed `isRewarded` from [SkuDetailsWrapper].
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any useful context to provide about why they are removed? E.g., link to discussion of removal in the SDK?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

* [SkuDetailsWrapper.introductoryPriceCycles] now returns `int` instead of `String`.
* 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).
* Additional information on some the changes:
* [Dropping reward SKU support](https://support.google.com/googleplay/android-developer/answer/9155268?hl=en)
* [Developer payload](https://developer.android.com/google/play/billing/developer-payload)

## 0.4.1

* Support InApp subscription upgrade/downgrade.
Expand Down
4 changes: 2 additions & 2 deletions packages/in_app_purchase/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ android {

dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'com.android.billingclient:billing:2.0.3'
implementation 'com.android.billingclient:billing:3.0.2'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.17.0'
testImplementation 'org.mockito:mockito-core:3.6.0'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is required to mock the objects in the new PBL.

androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
launchBillingFlow(
(String) call.argument("sku"),
(String) call.argument("accountId"),
(String) call.argument("obfuscatedProfileId"),
(String) call.argument("oldSku"),
(String) call.argument("purchaseToken"),
call.hasArgument("prorationMode")
? (int) call.argument("prorationMode")
: ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY,
Expand All @@ -138,16 +140,10 @@ 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"),
(String) call.argument("developerPayload"),
result);
consumeAsync((String) call.argument("purchaseToken"), result);
break;
case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE:
acknowledgePurchase(
(String) call.argument("purchaseToken"),
(String) call.argument("developerPayload"),
result);
acknowledgePurchase((String) call.argument("purchaseToken"), result);
break;
default:
result.notImplemented();
Expand Down Expand Up @@ -200,7 +196,9 @@ public void onSkuDetailsResponse(
private void launchBillingFlow(
String sku,
@Nullable String accountId,
@Nullable String obfuscatedProfileId,
@Nullable String oldSku,
@Nullable String purchaseToken,
int prorationMode,
MethodChannel.Result result) {
if (billingClientError(result)) {
Expand Down Expand Up @@ -248,10 +246,13 @@ private void launchBillingFlow(
BillingFlowParams.Builder paramsBuilder =
BillingFlowParams.newBuilder().setSkuDetails(skuDetails);
if (accountId != null && !accountId.isEmpty()) {
paramsBuilder.setAccountId(accountId);
paramsBuilder.setObfuscatedAccountId(accountId);
}
if (obfuscatedProfileId != null && !obfuscatedProfileId.isEmpty()) {
paramsBuilder.setObfuscatedProfileId(obfuscatedProfileId);
}
if (oldSku != null && !oldSku.isEmpty()) {
paramsBuilder.setOldSku(oldSku);
paramsBuilder.setOldSku(oldSku, 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
Expand All @@ -261,8 +262,7 @@ private void launchBillingFlow(
billingClient.launchBillingFlow(activity, paramsBuilder.build())));
}

private void consumeAsync(
String purchaseToken, String developerPayload, final MethodChannel.Result result) {
private void consumeAsync(String purchaseToken, final MethodChannel.Result result) {
if (billingClientError(result)) {
return;
}
Expand All @@ -277,9 +277,6 @@ public void onConsumeResponse(BillingResult billingResult, String outToken) {
ConsumeParams.Builder paramsBuilder =
ConsumeParams.newBuilder().setPurchaseToken(purchaseToken);

if (developerPayload != null) {
paramsBuilder.setDeveloperPayload(developerPayload);
}
ConsumeParams params = paramsBuilder.build();

billingClient.consumeAsync(params, listener);
Expand Down Expand Up @@ -348,16 +345,12 @@ public void onBillingServiceDisconnected() {
});
}

private void acknowledgePurchase(
String purchaseToken, @Nullable String developerPayload, final MethodChannel.Result result) {
private void acknowledgePurchase(String purchaseToken, final MethodChannel.Result result) {
if (billingClientError(result)) {
return;
}
AcknowledgePurchaseParams params =
AcknowledgePurchaseParams.newBuilder()
.setDeveloperPayload(developerPayload)
.setPurchaseToken(purchaseToken)
.build();
AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build();
billingClient.acknowledgePurchase(
params,
new AcknowledgePurchaseResponseListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ static HashMap<String, Object> fromSkuDetail(SkuDetails detail) {
info.put("priceCurrencyCode", detail.getPriceCurrencyCode());
info.put("sku", detail.getSku());
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());
Expand Down
4 changes: 2 additions & 2 deletions packages/in_app_purchase/example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ flutter {
}

dependencies {
implementation 'com.android.billingclient:billing:1.2'
implementation 'com.android.billingclient:billing:3.0.2'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.17.0'
testImplementation 'org.mockito:mockito-core:3.6.0'
testImplementation 'org.json:json:20180813'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -60,6 +61,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
Expand All @@ -79,7 +81,7 @@ public class MethodCallHandlerTest {

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
MockitoAnnotations.openMocks(this);
factory =
(@NonNull Context context,
@NonNull MethodChannel channel,
Expand Down Expand Up @@ -261,14 +263,18 @@ public void querySkuDetailsAsync_clientDisconnected() {
verify(result, never()).success(any());
}

// Test launchBillingFlow not crash if `accountId` is `null`
// Ideally, we should check if the `accountId` is null in the parameter; however,
// since PBL 3.0, the `accountId` variable is not public.
@Test
public void launchBillingFlow_ok_null_AccountId() {
public void launchBillingFlow_null_AccountId_do_not_crash() {
// Fetch the sku details first and then prepare the launch billing flow call
String skuId = "foo";
queryForSkus(singletonList(skuId));
HashMap<String, Object> arguments = new HashMap<>();
arguments.put("sku", skuId);
arguments.put("accountId", null);
arguments.put("obfuscatedProfileId", null);
MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments);

// Launch the billing flow
Expand All @@ -286,7 +292,6 @@ public void launchBillingFlow_ok_null_AccountId() {
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
assertEquals(params.getSku(), skuId);
assertNull(params.getAccountId());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

getAccountId is not available anymore so we can't really test this value, however, we can still test if the call crashes, so ill leave the test here

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rename the test then, to better explain what it's doing now? (Probably needs a comment in addition to a new name.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
Expand Down Expand Up @@ -320,7 +325,6 @@ public void launchBillingFlow_ok_null_OldSku() {
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
assertEquals(params.getSku(), skuId);
assertEquals(params.getAccountId(), accountId);
assertNull(params.getOldSku());
// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
Expand Down Expand Up @@ -374,7 +378,6 @@ public void launchBillingFlow_ok_oldSku() {
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
assertEquals(params.getSku(), skuId);
assertEquals(params.getAccountId(), accountId);
assertEquals(params.getOldSku(), oldSkuId);

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

// Verify we pass the response code to result
verify(result, never()).error(any(), any(), any());
Expand All @@ -420,13 +422,15 @@ public void launchBillingFlow_ok_Proration() {
// 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_PRORATED_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);

Expand All @@ -445,8 +449,8 @@ public void launchBillingFlow_ok_Proration() {
verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture());
BillingFlowParams params = billingFlowParamsCaptor.getValue();
assertEquals(params.getSku(), skuId);
assertEquals(params.getAccountId(), accountId);
assertEquals(params.getOldSku(), oldSkuId);
assertEquals(params.getOldSkuPurchaseToken(), purchaseToken);
assertEquals(params.getReplaceSkusProrationMode(), prorationMode);

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

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

ConsumeParams params =
ConsumeParams.newBuilder()
.setDeveloperPayload("mockPayload")
.setPurchaseToken("mockToken")
.build();
ConsumeParams params = ConsumeParams.newBuilder().setPurchaseToken("mockToken").build();

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

AcknowledgePurchaseParams params =
AcknowledgePurchaseParams.newBuilder()
.setDeveloperPayload("mockPayload")
.setPurchaseToken("mockToken")
.build();
AcknowledgePurchaseParams.newBuilder().setPurchaseToken("mockToken").build();

// Verify we pass the data to result
verify(mockBillingClient).acknowledgePurchase(refEq(params), listenerCaptor.capture());
Expand Down Expand Up @@ -774,6 +771,7 @@ private void queryForSkus(List<String> skusList) {
verify(mockBillingClient).querySkuDetailsAsync(any(), listenerCaptor.capture());
List<SkuDetails> skuDetailsResponse =
skusList.stream().map(this::buildSkuDetails).collect(toList());

BillingResult billingResult =
BillingResult.newBuilder()
.setResponseCode(100)
Expand All @@ -783,8 +781,16 @@ private void queryForSkus(List<String> skusList) {
}

private SkuDetails buildSkuDetails(String id) {
SkuDetails details = mock(SkuDetails.class);
when(details.getSku()).thenReturn(id);
String json =
String.format(
"{\"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}",
id);
SkuDetails details = null;
try {
details = new SkuDetails(json);
} catch (JSONException e) {
fail("buildSkuDetails failed with JSONException " + e.toString());
}
return details;
}

Expand Down
Loading