diff --git a/play-billing-library/app/build.gradle b/play-billing-library/app/build.gradle new file mode 100644 index 00000000..293a86d8 --- /dev/null +++ b/play-billing-library/app/build.gradle @@ -0,0 +1,75 @@ +/* Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +apply plugin: 'com.android.application' +apply plugin: 'com.google.android.gms.strict-version-matcher-plugin' +// OSS Plugin +apply plugin: 'com.google.android.gms.oss-licenses-plugin' + + +android { + compileSdk 36 + namespace="com.google.play.billing.samples.managedcatalogue" + + defaultConfig { + applicationId "com.google.play.billing.samples.managedcatalogue" + minSdk 23 + targetSdk 36 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + all { + proguardFiles 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + coreLibraryDesugaringEnabled true + } + +} + + + +dependencies { + + // OSS Plugin + implementation 'com.google.android.gms:play-services-oss-licenses:17.1.0' + + // Play Billing Library + implementation "com.android.billingclient:billing:8.1.0" + + implementation 'androidx.appcompat:appcompat:1.7.0' + // Align kotlin versions + implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.1.10")) + implementation 'com.google.android.material:material:1.13.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'com.google.guava:guava:31.1-android' + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.15.2' + testImplementation 'org.mockito:mockito-android:5.15.2' + androidTestImplementation 'androidx.test.ext:junit:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' +} diff --git a/play-billing-library/app/src/main/AndroidManifest.xml b/play-billing-library/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..90cdc429 --- /dev/null +++ b/play-billing-library/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/play-billing-library/app/src/main/ic_launcher-playstore.png b/play-billing-library/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..69fa8c0b Binary files /dev/null and b/play-billing-library/app/src/main/ic_launcher-playstore.png differ diff --git a/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/BillingManager.java b/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/BillingManager.java new file mode 100644 index 00000000..00eba90b --- /dev/null +++ b/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/BillingManager.java @@ -0,0 +1,368 @@ +package com.google.play.billing.samples.managedcatalogue;// Import necessary classes from the Android framework and Google Play Billing Library. +import static com.android.billingclient.api.InAppMessageParams.*; + +import android.app.Activity; +import android.content.Context; + +import androidx.annotation.NonNull; + +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.BillingConfig; +import com.android.billingclient.api.BillingConfigResponseListener; +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.GetBillingConfigParams; +import com.android.billingclient.api.InAppMessageParams; +import com.android.billingclient.api.InAppMessageResponseListener; +import com.android.billingclient.api.InAppMessageResult; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.ProductDetailsResponseListener; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryProductDetailsResult; +import com.android.billingclient.api.QueryPurchasesParams; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A compilable class that includes all the Java snippets from the Play Billing documentation. + * This class demonstrates how to initialize, connect, and use the BillingClient, + * as well as handle various billing-related tasks like querying products, + * launching purchase flows, and processing purchases. + */ +public class BillingManager { + + private final Context context; + private BillingClient billingClient; + + public BillingManager(Context context) { + this.context = context; + initializeBillingClient(); + } + + private static void onProductDetailsResponse(BillingResult billingResult, QueryProductDetailsResult productDetailsList) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + +// The original snippet had a conceptual error in accessing result types. +// The modern API provides the list directly. +// Handling for unfetched products is implicitly managed by the library. + } + } + + private void initializeBillingClient() { + // [START android_playbilling_initialize_java] + PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> { + // To be implemented in a later section. + // This is a placeholder for the onPurchasesUpdated method defined below. + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) { + for (Purchase purchase : purchases) { + handlePurchase(purchase); + } + } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) { + // Handle an error caused by a user canceling the purchase flow. + } else { + // Handle any other error codes. + } + }; + + billingClient = BillingClient.newBuilder(context) + .setListener(purchasesUpdatedListener) + // Configure other settings. + .build(); + // [END android_playbilling_initialize_java] + } + + public void connectToGooglePlay() { + // [START android_playbilling_startconnection_java] + billingClient.startConnection(new BillingClientStateListener() { + @Override + public void onBillingSetupFinished(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + // The BillingClient is ready. You can query purchases here. + queryPurchases(); + } + } + @Override + public void onBillingServiceDisconnected() { + // Try to restart the connection on the next request to + // Google Play by calling the startConnection() method. + } + }); + // [END android_playbilling_startconnection_java] + } + + private void queryPurchases() { + // Example method to query active purchases, not included in the original snippets + // but useful for a complete example. + if (billingClient.isReady()) { + billingClient.queryPurchasesAsync( + QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build(), + (billingResult, purchases) -> { + // Process the result + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) { + for (Purchase purchase : purchases) { + handlePurchase(purchase); + } + } + } + ); + } + } + + private void enableAutomaticReconnection(Context context, PurchasesUpdatedListener listener) { + // [START android_playbilling_enableautoreconnect_java] + BillingClient billingClient = BillingClient.newBuilder(context) + .setListener(listener) + //.enableAutoServiceReconnection() // This method is deprecated. Reconnection is now handled automatically by default. + .build(); + // [END android_playbilling_enableautoreconnect_java] + } + + public void queryProductDetails() { + // [START android_playbilling_queryproductdetails_java] + QueryProductDetailsParams queryProductDetailsParams = + QueryProductDetailsParams.newBuilder() + .setProductList( + Collections.singletonList( + QueryProductDetailsParams.Product.newBuilder() + .setProductId("product_id_example") + .setProductType(BillingClient.ProductType.SUBS) + .build())) + .build(); + + billingClient.queryProductDetailsAsync( + queryProductDetailsParams, + BillingManager::onProductDetailsResponse + ); + // [END android_playbilling_queryproductdetails_java] + } + + public void launchBillingFlow(Activity activity, ProductDetails productDetails, String offerToken) { + // [START android_playbilling_launchflow_java] + List productDetailsParamsList = + Collections.singletonList( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productDetails) + .setOfferToken(offerToken) + .build() + ); + + BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() + .setProductDetailsParamsList(productDetailsParamsList) + .build(); + + billingClient.launchBillingFlow(activity, billingFlowParams); + // [END android_playbilling_launchflow_java] + } + + + // This method is a wrapper for the snippet. The actual listener implementation is part of the BillingClient setup. + public void onPurchasesUpdatedWrapper(BillingResult billingResult, List purchases) { + // [START android_playbilling_onpurchasesupdated_java] + // @Override // @Override is commented out as this is a wrapper method, not a direct override. + // void onPurchasesUpdated(BillingResult billingResult, List purchases) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK + && purchases != null) { + for (Purchase purchase : purchases) { + // Process the purchase as described in the next section. + handlePurchase(purchase); + } + } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) { + // Handle an error caused by a user canceling the purchase flow. + } else { + // Handle any other error codes. + } + // } + // [END android_playbilling_onpurchasesupdated_java] + } + + public void consumeProduct(Purchase purchase) { + // [START android_playbilling_consumeproduct_java] + ConsumeParams consumeParams = + ConsumeParams.newBuilder() + .setPurchaseToken(purchase.getPurchaseToken()) + .build(); + + ConsumeResponseListener listener = (billingResult, purchaseToken) -> { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + // Handle the success of the consume operation. + } + }; + + billingClient.consumeAsync(consumeParams, listener); + // [END android_playbilling_consumeproduct_java] + } + + public void acknowledgePurchase(Purchase purchase) { + // [START android_playbilling_acknowledge_java] + BillingClient client = this.billingClient; // Assuming client is your BillingClient instance + AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = billingResult -> { + // Handle response + }; + + if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + if (!purchase.isAcknowledged()) { + AcknowledgePurchaseParams acknowledgePurchaseParams = + AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchase.getPurchaseToken()) + .build(); + client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener); + } + } + // [END android_playbilling_acknowledge_java] + } + + public void handlePurchaseRecap(Purchase purchaseParam) { + // [START android_playbilling_handlepurchaserecap_java] + // void handlePurchase(Purchase purchase) { + // Purchase retrieved from BillingClient#queryPurchasesAsync or your + // onPurchasesUpdated. + Purchase purchase = purchaseParam; // Use the passed parameter + + // Step 1: Send the purchase to your secure backend to verify the purchase + // following + // https://developer.android.com/google/play/billing/security#verify + + // Step 2: Update your entitlement storage with the purchase. If purchase is + // in PENDING state then ensure the entitlement is marked as pending and the + // user does not receive benefits yet. It is recommended that this step is + // done on your secure backend and can combine in the API call to your + // backend in step 1. + + // Step 3: Notify the user using appropriate messaging (delaying + // notification if needed as discussed above). + + // Step 4: Notify Google the purchase was processed using the steps + // discussed in the processing purchases section. (e.g., Acknowledge or Consume) + // } + // [END android_playbilling_handlepurchaserecap_java] + } + + public void getBillingConfigAsync() { + // [START android_playbilling_getbillingconfig_java] + // Use the default GetBillingConfigParams. + GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build(); + billingClient.getBillingConfigAsync(getBillingConfigParams, + (billingResult, billingConfig) -> { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK + && billingConfig != null) { + String countryCode = billingConfig.getCountryCode(); + // ... + } else { + // TODO: Handle errors + } + }); + // [END android_playbilling_getbillingconfig_java] + } + + public void changeSubscriptionPlan(Activity activity, String purchaseTokenOfExistingSubscription, ProductDetails newPlanDetails, ProductDetails existingAddonDetails) { + // [START android_playbilling_subscription_replace_java] + BillingClient billingClient = this.billingClient; + + int replacementModeForBasePlan = BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE; + // The following variable is unused in the snippet but retained for completeness. + int replacementModeForAddon = BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION; + + // ProductDetails obtained from queryProductDetailsAsync(). + BillingFlowParams.ProductDetailsParams productDetails1 = + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(newPlanDetails) // Assumes newPlanDetails is the new base plan + .build(); + + BillingFlowParams.ProductDetailsParams productDetails2 = + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(existingAddonDetails) // Assumes this is an add-on to keep + .build(); + + ArrayList productDetailsParamsList = new ArrayList<>(); + productDetailsParamsList.add(productDetails1); + productDetailsParamsList.add(productDetails2); + + BillingFlowParams billingFlowParams = + BillingFlowParams.newBuilder() + .setSubscriptionUpdateParams( + BillingFlowParams.SubscriptionUpdateParams.newBuilder() + .setOldPurchaseToken(purchaseTokenOfExistingSubscription) + .setSubscriptionReplacementMode(replacementModeForBasePlan) // Specify replacement mode for the base plan + .build()) + .setProductDetailsParamsList(productDetailsParamsList) + .build(); + + billingClient.launchBillingFlow(activity, billingFlowParams); + // [END android_playbilling_subscription_replace_java] + } + + public void changeSubscriptionPlanDeprecated(Activity activity, ProductDetails productDetails, int selectedOfferIndex) { + // [START android_playbilling_subscription_update_deprecated_java] + String offerToken = productDetails + .getSubscriptionOfferDetails().get(selectedOfferIndex) + .getOfferToken(); + + BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() + .setProductDetailsParamsList( + Collections.singletonList( + BillingFlowParams.ProductDetailsParams.newBuilder() + // fetched via queryProductDetailsAsync + .setProductDetails(productDetails) + // offerToken can be found in + // ProductDetails=>SubscriptionOfferDetails + .setOfferToken(offerToken) + .build())) + .setSubscriptionUpdateParams( + BillingFlowParams.SubscriptionUpdateParams.newBuilder() + // purchaseToken can be found in Purchase#getPurchaseToken + .setOldPurchaseToken("old_purchase_token") + .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE) + .build()) + .build(); + + BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams); + // ... + // [END android_playbilling_subscription_update_deprecated_java] + } + + public void showInAppMessages(Activity activity) { + // [START android_playbilling_inappmessaging_java] + InAppMessageParams inAppMessageParams = InAppMessageParams.newBuilder() + .addInAppMessageCategoryToShow(InAppMessageParams.InAppMessageCategoryId.TRANSACTIONAL) + .build(); + + billingClient.showInAppMessages(activity, + inAppMessageParams, + inAppMessageResult -> { + if (inAppMessageResult.getResponseCode() + == InAppMessageResult.InAppMessageResponseCode.NO_ACTION_NEEDED) { + // The flow has finished and there is no action needed from developers. + } else if (inAppMessageResult.getResponseCode() + == InAppMessageResult.InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED) { + // The subscription status changed. For example, a subscription + // has been recovered from a suspend state. Developers should + // expect the purchase token to be returned with this response + // code and use the purchase token with the Google Play + // Developer API. + queryPurchases(); // Re-query purchases to update UI + } + }); + // [END android_playbilling_inappmessaging_java] + } + + + + + // A placeholder method for handlePurchase since it's referenced in multiple snippets. + private void handlePurchase(Purchase purchase) { + if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + if (!purchase.isAcknowledged()) { + acknowledgePurchase(purchase); + } + } + } +} diff --git a/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/billing/BillingServiceClient.java b/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/billing/BillingServiceClient.java new file mode 100644 index 00000000..f81321da --- /dev/null +++ b/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/billing/BillingServiceClient.java @@ -0,0 +1,160 @@ +/* Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.play.billing.samples.managedcatalogue.billing; + +import android.app.Activity; +import android.util.Log; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AppCompatActivity; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClient.BillingResponseCode; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.PendingPurchasesParams; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.ProductDetailsResponseListener; +import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryProductDetailsParams.Product; +import com.android.billingclient.api.QueryProductDetailsResult; +import com.google.common.collect.ImmutableList; +import java.util.List; + +/** + * Manages interactions with the Google Play Billing Library for handling pre-orders. + * + *

This class encapsulates the setup of the {@link BillingClient}, manages the connection + * lifecycle (including retries), queries product details, and notifies a listener. + */ +public class BillingServiceClient { + + private static final String TAG = "BillingServiceClient"; + private BillingClient billingClient; + private final AppCompatActivity activity; + private final BillingServiceClientListener listener; + + public BillingServiceClient(AppCompatActivity activity, BillingServiceClientListener listener) { + this.activity = activity; + this.listener = listener; + billingClient = createBillingClient(); + } + + // Constructor for testing + @VisibleForTesting + BillingServiceClient( + AppCompatActivity activity, + BillingServiceClientListener listener, + BillingClient billingClient) { + this.activity = activity; + this.listener = listener; + this.billingClient = billingClient; + } + + /** + * Starts the billing connection with Google Play. This method should be called exactly once + * before any other methods in this class. + * + * @param productList The list of products to query for after the connection is established. + */ + public void startBillingConnection(List productList) { + billingClient.startConnection( + new BillingClientStateListener() { + @Override + public void onBillingSetupFinished(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingResponseCode.OK) { + Log.d(TAG, "Billing Client Connection Successful"); + queryProductDetails(productList); + } else { + Log.e(TAG, "Billing Client Connection Failed: " + billingResult.getDebugMessage()); + listener.onBillingSetupFailed(billingResult); // Propagate the error to the listener to show a message to the user. + } + } + + @Override + public void onBillingServiceDisconnected() { + Log.e(TAG, "Billing Client Connection Lost"); + listener.onBillingError("Billing Connection Lost"); + } + }); + } + + /** + * Launches the billing flow for the product with the given offer token. + * + * @param activity The activity instance from which the billing flow will be launched. + * @param productDetails The product details of the product to purchase. + * @param offerToken The offer token of the product to purchase. + * @return The result of the billing flow. + */ + public void launchPurchase(Activity activity, ProductDetails productDetails, String offerToken) { + ImmutableList productDetailsParamsList = + ImmutableList.of( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productDetails) + .setOfferToken(offerToken) + .build()); + BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() + .setProductDetailsParamsList(productDetailsParamsList) + .build(); + billingClient.launchBillingFlow(activity, billingFlowParams); +} + + /** + * Ends the billing connection with Google Play. This method should be called when the app is + * closed. + */ + public void endBillingConnection() { + if (billingClient != null && billingClient.isReady()) { + billingClient.endConnection(); + billingClient = null; + } + } + + private BillingClient createBillingClient() { + return BillingClient.newBuilder(activity) + .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) + // For one-time products, add a listener to acknowledge the purchases. This will notify + // Google the purchase was processed. + // For client-only apps, use billingClient.acknowledgePurchase(). + // If you have a secure backend, you must acknowledge purchases on your server using the + // server-side API. + // See https://developer.android.com/google/play/billing/security#acknowledge + .setListener((billingResult, purchases) -> {}) + .enableAutoServiceReconnection() + .build(); + } + + private void queryProductDetails(List productList) { + QueryProductDetailsParams queryProductDetailsParams = + QueryProductDetailsParams.newBuilder().setProductList(productList).build(); + + billingClient.queryProductDetailsAsync( + queryProductDetailsParams, + new ProductDetailsResponseListener() { + @Override + public void onProductDetailsResponse( + BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) { + if (billingResult.getResponseCode() == BillingResponseCode.OK) { + List productDetailsList = + productDetailsResponse.getProductDetailsList(); + listener.onProductDetailsResponse(productDetailsList); + } else { + Log.e(TAG, "QueryProductDetailsAsync Failed: " + billingResult.getDebugMessage()); + listener.onBillingError("Query Products Failed: " + billingResult.getResponseCode()); + } + } + }); + } +} diff --git a/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/billing/BillingServiceClientListener.java b/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/billing/BillingServiceClientListener.java new file mode 100644 index 00000000..8c228584 --- /dev/null +++ b/play-billing-library/app/src/main/java/com/google/play/billing/samples/managedcatalogue/billing/BillingServiceClientListener.java @@ -0,0 +1,28 @@ +/* Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.play.billing.samples.managedcatalogue.billing; + +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.ProductDetails; +import java.util.List; + +/** Listener interface for handling responses from the BillingServiceClient. */ +public interface BillingServiceClientListener { + void onProductDetailsResponse(List productDetailsList); + + void onBillingSetupFailed(BillingResult billingResult); + + void onBillingError(String errorMsg); +} diff --git a/play-billing-library/build.gradle b/play-billing-library/build.gradle new file mode 100644 index 00000000..a602bdf8 --- /dev/null +++ b/play-billing-library/build.gradle @@ -0,0 +1,45 @@ +/* Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:8.9.1" + + classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.1' + + // OSS plugin + classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/play-billing-library/gradle.properties b/play-billing-library/gradle.properties new file mode 100644 index 00000000..2a19ff5b --- /dev/null +++ b/play-billing-library/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + diff --git a/play-billing-library/gradle/wrapper/gradle-wrapper.jar b/play-billing-library/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..1b33c55b Binary files /dev/null and b/play-billing-library/gradle/wrapper/gradle-wrapper.jar differ diff --git a/play-billing-library/gradle/wrapper/gradle-wrapper.properties b/play-billing-library/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..3106b43b --- /dev/null +++ b/play-billing-library/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +#Fri Aug 14 10:59:44 CDT 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists