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.
Files changed (19) hide show
  1. sjbillingclient-0.1.4/README.md → sjbillingclient-0.2.1/PKG-INFO +216 -25
  2. sjbillingclient-0.1.4/PKG-INFO → sjbillingclient-0.2.1/README.md +193 -42
  3. sjbillingclient-0.2.1/pyproject.toml +24 -0
  4. sjbillingclient-0.2.1/sjbillingclient/jclass/purchase.py +16 -0
  5. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/queryproduct.py +9 -1
  6. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/product.py +4 -3
  7. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/tools/__init__.py +37 -3
  8. sjbillingclient-0.1.4/pyproject.toml +0 -16
  9. sjbillingclient-0.1.4/sjbillingclient/jclass/purchase.py +0 -8
  10. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/__init__.py +0 -0
  11. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/__init__.py +0 -0
  12. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/acknowledge.py +0 -0
  13. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/billing.py +0 -0
  14. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jclass/consume.py +0 -0
  15. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/__init__.py +0 -0
  16. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/acknowledge.py +0 -0
  17. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/billing.py +0 -0
  18. {sjbillingclient-0.1.4 → sjbillingclient-0.2.1}/sjbillingclient/jinterface/consume.py +0 -0
  19. {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 7.1.1 recommended)
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:7.1.1
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 purchase_or_subscribe(self):
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, product_details_list):
195
- for product_details in product_details_list:
196
- self.billing_client.get_product_details(
197
- product_details,
198
- ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP)
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.active else ProductType.INAPP,
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.purchase_or_subscribe()
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 `purchase_or_subscribe` 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 acknowledging purchases and consuming one-time purchases.
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
- - `__init__(on_purchases_updated)`: Initialize with a callback for purchase updates
288
- - `start_connection(on_billing_setup_finished, on_billing_service_disconnected)`: Start billing connection
289
- - `end_connection()`: End billing connection
290
- - `query_product_details_async(product_type, products_ids, on_product_details_response)`: Query product details
291
- - `get_product_details(product_details, product_type)`: Get formatted product details
292
- - `launch_billing_flow(product_details, offer_token=None)`: Launch purchase flow
293
- - `consume_async(purchase, on_consume_response)`: Consume a purchase
294
- - `acknowledge_purchase(purchase_token, on_acknowledge_purchase_response)`: Acknowledge a purchase
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
- - And many others
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 7.1.1 recommended)
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:7.1.1
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 purchase_or_subscribe(self):
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, product_details_list):
211
- for product_details in product_details_list:
212
- self.billing_client.get_product_details(
213
- product_details,
214
- ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP)
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.active else ProductType.INAPP,
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.purchase_or_subscribe()
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 `purchase_or_subscribe` 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 acknowledging purchases and consuming one-time purchases.
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
- - `__init__(on_purchases_updated)`: Initialize with a callback for purchase updates
304
- - `start_connection(on_billing_setup_finished, on_billing_service_disconnected)`: Start billing connection
305
- - `end_connection()`: End billing connection
306
- - `query_product_details_async(product_type, products_ids, on_product_details_response)`: Query product details
307
- - `get_product_details(product_details, product_type)`: Get formatted product details
308
- - `launch_billing_flow(product_details, offer_token=None)`: Launch purchase flow
309
- - `consume_async(purchase, on_consume_response)`: Consume a purchase
310
- - `acknowledge_purchase(purchase_token, on_acknowledge_purchase_response)`: Acknowledge a purchase
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
- - And many others
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;Ljava/util/List;)V")
14
- def onProductDetailsResponse(self, billing_result, product_details_list):
15
- self.callback(billing_result, product_details_list)
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
- def __init__(self, on_purchases_updated) -> None:
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;")