sjbillingclient 0.1.3__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 (21) hide show
  1. sjbillingclient-0.2.0/PKG-INFO +507 -0
  2. sjbillingclient-0.2.0/README.md +490 -0
  3. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/pyproject.toml +2 -1
  4. sjbillingclient-0.2.0/sjbillingclient/jclass/purchase.py +16 -0
  5. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jclass/queryproduct.py +9 -1
  6. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/product.py +4 -3
  7. sjbillingclient-0.2.0/sjbillingclient/tools/__init__.py +447 -0
  8. sjbillingclient-0.1.3/PKG-INFO +0 -29
  9. sjbillingclient-0.1.3/README.md +0 -13
  10. sjbillingclient-0.1.3/sjbillingclient/jclass/purchase.py +0 -8
  11. sjbillingclient-0.1.3/sjbillingclient/tools/__init__.py +0 -144
  12. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/__init__.py +0 -0
  13. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jclass/__init__.py +0 -0
  14. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jclass/acknowledge.py +0 -0
  15. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jclass/billing.py +0 -0
  16. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jclass/consume.py +0 -0
  17. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/__init__.py +0 -0
  18. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/acknowledge.py +0 -0
  19. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/billing.py +0 -0
  20. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/consume.py +0 -0
  21. {sjbillingclient-0.1.3 → sjbillingclient-0.2.0}/sjbillingclient/jinterface/purchases.py +0 -0
