sjbillingclient 0.1.4__tar.gz → 0.2.0__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 (18) hide show
  1. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/PKG-INFO +194 -26
  2. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/README.md +193 -25
  3. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/pyproject.toml +1 -1
  4. sjbillingclient-0.2.0/sjbillingclient/jclass/purchase.py +16 -0
  5. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jclass/queryproduct.py +9 -1
  6. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/product.py +4 -3
  7. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/tools/__init__.py +37 -3
  8. sjbillingclient-0.1.4/sjbillingclient/jclass/purchase.py +0 -8
  9. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/__init__.py +0 -0
  10. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jclass/__init__.py +0 -0
  11. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jclass/acknowledge.py +0 -0
  12. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jclass/billing.py +0 -0
  13. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jclass/consume.py +0 -0
  14. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/__init__.py +0 -0
  15. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/acknowledge.py +0 -0
  16. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/billing.py +0 -0
  17. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/consume.py +0 -0
  18. {sjbillingclient-0.1.4 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/purchases.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sjbillingclient
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary:
5
5
  Author: Kenechukwu Akubue
6
6
  Author-email: kengoon19@gmail.com
@@ -36,7 +36,7 @@ SJBillingClient is a Python wrapper for the Google Play Billing Library that fac
36
36
 
37
37
  - Python 3.9+
38
38
  - pyjnius 1.6.1+
39
- - Android application with Google Play Billing Library (version 7.1.1 recommended)
39
+ - Android application with Google Play Billing Library (version 8.0.0 recommended)
40
40
 
41
41
  ## Installation
42
42
 
@@ -46,7 +46,7 @@ pip install sjbillingclient
46
46
 
47
47
  # In Buildozer (add to buildozer.spec)
48
48
  requirements = sjbillingclient
49
- android.gradle_dependencies = com.android.billingclient:billing:7.1.1
49
+ android.gradle_dependencies = com.android.billingclient:billing:8.0.0
50
50
  ```
51
51
 
52
52
  ## Quick Start
@@ -170,14 +170,28 @@ from kivy.uix.screenmanager import ScreenManager, Screen
170
170
  from sjbillingclient.jclass.billing import BillingResponseCode, ProductType
171
171
  from sjbillingclient.tools import BillingClient
172
172
 
173
- # Load the KV file
174
173
  Builder.load_file(join(dirname(__file__), basename(__file__).split(".")[0] + ".kv"))
175
174
 
176
175
 
177
176
  class HomeScreen(Screen):
177
+ """
178
+ A screen that demonstrates Google Play Billing integration with Kivy.
179
+
180
+ This screen provides functionality to make in-app purchases and subscriptions
181
+ using the Google Play Billing Library through the SJBillingClient wrapper.
182
+
183
+ Attributes:
184
+ billing_client (BillingClient): The client used to interact with Google Play Billing.
185
+ """
178
186
  billing_client = None
179
187
 
180
- def purchase_or_subscribe(self):
188
+ def support(self):
189
+ """
190
+ Initializes the billing client and starts a connection to the Google Play Billing service.
191
+
192
+ This method is called when the user wants to make a purchase or subscription.
193
+ If a billing client already exists, it ends the connection before creating a new one.
194
+ """
181
195
  if self.billing_client:
182
196
  self.billing_client.end_connection()
183
197
 
@@ -188,6 +202,17 @@ class HomeScreen(Screen):
188
202
  )
189
203
 
190
204
  def on_purchases_updated(self, billing_result, null, purchases):
205
+ """
206
+ Callback method that is called when purchases are updated.
207
+
208
+ This method handles the result of a purchase flow, either acknowledging
209
+ a subscription or consuming a one-time purchase.
210
+
211
+ Args:
212
+ billing_result: The result of the billing operation.
213
+ null: Boolean indicating if the purchases list is null.
214
+ purchases: List of purchases that were updated.
215
+ """
191
216
  if billing_result.getResponseCode() == BillingResponseCode.OK and not null:
192
217
  for purchase in purchases:
193
218
  if self.ids.subscribe.active:
@@ -197,40 +222,96 @@ class HomeScreen(Screen):
197
222
  )
198
223
  else:
199
224
  self.billing_client.consume_async(purchase, self.on_consume_response)
225
+ print(billing_result.getResponseCode(), billing_result.getDebugMessage())
200
226
 
201
227
  def on_acknowledge_purchase_response(self, billing_result):
228
+ """
229
+ Callback method that is called when a purchase acknowledgement is complete.
230
+
231
+ Args:
232
+ billing_result: The result of the acknowledgement operation.
233
+ """
202
234
  print(billing_result.getDebugMessage())
203
235
  if billing_result.getResponseCode() == BillingResponseCode.OK:
204
236
  self.toast("Thank you for subscribing to buy us a cup of coffee! monthly")
205
237
 
206
238
  def on_consume_response(self, billing_result):
239
+ """
240
+ Callback method that is called when a purchase consumption is complete.
241
+
242
+ Args:
243
+ billing_result: The result of the consumption operation.
244
+ """
207
245
  if billing_result.getResponseCode() == BillingResponseCode.OK:
208
246
  self.toast("Thank you for buying us a cup of coffee!")
209
247
 
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)
248
+ def on_product_details_response(self, billing_result, product_details_result):
249
+ """
250
+ Callback method that is called when product details are retrieved.
251
+
252
+ This method processes the product details and launches the billing flow.
253
+
254
+ Args:
255
+ billing_result: The result of the product details query.
256
+ product_details_result: The result containing product details and unfetched products.
257
+ """
258
+ product_details_list = product_details_result.getProductDetailsList()
259
+ unfetched_product_list = product_details_result.getUnfetchedProductList()
260
+
215
261
  if billing_result.getResponseCode() == BillingResponseCode.OK:
262
+ for product_details in product_details_list:
263
+ self.billing_client.get_product_details(
264
+ product_details,
265
+ ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP)
266
+ for unfetched_product in unfetched_product_list:
267
+ print(self.billing_client.get_unfetched_product(unfetched_product))
216
268
  self.billing_client.launch_billing_flow(product_details=product_details_list)
217
269
 
218
270
  def on_billing_setup_finished(self, billing_result):
271
+ """
272
+ Callback method that is called when the billing setup is complete.
273
+
274
+ This method queries product details if the billing setup was successful.
275
+
276
+ Args:
277
+ billing_result: The result of the billing setup operation.
278
+ """
219
279
  product_id = self.ids.btn.product_id
220
280
  if billing_result.getResponseCode() == BillingResponseCode.OK:
221
281
  self.billing_client.query_product_details_async(
222
- product_type=ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP,
282
+ product_type=ProductType.SUBS if self.ids.subscribe else ProductType.INAPP,
223
283
  products_ids=[product_id],
224
284
  on_product_details_response=self.on_product_details_response,
225
285
  )
226
286
 
227
287
  def toast(self, message):
288
+ """
289
+ Display a toast message.
290
+
291
+ This is a simple implementation that just prints the message.
292
+ In a real app, you would use platform-specific toast functionality.
293
+
294
+ Args:
295
+ message: The message to display.
296
+ """
228
297
  # Implementation of toast message (platform specific)
229
298
  print(message)
230
299
 
231
300
 
232
301
  class BillingApp(App):
302
+ """
303
+ Main application class for the SJBillingClient demo.
304
+
305
+ This class sets up the application and creates the screen manager
306
+ with the HomeScreen.
307
+ """
233
308
  def build(self):
309
+ """
310
+ Build the application UI.
311
+
312
+ Returns:
313
+ ScreenManager: The root widget of the application.
314
+ """
234
315
  # Create screen manager
235
316
  sm = ScreenManager()
236
317
  sm.add_widget(HomeScreen(name='home'))
@@ -276,7 +357,7 @@ if __name__ == '__main__':
276
357
  product_id: 'coffee_product_id'
277
358
  size_hint_y: None
278
359
  height: '60dp'
279
- on_release: root.purchase_or_subscribe()
360
+ on_release: root.support()
280
361
 
281
362
  Widget:
282
363
  # Spacer
@@ -284,13 +365,20 @@ if __name__ == '__main__':
284
365
 
285
366
  This example demonstrates:
286
367
 
287
- 1. A `HomeScreen` class that handles all billing operations
368
+ 1. A `HomeScreen` class that extends `Screen` and handles all billing operations
288
369
  2. A `BillingApp` class that sets up the Kivy application and screen manager
289
370
  3. A Kivy layout file that defines the UI with:
290
371
  - A checkbox to toggle between one-time purchase and subscription
291
372
  - A button to initiate the purchase flow
292
373
 
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.
374
+ 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:
375
+ - Handling purchase updates with `on_purchases_updated`
376
+ - Acknowledging subscription purchases with `acknowledge_purchase`
377
+ - Consuming one-time purchases with `consume_async`
378
+ - Processing product details with `on_product_details_response`, including handling unfetched products
379
+ - Querying product details with `query_product_details_async`
380
+
381
+ 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
382
 
295
383
  ## API Reference
296
384
 
@@ -298,16 +386,89 @@ The `purchase_or_subscribe` method is called when the button is pressed, which i
298
386
 
299
387
  The main class for interacting with Google Play Billing.
300
388
 
389
+ #### Constructor
390
+
391
+ - `__init__(on_purchases_updated, enable_one_time_products=True, enable_prepaid_plans=False)`:
392
+ - Initializes a new BillingClient instance
393
+ - `on_purchases_updated`: Callback function that will be triggered when purchases are updated
394
+ - `enable_one_time_products`: Boolean to enable one-time products (default: True)
395
+ - `enable_prepaid_plans`: Boolean to enable prepaid plans (default: False)
396
+
397
+ #### Connection Methods
398
+
399
+ - `start_connection(on_billing_setup_finished, on_billing_service_disconnected)`:
400
+ - Starts a connection with the billing client
401
+ - `on_billing_setup_finished`: Callback when billing setup is complete
402
+ - `on_billing_service_disconnected`: Callback when billing service is disconnected
403
+
404
+ - `end_connection()`:
405
+ - Ends the connection with the billing client
406
+
407
+ #### Product Details Methods
408
+
409
+ - `query_product_details_async(product_type, products_ids, on_product_details_response)`:
410
+ - Queries product details asynchronously
411
+ - `product_type`: Type of products (INAPP or SUBS)
412
+ - `products_ids`: List of product IDs to query
413
+ - `on_product_details_response`: Callback for product details response
414
+
415
+ - `get_product_details(product_details, product_type)`:
416
+ - Gets formatted product details
417
+ - `product_details`: Product details object
418
+ - `product_type`: Type of product (INAPP or SUBS)
419
+ - Returns a list of dictionaries with product details
420
+
421
+ - `get_unfetched_product(unfetched_product)`:
422
+ - Gets details about an unfetched product
423
+ - `unfetched_product`: Unfetched product object
424
+ - Returns a dictionary with product ID, type, and status code
425
+
426
+ #### Purchase Methods
427
+
428
+ - `launch_billing_flow(product_details, offer_token=None)`:
429
+ - Launches the billing flow for purchase
430
+ - `product_details`: List of product details objects
431
+ - `offer_token`: Optional token for subscription offers
432
+
433
+ - `consume_async(purchase, on_consume_response)`:
434
+ - Consumes a purchase asynchronously
435
+ - `purchase`: Purchase object to consume
436
+ - `on_consume_response`: Callback for consume response
437
+
438
+ - `acknowledge_purchase(purchase_token, on_acknowledge_purchase_response)`:
439
+ - Acknowledges a purchase
440
+ - `purchase_token`: Token of the purchase to acknowledge
441
+ - `on_acknowledge_purchase_response`: Callback for acknowledge response
442
+
443
+ ### PendingPurchasesParams
444
+
445
+ Parameters for handling pending purchases.
446
+
447
+ #### Methods
448
+
449
+ - `newBuilder()`: Creates a new builder for PendingPurchasesParams
450
+ - `build()`: Builds the PendingPurchasesParams object
451
+ - `enableOneTimeProducts()`: Enables one-time products
452
+ - `enablePrepaidPlans()`: Enables prepaid plans
453
+
454
+ ### QueryProductDetailsParams
455
+
456
+ Parameters for querying product details.
457
+
458
+ #### Methods
459
+
460
+ - `newBuilder()`: Creates a new builder for QueryProductDetailsParams
461
+ - `setProductList(product_list)`: Sets the list of products to query
462
+ - `build()`: Builds the QueryProductDetailsParams object
463
+
464
+ ### QueryProductDetailsResult
465
+
466
+ Result of a product details query.
467
+
301
468
  #### Methods
302
469
 
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
470
+ - `getProductDetailsList()`: Gets the list of product details
471
+ - `getUnfetchedProductList()`: Gets the list of unfetched products
311
472
 
312
473
  ### ProductType
313
474
 
@@ -320,10 +481,17 @@ Constants for product types:
320
481
 
321
482
  Constants for billing response codes:
322
483
 
323
- - `BillingResponseCode.OK`: Success
324
- - `BillingResponseCode.USER_CANCELED`: User canceled
325
- - `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable
326
- - And many others
484
+ - `BillingResponseCode.OK`: Success (0)
485
+ - `BillingResponseCode.USER_CANCELED`: User canceled (1)
486
+ - `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable (2)
487
+ - `BillingResponseCode.BILLING_UNAVAILABLE`: Billing unavailable (3)
488
+ - `BillingResponseCode.ITEM_UNAVAILABLE`: Item unavailable (4)
489
+ - `BillingResponseCode.DEVELOPER_ERROR`: Developer error (5)
490
+ - `BillingResponseCode.ERROR`: General error (6)
491
+ - `BillingResponseCode.ITEM_ALREADY_OWNED`: Item already owned (7)
492
+ - `BillingResponseCode.ITEM_NOT_OWNED`: Item not owned (8)
493
+ - `BillingResponseCode.SERVICE_DISCONNECTED`: Service disconnected (10)
494
+ - `BillingResponseCode.FEATURE_NOT_SUPPORTED`: Feature not supported (12)
327
495
 
328
496
  ## Contributing
329
497
 
@@ -20,7 +20,7 @@ SJBillingClient is a Python wrapper for the Google Play Billing Library that fac
20
20
 
21
21
  - Python 3.9+
22
22
  - pyjnius 1.6.1+
23
- - 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)
24
24
 
25
25
  ## Installation
26
26
 
@@ -30,7 +30,7 @@ pip install sjbillingclient
30
30
 
31
31
  # In Buildozer (add to buildozer.spec)
32
32
  requirements = sjbillingclient
33
- android.gradle_dependencies = com.android.billingclient:billing:7.1.1
33
+ android.gradle_dependencies = com.android.billingclient:billing:8.0.0
34
34
  ```
35
35
 
36
36
  ## Quick Start
@@ -154,14 +154,28 @@ from kivy.uix.screenmanager import ScreenManager, Screen
154
154
  from sjbillingclient.jclass.billing import BillingResponseCode, ProductType
155
155
  from sjbillingclient.tools import BillingClient
156
156
 
157
- # Load the KV file
158
157
  Builder.load_file(join(dirname(__file__), basename(__file__).split(".")[0] + ".kv"))
159
158
 
160
159
 
161
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
+ """
162
170
  billing_client = None
163
171
 
164
- 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
+ """
165
179
  if self.billing_client:
166
180
  self.billing_client.end_connection()
167
181
 
@@ -172,6 +186,17 @@ class HomeScreen(Screen):
172
186
  )
