sjbillingclient 0.1.4__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sjbillingclient-0.1.4/README.md → sjbillingclient-0.2.1/PKG-INFO +216 -25
- sjbillingclient-0.1.4/PKG-INFO → sjbillingclient-0.2.1/README.md +193 -42
- sjbillingclient-0.2.1/pyproject.toml +24 -0
- sjbillingclient-0.2.1/sjbillingclient/jclass/purchase.py +16 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/queryproduct.py +9 -1
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/product.py +4 -3
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/tools/__init__.py +37 -3
- sjbillingclient-0.1.4/pyproject.toml +0 -16
- sjbillingclient-0.1.4/sjbillingclient/jclass/purchase.py +0 -8
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/__init__.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/__init__.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/acknowledge.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/billing.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/consume.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/__init__.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/acknowledge.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/billing.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/consume.py +0 -0
- {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/purchases.py +0 -0
@@ -1,3 +1,25 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: sjbillingclient
|
3
|
+
Version: 0.2.1
|
4
|
+
Summary: A Python wrapper for the Google Play Billing Library that facilitates in-app purchases and subscriptions in Android applications
|
5
|
+
Home-page: https://github.com/SimpleJnius/sj-android-billingclient
|
6
|
+
Author: Kenechukwu Akubue
|
7
|
+
Author-email: kengoon19@gmail.com
|
8
|
+
Requires-Python: >=3.9,<4.0
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
14
|
+
Requires-Dist: poetry-core (>=2.1.3,<3.0.0)
|
15
|
+
Requires-Dist: pyjnius (>=1.6.1,<2.0.0)
|
16
|
+
Project-URL: Bug Tracker, https://github.com/SimpleJnius/sj-android-billingclient/issues
|
17
|
+
Project-URL: Documentation, https://github.com/SimpleJnius/sj-android-billingclient/blob/master/README.md
|
18
|
+
Project-URL: Pull Requests, https://github.com/SimpleJnius/sj-android-billingclient/pulls
|
19
|
+
Project-URL: Repository, https://github.com/SimpleJnius/sj-android-billingclient.git
|
20
|
+
Project-URL: Source, https://github.com/SimpleJnius/sj-android-billingclient
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
|
1
23
|
# SJBillingClient (Google Play Billing SDK for Python)
|
2
24
|
|
3
25
|
<!-- GitAds-Verify: 71CCWMQSMVD67LS4WF4N44EXISSL2UTQ -->
|
@@ -20,7 +42,7 @@ SJBillingClient is a Python wrapper for the Google Play Billing Library that fac
|
|
20
42
|
|
21
43
|
- Python 3.9+
|
22
44
|
- pyjnius 1.6.1+
|
23
|
-
- Android application with Google Play Billing Library (version
|
45
|
+
- Android application with Google Play Billing Library (version 8.0.0 recommended)
|
24
46
|
|
25
47
|
## Installation
|
26
48
|
|
@@ -30,7 +52,7 @@ pip install sjbillingclient
|
|
30
52
|
|
31
53
|
# In Buildozer (add to buildozer.spec)
|
32
54
|
requirements = sjbillingclient
|
33
|
-
android.gradle_dependencies = com.android.billingclient:billing:
|
55
|
+
android.gradle_dependencies = com.android.billingclient:billing:8.0.0
|
34
56
|
```
|
35
57
|
|
36
58
|
## Quick Start
|
@@ -154,14 +176,28 @@ from kivy.uix.screenmanager import ScreenManager, Screen
|
|
154
176
|
from sjbillingclient.jclass.billing import BillingResponseCode, ProductType
|
155
177
|
from sjbillingclient.tools import BillingClient
|
156
178
|
|
157
|
-
# Load the KV file
|
158
179
|
Builder.load_file(join(dirname(__file__), basename(__file__).split(".")[0] + ".kv"))
|
159
180
|
|
160
181
|
|
161
182
|
class HomeScreen(Screen):
|
183
|
+
"""
|
184
|
+
A screen that demonstrates Google Play Billing integration with Kivy.
|
185
|
+
|
186
|
+
This screen provides functionality to make in-app purchases and subscriptions
|
187
|
+
using the Google Play Billing Library through the SJBillingClient wrapper.
|
188
|
+
|
189
|
+
Attributes:
|
190
|
+
billing_client (BillingClient): The client used to interact with Google Play Billing.
|
191
|
+
"""
|
162
192
|
billing_client = None
|
163
193
|
|
164
|
-
def
|
194
|
+
def support(self):
|
195
|
+
"""
|
196
|
+
Initializes the billing client and starts a connection to the Google Play Billing service.
|
197
|
+
|
198
|
+
This method is called when the user wants to make a purchase or subscription.
|
199
|
+
If a billing client already exists, it ends the connection before creating a new one.
|
200
|
+
"""
|
165
201
|
if self.billing_client:
|
166
202
|
self.billing_client.end_connection()
|
167
203
|
|
@@ -172,6 +208,17 @@ class HomeScreen(Screen):
|
|
172
208
|
)
|
173
209
|
|
174
210
|
def on_purchases_updated(self, billing_result, null, purchases):
|
211
|
+
"""
|
212
|
+
Callback method that is called when purchases are updated.
|
213
|
+
|
214
|
+
This method handles the result of a purchase flow, either acknowledging
|
215
|
+
a subscription or consuming a one-time purchase.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
billing_result: The result of the billing operation.
|
219
|
+
null: Boolean indicating if the purchases list is null.
|
220
|
+
purchases: List of purchases that were updated.
|
221
|
+
"""
|
175
222
|
if billing_result.getResponseCode() == BillingResponseCode.OK and not null:
|
176
223
|
for purchase in purchases:
|
177
224
|
if self.ids.subscribe.active:
|
@@ -181,40 +228,96 @@ class HomeScreen(Screen):
|
|
181
228
|
)
|
182
229
|
else:
|
183
230
|
self.billing_client.consume_async(purchase, self.on_consume_response)
|
231
|
+
print(billing_result.getResponseCode(), billing_result.getDebugMessage())
|
184
232
|
|
185
233
|
def on_acknowledge_purchase_response(self, billing_result):
|
234
|
+
"""
|
235
|
+
Callback method that is called when a purchase acknowledgement is complete.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
billing_result: The result of the acknowledgement operation.
|
239
|
+
"""
|
186
240
|
print(billing_result.getDebugMessage())
|
187
241
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
188
242
|
self.toast("Thank you for subscribing to buy us a cup of coffee! monthly")
|
189
243
|
|
190
244
|
def on_consume_response(self, billing_result):
|
245
|
+
"""
|
246
|
+
Callback method that is called when a purchase consumption is complete.
|
247
|
+
|
248
|
+
Args:
|
249
|
+
billing_result: The result of the consumption operation.
|
250
|
+
"""
|
191
251
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
192
252
|
self.toast("Thank you for buying us a cup of coffee!")
|
193
253
|
|
194
|
-
def on_product_details_response(self, billing_result,
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
254
|
+
def on_product_details_response(self, billing_result, product_details_result):
|
255
|
+
"""
|
256
|
+
Callback method that is called when product details are retrieved.
|
257
|
+
|
258
|
+
This method processes the product details and launches the billing flow.
|
259
|
+
|
260
|
+
Args:
|
261
|
+
billing_result: The result of the product details query.
|
262
|
+
product_details_result: The result containing product details and unfetched products.
|
263
|
+
"""
|
264
|
+
product_details_list = product_details_result.getProductDetailsList()
|
265
|
+
unfetched_product_list = product_details_result.getUnfetchedProductList()
|
266
|
+
|
199
267
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
268
|
+
for product_details in product_details_list:
|
269
|
+
self.billing_client.get_product_details(
|
270
|
+
product_details,
|
271
|
+
ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP)
|
272
|
+
for unfetched_product in unfetched_product_list:
|
273
|
+
print(self.billing_client.get_unfetched_product(unfetched_product))
|
200
274
|
self.billing_client.launch_billing_flow(product_details=product_details_list)
|
201
275
|
|
202
276
|
def on_billing_setup_finished(self, billing_result):
|
277
|
+
"""
|
278
|
+
Callback method that is called when the billing setup is complete.
|
279
|
+
|
280
|
+
This method queries product details if the billing setup was successful.
|
281
|
+
|
282
|
+
Args:
|
283
|
+
billing_result: The result of the billing setup operation.
|
284
|
+
"""
|
203
285
|
product_id = self.ids.btn.product_id
|
204
286
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
205
287
|
self.billing_client.query_product_details_async(
|
206
|
-
product_type=ProductType.SUBS if self.ids.subscribe
|
288
|
+
product_type=ProductType.SUBS if self.ids.subscribe else ProductType.INAPP,
|
207
289
|
products_ids=[product_id],
|
208
290
|
on_product_details_response=self.on_product_details_response,
|
209
291
|
)
|
210
292
|
|
211
293
|
def toast(self, message):
|
294
|
+
"""
|
295
|
+
Display a toast message.
|
296
|
+
|
297
|
+
This is a simple implementation that just prints the message.
|
298
|
+
In a real app, you would use platform-specific toast functionality.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
message: The message to display.
|
302
|
+
"""
|
212
303
|
# Implementation of toast message (platform specific)
|
213
304
|
print(message)
|
214
305
|
|
215
306
|
|
216
307
|
class BillingApp(App):
|
308
|
+
"""
|
309
|
+
Main application class for the SJBillingClient demo.
|
310
|
+
|
311
|
+
This class sets up the application and creates the screen manager
|
312
|
+
with the HomeScreen.
|
313
|
+
"""
|
217
314
|
def build(self):
|
315
|
+
"""
|
316
|
+
Build the application UI.
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
ScreenManager: The root widget of the application.
|
320
|
+
"""
|
218
321
|
# Create screen manager
|
219
322
|
sm = ScreenManager()
|
220
323
|
sm.add_widget(HomeScreen(name='home'))
|
@@ -260,7 +363,7 @@ if __name__ == '__main__':
|
|
260
363
|
product_id: 'coffee_product_id'
|
261
364
|
size_hint_y: None
|
262
365
|
height: '60dp'
|
263
|
-
on_release: root.
|
366
|
+
on_release: root.support()
|
264
367
|
|
265
368
|
Widget:
|
266
369
|
# Spacer
|
@@ -268,13 +371,20 @@ if __name__ == '__main__':
|
|
268
371
|
|
269
372
|
This example demonstrates:
|
270
373
|
|
271
|
-
1. A `HomeScreen` class that handles all billing operations
|
374
|
+
1. A `HomeScreen` class that extends `Screen` and handles all billing operations
|
272
375
|
2. A `BillingApp` class that sets up the Kivy application and screen manager
|
273
376
|
3. A Kivy layout file that defines the UI with:
|
274
377
|
- A checkbox to toggle between one-time purchase and subscription
|
275
378
|
- A button to initiate the purchase flow
|
276
379
|
|
277
|
-
The `
|
380
|
+
The `support` method is called when the button is pressed, which initializes the billing client and starts the connection. The various callback methods handle different stages of the billing process, including:
|
381
|
+
- Handling purchase updates with `on_purchases_updated`
|
382
|
+
- Acknowledging subscription purchases with `acknowledge_purchase`
|
383
|
+
- Consuming one-time purchases with `consume_async`
|
384
|
+
- Processing product details with `on_product_details_response`, including handling unfetched products
|
385
|
+
- Querying product details with `query_product_details_async`
|
386
|
+
|
387
|
+
This example is designed to be copy-and-paste runnable, with no need for the user to add or remove anything to test it.
|
278
388
|
|
279
389
|
## API Reference
|
280
390
|
|
@@ -282,16 +392,89 @@ The `purchase_or_subscribe` method is called when the button is pressed, which i
|
|
282
392
|
|
283
393
|
The main class for interacting with Google Play Billing.
|
284
394
|
|
395
|
+
#### Constructor
|
396
|
+
|
397
|
+
- `__init__(on_purchases_updated, enable_one_time_products=True, enable_prepaid_plans=False)`:
|
398
|
+
- Initializes a new BillingClient instance
|
399
|
+
- `on_purchases_updated`: Callback function that will be triggered when purchases are updated
|
400
|
+
- `enable_one_time_products`: Boolean to enable one-time products (default: True)
|
401
|
+
- `enable_prepaid_plans`: Boolean to enable prepaid plans (default: False)
|
402
|
+
|
403
|
+
#### Connection Methods
|
404
|
+
|
405
|
+
- `start_connection(on_billing_setup_finished, on_billing_service_disconnected)`:
|
406
|
+
- Starts a connection with the billing client
|
407
|
+
- `on_billing_setup_finished`: Callback when billing setup is complete
|
408
|
+
- `on_billing_service_disconnected`: Callback when billing service is disconnected
|
409
|
+
|
410
|
+
- `end_connection()`:
|
411
|
+
- Ends the connection with the billing client
|
412
|
+
|
413
|
+
#### Product Details Methods
|
414
|
+
|
415
|
+
- `query_product_details_async(product_type, products_ids, on_product_details_response)`:
|
416
|
+
- Queries product details asynchronously
|
417
|
+
- `product_type`: Type of products (INAPP or SUBS)
|
418
|
+
- `products_ids`: List of product IDs to query
|
419
|
+
- `on_product_details_response`: Callback for product details response
|
420
|
+
|
421
|
+
- `get_product_details(product_details, product_type)`:
|
422
|
+
- Gets formatted product details
|
423
|
+
- `product_details`: Product details object
|
424
|
+
- `product_type`: Type of product (INAPP or SUBS)
|
425
|
+
- Returns a list of dictionaries with product details
|
426
|
+
|
427
|
+
- `get_unfetched_product(unfetched_product)`:
|
428
|
+
- Gets details about an unfetched product
|
429
|
+
- `unfetched_product`: Unfetched product object
|
430
|
+
- Returns a dictionary with product ID, type, and status code
|
431
|
+
|
432
|
+
#### Purchase Methods
|
433
|
+
|
434
|
+
- `launch_billing_flow(product_details, offer_token=None)`:
|
435
|
+
- Launches the billing flow for purchase
|
436
|
+
- `product_details`: List of product details objects
|
437
|
+
- `offer_token`: Optional token for subscription offers
|
438
|
+
|
439
|
+
- `consume_async(purchase, on_consume_response)`:
|
440
|
+
- Consumes a purchase asynchronously
|
441
|
+
- `purchase`: Purchase object to consume
|
442
|
+
- `on_consume_response`: Callback for consume response
|
443
|
+
|
444
|
+
- `acknowledge_purchase(purchase_token, on_acknowledge_purchase_response)`:
|
445
|
+
- Acknowledges a purchase
|
446
|
+
- `purchase_token`: Token of the purchase to acknowledge
|
447
|
+
- `on_acknowledge_purchase_response`: Callback for acknowledge response
|
448
|
+
|
449
|
+
### PendingPurchasesParams
|
450
|
+
|
451
|
+
Parameters for handling pending purchases.
|
452
|
+
|
285
453
|
#### Methods
|
286
454
|
|
287
|
-
- `
|
288
|
-
- `
|
289
|
-
- `
|
290
|
-
- `
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
455
|
+
- `newBuilder()`: Creates a new builder for PendingPurchasesParams
|
456
|
+
- `build()`: Builds the PendingPurchasesParams object
|
457
|
+
- `enableOneTimeProducts()`: Enables one-time products
|
458
|
+
- `enablePrepaidPlans()`: Enables prepaid plans
|
459
|
+
|
460
|
+
### QueryProductDetailsParams
|
461
|
+
|
462
|
+
Parameters for querying product details.
|
463
|
+
|
464
|
+
#### Methods
|
465
|
+
|
466
|
+
- `newBuilder()`: Creates a new builder for QueryProductDetailsParams
|
467
|
+
- `setProductList(product_list)`: Sets the list of products to query
|
468
|
+
- `build()`: Builds the QueryProductDetailsParams object
|
469
|
+
|
470
|
+
### QueryProductDetailsResult
|
471
|
+
|
472
|
+
Result of a product details query.
|
473
|
+
|
474
|
+
#### Methods
|
475
|
+
|
476
|
+
- `getProductDetailsList()`: Gets the list of product details
|
477
|
+
- `getUnfetchedProductList()`: Gets the list of unfetched products
|
295
478
|
|
296
479
|
### ProductType
|
297
480
|
|
@@ -304,10 +487,17 @@ Constants for product types:
|
|
304
487
|
|
305
488
|
Constants for billing response codes:
|
306
489
|
|
307
|
-
- `BillingResponseCode.OK`: Success
|
308
|
-
- `BillingResponseCode.USER_CANCELED`: User canceled
|
309
|
-
- `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable
|
310
|
-
-
|
490
|
+
- `BillingResponseCode.OK`: Success (0)
|
491
|
+
- `BillingResponseCode.USER_CANCELED`: User canceled (1)
|
492
|
+
- `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable (2)
|
493
|
+
- `BillingResponseCode.BILLING_UNAVAILABLE`: Billing unavailable (3)
|
494
|
+
- `BillingResponseCode.ITEM_UNAVAILABLE`: Item unavailable (4)
|
495
|
+
- `BillingResponseCode.DEVELOPER_ERROR`: Developer error (5)
|
496
|
+
- `BillingResponseCode.ERROR`: General error (6)
|
497
|
+
- `BillingResponseCode.ITEM_ALREADY_OWNED`: Item already owned (7)
|
498
|
+
- `BillingResponseCode.ITEM_NOT_OWNED`: Item not owned (8)
|
499
|
+
- `BillingResponseCode.SERVICE_DISCONNECTED`: Service disconnected (10)
|
500
|
+
- `BillingResponseCode.FEATURE_NOT_SUPPORTED`: Feature not supported (12)
|
311
501
|
|
312
502
|
## Contributing
|
313
503
|
|
@@ -320,3 +510,4 @@ This project is licensed under the MIT License - see the LICENSE file for detail
|
|
320
510
|
## Author
|
321
511
|
|
322
512
|
Kenechukwu Akubue <kengoon19@gmail.com>
|
513
|
+
|
@@ -1,19 +1,3 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: sjbillingclient
|
3
|
-
Version: 0.1.4
|
4
|
-
Summary:
|
5
|
-
Author: Kenechukwu Akubue
|
6
|
-
Author-email: kengoon19@gmail.com
|
7
|
-
Requires-Python: >=3.9,<4.0
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
13
|
-
Requires-Dist: poetry-core (>=2.1.3,<3.0.0)
|
14
|
-
Requires-Dist: pyjnius (>=1.6.1,<2.0.0)
|
15
|
-
Description-Content-Type: text/markdown
|
16
|
-
|
17
1
|
# SJBillingClient (Google Play Billing SDK for Python)
|
18
2
|
|
19
3
|
<!-- GitAds-Verify: 71CCWMQSMVD67LS4WF4N44EXISSL2UTQ -->
|
@@ -36,7 +20,7 @@ SJBillingClient is a Python wrapper for the Google Play Billing Library that fac
|
|
36
20
|
|
37
21
|
- Python 3.9+
|
38
22
|
- pyjnius 1.6.1+
|
39
|
-
- Android application with Google Play Billing Library (version
|
23
|
+
- Android application with Google Play Billing Library (version 8.0.0 recommended)
|
40
24
|
|
41
25
|
## Installation
|
42
26
|
|
@@ -46,7 +30,7 @@ pip install sjbillingclient
|
|
46
30
|
|
47
31
|
# In Buildozer (add to buildozer.spec)
|
48
32
|
requirements = sjbillingclient
|
49
|
-
android.gradle_dependencies = com.android.billingclient:billing:
|
33
|
+
android.gradle_dependencies = com.android.billingclient:billing:8.0.0
|
50
34
|
```
|
51
35
|
|
52
36
|
## Quick Start
|
@@ -170,14 +154,28 @@ from kivy.uix.screenmanager import ScreenManager, Screen
|
|
170
154
|
from sjbillingclient.jclass.billing import BillingResponseCode, ProductType
|
171
155
|
from sjbillingclient.tools import BillingClient
|
172
156
|
|
173
|
-
# Load the KV file
|
174
157
|
Builder.load_file(join(dirname(__file__), basename(__file__).split(".")[0] + ".kv"))
|
175
158
|
|
176
159
|
|
177
160
|
class HomeScreen(Screen):
|
161
|
+
"""
|
162
|
+
A screen that demonstrates Google Play Billing integration with Kivy.
|
163
|
+
|
164
|
+
This screen provides functionality to make in-app purchases and subscriptions
|
165
|
+
using the Google Play Billing Library through the SJBillingClient wrapper.
|
166
|
+
|
167
|
+
Attributes:
|
168
|
+
billing_client (BillingClient): The client used to interact with Google Play Billing.
|
169
|
+
"""
|
178
170
|
billing_client = None
|
179
171
|
|
180
|
-
def
|
172
|
+
def support(self):
|
173
|
+
"""
|
174
|
+
Initializes the billing client and starts a connection to the Google Play Billing service.
|
175
|
+
|
176
|
+
This method is called when the user wants to make a purchase or subscription.
|
177
|
+
If a billing client already exists, it ends the connection before creating a new one.
|
178
|
+
"""
|
181
179
|
if self.billing_client:
|
182
180
|
self.billing_client.end_connection()
|
183
181
|
|
@@ -188,6 +186,17 @@ class HomeScreen(Screen):
|
|
188
186
|
)
|
189
187
|
|
190
188
|
def on_purchases_updated(self, billing_result, null, purchases):
|
189
|
+
"""
|
190
|
+
Callback method that is called when purchases are updated.
|
191
|
+
|
192
|
+
This method handles the result of a purchase flow, either acknowledging
|
193
|
+
a subscription or consuming a one-time purchase.
|
194
|
+
|
195
|
+
Args:
|
196
|
+
billing_result: The result of the billing operation.
|
197
|
+
null: Boolean indicating if the purchases list is null.
|
198
|
+
purchases: List of purchases that were updated.
|
199
|
+
"""
|
191
200
|
if billing_result.getResponseCode() == BillingResponseCode.OK and not null:
|
192
201
|
for purchase in purchases:
|
193
202
|
if self.ids.subscribe.active:
|
@@ -197,40 +206,96 @@ class HomeScreen(Screen):
|
|
197
206
|
)
|
198
207
|
else:
|
199
208
|
self.billing_client.consume_async(purchase, self.on_consume_response)
|
209
|
+
print(billing_result.getResponseCode(), billing_result.getDebugMessage())
|
200
210
|
|
201
211
|
def on_acknowledge_purchase_response(self, billing_result):
|
212
|
+
"""
|
213
|
+
Callback method that is called when a purchase acknowledgement is complete.
|
214
|
+
|
215
|
+
Args:
|
216
|
+
billing_result: The result of the acknowledgement operation.
|
217
|
+
"""
|
202
218
|
print(billing_result.getDebugMessage())
|
203
219
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
204
220
|
self.toast("Thank you for subscribing to buy us a cup of coffee! monthly")
|
205
221
|
|
206
222
|
def on_consume_response(self, billing_result):
|
223
|
+
"""
|
224
|
+
Callback method that is called when a purchase consumption is complete.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
billing_result: The result of the consumption operation.
|
228
|
+
"""
|
207
229
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
208
230
|
self.toast("Thank you for buying us a cup of coffee!")
|
209
231
|
|
210
|
-
def on_product_details_response(self, billing_result,
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
232
|
+
def on_product_details_response(self, billing_result, product_details_result):
|
233
|
+
"""
|
234
|
+
Callback method that is called when product details are retrieved.
|
235
|
+
|
236
|
+
This method processes the product details and launches the billing flow.
|
237
|
+
|
238
|
+
Args:
|
239
|
+
billing_result: The result of the product details query.
|
240
|
+
product_details_result: The result containing product details and unfetched products.
|
241
|
+
"""
|
242
|
+
product_details_list = product_details_result.getProductDetailsList()
|
243
|
+
unfetched_product_list = product_details_result.getUnfetchedProductList()
|
244
|
+
|
215
245
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
246
|
+
for product_details in product_details_list:
|
247
|
+
self.billing_client.get_product_details(
|
248
|
+
product_details,
|
249
|
+
ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP)
|
250
|
+
for unfetched_product in unfetched_product_list:
|
251
|
+
print(self.billing_client.get_unfetched_product(unfetched_product))
|
216
252
|
self.billing_client.launch_billing_flow(product_details=product_details_list)
|
217
253
|
|
218
254
|
def on_billing_setup_finished(self, billing_result):
|
255
|
+
"""
|
256
|
+
Callback method that is called when the billing setup is complete.
|
257
|
+
|
258
|
+
This method queries product details if the billing setup was successful.
|
259
|
+
|
260
|
+
Args:
|
261
|
+
billing_result: The result of the billing setup operation.
|
262
|
+
"""
|
219
263
|
product_id = self.ids.btn.product_id
|
220
264
|
if billing_result.getResponseCode() == BillingResponseCode.OK:
|
221
265
|
self.billing_client.query_product_details_async(
|
222
|
-
product_type=ProductType.SUBS if self.ids.subscribe
|
266
|
+
product_type=ProductType.SUBS if self.ids.subscribe else ProductType.INAPP,
|
223
267
|
products_ids=[product_id],
|
224
268
|
on_product_details_response=self.on_product_details_response,
|
225
269
|
)
|
226
270
|
|
227
271
|
def toast(self, message):
|
272
|
+
"""
|
273
|
+
Display a toast message.
|
274
|
+
|
275
|
+
This is a simple implementation that just prints the message.
|
276
|
+
In a real app, you would use platform-specific toast functionality.
|
277
|
+
|
278
|
+
Args:
|
279
|
+
message: The message to display.
|
280
|
+
"""
|
228
281
|
# Implementation of toast message (platform specific)
|
229
282
|
print(message)
|
230
283
|
|
231
284
|
|
232
285
|
class BillingApp(App):
|
286
|
+
"""
|
287
|
+
Main application class for the SJBillingClient demo.
|
288
|
+
|
289
|
+
This class sets up the application and creates the screen manager
|
290
|
+
with the HomeScreen.
|
291
|
+
"""
|
233
292
|
def build(self):
|
293
|
+
"""
|
294
|
+
Build the application UI.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
ScreenManager: The root widget of the application.
|
298
|
+
"""
|
234
299
|
# Create screen manager
|
235
300
|
sm = ScreenManager()
|
236
301
|
sm.add_widget(HomeScreen(name='home'))
|
@@ -276,7 +341,7 @@ if __name__ == '__main__':
|
|
276
341
|
product_id: 'coffee_product_id'
|
277
342
|
size_hint_y: None
|
278
343
|
height: '60dp'
|
279
|
-
on_release: root.
|
344
|
+
on_release: root.support()
|
280
345
|
|
281
346
|
Widget:
|
282
347
|
# Spacer
|
@@ -284,13 +349,20 @@ if __name__ == '__main__':
|
|
284
349
|
|
285
350
|
This example demonstrates:
|
286
351
|
|
287
|
-
1. A `HomeScreen` class that handles all billing operations
|
352
|
+
1. A `HomeScreen` class that extends `Screen` and handles all billing operations
|
288
353
|
2. A `BillingApp` class that sets up the Kivy application and screen manager
|
289
354
|
3. A Kivy layout file that defines the UI with:
|
290
355
|
- A checkbox to toggle between one-time purchase and subscription
|
291
356
|
- A button to initiate the purchase flow
|
292
357
|
|
293
|
-
The `
|
358
|
+
The `support` method is called when the button is pressed, which initializes the billing client and starts the connection. The various callback methods handle different stages of the billing process, including:
|
359
|
+
- Handling purchase updates with `on_purchases_updated`
|
360
|
+
- Acknowledging subscription purchases with `acknowledge_purchase`
|
361
|
+
- Consuming one-time purchases with `consume_async`
|
362
|
+
- Processing product details with `on_product_details_response`, including handling unfetched products
|
363
|
+
- Querying product details with `query_product_details_async`
|
364
|
+
|
365
|
+
This example is designed to be copy-and-paste runnable, with no need for the user to add or remove anything to test it.
|
294
366
|
|
295
367
|
## API Reference
|
296
368
|
|
@@ -298,16 +370,89 @@ The `purchase_or_subscribe` method is called when the button is pressed, which i
|
|
298
370
|
|
299
371
|
The main class for interacting with Google Play Billing.
|
300
372
|
|
373
|
+
#### Constructor
|
374
|
+
|
375
|
+
- `__init__(on_purchases_updated, enable_one_time_products=True, enable_prepaid_plans=False)`:
|
376
|
+
- Initializes a new BillingClient instance
|
377
|
+
- `on_purchases_updated`: Callback function that will be triggered when purchases are updated
|
378
|
+
- `enable_one_time_products`: Boolean to enable one-time products (default: True)
|
379
|
+
- `enable_prepaid_plans`: Boolean to enable prepaid plans (default: False)
|
380
|
+
|
381
|
+
#### Connection Methods
|
382
|
+
|
383
|
+
- `start_connection(on_billing_setup_finished, on_billing_service_disconnected)`:
|
384
|
+
- Starts a connection with the billing client
|
385
|
+
- `on_billing_setup_finished`: Callback when billing setup is complete
|
386
|
+
- `on_billing_service_disconnected`: Callback when billing service is disconnected
|
387
|
+
|
388
|
+
- `end_connection()`:
|
389
|
+
- Ends the connection with the billing client
|
390
|
+
|
391
|
+
#### Product Details Methods
|
392
|
+
|
393
|
+
- `query_product_details_async(product_type, products_ids, on_product_details_response)`:
|
394
|
+
- Queries product details asynchronously
|
395
|
+
- `product_type`: Type of products (INAPP or SUBS)
|
396
|
+
- `products_ids`: List of product IDs to query
|
397
|
+
- `on_product_details_response`: Callback for product details response
|
398
|
+
|
399
|
+
- `get_product_details(product_details, product_type)`:
|
400
|
+
- Gets formatted product details
|
401
|
+
- `product_details`: Product details object
|
402
|
+
- `product_type`: Type of product (INAPP or SUBS)
|
403
|
+
- Returns a list of dictionaries with product details
|
404
|
+
|
405
|
+
- `get_unfetched_product(unfetched_product)`:
|
406
|
+
- Gets details about an unfetched product
|
407
|
+
- `unfetched_product`: Unfetched product object
|
408
|
+
- Returns a dictionary with product ID, type, and status code
|
409
|
+
|
410
|
+
#### Purchase Methods
|
411
|
+
|
412
|
+
- `launch_billing_flow(product_details, offer_token=None)`:
|
413
|
+
- Launches the billing flow for purchase
|
414
|
+
- `product_details`: List of product details objects
|
415
|
+
- `offer_token`: Optional token for subscription offers
|
416
|
+
|
417
|
+
- `consume_async(purchase, on_consume_response)`:
|
418
|
+
- Consumes a purchase asynchronously
|
419
|
+
- `purchase`: Purchase object to consume
|
420
|
+
- `on_consume_response`: Callback for consume response
|
421
|
+
|
422
|
+
- `acknowledge_purchase(purchase_token, on_acknowledge_purchase_response)`:
|
423
|
+
- Acknowledges a purchase
|
424
|
+
- `purchase_token`: Token of the purchase to acknowledge
|
425
|
+
- `on_acknowledge_purchase_response`: Callback for acknowledge response
|
426
|
+
|
427
|
+
### PendingPurchasesParams
|
428
|
+
|
429
|
+
Parameters for handling pending purchases.
|
430
|
+
|
301
431
|
#### Methods
|
302
432
|
|
303
|
-
- `
|
304
|
-
- `
|
305
|
-
- `
|
306
|
-
- `
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
433
|
+
- `newBuilder()`: Creates a new builder for PendingPurchasesParams
|
434
|
+
- `build()`: Builds the PendingPurchasesParams object
|
435
|
+
- `enableOneTimeProducts()`: Enables one-time products
|
436
|
+
- `enablePrepaidPlans()`: Enables prepaid plans
|
437
|
+
|
438
|
+
### QueryProductDetailsParams
|
439
|
+
|
440
|
+
Parameters for querying product details.
|
441
|
+
|
442
|
+
#### Methods
|
443
|
+
|
444
|
+
- `newBuilder()`: Creates a new builder for QueryProductDetailsParams
|
445
|
+
- `setProductList(product_list)`: Sets the list of products to query
|
446
|
+
- `build()`: Builds the QueryProductDetailsParams object
|
447
|
+
|
448
|
+
### QueryProductDetailsResult
|
449
|
+
|
450
|
+
Result of a product details query.
|
451
|
+
|
452
|
+
#### Methods
|
453
|
+
|
454
|
+
- `getProductDetailsList()`: Gets the list of product details
|
455
|
+
- `getUnfetchedProductList()`: Gets the list of unfetched products
|
311
456
|
|
312
457
|
### ProductType
|
313
458
|
|
@@ -320,10 +465,17 @@ Constants for product types:
|
|
320
465
|
|
321
466
|
Constants for billing response codes:
|
322
467
|
|
323
|
-
- `BillingResponseCode.OK`: Success
|
324
|
-
- `BillingResponseCode.USER_CANCELED`: User canceled
|
325
|
-
- `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable
|
326
|
-
-
|
468
|
+
- `BillingResponseCode.OK`: Success (0)
|
469
|
+
- `BillingResponseCode.USER_CANCELED`: User canceled (1)
|
470
|
+
- `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable (2)
|
471
|
+
- `BillingResponseCode.BILLING_UNAVAILABLE`: Billing unavailable (3)
|
472
|
+
- `BillingResponseCode.ITEM_UNAVAILABLE`: Item unavailable (4)
|
473
|
+
- `BillingResponseCode.DEVELOPER_ERROR`: Developer error (5)
|
474
|
+
- `BillingResponseCode.ERROR`: General error (6)
|
475
|
+
- `BillingResponseCode.ITEM_ALREADY_OWNED`: Item already owned (7)
|
476
|
+
- `BillingResponseCode.ITEM_NOT_OWNED`: Item not owned (8)
|
477
|
+
- `BillingResponseCode.SERVICE_DISCONNECTED`: Service disconnected (10)
|
478
|
+
- `BillingResponseCode.FEATURE_NOT_SUPPORTED`: Feature not supported (12)
|
327
479
|
|
328
480
|
## Contributing
|
329
481
|
|
@@ -336,4 +488,3 @@ This project is licensed under the MIT License - see the LICENSE file for detail
|
|
336
488
|
## Author
|
337
489
|
|
338
490
|
Kenechukwu Akubue <kengoon19@gmail.com>
|
339
|
-
|
@@ -0,0 +1,24 @@
|
|
1
|
+
[tool.poetry]
|
2
|
+
name = "sjbillingclient"
|
3
|
+
version = "0.2.1"
|
4
|
+
description = "A Python wrapper for the Google Play Billing Library that facilitates in-app purchases and subscriptions in Android applications"
|
5
|
+
authors = ["Kenechukwu Akubue <kengoon19@gmail.com>"]
|
6
|
+
readme = "README.md"
|
7
|
+
homepage = "https://github.com/SimpleJnius/sj-android-billingclient"
|
8
|
+
repository = "https://github.com/SimpleJnius/sj-android-billingclient.git"
|
9
|
+
documentation = "https://github.com/SimpleJnius/sj-android-billingclient/blob/master/README.md"
|
10
|
+
|
11
|
+
[tool.poetry.urls]
|
12
|
+
"Bug Tracker" = "https://github.com/SimpleJnius/sj-android-billingclient/issues"
|
13
|
+
"Source" = "https://github.com/SimpleJnius/sj-android-billingclient"
|
14
|
+
"Pull Requests" = "https://github.com/SimpleJnius/sj-android-billingclient/pulls"
|
15
|
+
|
16
|
+
[tool.poetry.dependencies]
|
17
|
+
python = "^3.9"
|
18
|
+
pyjnius = "^1.6.1"
|
19
|
+
poetry-core = "^2.1.3"
|
20
|
+
|
21
|
+
|
22
|
+
[build-system]
|
23
|
+
requires = ["poetry-core"]
|
24
|
+
build-backend = "poetry.core.masonry.api"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from jnius import JavaClass, MetaJavaClass, JavaStaticMethod, JavaMethod
|
2
|
+
|
3
|
+
__all__ = ("PendingPurchasesParams", "PendingPurchasesParamsBuilder")
|
4
|
+
|
5
|
+
|
6
|
+
class PendingPurchasesParams(JavaClass, metaclass=MetaJavaClass):
|
7
|
+
__javaclass__ = f"com/android/billingclient/api/PendingPurchasesParams"
|
8
|
+
newBuilder = JavaStaticMethod("()Lcom/android/billingclient/api/PendingPurchasesParams$Builder;")
|
9
|
+
|
10
|
+
|
11
|
+
class PendingPurchasesParamsBuilder(JavaClass, metaclass=MetaJavaClass):
|
12
|
+
__javaclass__ = f"com/android/billingclient/api/PendingPurchasesParams$Builder"
|
13
|
+
|
14
|
+
build = JavaMethod("()Lcom/android/billingclient/api/PendingPurchasesParams;")
|
15
|
+
enableOneTimeProducts = JavaMethod("()Lcom/android/billingclient/api/PendingPurchasesParams$Builder;")
|
16
|
+
enablePrepaidPlans = JavaMethod("()Lcom/android/billingclient/api/PendingPurchasesParams$Builder;")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from jnius import JavaClass, MetaJavaClass, JavaStaticMethod
|
1
|
+
from jnius import JavaClass, MetaJavaClass, JavaStaticMethod, JavaMethod
|
2
2
|
|
3
3
|
__all__ = ("QueryProductDetailsParams", "QueryProductDetailsParamsProduct")
|
4
4
|
|
@@ -11,3 +11,11 @@ class QueryProductDetailsParams(JavaClass, metaclass=MetaJavaClass):
|
|
11
11
|
class QueryProductDetailsParamsProduct(JavaClass, metaclass=MetaJavaClass):
|
12
12
|
__javaclass__ = f"com/android/billingclient/api/QueryProductDetailsParams$Product"
|
13
13
|
newBuilder = JavaStaticMethod("()Lcom/android/billingclient/api/QueryProductDetailsParams$Product$Builder;")
|
14
|
+
|
15
|
+
|
16
|
+
class QueryProductDetailsResult(JavaClass, metaclass=MetaJavaClass):
|
17
|
+
__javaclass__ = f"com/android/billingclient/api/QueryProductDetailsResult"
|
18
|
+
create = JavaStaticMethod("(Ljava/util/List;Ljava/util/List;)"
|
19
|
+
"Lcom/android/billingclient/api/QueryProductDetailsResult;")
|
20
|
+
getProductDetailsList = JavaMethod("()Ljava/util/List;")
|
21
|
+
getUnfetchedProductList = JavaMethod("()Ljava/util/List;")
|
@@ -10,6 +10,7 @@ class ProductDetailsResponseListener(PythonJavaClass):
|
|
10
10
|
def __init__(self, callback):
|
11
11
|
self.callback = callback
|
12
12
|
|
13
|
-
@java_method("(Lcom/android/billingclient/api/BillingResult;
|
14
|
-
|
15
|
-
|
13
|
+
@java_method("(Lcom/android/billingclient/api/BillingResult;"
|
14
|
+
"Lcom/android/billingclient/api/QueryProductDetailsResult;)V")
|
15
|
+
def onProductDetailsResponse(self, billing_result, product_details_result):
|
16
|
+
self.callback(billing_result, product_details_result)
|
@@ -42,6 +42,7 @@ from sjbillingclient.jclass.billing import BillingClient as SJBillingClient, Pro
|
|
42
42
|
BillingFlowParams
|
43
43
|
from android.activity import _activity as activity # noqa
|
44
44
|
from sjbillingclient.jclass.consume import ConsumeParams
|
45
|
+
from sjbillingclient.jclass.purchase import PendingPurchasesParams
|
45
46
|
from sjbillingclient.jclass.queryproduct import QueryProductDetailsParams, QueryProductDetailsParamsProduct
|
46
47
|
from sjbillingclient.jinterface.acknowledge import AcknowledgePurchaseResponseListener
|
47
48
|
from sjbillingclient.jinterface.billing import BillingClientStateListener
|
@@ -49,7 +50,6 @@ from sjbillingclient.jinterface.consume import ConsumeResponseListener
|
|
49
50
|
from sjbillingclient.jinterface.product import ProductDetailsResponseListener
|
50
51
|
from sjbillingclient.jinterface.purchases import PurchasesUpdatedListener
|
51
52
|
|
52
|
-
|
53
53
|
ERROR_NO_BASE_PLAN = "You don't have a base plan"
|
54
54
|
ERROR_NO_BASE_PLAN_ID = "You don't have a base plan id"
|
55
55
|
ERROR_INVALID_PRODUCT_TYPE = "product_type not supported. Must be one of `ProductType.SUBS`, `ProductType.INAPP`"
|
@@ -77,7 +77,13 @@ class BillingClient:
|
|
77
77
|
:ivar __acknowledge_purchase_response_listener: Listener handling responses for acknowledging purchases.
|
78
78
|
:type __acknowledge_purchase_response_listener: AcknowledgePurchaseResponseListener | None
|
79
79
|
"""
|
80
|
-
|
80
|
+
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
on_purchases_updated,
|
84
|
+
enable_one_time_products: bool = True,
|
85
|
+
enable_prepaid_plans: bool = False
|
86
|
+
) -> None:
|
81
87
|
"""
|
82
88
|
Initializes an instance of the class with the given purchase update callback.
|
83
89
|
|
@@ -92,10 +98,15 @@ class BillingClient:
|
|
92
98
|
self.__acknowledge_purchase_response_listener = None
|
93
99
|
|
94
100
|
self.__purchase_update_listener = PurchasesUpdatedListener(on_purchases_updated)
|
101
|
+
pending_purchase_params = PendingPurchasesParams.newBuilder()
|
102
|
+
if enable_one_time_products:
|
103
|
+
pending_purchase_params.enableOneTimeProducts()
|
104
|
+
if enable_prepaid_plans:
|
105
|
+
pending_purchase_params.enablePrepaidPlans()
|
95
106
|
self.__billing_client = (
|
96
107
|
SJBillingClient.newBuilder(activity.context)
|
97
108
|
.setListener(self.__purchase_update_listener)
|
98
|
-
.enablePendingPurchases()
|
109
|
+
.enablePendingPurchases(pending_purchase_params.build())
|
99
110
|
.build()
|
100
111
|
)
|
101
112
|
|
@@ -177,6 +188,28 @@ class BillingClient:
|
|
177
188
|
.setProductType(product_type)
|
178
189
|
.build())
|
179
190
|
|
191
|
+
@staticmethod
|
192
|
+
def get_unfetched_product(unfetched_product) -> Dict:
|
193
|
+
"""
|
194
|
+
Retrieves detailed product information for an unfetched product.
|
195
|
+
|
196
|
+
This function takes an object representing an unfetched product and extracts
|
197
|
+
important details such as the product ID, product type, and status code.
|
198
|
+
The extracted details are then returned as a dictionary.
|
199
|
+
|
200
|
+
:param unfetched_product: The product object that has not yet been fetched.
|
201
|
+
Must provide methods to retrieve product ID, type, and status code.
|
202
|
+
:type unfetched_product: Any
|
203
|
+
:return: A dictionary containing detailed information about the unfetched
|
204
|
+
product, including its ID, type, and status code.
|
205
|
+
:rtype: Dict
|
206
|
+
"""
|
207
|
+
return {
|
208
|
+
"product_id": unfetched_product.getProductId(),
|
209
|
+
"product_type": unfetched_product.getProductType(),
|
210
|
+
"status_code": unfetched_product.getStatusCode(),
|
211
|
+
}
|
212
|
+
|
180
213
|
def get_product_details(self, product_details, product_type: str) -> List[Dict]:
|
181
214
|
"""
|
182
215
|
Retrieves the details of a product based on the provided product type. The function processes
|
@@ -292,6 +325,7 @@ class BillingClient:
|
|
292
325
|
self._create_product_params(product_detail, offer_token)
|
293
326
|
for product_detail in product_details
|
294
327
|
]
|
328
|
+
print(product_params_list)
|
295
329
|
|
296
330
|
billing_flow_params = (BillingFlowParams.newBuilder()
|
297
331
|
.setProductDetailsParamsList(JavaList.of(*product_params_list))
|
@@ -1,16 +0,0 @@
|
|
1
|
-
[tool.poetry]
|
2
|
-
name = "sjbillingclient"
|
3
|
-
version = "0.1.4"
|
4
|
-
description = ""
|
5
|
-
authors = ["Kenechukwu Akubue <kengoon19@gmail.com>"]
|
6
|
-
readme = "README.md"
|
7
|
-
|
8
|
-
[tool.poetry.dependencies]
|
9
|
-
python = "^3.9"
|
10
|
-
pyjnius = "^1.6.1"
|
11
|
-
poetry-core = "^2.1.3"
|
12
|
-
|
13
|
-
|
14
|
-
[build-system]
|
15
|
-
requires = ["poetry-core"]
|
16
|
-
build-backend = "poetry.core.masonry.api"
|
@@ -1,8 +0,0 @@
|
|
1
|
-
from jnius import JavaClass, MetaJavaClass, JavaStaticMethod
|
2
|
-
|
3
|
-
__all__ = ("PendingPurchasesParams",)
|
4
|
-
|
5
|
-
|
6
|
-
class PendingPurchasesParams(JavaClass, metaclass=MetaJavaClass):
|
7
|
-
__javaclass__ = f"com/android/billingclient/api/PendingPurchasesParams"
|
8
|
-
newBuilder = JavaStaticMethod("()Lcom/android/billingclient/api/PendingPurchasesParams$Builder;")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|