├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
└── src
└── main
├── AndroidManifest.xml
└── java
└── billing
├── BillingManager.kt
└── BillingModel.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/dictionaries
41 | .idea/libraries
42 |
43 | # Keystore files
44 | *.jks
45 |
46 | # External native build folder generated in Android Studio 2.2 and later
47 | .externalNativeBuild
48 |
49 | # Google Services (e.g. APIs or Firebase)
50 | google-services.json
51 |
52 | # Freeline
53 | freeline.py
54 | freeline/
55 | freeline_project_description.json
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Vladimir Berezkin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # billing-android
2 | RxJava wrapper for Android Play Billing Library
3 | ## Usage
4 | Make sure that your root project build.gradle file has this section
5 |
6 | ext {
7 | compileSdkVersion = 28
8 | androidBillingLibraryVersion = ‘4.0.0’
9 | rxJavaVersion = ‘2.2.9’
10 | }
11 |
12 | Extend [BillingManager](src/main/java/billing/BillingManager.kt) with your payment requests
13 |
14 | fun BillingManager.startBuyingCoins(activity: Activity) {
15 | initiatePurchaseFlow(activity, "buy_coins", BillingClient.SkuType.INAPP)
16 | }
17 |
18 | Instantiate [BillingModel](src/main/java/billing/BillingModel.kt) once as singleton or via Dagger2.
19 | In every Android component where you are going to use billing, add the next lines:
20 |
21 | @Inject
22 | BillingModel billingModel;
23 | private Disposable billingSubscription;
24 |
25 | @Override
26 | public void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | ...
29 | billingSubscription = billingModel.purchases.subscribe(purchases -> {
30 | // Here our purchases come
31 | });
32 | ...
33 | }
34 |
35 | @Override
36 | public void onDestroy() {
37 | ...
38 | billingSubscription.dispose();
39 | super.onDestroy();
40 | }
41 |
42 | To perform a purchaise, just add
43 |
44 | BillingManager.startBuyingCoins(this)
45 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion rootProject.ext.compileSdkVersion
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | }
11 | }
12 |
13 | dependencies {
14 | implementation "com.android.billingclient:billing:$rootProject.ext.androidBillingLibraryVersion"
15 | implementation "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxJavaVersion"
16 |
17 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
18 | }
19 | repositories {
20 | mavenCentral()
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main/java/billing/BillingManager.kt:
--------------------------------------------------------------------------------
1 | package billing
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.util.Log
6 | import com.android.billingclient.api.*
7 | import io.reactivex.functions.Consumer
8 |
9 | /**
10 | * Created by berezkin on 30/06/17.
11 | */
12 |
13 | private val TAG = BillingManager::class.java.simpleName
14 |
15 | class BillingManager(context: Context, private val purchaseConsumer: Consumer>) : PurchasesUpdatedListener {
16 | private val billingClient: BillingClient = BillingClient.newBuilder(context).enablePendingPurchases().setListener(this).build()
17 | private var isServiceConnected = false
18 |
19 | init {
20 | startServiceConnection(Runnable {
21 | queryPurchases()
22 | })
23 | }
24 |
25 | override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList?) {
26 | if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
27 | purchaseConsumer.accept(purchases)
28 | }
29 | }
30 |
31 | fun destroy() {
32 | if (billingClient.isReady) {
33 | billingClient.endConnection()
34 | }
35 | }
36 |
37 | fun consumeAsync(purchaseToken: String, listener: ConsumeResponseListener) {
38 | val consumeRequest = Runnable {
39 | val params = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build()
40 | billingClient.consumeAsync(params) { purchaseToken_, resultCode ->
41 | Log.d(TAG, "consumeAsync $resultCode")
42 | listener.onConsumeResponse(purchaseToken_, resultCode)
43 | }
44 | }
45 | executeServiceRequest(consumeRequest)
46 | }
47 |
48 | private fun startServiceConnection(executeOnSuccess: Runnable?) {
49 | billingClient.startConnection(object : BillingClientStateListener {
50 | override fun onBillingServiceDisconnected() {
51 | isServiceConnected = false
52 | }
53 |
54 | override fun onBillingSetupFinished(result: BillingResult) {
55 | if (result.responseCode == BillingClient.BillingResponseCode.OK) {
56 | isServiceConnected = true
57 | executeOnSuccess?.run()
58 | }
59 | }
60 | })
61 | }
62 |
63 | private fun executeServiceRequest(runnable: Runnable) {
64 | if (isServiceConnected) {
65 | runnable.run()
66 | } else {
67 | startServiceConnection(runnable)
68 | }
69 | }
70 |
71 | private fun queryPurchases() {
72 | executeServiceRequest(Runnable {
73 | val time = System.currentTimeMillis()
74 | billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, object : PurchasesResponseListener {
75 | override fun onQueryPurchasesResponse(
76 | purchasesResult: BillingResult,
77 | purchasesList: MutableList
78 | ) {
79 | Log.d(TAG, "Querying purchases elapsed time: ${System.currentTimeMillis() - time} ms")
80 | onPurchasesUpdated(purchasesResult, purchasesList)
81 | }
82 | })
83 | })
84 | }
85 |
86 | fun initiatePurchaseFlow(activity: Activity, skuId: String, @BillingClient.SkuType billingType: String) {
87 | val purchaseFlowRequest = Runnable {
88 | Log.d(TAG, "Launching in-app purchase flow")
89 | val skuDetails = SkuDetailsParams.newBuilder().setSkusList(listOf(skuId)).setType(billingType).build()
90 | billingClient.querySkuDetailsAsync(skuDetails, object : SkuDetailsResponseListener {
91 | override fun onSkuDetailsResponse(result: BillingResult, details: MutableList?) {
92 | details?.firstOrNull()?.let {
93 | val purchaseParams = BillingFlowParams.newBuilder().setSkuDetails(it).build()
94 | billingClient.launchBillingFlow(activity, purchaseParams)
95 | }
96 | }
97 | })
98 | }
99 | executeServiceRequest(purchaseFlowRequest)
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/billing/BillingModel.kt:
--------------------------------------------------------------------------------
1 | package billing
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import com.android.billingclient.api.Purchase
6 | import io.reactivex.Observable
7 | import io.reactivex.disposables.Disposable
8 | import io.reactivex.functions.Consumer
9 |
10 | /**
11 | * Created by berezkin on 01/07/17.
12 | */
13 | private val TAG = BillingModel::class.java.simpleName
14 |
15 | open class BillingModel(private val context: Context) {
16 |
17 | private val billingObservable = Observable.create> { emitter ->
18 | Log.d(TAG, "subscribed")
19 | billingManager = BillingManager(context, Consumer {
20 | if (purchasesCache != it) {
21 | purchasesCache = it
22 | emitter.onNext(it)
23 | }
24 | })
25 | emitter.setCancellable{
26 | Log.d(TAG, "unsubscribed")
27 | billingManager?.destroy()
28 | billingManager = null
29 | }
30 | }.share()
31 |
32 | val purchases = billingObservable.startWith(Observable.create { em ->
33 | purchasesCache?.let { em.onNext(it) }
34 | em.onComplete()
35 | })
36 |
37 | var billingManager: BillingManager? = null
38 | private var purchasesCache: List? = null
39 | }
40 |
--------------------------------------------------------------------------------