173
187
 
174
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
+ """
175
200
  if billing_result.getResponseCode() == BillingResponseCode.OK and not null:
176
201
  for purchase in purchases:
177
202
  if self.ids.subscribe.active:
@@ -181,40 +206,96 @@ class HomeScreen(Screen):
181
206
  )
182
207
  else:
183
208
  self.billing_client.consume_async(purchase, self.on_consume_response)
209
+ print(billing_result.getResponseCode(), billing_result.getDebugMessage())
184
210
 
185
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
+ """
186
218
  print(billing_result.getDebugMessage())
187
219
  if billing_result.getResponseCode() == BillingResponseCode.OK:
188
220
  self.toast("Thank you for subscribing to buy us a cup of coffee! monthly")
189
221
 
190
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
+ """
191
229
  if billing_result.getResponseCode() == BillingResponseCode.OK:
192
230
  self.toast("Thank you for buying us a cup of coffee!")
193
231
 
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)
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
+
199
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))
200
252
  self.billing_client.launch_billing_flow(product_details=product_details_list)
201
253
 
202
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
+ """
203
263
  product_id = self.ids.btn.product_id
204
264
  if billing_result.getResponseCode() == BillingResponseCode.OK:
205
265
  self.billing_client.query_product_details_async(
206
- product_type=ProductType.SUBS if self.ids.subscribe.active else ProductType.INAPP,
266
+ product_type=ProductType.SUBS if self.ids.subscribe else ProductType.INAPP,
207
267
  products_ids=[product_id],
208
268
  on_product_details_response=self.on_product_details_response,
209
269
  )
