webirr 1.0.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.
- webirr-1.0.0/LICENSE +21 -0
- webirr-1.0.0/PKG-INFO +493 -0
- webirr-1.0.0/README.md +470 -0
- webirr-1.0.0/pyproject.toml +38 -0
- webirr-1.0.0/setup.cfg +4 -0
- webirr-1.0.0/src/webirr/__init__.py +25 -0
- webirr-1.0.0/src/webirr/client.py +182 -0
- webirr-1.0.0/src/webirr/models.py +293 -0
- webirr-1.0.0/src/webirr/py.typed +1 -0
- webirr-1.0.0/src/webirr.egg-info/PKG-INFO +493 -0
- webirr-1.0.0/src/webirr.egg-info/SOURCES.txt +14 -0
- webirr-1.0.0/src/webirr.egg-info/dependency_links.txt +1 -0
- webirr-1.0.0/src/webirr.egg-info/requires.txt +1 -0
- webirr-1.0.0/src/webirr.egg-info/top_level.txt +1 -0
- webirr-1.0.0/tests/test_client.py +297 -0
- webirr-1.0.0/tests/test_live_test_env.py +158 -0
webirr-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) WeBirr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
webirr-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webirr
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python Client Library for WeBirr Payment Gateway APIs
|
|
5
|
+
Author: WeBirr
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/webirr/webirr-api-python-client
|
|
8
|
+
Project-URL: Source, https://github.com/webirr/webirr-api-python-client
|
|
9
|
+
Project-URL: Issues, https://github.com/webirr/webirr-api-python-client/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests>=2.28
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
Official Python Client Library for WeBirr Payment Gateway APIs
|
|
25
|
+
|
|
26
|
+
This Client Library provides convenient access to WeBirr Payment Gateway APIs from Python Applications.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
$ pip install webirr
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For local development from this repository:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
$ pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
The library needs to be configured with a *merchant Id* & *API key*. You can get it by contacting [webirr.com](https://webirr.net)
|
|
43
|
+
|
|
44
|
+
> You can use this library for production or test environments. you will need to set is_test_env=True for test, and False for production apps when creating objects of class WeBirrClient
|
|
45
|
+
|
|
46
|
+
Examples assume the WeBirr TestEnv and read credentials from environment variables:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export WEBIRR_TEST_ENV_MERCHANT_ID="YOUR_TEST_MERCHANT_ID"
|
|
50
|
+
export WEBIRR_TEST_ENV_API_KEY="YOUR_TEST_API_KEY"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Create the client with merchant ID, API key, and environment once. The client automatically sets `Bill.merchant_id` before sending bill create/update requests, so application code and examples should not set `merchant_id` on the bill object.
|
|
54
|
+
|
|
55
|
+
## Example
|
|
56
|
+
|
|
57
|
+
The examples below keep the PHP and .NET README flow: create the client, call the API, check `error`, handle the success branch, and print `error_code` on failure.
|
|
58
|
+
|
|
59
|
+
### Creating a new Bill / Updating an existing Bill on WeBirr Servers
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import os
|
|
63
|
+
from datetime import datetime
|
|
64
|
+
|
|
65
|
+
from webirr import Bill, WeBirrClient
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main():
|
|
69
|
+
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
|
|
70
|
+
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
|
|
71
|
+
|
|
72
|
+
# api_key = "YOUR_API_KEY"
|
|
73
|
+
# merchant_id = "YOUR_MERCHANT_ID"
|
|
74
|
+
|
|
75
|
+
api = WeBirrClient(merchant_id, api_key, True)
|
|
76
|
+
|
|
77
|
+
bill = Bill()
|
|
78
|
+
bill.amount = "270.90"
|
|
79
|
+
bill.customer_code = "cc01" # it can be email address or phone number if you dont have customer code
|
|
80
|
+
bill.customer_name = "Elias Haileselassie"
|
|
81
|
+
bill.customer_phone = "0911000000" # optional; used for SMS notification when enabled for the merchant
|
|
82
|
+
bill.time = "2021-07-22 22:14" # your bill time, always in this format
|
|
83
|
+
bill.description = "hotel booking"
|
|
84
|
+
bill.bill_reference = "python/example/" + datetime.now().strftime("%Y%m%d%H%M%S") # your unique reference number
|
|
85
|
+
|
|
86
|
+
print("\nCreating Bill...")
|
|
87
|
+
res = api.create_bill(bill)
|
|
88
|
+
|
|
89
|
+
if not res.error:
|
|
90
|
+
# success
|
|
91
|
+
payment_code = res.res # returns paymentcode such as 429 723 975
|
|
92
|
+
print(f"\nPayment Code = {payment_code}") # we may want to save payment code in local db.
|
|
93
|
+
else:
|
|
94
|
+
# fail
|
|
95
|
+
print(f"\nerror: {res.error}")
|
|
96
|
+
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT_DUP_REF
|
|
97
|
+
|
|
98
|
+
# Update existing bill if it is not paid
|
|
99
|
+
bill.amount = "278.00"
|
|
100
|
+
bill.customer_name = "Elias python"
|
|
101
|
+
# bill.bill_reference = "WE CAN NOT CHANGE THIS"
|
|
102
|
+
|
|
103
|
+
print("\nUpdating Bill...")
|
|
104
|
+
res = api.update_bill(bill)
|
|
105
|
+
|
|
106
|
+
if not res.error:
|
|
107
|
+
# success
|
|
108
|
+
print("\nbill is updated succesfully") # res.res will be 'OK' no need to check here!
|
|
109
|
+
else:
|
|
110
|
+
# fail
|
|
111
|
+
print(f"\nerror: {res.error}")
|
|
112
|
+
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
main()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Getting a Bill and Listing Bills
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import os
|
|
122
|
+
|
|
123
|
+
from webirr import WeBirrClient
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main():
|
|
127
|
+
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
|
|
128
|
+
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
|
|
129
|
+
|
|
130
|
+
api = WeBirrClient(merchant_id, api_key, True)
|
|
131
|
+
|
|
132
|
+
bill_reference = "YOUR_BILL_REFERENCE" # BILL_REFERENCE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL
|
|
133
|
+
payment_code = "YOUR_PAYMENT_CODE" # PAYMENT_CODE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL
|
|
134
|
+
|
|
135
|
+
print("\nGetting bill by reference...")
|
|
136
|
+
res = api.get_bill_by_reference(bill_reference)
|
|
137
|
+
if not res.error:
|
|
138
|
+
# success
|
|
139
|
+
print("\nBill found by reference.")
|
|
140
|
+
print(f"\nBill Reference: {res.res.bill_reference}")
|
|
141
|
+
print(f"\nPayment Code: {res.res.wbc_code}")
|
|
142
|
+
print(f"\nAmount: {res.res.amount}")
|
|
143
|
+
print(f"\nPayment Status: {res.res.payment_status}")
|
|
144
|
+
print(f"\nUpdate Timestamp: {res.res.update_time_stamp}")
|
|
145
|
+
else:
|
|
146
|
+
# fail
|
|
147
|
+
print(f"\nError: {res.error}")
|
|
148
|
+
print(f"\nError Code: {res.error_code}")
|
|
149
|
+
|
|
150
|
+
print("\nGetting bill by payment code...")
|
|
151
|
+
res = api.get_bill_by_payment_code(payment_code)
|
|
152
|
+
if not res.error:
|
|
153
|
+
# success
|
|
154
|
+
print("\nBill found by payment code.")
|
|
155
|
+
print(f"\nBill Reference: {res.res.bill_reference}")
|
|
156
|
+
print(f"\nPayment Code: {res.res.wbc_code}")
|
|
157
|
+
else:
|
|
158
|
+
# fail
|
|
159
|
+
print(f"\nError: {res.error}")
|
|
160
|
+
print(f"\nError Code: {res.error_code}")
|
|
161
|
+
|
|
162
|
+
print("\nListing bills...")
|
|
163
|
+
payment_status = -1 # -1 all, 0 pending, 1 unconfirmed payment, 2 paid.
|
|
164
|
+
last_time_stamp = "20251231" # Date-only cursor; use "20251231235959" when you need time precision.
|
|
165
|
+
limit = 10
|
|
166
|
+
|
|
167
|
+
res = api.get_bills(payment_status, last_time_stamp, limit)
|
|
168
|
+
if not res.error:
|
|
169
|
+
# success
|
|
170
|
+
print(f"\nBills returned: {len(res.res)}")
|
|
171
|
+
for bill in res.res:
|
|
172
|
+
print("\n-----------------------------")
|
|
173
|
+
print(f"\nBill Reference: {bill.bill_reference}")
|
|
174
|
+
print(f"\nPayment Code: {bill.wbc_code}")
|
|
175
|
+
print(f"\nAmount: {bill.amount}")
|
|
176
|
+
print(f"\nPayment Status: {bill.payment_status}")
|
|
177
|
+
print(f"\nUpdate Timestamp: {bill.update_time_stamp}")
|
|
178
|
+
else:
|
|
179
|
+
# fail
|
|
180
|
+
print(f"\nError: {res.error}")
|
|
181
|
+
print(f"\nError Code: {res.error_code}")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
main()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Timestamp cursors can be date-only (`yyyyMMdd`) or include time (`yyyyMMddHHmmss`). Use empty string only when you intentionally want all history from the beginning.
|
|
188
|
+
|
|
189
|
+
### Getting Payment status of an existing Bill from WeBirr Servers
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
import os
|
|
193
|
+
|
|
194
|
+
from webirr import WeBirrClient
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def main():
|
|
198
|
+
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
|
|
199
|
+
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
|
|
200
|
+
|
|
201
|
+
# api_key = "YOUR_API_KEY"
|
|
202
|
+
# merchant_id = "YOUR_MERCHANT_ID"
|
|
203
|
+
|
|
204
|
+
api = WeBirrClient(merchant_id, api_key, True)
|
|
205
|
+
payment_code = "PAYMENT_CODE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL"
|
|
206
|
+
|
|
207
|
+
print("\nGetting Payment Status...")
|
|
208
|
+
res = api.get_payment_status(payment_code)
|
|
209
|
+
|
|
210
|
+
if not res.error:
|
|
211
|
+
# success
|
|
212
|
+
if res.res and res.res.is_paid:
|
|
213
|
+
payment = res.res.data
|
|
214
|
+
print("\nbill is paid")
|
|
215
|
+
print("\nbill payment detail")
|
|
216
|
+
print(f"\nBank: {payment.bank_id}")
|
|
217
|
+
print(f"\nBank Reference Number: {payment.payment_reference}")
|
|
218
|
+
print(f"\nAmount Paid: {payment.amount}")
|
|
219
|
+
print(f"\nPayment Date: {payment.payment_date}")
|
|
220
|
+
else:
|
|
221
|
+
print("\nbill is pending payment")
|
|
222
|
+
else:
|
|
223
|
+
# fail
|
|
224
|
+
print(f"\nerror: {res.error}")
|
|
225
|
+
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
main()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
*Sample object returned from getPaymentStatus()*
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
sample = {
|
|
235
|
+
"error": None,
|
|
236
|
+
"res": {
|
|
237
|
+
"status": 2,
|
|
238
|
+
"data": {
|
|
239
|
+
"status": 2,
|
|
240
|
+
"id": 111219507,
|
|
241
|
+
"bankID": "cbe_mobile",
|
|
242
|
+
"paymentReference": "TX70e78862148f4c249606",
|
|
243
|
+
"paymentDate": "2025-02-26 22:17:19",
|
|
244
|
+
"confirmed": True,
|
|
245
|
+
"confirmedTime": "2025-02-26 22:17:19",
|
|
246
|
+
"amount": "278",
|
|
247
|
+
"wbcCode": "149 233 514",
|
|
248
|
+
"updateTimeStamp": "2025022622171981338",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
"errorCode": None,
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Use `payment_date` as the payment time field. `time` remains available as a deprecated backward-compatible alias.
|
|
256
|
+
|
|
257
|
+
### Deleting an existing Bill from WeBirr Servers (if it is not paid)
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import os
|
|
261
|
+
|
|
262
|
+
from webirr import WeBirrClient
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def main():
|
|
266
|
+
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
|
|
267
|
+
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
|
|
268
|
+
|
|
269
|
+
# api_key = "YOUR_API_KEY"
|
|
270
|
+
# merchant_id = "YOUR_MERCHANT_ID"
|
|
271
|
+
|
|
272
|
+
api = WeBirrClient(merchant_id, api_key, True)
|
|
273
|
+
payment_code = "PAYMENT_CODE_YOU_SAVED_AFTER_CREATING_A_NEW_BILL"
|
|
274
|
+
|
|
275
|
+
print("\nDeleting Bill...")
|
|
276
|
+
res = api.delete_bill(payment_code)
|
|
277
|
+
|
|
278
|
+
if not res.error:
|
|
279
|
+
# success
|
|
280
|
+
print("\nbill is deleted succesfully") # res.res will be 'OK' no need to check here!
|
|
281
|
+
else:
|
|
282
|
+
# fail
|
|
283
|
+
print(f"\nerror: {res.error}")
|
|
284
|
+
print(f"\nerrorCode: {res.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
main()
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Getting list of Payments and process them with Bulk Polling Consumer
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
import os
|
|
294
|
+
import time
|
|
295
|
+
|
|
296
|
+
from webirr import WeBirrClient
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class PaymentProcessor:
|
|
300
|
+
def __init__(self):
|
|
301
|
+
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
|
|
302
|
+
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
|
|
303
|
+
self.api = WeBirrClient(merchant_id, api_key, True)
|
|
304
|
+
self.last_time_stamp = "20251231" # Example cursor. Save the last processed payment updateTimeStamp in your database.
|
|
305
|
+
|
|
306
|
+
def run_once(self):
|
|
307
|
+
print("\nRetrieving Payments...")
|
|
308
|
+
self.fetch_and_process_payments()
|
|
309
|
+
|
|
310
|
+
def run_forever(self):
|
|
311
|
+
while True:
|
|
312
|
+
self.run_once()
|
|
313
|
+
print("\nSleeping for 5 seconds...")
|
|
314
|
+
time.sleep(5)
|
|
315
|
+
|
|
316
|
+
def fetch_and_process_payments(self):
|
|
317
|
+
limit = 100 # Number of records to retrieve depending on your processing requirement & capacity
|
|
318
|
+
response = self.api.get_payments(self.last_time_stamp, limit)
|
|
319
|
+
|
|
320
|
+
if not response.error:
|
|
321
|
+
# success
|
|
322
|
+
if len(response.res) == 0:
|
|
323
|
+
print("\nNo new payments found.")
|
|
324
|
+
for payment in response.res:
|
|
325
|
+
self.process_payment(payment)
|
|
326
|
+
print("\n-----------------------------")
|
|
327
|
+
|
|
328
|
+
if len(response.res) > 0:
|
|
329
|
+
self.last_time_stamp = response.res[-1].update_time_stamp
|
|
330
|
+
print(f"\nLast Timestamp: {self.last_time_stamp}") # save updateTimeStamp to your database for the next get_payments() call
|
|
331
|
+
else:
|
|
332
|
+
# fail
|
|
333
|
+
print(f"\nerror: {response.error}")
|
|
334
|
+
print(f"\nerrorCode: {response.error_code}") # can be used to handle specific business error such as ERROR_INVALID_INPUT
|
|
335
|
+
|
|
336
|
+
def process_payment(self, payment):
|
|
337
|
+
# Process Payment should be implemented as idempotent operation for production use cases.
|
|
338
|
+
# This method and logic can be shared among all payment processing consumers: 1. bulk polling, 2. webhook, 3. single payment polling.
|
|
339
|
+
print(f"\nPayment Status: {payment.status}")
|
|
340
|
+
if payment.is_paid:
|
|
341
|
+
print("\nPayment Status Text: Paid.")
|
|
342
|
+
if payment.is_reversed:
|
|
343
|
+
print("\nPayment Status Text: Reversed.")
|
|
344
|
+
print(f"\nBank: {payment.bank_id}")
|
|
345
|
+
print(f"\nBank Reference Number: {payment.payment_reference}")
|
|
346
|
+
print(f"\nAmount Paid: {payment.amount}")
|
|
347
|
+
print(f"\nPayment Date: {payment.payment_date}")
|
|
348
|
+
print(f"\nReversal/Cancel Date: {payment.canceled_time}")
|
|
349
|
+
print(f"\nUpdate Timestamp: {payment.update_time_stamp}")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
PaymentProcessor().run_once()
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Bulk polling should persist `updateTimeStamp` only after processing the batch successfully. Polling processors should be idempotent because duplicate/redundant reads are possible.
|
|
356
|
+
|
|
357
|
+
### Webhooks - Payment processing using Webhook Callbacks
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
import hmac
|
|
361
|
+
import json
|
|
362
|
+
import os
|
|
363
|
+
|
|
364
|
+
from webirr import PaymentResponse
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class Webhook:
|
|
368
|
+
# Webhook handler for processing payment updates from WeBirr.
|
|
369
|
+
# This endpoint should be hosted on a secure server with HTTPS enabled.
|
|
370
|
+
def handle_request(self, method, provided_auth_key, raw_payload):
|
|
371
|
+
# Validate request method is POST
|
|
372
|
+
if method.upper() != "POST":
|
|
373
|
+
return self.json_response(405, {"error": "Method Not Allowed. POST required."})
|
|
374
|
+
|
|
375
|
+
# Authenticate using authKey query string parameter.
|
|
376
|
+
if not self.is_authenticated(provided_auth_key):
|
|
377
|
+
return self.json_response(403, {"error": "Unauthorized access. Invalid authKey."})
|
|
378
|
+
|
|
379
|
+
if not raw_payload:
|
|
380
|
+
return self.json_response(400, {"error": "Empty request body."})
|
|
381
|
+
|
|
382
|
+
try:
|
|
383
|
+
payload = json.loads(raw_payload)
|
|
384
|
+
except json.JSONDecodeError:
|
|
385
|
+
return self.json_response(400, {"error": "Invalid JSON format."})
|
|
386
|
+
|
|
387
|
+
payment_data = payload.get("data", payload)
|
|
388
|
+
if not payment_data:
|
|
389
|
+
return self.json_response(400, {"error": "Invalid payment data."})
|
|
390
|
+
|
|
391
|
+
payment = PaymentResponse.from_dict(payment_data)
|
|
392
|
+
self.process_payment(payment)
|
|
393
|
+
|
|
394
|
+
return self.json_response(200, {"success": True, "message": "Payment received and queued for processing"})
|
|
395
|
+
|
|
396
|
+
def is_authenticated(self, provided_auth_key):
|
|
397
|
+
expected_auth_key = os.getenv("WEBIRR_WEBHOOK_AUTH_KEY", "YOUR_WEBHOOK_AUTH_KEY")
|
|
398
|
+
return bool(expected_auth_key) and hmac.compare_digest(expected_auth_key, provided_auth_key or "")
|
|
399
|
+
|
|
400
|
+
def process_payment(self, payment):
|
|
401
|
+
# Process Payment should be implemented as idempotent operation for production use cases.
|
|
402
|
+
# This method and logic can be shared among all payment processing consumers: 1. bulk polling, 2. webhook, 3. single payment polling.
|
|
403
|
+
print(f"\nPayment Status: {payment.status}")
|
|
404
|
+
if payment.is_paid:
|
|
405
|
+
print("\nPayment Status Text: Paid.")
|
|
406
|
+
if payment.is_reversed:
|
|
407
|
+
print("\nPayment Status Text: Reversed.")
|
|
408
|
+
print(f"\nBank: {payment.bank_id}")
|
|
409
|
+
print(f"\nBank Reference Number: {payment.payment_reference}")
|
|
410
|
+
print(f"\nAmount Paid: {payment.amount}")
|
|
411
|
+
print(f"\nPayment Date: {payment.payment_date}")
|
|
412
|
+
print(f"\nReversal/Cancel Date: {payment.canceled_time}")
|
|
413
|
+
print(f"\nUpdate Timestamp: {payment.update_time_stamp}")
|
|
414
|
+
|
|
415
|
+
def json_response(self, status_code, body):
|
|
416
|
+
return {"status_code": status_code, "content_type": "application/json", "body": json.dumps(body)}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# Once hosted, the webhook URL needs to be shared with WeBirr for configuration.
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Gettting basic Statistics about bills created and payments received for a date range
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
import os
|
|
426
|
+
|
|
427
|
+
from webirr import WeBirrClient
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def main():
|
|
431
|
+
api_key = os.getenv("WEBIRR_TEST_ENV_API_KEY", "")
|
|
432
|
+
merchant_id = os.getenv("WEBIRR_TEST_ENV_MERCHANT_ID", "")
|
|
433
|
+
|
|
434
|
+
# api_key = "YOUR_API_KEY"
|
|
435
|
+
# merchant_id = "YOUR_MERCHANT_ID"
|
|
436
|
+
|
|
437
|
+
api = WeBirrClient(merchant_id, api_key, True)
|
|
438
|
+
|
|
439
|
+
date_from = "2025-01-01" # YYYY-MM-DD
|
|
440
|
+
date_to = "2030-01-31" # YYYY-MM-DD
|
|
441
|
+
|
|
442
|
+
print("\nRetrieving Statistics...")
|
|
443
|
+
print(f"\nDate From: {date_from}")
|
|
444
|
+
print(f"\nDate To: {date_to}")
|
|
445
|
+
|
|
446
|
+
response = api.get_stat(date_from, date_to)
|
|
447
|
+
|
|
448
|
+
if not response.error:
|
|
449
|
+
# success
|
|
450
|
+
stat = response.res
|
|
451
|
+
print(f"\nNumber of Bills Created: {stat.n_bills}")
|
|
452
|
+
print(f"\nNumber of Paid Bills: {stat.n_bills_paid}")
|
|
453
|
+
print(f"\nNumber of Unpaid Bills: {stat.n_bills_unpaid}")
|
|
454
|
+
print(f"\nAmount of Bills: {stat.amount_bills}")
|
|
455
|
+
print(f"\nAmount Paid: {stat.amount_paid}")
|
|
456
|
+
print(f"\nAmount Unpaid: {stat.amount_unpaid}")
|
|
457
|
+
else:
|
|
458
|
+
# fail
|
|
459
|
+
print(f"\nError: {response.error}")
|
|
460
|
+
print(f"\nError Code: {response.error_code}")
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
main()
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Examples
|
|
467
|
+
|
|
468
|
+
The `examples` directory includes separate workflows matching the PHP SDK examples:
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
python examples/example1_create_update_bill.py
|
|
472
|
+
python examples/example2_payment_status_single_poll.py
|
|
473
|
+
python examples/example3_delete_bill.py
|
|
474
|
+
python examples/example4_payment_status_bulk_poll.py
|
|
475
|
+
python examples/example5_stat_report.py
|
|
476
|
+
python examples/example6_payment_status_webhook.py
|
|
477
|
+
python examples/example7_get_bill_and_list_bills.py
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Reusable HTTP Session
|
|
481
|
+
|
|
482
|
+
For batch or mass bill workloads, pass a configured `requests.Session` so your application can reuse connections, configure adapters, and apply its own retry policy:
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
import requests
|
|
486
|
+
|
|
487
|
+
from webirr import WeBirrClient
|
|
488
|
+
|
|
489
|
+
session = requests.Session()
|
|
490
|
+
api = WeBirrClient(merchant_id, api_key, True, session=session)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
The SDK does not silently retry bill creation. Configure retry behavior in your application so duplicate create/update processing remains under your control.
|