()
113 | {
114 | public PurchaseInfo createFromParcel(Parcel source)
115 | {
116 | return new PurchaseInfo(source);
117 | }
118 |
119 | public PurchaseInfo[] newArray(int size)
120 | {
121 | return new PurchaseInfo[size];
122 | }
123 | };
124 |
125 | @Override
126 | public boolean equals(Object o)
127 | {
128 | if (this == o)
129 | {
130 | return true;
131 | }
132 | if (o == null || !(o instanceof PurchaseInfo))
133 | {
134 | return false;
135 | }
136 | PurchaseInfo other = (PurchaseInfo) o;
137 | return responseData.equals(other.responseData)
138 | && signature.equals(other.signature)
139 | && developerPayload.equals(other.developerPayload)
140 | && purchaseData.purchaseToken.equals(other.purchaseData.purchaseToken)
141 | && purchaseData.purchaseTime.equals(other.purchaseData.purchaseTime);
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/library/src/main/java/com/anjlab/android/iab/v3/PurchaseState.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 AnjLab
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.anjlab.android.iab.v3;
17 |
18 | public enum PurchaseState
19 | {
20 | PurchasedSuccessfully,
21 | Canceled,
22 | Refunded,
23 | SubscriptionExpired
24 | }
25 |
--------------------------------------------------------------------------------
/library/src/main/java/com/anjlab/android/iab/v3/Security.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2012 Google Inc.
2 | *
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | package com.anjlab.android.iab.v3;
17 |
18 | import android.text.TextUtils;
19 | import android.util.Base64;
20 | import android.util.Log;
21 |
22 | import java.security.InvalidKeyException;
23 | import java.security.KeyFactory;
24 | import java.security.NoSuchAlgorithmException;
25 | import java.security.PublicKey;
26 | import java.security.Signature;
27 | import java.security.SignatureException;
28 | import java.security.spec.InvalidKeySpecException;
29 | import java.security.spec.X509EncodedKeySpec;
30 |
31 | /**
32 | * Security-related methods. For a secure implementation, all of this code
33 | * should be implemented on a server that communicates with the
34 | * application on the device. For the sake of simplicity and clarity of this
35 | * example, this code is included here and is executed on the device. If you
36 | * must verify the purchases on the phone, you should obfuscate this code to
37 | * make it harder for an attacker to replace the code with stubs that treat all
38 | * purchases as verified.
39 | */
40 | class Security
41 | {
42 | private static final String TAG = "IABUtil/Security";
43 |
44 | private static final String KEY_FACTORY_ALGORITHM = "RSA";
45 | private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
46 |
47 | /**
48 | * Verifies that the data was signed with the given signature, and returns
49 | * the verified purchase. The data is in JSON format and signed
50 | * with a private key. The data also contains the {@link PurchaseState}
51 | * and product ID of the purchase.
52 | *
53 | * @param productId the product Id used for debug validation.
54 | * @param base64PublicKey the base64-encoded public key to use for verifying.
55 | * @param signedData the signed JSON string (signed, not encrypted)
56 | * @param signature the signature for the data, signed with the private key
57 | */
58 | public static boolean verifyPurchase(String productId, String base64PublicKey,
59 | String signedData, String signature)
60 | {
61 | if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
62 | TextUtils.isEmpty(signature))
63 | {
64 |
65 | if (
66 | productId.equals("android.test.purchased") ||
67 | productId.equals("android.test.canceled") ||
68 | productId.equals("android.test.refunded") ||
69 | productId.equals("android.test.item_unavailable")
70 | )
71 | {
72 | return true;
73 | }
74 |
75 | Log.e(TAG, "Purchase verification failed: missing data.");
76 | return false;
77 | }
78 |
79 | PublicKey key = Security.generatePublicKey(base64PublicKey);
80 | return Security.verify(key, signedData, signature);
81 | }
82 |
83 | /**
84 | * Generates a PublicKey instance from a string containing the
85 | * Base64-encoded public key.
86 | *
87 | * @param encodedPublicKey Base64-encoded public key
88 | * @throws IllegalArgumentException if encodedPublicKey is invalid
89 | */
90 | public static PublicKey generatePublicKey(String encodedPublicKey)
91 | {
92 | try
93 | {
94 | byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
95 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
96 | return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
97 | }
98 | catch (NoSuchAlgorithmException e)
99 | {
100 | throw new RuntimeException(e);
101 | }
102 | catch (InvalidKeySpecException e)
103 | {
104 | Log.e(TAG, "Invalid key specification.");
105 | throw new IllegalArgumentException(e);
106 | }
107 | catch (IllegalArgumentException e)
108 | {
109 | Log.e(TAG, "Base64 decoding failed.");
110 | throw e;
111 | }
112 | }
113 |
114 | /**
115 | * Verifies that the signature from the server matches the computed
116 | * signature on the data. Returns true if the data is correctly signed.
117 | *
118 | * @param publicKey public key associated with the developer account
119 | * @param signedData signed data from server
120 | * @param signature server signature
121 | * @return true if the data and signature match
122 | */
123 | public static boolean verify(PublicKey publicKey, String signedData, String signature)
124 | {
125 | Signature sig;
126 | try
127 | {
128 | sig = Signature.getInstance(SIGNATURE_ALGORITHM);
129 | sig.initVerify(publicKey);
130 | sig.update(signedData.getBytes());
131 | if (!sig.verify(Base64.decode(signature, Base64.DEFAULT)))
132 | {
133 | Log.e(TAG, "Signature verification failed.");
134 | return false;
135 | }
136 | return true;
137 | }
138 | catch (NoSuchAlgorithmException e)
139 | {
140 | Log.e(TAG, "NoSuchAlgorithmException.");
141 | }
142 | catch (InvalidKeyException e)
143 | {
144 | Log.e(TAG, "Invalid key specification.");
145 | }
146 | catch (SignatureException e)
147 | {
148 | Log.e(TAG, "Signature exception.");
149 | }
150 | catch (IllegalArgumentException e)
151 | {
152 | Log.e(TAG, "Base64 decoding failed.");
153 | }
154 | return false;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/library/src/main/java/com/anjlab/android/iab/v3/SkuDetails.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 AnjLab
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.anjlab.android.iab.v3;
17 |
18 | import android.os.Parcel;
19 | import android.os.Parcelable;
20 | import android.text.TextUtils;
21 |
22 | import org.json.JSONException;
23 | import org.json.JSONObject;
24 |
25 | import java.util.Locale;
26 |
27 | public class SkuDetails implements Parcelable
28 | {
29 |
30 | public final String productId;
31 |
32 | public final String title;
33 |
34 | public final String description;
35 |
36 | public final boolean isSubscription;
37 |
38 | public final String currency;
39 |
40 | public final Double priceValue;
41 |
42 | public final String subscriptionPeriod;
43 |
44 | public final String subscriptionFreeTrialPeriod;
45 |
46 | public final boolean haveTrialPeriod;
47 |
48 | public final double introductoryPriceValue;
49 |
50 | public final String introductoryPricePeriod;
51 |
52 | public final boolean haveIntroductoryPeriod;
53 |
54 | public final int introductoryPriceCycles;
55 |
56 | /**
57 | * Use this value to return the raw price from the product.
58 | * This allows math to be performed without needing to worry about errors
59 | * caused by floating point representations of the product's price.
60 | *
61 | * This is in micros from the Play Store.
62 | */
63 | public final long priceLong;
64 |
65 | public final String priceText;
66 |
67 | public final long introductoryPriceLong;
68 |
69 | public final String introductoryPriceText;
70 |
71 | public final String responseData;
72 |
73 | public SkuDetails(JSONObject source) throws JSONException
74 | {
75 | String responseType = source.optString(Constants.RESPONSE_TYPE);
76 | if (responseType == null)
77 | {
78 | responseType = Constants.PRODUCT_TYPE_MANAGED;
79 | }
80 | productId = source.optString(Constants.RESPONSE_PRODUCT_ID);
81 | title = source.optString(Constants.RESPONSE_TITLE);
82 | description = source.optString(Constants.RESPONSE_DESCRIPTION);
83 | isSubscription = responseType.equalsIgnoreCase(Constants.PRODUCT_TYPE_SUBSCRIPTION);
84 | currency = source.optString(Constants.RESPONSE_PRICE_CURRENCY);
85 | priceLong = source.optLong(Constants.RESPONSE_PRICE_MICROS);
86 | priceValue = priceLong / 1000000d;
87 | priceText = source.optString(Constants.RESPONSE_PRICE);
88 | subscriptionPeriod = source.optString(Constants.RESPONSE_SUBSCRIPTION_PERIOD);
89 | subscriptionFreeTrialPeriod = source.optString(Constants.RESPONSE_FREE_TRIAL_PERIOD);
90 | haveTrialPeriod = !TextUtils.isEmpty(subscriptionFreeTrialPeriod);
91 | introductoryPriceLong = source.optLong(Constants.RESPONSE_INTRODUCTORY_PRICE_MICROS);
92 | introductoryPriceValue = introductoryPriceLong / 1000000d;
93 | introductoryPriceText = source.optString(Constants.RESPONSE_INTRODUCTORY_PRICE);
94 | introductoryPricePeriod = source.optString(Constants.RESPONSE_INTRODUCTORY_PRICE_PERIOD);
95 | haveIntroductoryPeriod = !TextUtils.isEmpty(introductoryPricePeriod);
96 | introductoryPriceCycles = source.optInt(Constants.RESPONSE_INTRODUCTORY_PRICE_CYCLES);
97 | responseData = source.toString();
98 | }
99 |
100 | @Override
101 | public String toString()
102 | {
103 | return String.format(Locale.US, "%s: %s(%s) %f in %s (%s)",
104 | productId,
105 | title,
106 | description,
107 | priceValue,
108 | currency,
109 | priceText);
110 | }
111 |
112 | @Override
113 | public boolean equals(Object o)
114 | {
115 | if (this == o)
116 | {
117 | return true;
118 | }
119 | if (o == null || getClass() != o.getClass())
120 | {
121 | return false;
122 | }
123 |
124 | SkuDetails that = (SkuDetails) o;
125 |
126 | if (isSubscription != that.isSubscription)
127 | {
128 | return false;
129 | }
130 | return !(productId != null ? !productId.equals(that.productId) : that.productId != null);
131 | }
132 |
133 | @Override
134 | public int hashCode()
135 | {
136 | int result = productId != null ? productId.hashCode() : 0;
137 | result = 31 * result + (isSubscription ? 1 : 0);
138 | return result;
139 | }
140 |
141 | @Override
142 | public int describeContents()
143 | {
144 | return 0;
145 | }
146 |
147 | @Override
148 | public void writeToParcel(Parcel dest, int flags)
149 | {
150 | dest.writeString(this.productId);
151 | dest.writeString(this.title);
152 | dest.writeString(this.description);
153 | dest.writeByte(isSubscription ? (byte) 1 : (byte) 0);
154 | dest.writeString(this.currency);
155 | dest.writeDouble(this.priceValue);
156 | dest.writeLong(this.priceLong);
157 | dest.writeString(this.priceText);
158 | dest.writeString(this.subscriptionPeriod);
159 | dest.writeString(this.subscriptionFreeTrialPeriod);
160 | dest.writeByte(this.haveTrialPeriod ? (byte) 1 : (byte) 0);
161 | dest.writeDouble(this.introductoryPriceValue);
162 | dest.writeLong(this.introductoryPriceLong);
163 | dest.writeString(this.introductoryPriceText);
164 | dest.writeString(this.introductoryPricePeriod);
165 | dest.writeByte(this.haveIntroductoryPeriod ? (byte) 1 : (byte) 0);
166 | dest.writeInt(this.introductoryPriceCycles);
167 | dest.writeString(this.responseData);
168 | }
169 |
170 | protected SkuDetails(Parcel in)
171 | {
172 | this.productId = in.readString();
173 | this.title = in.readString();
174 | this.description = in.readString();
175 | this.isSubscription = in.readByte() != 0;
176 | this.currency = in.readString();
177 | this.priceValue = in.readDouble();
178 | this.priceLong = in.readLong();
179 | this.priceText = in.readString();
180 | this.subscriptionPeriod = in.readString();
181 | this.subscriptionFreeTrialPeriod = in.readString();
182 | this.haveTrialPeriod = in.readByte() != 0;
183 | this.introductoryPriceValue = in.readDouble();
184 | this.introductoryPriceLong = in.readLong();
185 | this.introductoryPriceText = in.readString();
186 | this.introductoryPricePeriod = in.readString();
187 | this.haveIntroductoryPeriod = in.readByte() != 0;
188 | this.introductoryPriceCycles = in.readInt();
189 | this.responseData = in.readString();
190 | }
191 |
192 | public static final Parcelable.Creator CREATOR =
193 | new Parcelable.Creator()
194 | {
195 | public SkuDetails createFromParcel(Parcel source)
196 | {
197 | return new SkuDetails(source);
198 | }
199 |
200 | public SkuDetails[] newArray(int size)
201 | {
202 | return new SkuDetails[size];
203 | }
204 | };
205 | }
206 |
--------------------------------------------------------------------------------
/sample/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
3 | }
4 | apply plugin: 'com.android.application'
5 |
6 | dependencies {
7 | implementation project(':library')
8 | // implementation fileTree(dir: 'libs', include: '*.jar')
9 | // implementation 'com.anjlab.android.iab.v3:library:2.1.0'
10 | implementation 'androidx.annotation:annotation:1.3.0'
11 | }
12 |
13 | android {
14 | namespace 'com.anjlab.android.iab.v3.sample2'
15 |
16 | defaultConfig {
17 | versionCode 6
18 | versionName '6.0'
19 | minSdkVersion 21
20 | targetSdkVersion 34
21 | compileSdk = 34
22 | buildToolsVersion = '30.0.3'
23 | }
24 |
25 | sourceSets.main {
26 | manifest.srcFile 'AndroidManifest.xml'
27 | java.srcDir 'src'
28 | resources.srcDir 'src'
29 | res.srcDir 'res'
30 | }
31 |
32 | signingConfigs {
33 | release
34 | }
35 |
36 | buildTypes {
37 | release {
38 | signingConfig signingConfigs.release
39 | }
40 | }
41 |
42 | if (project.hasProperty('keyStoreFile')) {
43 | android.signingConfigs.release.storeFile = file(keyStoreFile)
44 | }
45 |
46 | if (project.hasProperty('keyStorePassword')) {
47 | android.signingConfigs.release.storePassword = keyStorePassword
48 | }
49 |
50 | if (project.hasProperty('keyStoreKeyAlias')) {
51 | android.signingConfigs.release.keyAlias = keyStoreKeyAlias
52 | }
53 |
54 | if (project.hasProperty('keyStoreKeyPassword')) {
55 | android.signingConfigs.release.keyPassword = keyStoreKeyPassword
56 | }
57 | }
--------------------------------------------------------------------------------
/sample/libs/anjlab-iabv3-current.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anjlab/android-inapp-billing-v3/a13bb215b1176eac9d1d78322262de2bd237303e/sample/libs/anjlab-iabv3-current.jar
--------------------------------------------------------------------------------
/sample/res/drawable-anydpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anjlab/android-inapp-billing-v3/a13bb215b1176eac9d1d78322262de2bd237303e/sample/res/drawable-anydpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
20 |
21 |
29 |
30 |
36 |
37 |
43 |
44 |
50 |
51 |
58 |
59 |
66 |
67 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
96 |
97 |
104 |
105 |
112 |
113 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/sample/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | In-app Billing v3 Sample
5 | wait…
6 | Sample Activity #%d
7 |
8 |
9 |
--------------------------------------------------------------------------------
/sample/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sample/src/com/anjlab/android/iab/v3/sample2/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 AnjLab
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | */
16 | package com.anjlab.android.iab.v3.sample2;
17 |
18 | import com.anjlab.android.iab.v3.BillingProcessor;
19 | import com.anjlab.android.iab.v3.PurchaseInfo;
20 | import com.anjlab.android.iab.v3.SkuDetails;
21 |
22 | import java.util.List;
23 |
24 | import android.app.Activity;
25 | import android.content.Intent;
26 | import android.os.Bundle;
27 | import android.util.Log;
28 | import android.view.View;
29 | import android.widget.TextView;
30 | import android.widget.Toast;
31 | import androidx.annotation.NonNull;
32 | import androidx.annotation.Nullable;
33 |
34 | public class MainActivity extends Activity {
35 | // SAMPLE APP CONSTANTS
36 | private static final String ACTIVITY_NUMBER = "activity_num";
37 | private static final String LOG_TAG = "iabv3";
38 |
39 | // PRODUCT & SUBSCRIPTION IDS
40 | private static final String PRODUCT_ID = "com.anjlab.test.iab.s2.p5";
41 | private static final String SUBSCRIPTION_ID = "com.anjlab.test.iab.subs1";
42 | private static final String LICENSE_KEY = BuildConfig.licenceKey; // PUT YOUR MERCHANT KEY HERE;
43 | // put your Google merchant id here (as stated in public profile of your Payments Merchant Center)
44 | // if filled library will provide protection against Freedom alike Play Market simulators
45 | private static final String MERCHANT_ID=null;
46 |
47 | private BillingProcessor bp;
48 | private boolean readyToPurchase = false;
49 |
50 |
51 | @Override
52 | protected void onCreate(Bundle savedInstanceState) {
53 | super.onCreate(savedInstanceState);
54 | setContentView(R.layout.activity_main);
55 |
56 | TextView title = findViewById(R.id.titleTextView);
57 | title.setText(String.format(getString(R.string.title), getIntent().getIntExtra(ACTIVITY_NUMBER, 1)));
58 |
59 | if(!BillingProcessor.isIabServiceAvailable(this)) {
60 | showToast("In-app billing service is unavailable, please upgrade Android Market/Play to version >= 3.9.16");
61 | }
62 |
63 | bp = new BillingProcessor(this, LICENSE_KEY, MERCHANT_ID, new BillingProcessor.IBillingHandler() {
64 | @Override
65 | public void onProductPurchased(@NonNull String productId, @Nullable PurchaseInfo purchaseInfo) {
66 | showToast("onProductPurchased: " + productId);
67 | updateTextViews();
68 | }
69 | @Override
70 | public void onBillingError(int errorCode, @Nullable Throwable error) {
71 | showToast("onBillingError: " + errorCode);
72 | }
73 | @Override
74 | public void onBillingInitialized() {
75 | showToast("onBillingInitialized");
76 | readyToPurchase = true;
77 | updateTextViews();
78 | }
79 | @Override
80 | public void onPurchaseHistoryRestored() {
81 | showToast("onPurchaseHistoryRestored");
82 | for(String sku : bp.listOwnedProducts())
83 | Log.d(LOG_TAG, "Owned Managed Product: " + sku);
84 | for(String sku : bp.listOwnedSubscriptions())
85 | Log.d(LOG_TAG, "Owned Subscription: " + sku);
86 | updateTextViews();
87 | }
88 | });
89 | }
90 |
91 | @Override
92 | protected void onResume() {
93 | super.onResume();
94 |
95 | updateTextViews();
96 | }
97 |
98 | @Override
99 | public void onDestroy() {
100 | if (bp != null)
101 | bp.release();
102 | super.onDestroy();
103 | }
104 |
105 | private void updateTextViews() {
106 | TextView text = findViewById(R.id.productIdTextView);
107 | text.setText(String.format("%s is%s purchased", PRODUCT_ID, bp.isPurchased(PRODUCT_ID) ? "" : " not"));
108 | text = findViewById(R.id.subscriptionIdTextView);
109 | text.setText(String.format("%s is%s subscribed", SUBSCRIPTION_ID, bp.isSubscribed(SUBSCRIPTION_ID) ? "" : " not"));
110 | }
111 |
112 | private void showToast(String message) {
113 | Toast.makeText(this, message, Toast.LENGTH_LONG).show();
114 | }
115 |
116 | public void onClick(View v) {
117 | if (!readyToPurchase) {
118 | showToast("Billing not initialized.");
119 | return;
120 | }
121 | if (v.getId() == R.id.purchaseButton) {
122 | bp.purchase(this,PRODUCT_ID);
123 | } else if (v.getId() == R.id.consumeButton) {
124 | bp.consumePurchaseAsync(PRODUCT_ID, new BillingProcessor.IPurchasesResponseListener()
125 | {
126 | @Override
127 | public void onPurchasesSuccess()
128 | {
129 | showToast("Successfully consumed");
130 | updateTextViews();
131 | }
132 |
133 | @Override
134 | public void onPurchasesError()
135 | {
136 | showToast("Not consumed");
137 | }
138 | });
139 | } else if (v.getId() == R.id.productDetailsButton) {
140 | bp.getPurchaseListingDetailsAsync(PRODUCT_ID, new BillingProcessor.ISkuDetailsResponseListener() {
141 | @Override
142 | public void onSkuDetailsResponse(@Nullable List products) {
143 | if (products != null && !products.isEmpty()) {
144 | showToast(products.get(0).toString());
145 | } else {
146 | showToast("Failed to load SKU details");
147 | }
148 | }
149 |
150 | @Override
151 | public void onSkuDetailsError(String error) {
152 | showToast(error);
153 | }
154 | });
155 | } else if (v.getId() == R.id.subscribeButton) {
156 | bp.subscribe(this,SUBSCRIPTION_ID);
157 | } else if (v.getId() == R.id.updateSubscriptionsButton) {
158 | bp.loadOwnedPurchasesFromGoogleAsync(new BillingProcessor.IPurchasesResponseListener() {
159 | @Override
160 | public void onPurchasesSuccess()
161 | {
162 | showToast("Subscriptions updated.");
163 | updateTextViews();
164 | }
165 |
166 | @Override
167 | public void onPurchasesError()
168 | {
169 | showToast("Subscriptions update eroor.");
170 | updateTextViews();
171 | }
172 | });
173 | } else if (v.getId() == R.id.subsDetailsButton) {
174 | bp.getSubscriptionListingDetailsAsync(SUBSCRIPTION_ID, new BillingProcessor.ISkuDetailsResponseListener()
175 | {
176 | @Override
177 | public void onSkuDetailsResponse(@Nullable final List products) {
178 | showToast(products != null ? products.toString() : "Failed to load subscription details");
179 | }
180 |
181 | @Override
182 | public void onSkuDetailsError(String string) {
183 | showToast(string);
184 | }
185 | });
186 | } else if (v.getId() == R.id.launchMoreButton)
187 | {
188 | Intent intent = new Intent(this, MainActivity.class);
189 | intent.putExtra(ACTIVITY_NUMBER, getIntent().getIntExtra(ACTIVITY_NUMBER, 1) + 1);
190 | startActivity(intent);
191 | }
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'sample', 'library'
2 |
--------------------------------------------------------------------------------