210
270
 
211
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
+ """
212
281
  # Implementation of toast message (platform specific)
213
282
  print(message)
214
283
 
215
284
 
216
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
+ """
217
292
  def build(self):
293
+ """
294
+ Build the application UI.
295
+
296
+ Returns:
297
+ ScreenManager: The root widget of the application.
298
+ """
218
299
  # Create screen manager
219
300
  sm = ScreenManager()
220
301
  sm.add_widget(HomeScreen(name='home'))
@@ -260,7 +341,7 @@ if __name__ == '__main__':
260
341
  product_id: 'coffee_product_id'
261
342
  size_hint_y: None
262
343
  height: '60dp'
263
- on_release: root.purchase_or_subscribe()
344
+ on_release: root.support()
264
345
 
265
346
  Widget:
266
347
  # Spacer
@@ -268,13 +349,20 @@ if __name__ == '__main__':
268
349
 
269
350
  This example demonstrates:
270
351
 
271
- 1. A `HomeScreen` class that handles all billing operations
352
+ 1. A `HomeScreen` class that extends `Screen` and handles all billing operations
272
353
  2. A `BillingApp` class that sets up the Kivy application and screen manager
273
354
  3. A Kivy layout file that defines the UI with:
274
355
  - A checkbox to toggle between one-time purchase and subscription
