Skip to content

[in_app_purchase] Fully migrate to BillingClient V5 #3752

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 17, 2023
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.0
* **BREAKING CHANGE**: Removes `launchPriceChangeConfirmationFlow` from `InAppPurchaseAndroidPlatform`. Price changes are now [handled by Google Play](https://developer.android.com/google/play/billing/subscriptions#price-change).
* Returns both base plans and offers when `queryProductDetailsAsync` is called.

## 0.2.5+5

* Updates gradle, AGP and fixes some lint errors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,21 @@ static final class MethodNames {
"BillingClient#startConnection(BillingClientStateListener)";
static final String END_CONNECTION = "BillingClient#endConnection()";
static final String ON_DISCONNECT = "BillingClientStateListener#onBillingServiceDisconnected()";
static final String QUERY_SKU_DETAILS =
"BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)";
static final String QUERY_PRODUCT_DETAILS =
"BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener)";
static final String LAUNCH_BILLING_FLOW =
"BillingClient#launchBillingFlow(Activity, BillingFlowParams)";
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)";
"PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List<Purchase>)";
static final String QUERY_PURCHASES_ASYNC =
"BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)";
static final String QUERY_PURCHASE_HISTORY_ASYNC =
"BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)";
"BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)";
static final String CONSUME_PURCHASE_ASYNC =
"BillingClient#consumeAsync(String, ConsumeResponseListener)";
"BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)";
static final String ACKNOWLEDGE_PURCHASE =
"BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)";
"BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)";
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() {};
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,173 @@

package io.flutter.plugins.inapppurchase;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.billingclient.api.AccountIdentifiers;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchaseHistoryRecord;
import com.android.billingclient.api.QueryProductDetailsParams;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Currency;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/** Handles serialization of {@link com.android.billingclient.api.BillingClient} related objects. */
/**
* Handles serialization and deserialization of {@link com.android.billingclient.api.BillingClient}
* related objects.
*/
/*package*/ class Translator {
// TODO(stuartmorgan): Migrate this code. See TODO on MethodCallHandlerImpl.querySkuDetailsAsync.
@SuppressWarnings("deprecation")
static HashMap<String, Object> fromSkuDetail(com.android.billingclient.api.SkuDetails detail) {
static HashMap<String, Object> fromProductDetail(ProductDetails detail) {
HashMap<String, Object> info = new HashMap<>();
info.put("title", detail.getTitle());
info.put("description", detail.getDescription());
info.put("freeTrialPeriod", detail.getFreeTrialPeriod());
info.put("introductoryPrice", detail.getIntroductoryPrice());
info.put("introductoryPriceAmountMicros", detail.getIntroductoryPriceAmountMicros());
info.put("introductoryPriceCycles", detail.getIntroductoryPriceCycles());
info.put("introductoryPricePeriod", detail.getIntroductoryPricePeriod());
info.put("price", detail.getPrice());
info.put("priceAmountMicros", detail.getPriceAmountMicros());
info.put("priceCurrencyCode", detail.getPriceCurrencyCode());
info.put("priceCurrencySymbol", currencySymbolFromCode(detail.getPriceCurrencyCode()));
info.put("sku", detail.getSku());
info.put("type", detail.getType());
info.put("subscriptionPeriod", detail.getSubscriptionPeriod());
info.put("originalPrice", detail.getOriginalPrice());
info.put("originalPriceAmountMicros", detail.getOriginalPriceAmountMicros());
info.put("productId", detail.getProductId());
info.put("productType", detail.getProductType());
info.put("name", detail.getName());

@Nullable
ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails =
detail.getOneTimePurchaseOfferDetails();
if (oneTimePurchaseOfferDetails != null) {
info.put(
"oneTimePurchaseOfferDetails",
fromOneTimePurchaseOfferDetails(oneTimePurchaseOfferDetails));
}

@Nullable
List<ProductDetails.SubscriptionOfferDetails> subscriptionOfferDetailsList =
detail.getSubscriptionOfferDetails();
if (subscriptionOfferDetailsList != null) {
info.put(
"subscriptionOfferDetails",
fromSubscriptionOfferDetailsList(subscriptionOfferDetailsList));
}

return info;
}

// TODO(stuartmorgan): Migrate this code. See TODO on MethodCallHandlerImpl.querySkuDetailsAsync.
@SuppressWarnings("deprecation")
static List<HashMap<String, Object>> fromSkuDetailsList(
@Nullable List<com.android.billingclient.api.SkuDetails> skuDetailsList) {
if (skuDetailsList == null) {
static List<QueryProductDetailsParams.Product> toProductList(List<Object> serialized) {
List<QueryProductDetailsParams.Product> products = new ArrayList<>();
for (Object productSerialized : serialized) {
@SuppressWarnings(value = "unchecked")
Map<String, Object> productMap = (Map<String, Object>) productSerialized;
products.add(toProduct(productMap));
}
return products;
}

static QueryProductDetailsParams.Product toProduct(Map<String, Object> serialized) {
String productId = (String) serialized.get("productId");
String productType = (String) serialized.get("productType");
return QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(productType)
.build();
}

static List<HashMap<String, Object>> fromProductDetailsList(
@Nullable List<ProductDetails> productDetailsList) {
if (productDetailsList == null) {
return Collections.emptyList();
}

ArrayList<HashMap<String, Object>> output = new ArrayList<>();
for (com.android.billingclient.api.SkuDetails detail : skuDetailsList) {
output.add(fromSkuDetail(detail));
for (ProductDetails detail : productDetailsList) {
output.add(fromProductDetail(detail));
}
return output;
}

static HashMap<String, Object> fromOneTimePurchaseOfferDetails(
@Nullable ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails) {
HashMap<String, Object> serialized = new HashMap<>();
if (oneTimePurchaseOfferDetails == null) {
return serialized;
}

serialized.put("priceAmountMicros", oneTimePurchaseOfferDetails.getPriceAmountMicros());
serialized.put("priceCurrencyCode", oneTimePurchaseOfferDetails.getPriceCurrencyCode());
serialized.put("formattedPrice", oneTimePurchaseOfferDetails.getFormattedPrice());

return serialized;
}

static List<HashMap<String, Object>> fromSubscriptionOfferDetailsList(
@Nullable List<ProductDetails.SubscriptionOfferDetails> subscriptionOfferDetailsList) {
if (subscriptionOfferDetailsList == null) {
return Collections.emptyList();
}

ArrayList<HashMap<String, Object>> serialized = new ArrayList<>();

for (ProductDetails.SubscriptionOfferDetails subscriptionOfferDetails :
subscriptionOfferDetailsList) {
serialized.add(fromSubscriptionOfferDetails(subscriptionOfferDetails));
}

return serialized;
}

static HashMap<String, Object> fromSubscriptionOfferDetails(
@Nullable ProductDetails.SubscriptionOfferDetails subscriptionOfferDetails) {
HashMap<String, Object> serialized = new HashMap<>();
if (subscriptionOfferDetails == null) {
return serialized;
}

serialized.put("offerId", subscriptionOfferDetails.getOfferId());
serialized.put("basePlanId", subscriptionOfferDetails.getBasePlanId());
serialized.put("offerTags", subscriptionOfferDetails.getOfferTags());
serialized.put("offerIdToken", subscriptionOfferDetails.getOfferToken());

ProductDetails.PricingPhases pricingPhases = subscriptionOfferDetails.getPricingPhases();
serialized.put("pricingPhases", fromPricingPhases(pricingPhases));

return serialized;
}

static List<HashMap<String, Object>> fromPricingPhases(
@NonNull ProductDetails.PricingPhases pricingPhases) {
ArrayList<HashMap<String, Object>> serialized = new ArrayList<>();

for (ProductDetails.PricingPhase pricingPhase : pricingPhases.getPricingPhaseList()) {
serialized.add(fromPricingPhase(pricingPhase));
}
return serialized;
}

static HashMap<String, Object> fromPricingPhase(
@Nullable ProductDetails.PricingPhase pricingPhase) {
HashMap<String, Object> serialized = new HashMap<>();

if (pricingPhase == null) {
return serialized;
}

serialized.put("formattedPrice", pricingPhase.getFormattedPrice());
serialized.put("priceCurrencyCode", pricingPhase.getPriceCurrencyCode());
serialized.put("priceAmountMicros", pricingPhase.getPriceAmountMicros());
serialized.put("billingCycleCount", pricingPhase.getBillingCycleCount());
serialized.put("billingPeriod", pricingPhase.getBillingPeriod());
serialized.put("recurrenceMode", pricingPhase.getRecurrenceMode());

return serialized;
}

static HashMap<String, Object> fromPurchase(Purchase purchase) {
HashMap<String, Object> info = new HashMap<>();
// TODO(stuartmorgan): Migrate this code. See TODO on MethodCallHandlerImpl.querySkuDetailsAsync.
@SuppressWarnings("deprecation")
List<String> skus = purchase.getSkus();
List<String> products = purchase.getProducts();
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("skus", skus);
info.put("products", products);
info.put("isAutoRenewing", purchase.isAutoRenewing());
info.put("originalJson", purchase.getOriginalJson());
info.put("developerPayload", purchase.getDeveloperPayload());
Expand All @@ -84,13 +188,11 @@ static HashMap<String, Object> fromPurchase(Purchase purchase) {
static HashMap<String, Object> fromPurchaseHistoryRecord(
PurchaseHistoryRecord purchaseHistoryRecord) {
HashMap<String, Object> info = new HashMap<>();
// TODO(stuartmorgan): Migrate this code. See TODO on MethodCallHandlerImpl.querySkuDetailsAsync.
@SuppressWarnings("deprecation")
List<String> skus = purchaseHistoryRecord.getSkus();
List<String> products = purchaseHistoryRecord.getProducts();
info.put("purchaseTime", purchaseHistoryRecord.getPurchaseTime());
info.put("purchaseToken", purchaseHistoryRecord.getPurchaseToken());
info.put("signature", purchaseHistoryRecord.getSignature());
info.put("skus", skus);
info.put("products", products);
info.put("developerPayload", purchaseHistoryRecord.getDeveloperPayload());
info.put("originalJson", purchaseHistoryRecord.getOriginalJson());
info.put("quantity", purchaseHistoryRecord.getQuantity());
Expand Down
Loading