@@ -0,0 +1,507 @@
1
+ Metadata-Version: 2.1
2
+ Name: sjbillingclient
3
+ Version: 0.2.0
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
+ # SJBillingClient (Google Play Billing SDK for Python)
18
+
19
+ <!-- GitAds-Verify: 71CCWMQSMVD67LS4WF4N44EXISSL2UTQ -->
20
+ ## GitAds Sponsored
21
+ [![Sponsored by GitAds](https://gitads.dev/v1/ad-serve?source=simplejnius/sj-android-billingclient@github)](https://gitads.dev/v1/ad-track?source=simplejnius/sj-android-billingclient@github)
22
+
23
+ ## Overview
24
+
25
+ SJBillingClient is a Python wrapper for the Google Play Billing Library that facilitates in-app purchases and subscriptions in Android applications. It provides a high-level, Pythonic interface to interact with Google Play's billing system, making it easier to implement and manage in-app purchases in Python-based Android apps (like those built with Kivy/Python-for-Android).
26
+
27
+ ### Key Features
28
+
29
+ - **Simplified Billing Integration**: Easy-to-use Python API for Google Play Billing
30
+ - **Asynchronous Operations**: Non-blocking billing operations
31
+ - **Comprehensive Purchase Management**: Support for querying, purchasing, consuming, and acknowledging products
32
+ - **Product Types Support**: Handles both one-time purchases (INAPP) and subscriptions (SUBS)
33
+ - **Detailed Product Information**: Access to formatted prices, currency codes, and other product details
34
+
35
+ ## Requirements
36
+
37
+ - Python 3.9+
38
+ - pyjnius 1.6.1+
39
+ - Android application with Google Play Billing Library (version 8.0.0 recommended)
40
+
41
+ ## Installation
42
+
43
+ ```shell
44
+ # Using pip
45
+ pip install sjbillingclient
46
+
47
+ # In Buildozer (add to buildozer.spec)
48
+ requirements = sjbillingclient
49
+ android.gradle_dependencies = com.android.billingclient:billing:8.0.0
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ Here's a basic example of how to initialize the billing client and start a connection:
55
+
56
+ ```python
57
+ from sjbillingclient.tools import BillingClient
58
+ from sjbillingclient.jclass.billing import ProductType, BillingResponseCode
59
+
60
+ # Define callback for purchase updates
61
+ def on_purchases_updated(billing_result, is_null, purchases):
62
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
63
+ if not is_null:
64
+ for purchase in purchases:
65
+ print(f"Purchase: {purchase.getProducts().get(0)}")
66
+ # Handle purchase here
67
+
68
+ # Create billing client
69
+ client = BillingClient(on_purchases_updated)
70
+
71
+ # Start connection
72
+ client.start_connection(
73
+ on_billing_setup_finished=lambda result: print(f"Billing setup complete: {result.getResponseCode()}"),
74
+ on_billing_service_disconnected=lambda: print("Billing service disconnected")
75
+ )
76
+ ```
77
+
78
+ ## Usage Examples
79
+
80
+ ### Querying Product Details
81
+
82
+ ```python
83
+ from sjbillingclient.tools import BillingClient
84
+ from sjbillingclient.jclass.billing import ProductType, BillingResponseCode
85
+
86
+ def on_product_details_response(billing_result, product_details_list):
87
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
88
+ if product_details_list and not product_details_list.isEmpty():
89
+ # Process product details
90
+ for i in range(product_details_list.size()):
91
+ product_detail = product_details_list.get(i)
92
+ print(f"Product: {product_detail.getProductId()}")
93
+
94
+ # Get formatted details
95
+ details = client.get_product_details(product_detail, ProductType.INAPP)
96
+ for detail in details:
97
+ print(f"Price: {detail['formatted_price']}")
98
+
99
+ # Query product details
100
+ client.query_product_details_async(
101
+ product_type=ProductType.INAPP,
102
+ products_ids=["product_id_1", "product_id_2"],
103
+ on_product_details_response=on_product_details_response
104
+ )
105
+ ```
106
+
107
+ ### Launching a Purchase Flow
108
+
109
+ ```python
110
+ from sjbillingclient.tools import BillingClient
111
+ from sjbillingclient.jclass.billing import ProductType, BillingResponseCode
112
+
113
+ def on_product_details_response(billing_result, product_details_list):
114
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
115
+ if product_details_list and not product_details_list.isEmpty():
116
+ # Launch billing flow with the first product
117
+ product_detail = product_details_list.get(0)
118
+ result = client.launch_billing_flow([product_detail])
119
+ print(f"Launch billing flow result: {result.getResponseCode()}")
120
+
121
+ # Query product details and then launch purchase
122
+ client.query_product_details_async(
123
+ product_type=ProductType.INAPP,
124
+ products_ids=["product_id"],
125
+ on_product_details_response=on_product_details_response
126
+ )
127
+ ```
128
+
129
+ ### Consuming a Purchase
130
+
131
+ ```python
132
+ from sjbillingclient.tools import BillingClient
133
+ from sjbillingclient.jclass.billing import BillingResponseCode
134
+
135
+ def on_consume_response(billing_result, purchase_token):
136
+ print(f"Consume result: {billing_result.getResponseCode()}")
137
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
138
+ print(f"Successfully consumed: {purchase_token}")
139
+
140
+ # Consume a purchase
141
+ client.consume_async(purchase, on_consume_response)
142
+ ```
143
+
144
+ ### Acknowledging a Purchase
145
+
146
+ ```python
147
+ from sjbillingclient.tools import BillingClient
148
+ from sjbillingclient.jclass.billing import BillingResponseCode
149
+
150
+ def on_acknowledge_purchase_response(billing_result):
151
+ print(f"Acknowledge result: {billing_result.getResponseCode()}")
152
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
153
+ print("Successfully acknowledged purchase")
154
+
155
+ # Acknowledge a purchase
156
+ client.acknowledge_purchase(purchase.getPurchaseToken(), on_acknowledge_purchase_response)
157
+ ```
158
+
159
+ ### Kivy Integration Example
160
+
161
+ Here's a complete example of integrating SJBillingClient with a Kivy application:
162
+
163
+ #### Python Code (main.py)
164
+
165
+ ```python
166
+ from os.path import join, dirname, basename
167
+ from kivy.app import App
168
+ from kivy.lang import Builder
169
+ from kivy.uix.screenmanager import ScreenManager, Screen
170
+ from sjbillingclient.jclass.billing import BillingResponseCode, ProductType
171
+ from sjbillingclient.tools import BillingClient
172
+
173
+ Builder.load_file(join(dirname(__file__), basename(__file__).split(".")[0] + ".kv"))
174
+
175
+
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
+ """
186
+ billing_client = None
187
+
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
+ """
195
+ if self.billing_client:
196
+ self.billing_client.end_connection()
197
+
198
+ self.billing_client = BillingClient(on_purchases_updated=self.on_purchases_updated)
199
+ self.billing_client.start_connection(
200
+ on_billing_setup_finished=self.on_billing_setup_finished,
201
+ on_billing_service_disconnected=lambda: print("disconnected")
202
+ )
203
+
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
+ """
216
+ if billing_result.getResponseCode() == BillingResponseCode.OK and not null:
217
+ for purchase in purchases:
218
+ if self.ids.subscribe.active:
219
+ self.billing_client.acknowledge_purchase(
220
+ purchase_token=purchase.getPurchaseToken(),
221
+ on_acknowledge_purchase_response=self.on_acknowledge_purchase_response
222
+ )
223
+ else:
224
+ self.billing_client.consume_async(purchase, self.on_consume_response)
225
+ print(billing_result.getResponseCode(), billing_result.getDebugMessage())
226
+
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
+ """
234
+ print(billing_result.getDebugMessage())
235
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
236
+ self.toast("Thank you for subscribing to buy us a cup of coffee! monthly")
237
+
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
+ """
245
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
246
+ self.toast("Thank you for buying us a cup of coffee!")
247
+
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
+
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))
268
+ self.billing_client.launch_billing_flow(product_details=product_details_list)
269
+
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
+ """
279
+ product_id = self.ids.btn.product_id
280
+ if billing_result.getResponseCode() == BillingResponseCode.OK:
281
+ self.billing_client.query_product_details_async(
282
+ product_type=ProductType.SUBS if self.ids.subscribe else ProductType.INAPP,
283
+ products_ids=[product_id],
284
+ on_product_details_response=self.on_product_details_response,
285
+ )
286
+
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
+ """
297
+ # Implementation of toast message (platform specific)
298
+ print(message)
299
+
300
+
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
+ """
308
+ def build(self):
309
+ """
310
+ Build the application UI.
311
+
312
+ Returns:
313
+ ScreenManager: The root widget of the application.
314
+ """
315
+ # Create screen manager
316
+ sm = ScreenManager()
317
+ sm.add_widget(HomeScreen(name='home'))
318
+ return sm
319
+
320
+
321
+ if __name__ == '__main__':
322
+ BillingApp().run()
323
+ ```
324
+
325
+ #### Kivy Layout File (main.kv)
326
+
327
+ ```kivy
328
+ <HomeScreen>:
329
+ BoxLayout:
330
+ orientation: 'vertical'
331
+ padding: '20dp'
332
+ spacing: '10dp'
333
+
334
+ Label:
335
+ text: 'SJBillingClient Demo'
336
+ font_size: '24sp'
337
+ size_hint_y: None
338
+ height: '50dp'
339
+
340
+ BoxLayout:
341
+ orientation: 'horizontal'
342
+ size_hint_y: None
343
+ height: '50dp'
344
+
345
+ Label:
346
+ text: 'Subscribe'
347
+ size_hint_x: 0.5
348
+
349
+ CheckBox:
350
+ id: subscribe
351
+ size_hint_x: 0.5
352
+ active: False
353
+
354
+ Button:
355
+ id: btn
356
+ text: 'Buy Coffee'
357
+ product_id: 'coffee_product_id'
358
+ size_hint_y: None
359
+ height: '60dp'
360
+ on_release: root.support()
361
+
362
+ Widget:
363
+ # Spacer
364
+ ```
365
+
366
+ This example demonstrates:
367
+
368
+ 1. A `HomeScreen` class that extends `Screen` and handles all billing operations
369
+ 2. A `BillingApp` class that sets up the Kivy application and screen manager
370
+ 3. A Kivy layout file that defines the UI with:
371
+ - A checkbox to toggle between one-time purchase and subscription
372
+ - A button to initiate the purchase flow
373
+
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.
382
+
383
+ ## API Reference
384
+
385
+ ### BillingClient
386
+
387
+ The main class for interacting with Google Play Billing.
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
+
468
+ #### Methods
469
+
470
+ - `getProductDetailsList()`: Gets the list of product details
471
+ - `getUnfetchedProductList()`: Gets the list of unfetched products
472
+
473
+ ### ProductType
474
+
475
+ Constants for product types:
476
+
477
+ - `ProductType.INAPP`: One-time purchases
478
+ - `ProductType.SUBS`: Subscriptions
479
+
480
+ ### BillingResponseCode
481
+
482
+ Constants for billing response codes:
483
+
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)
495
+
496
+ ## Contributing
497
+
498
+ Contributions are welcome! Please feel free to submit a Pull Request.
499
+
500
+ ## License
501
+
502
+ This project is licensed under the MIT License - see the LICENSE file for details.
503
+
504
+ ## Author
505
+
506
+ Kenechukwu Akubue <kengoon19@gmail.com>
507
+