275
356
  - A button to initiate the purchase flow
276
357
 
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.
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.
278
366
 
279
367
  ## API Reference
280
368
 
@@ -282,16 +370,89 @@ The `purchase_or_subscribe` method is called when the button is pressed, which i
282
370
 
283
371
  The main class for interacting with Google Play Billing.
284
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
+
431
+ #### Methods
432
+
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
+
285
452
  #### Methods
286
453
 
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
454
+ - `getProductDetailsList()`: Gets the list of product details
455
+ - `getUnfetchedProductList()`: Gets the list of unfetched products
295
456
 
296
457
  ### ProductType
297
458
 
@@ -304,10 +465,17 @@ Constants for product types:
304
465
 
305
466
  Constants for billing response codes:
306
467
 
307
- - `BillingResponseCode.OK`: Success
308
- - `BillingResponseCode.USER_CANCELED`: User canceled
309
- - `BillingResponseCode.SERVICE_UNAVAILABLE`: Service unavailable
310
- - 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)
311
479
 
312
480
  ## Contributing
313
481
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sjbillingclient"
3
- version = "0.1.4"
3
+ version = "0.2.0"
4
4
  description = ""
5
5
  authors = ["Kenechukwu Akubue <kengoon19@gmail.com>"]
6
6
  readme = "README.md"
@@ -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,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;")