smartpaystack 0.1.0__tar.gz → 0.1.2__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.
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/PKG-INFO +33 -6
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/README.md +32 -5
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/pyproject.toml +1 -1
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/smartpaystack.egg-info/PKG-INFO +33 -6
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/smartpaystack.egg-info/SOURCES.txt +0 -6
- smartpaystack-0.1.0/calculator.py +0 -37
- smartpaystack-0.1.0/client.py +0 -127
- smartpaystack-0.1.0/config.py +0 -30
- smartpaystack-0.1.0/enums.py +0 -21
- smartpaystack-0.1.0/exceptions.py +0 -11
- smartpaystack-0.1.0/webhooks.py +0 -26
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/setup.cfg +0 -0
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/smartpaystack.egg-info/dependency_links.txt +0 -0
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/smartpaystack.egg-info/requires.txt +0 -0
- {smartpaystack-0.1.0 → smartpaystack-0.1.2}/smartpaystack.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartpaystack
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A smart, strategy-based, multi-currency Paystack SDK for Python.
|
|
5
5
|
Author-email: Fidelis Chukwunyere <fidelchukwunyere@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -43,7 +43,7 @@ pip install smartpaystack
|
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## 🚀 Quickstart
|
|
47
47
|
|
|
48
48
|
### 1. Initialization
|
|
49
49
|
|
|
@@ -99,7 +99,26 @@ print(response["authorization_url"])
|
|
|
99
99
|
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
### 3.
|
|
102
|
+
### 3. Passing Custom Metadata
|
|
103
|
+
|
|
104
|
+
You can easily attach your own custom data (like order IDs or user IDs) to a charge. The package will safely merge your custom dictionary with its own internal fee calculations so you can access both later in your webhook!
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
response = client.create_charge(
|
|
108
|
+
email="buyer@email.com",
|
|
109
|
+
amount=15000,
|
|
110
|
+
charge_strategy=ChargeStrategy.PASS,
|
|
111
|
+
metadata={
|
|
112
|
+
"custom_order_id": "ORD-88291",
|
|
113
|
+
"cart_items": 3
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
*When this transaction succeeds, your webhook will receive your custom fields alongside `smartpaystack_strategy`, `merchant_expected`, and `customer_amount`.*
|
|
120
|
+
|
|
121
|
+
### 4. Sending Money (Transfers)
|
|
103
122
|
|
|
104
123
|
Sending money is a two-step process: create a recipient, then initiate the transfer.
|
|
105
124
|
|
|
@@ -108,11 +127,12 @@ Sending money is a two-step process: create a recipient, then initiate the trans
|
|
|
108
127
|
account = client.resolve_account_number(account_number="0123456789", bank_code="033")
|
|
109
128
|
print(f"Resolved Name: {account['account_name']}")
|
|
110
129
|
|
|
111
|
-
# 2. Create the recipient
|
|
130
|
+
# 2. Create the recipient (You can pass metadata here, too!)
|
|
112
131
|
recipient = client.create_transfer_recipient(
|
|
113
132
|
name=account["account_name"],
|
|
114
133
|
account_number="0123456789",
|
|
115
|
-
bank_code="033"
|
|
134
|
+
bank_code="033",
|
|
135
|
+
metadata={"internal_worker_id": "W-990"}
|
|
116
136
|
)
|
|
117
137
|
recipient_code = recipient["recipient_code"]
|
|
118
138
|
|
|
@@ -184,7 +204,12 @@ async def paystack_webhook(request: Request, x_paystack_signature: str = Header(
|
|
|
184
204
|
|
|
185
205
|
# Handle the event
|
|
186
206
|
if event_data["event"] == "charge.success":
|
|
187
|
-
|
|
207
|
+
data = event_data["data"]
|
|
208
|
+
# Retrieve the math breakdown and your custom metadata!
|
|
209
|
+
merchant_keeps = data["metadata"]["merchant_expected"]
|
|
210
|
+
order_id = data["metadata"].get("custom_order_id")
|
|
211
|
+
|
|
212
|
+
print(f"Payment successful for Order {order_id}! Expected payout: {merchant_keeps}")
|
|
188
213
|
|
|
189
214
|
return {"status": "success"}
|
|
190
215
|
|
|
@@ -251,3 +276,5 @@ def paystack_webhook(request):
|
|
|
251
276
|
|
|
252
277
|
```
|
|
253
278
|
|
|
279
|
+
```
|
|
280
|
+
|
|
@@ -23,7 +23,7 @@ pip install smartpaystack
|
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
|
-
##
|
|
26
|
+
## 🚀 Quickstart
|
|
27
27
|
|
|
28
28
|
### 1. Initialization
|
|
29
29
|
|
|
@@ -79,7 +79,26 @@ print(response["authorization_url"])
|
|
|
79
79
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
### 3.
|
|
82
|
+
### 3. Passing Custom Metadata
|
|
83
|
+
|
|
84
|
+
You can easily attach your own custom data (like order IDs or user IDs) to a charge. The package will safely merge your custom dictionary with its own internal fee calculations so you can access both later in your webhook!
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
response = client.create_charge(
|
|
88
|
+
email="buyer@email.com",
|
|
89
|
+
amount=15000,
|
|
90
|
+
charge_strategy=ChargeStrategy.PASS,
|
|
91
|
+
metadata={
|
|
92
|
+
"custom_order_id": "ORD-88291",
|
|
93
|
+
"cart_items": 3
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
*When this transaction succeeds, your webhook will receive your custom fields alongside `smartpaystack_strategy`, `merchant_expected`, and `customer_amount`.*
|
|
100
|
+
|
|
101
|
+
### 4. Sending Money (Transfers)
|
|
83
102
|
|
|
84
103
|
Sending money is a two-step process: create a recipient, then initiate the transfer.
|
|
85
104
|
|
|
@@ -88,11 +107,12 @@ Sending money is a two-step process: create a recipient, then initiate the trans
|
|
|
88
107
|
account = client.resolve_account_number(account_number="0123456789", bank_code="033")
|
|
89
108
|
print(f"Resolved Name: {account['account_name']}")
|
|
90
109
|
|
|
91
|
-
# 2. Create the recipient
|
|
110
|
+
# 2. Create the recipient (You can pass metadata here, too!)
|
|
92
111
|
recipient = client.create_transfer_recipient(
|
|
93
112
|
name=account["account_name"],
|
|
94
113
|
account_number="0123456789",
|
|
95
|
-
bank_code="033"
|
|
114
|
+
bank_code="033",
|
|
115
|
+
metadata={"internal_worker_id": "W-990"}
|
|
96
116
|
)
|
|
97
117
|
recipient_code = recipient["recipient_code"]
|
|
98
118
|
|
|
@@ -164,7 +184,12 @@ async def paystack_webhook(request: Request, x_paystack_signature: str = Header(
|
|
|
164
184
|
|
|
165
185
|
# Handle the event
|
|
166
186
|
if event_data["event"] == "charge.success":
|
|
167
|
-
|
|
187
|
+
data = event_data["data"]
|
|
188
|
+
# Retrieve the math breakdown and your custom metadata!
|
|
189
|
+
merchant_keeps = data["metadata"]["merchant_expected"]
|
|
190
|
+
order_id = data["metadata"].get("custom_order_id")
|
|
191
|
+
|
|
192
|
+
print(f"Payment successful for Order {order_id}! Expected payout: {merchant_keeps}")
|
|
168
193
|
|
|
169
194
|
return {"status": "success"}
|
|
170
195
|
|
|
@@ -231,3 +256,5 @@ def paystack_webhook(request):
|
|
|
231
256
|
|
|
232
257
|
```
|
|
233
258
|
|
|
259
|
+
```
|
|
260
|
+
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "smartpaystack"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "A smart, strategy-based, multi-currency Paystack SDK for Python."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Fidelis Chukwunyere", email = "fidelchukwunyere@gmail.com" }]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartpaystack
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A smart, strategy-based, multi-currency Paystack SDK for Python.
|
|
5
5
|
Author-email: Fidelis Chukwunyere <fidelchukwunyere@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -43,7 +43,7 @@ pip install smartpaystack
|
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## 🚀 Quickstart
|
|
47
47
|
|
|
48
48
|
### 1. Initialization
|
|
49
49
|
|
|
@@ -99,7 +99,26 @@ print(response["authorization_url"])
|
|
|
99
99
|
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
### 3.
|
|
102
|
+
### 3. Passing Custom Metadata
|
|
103
|
+
|
|
104
|
+
You can easily attach your own custom data (like order IDs or user IDs) to a charge. The package will safely merge your custom dictionary with its own internal fee calculations so you can access both later in your webhook!
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
response = client.create_charge(
|
|
108
|
+
email="buyer@email.com",
|
|
109
|
+
amount=15000,
|
|
110
|
+
charge_strategy=ChargeStrategy.PASS,
|
|
111
|
+
metadata={
|
|
112
|
+
"custom_order_id": "ORD-88291",
|
|
113
|
+
"cart_items": 3
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
*When this transaction succeeds, your webhook will receive your custom fields alongside `smartpaystack_strategy`, `merchant_expected`, and `customer_amount`.*
|
|
120
|
+
|
|
121
|
+
### 4. Sending Money (Transfers)
|
|
103
122
|
|
|
104
123
|
Sending money is a two-step process: create a recipient, then initiate the transfer.
|
|
105
124
|
|
|
@@ -108,11 +127,12 @@ Sending money is a two-step process: create a recipient, then initiate the trans
|
|
|
108
127
|
account = client.resolve_account_number(account_number="0123456789", bank_code="033")
|
|
109
128
|
print(f"Resolved Name: {account['account_name']}")
|
|
110
129
|
|
|
111
|
-
# 2. Create the recipient
|
|
130
|
+
# 2. Create the recipient (You can pass metadata here, too!)
|
|
112
131
|
recipient = client.create_transfer_recipient(
|
|
113
132
|
name=account["account_name"],
|
|
114
133
|
account_number="0123456789",
|
|
115
|
-
bank_code="033"
|
|
134
|
+
bank_code="033",
|
|
135
|
+
metadata={"internal_worker_id": "W-990"}
|
|
116
136
|
)
|
|
117
137
|
recipient_code = recipient["recipient_code"]
|
|
118
138
|
|
|
@@ -184,7 +204,12 @@ async def paystack_webhook(request: Request, x_paystack_signature: str = Header(
|
|
|
184
204
|
|
|
185
205
|
# Handle the event
|
|
186
206
|
if event_data["event"] == "charge.success":
|
|
187
|
-
|
|
207
|
+
data = event_data["data"]
|
|
208
|
+
# Retrieve the math breakdown and your custom metadata!
|
|
209
|
+
merchant_keeps = data["metadata"]["merchant_expected"]
|
|
210
|
+
order_id = data["metadata"].get("custom_order_id")
|
|
211
|
+
|
|
212
|
+
print(f"Payment successful for Order {order_id}! Expected payout: {merchant_keeps}")
|
|
188
213
|
|
|
189
214
|
return {"status": "success"}
|
|
190
215
|
|
|
@@ -251,3 +276,5 @@ def paystack_webhook(request):
|
|
|
251
276
|
|
|
252
277
|
```
|
|
253
278
|
|
|
279
|
+
```
|
|
280
|
+
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from decimal import Decimal, ROUND_HALF_UP
|
|
2
|
-
from .enums import Currency
|
|
3
|
-
from .config import CURRENCY_RULES
|
|
4
|
-
|
|
5
|
-
class PaystackFeeCalculator:
|
|
6
|
-
"""Calculates Paystack transaction fees accurately to avoid floating-point errors."""
|
|
7
|
-
|
|
8
|
-
@classmethod
|
|
9
|
-
def calculate_fee(cls, amount: Decimal, currency: Currency = Currency.NGN) -> Decimal:
|
|
10
|
-
rule = CURRENCY_RULES.get(currency, CURRENCY_RULES[Currency.NGN])
|
|
11
|
-
amount = Decimal(str(amount))
|
|
12
|
-
|
|
13
|
-
fee = amount * rule["percentage"]
|
|
14
|
-
|
|
15
|
-
if rule["flat_fee"] > 0 and amount >= rule["threshold"]:
|
|
16
|
-
fee += rule["flat_fee"]
|
|
17
|
-
|
|
18
|
-
if rule["cap"] is not None:
|
|
19
|
-
fee = min(fee, rule["cap"])
|
|
20
|
-
|
|
21
|
-
return fee.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
|
22
|
-
|
|
23
|
-
@classmethod
|
|
24
|
-
def gross_up_amount(cls, desired_amount: Decimal, currency: Currency = Currency.NGN) -> Decimal:
|
|
25
|
-
"""Calculates the exact amount to charge a customer so the merchant receives the `desired_amount`."""
|
|
26
|
-
rule = CURRENCY_RULES.get(currency, CURRENCY_RULES[Currency.NGN])
|
|
27
|
-
desired_amount = Decimal(str(desired_amount))
|
|
28
|
-
|
|
29
|
-
gross = desired_amount / (Decimal("1") - rule["percentage"])
|
|
30
|
-
|
|
31
|
-
if rule["flat_fee"] > 0 and gross >= rule["threshold"]:
|
|
32
|
-
gross = (desired_amount + rule["flat_fee"]) / (Decimal("1") - rule["percentage"])
|
|
33
|
-
|
|
34
|
-
if rule["cap"] is not None and (gross - desired_amount) > rule["cap"]:
|
|
35
|
-
gross = desired_amount + rule["cap"]
|
|
36
|
-
|
|
37
|
-
return gross.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
smartpaystack-0.1.0/client.py
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import uuid
|
|
3
|
-
import requests
|
|
4
|
-
from decimal import Decimal
|
|
5
|
-
from typing import Optional, Dict, Any, Union
|
|
6
|
-
|
|
7
|
-
from .calculator import PaystackFeeCalculator
|
|
8
|
-
from .enums import ChargeStrategy, Currency, RecipientType, TransferSource
|
|
9
|
-
from .exceptions import PaystackAPIError
|
|
10
|
-
|
|
11
|
-
class SmartPaystack:
|
|
12
|
-
"""The main client for interacting with the Paystack API."""
|
|
13
|
-
BASE_URL = "https://api.paystack.co"
|
|
14
|
-
|
|
15
|
-
def __init__(self, secret_key: Optional[str] = None) -> None:
|
|
16
|
-
self.secret_key = secret_key or os.environ.get("PAYSTACK_SECRET_KEY")
|
|
17
|
-
|
|
18
|
-
if not self.secret_key:
|
|
19
|
-
raise ValueError(
|
|
20
|
-
"Paystack secret key missing. Pass it to the client "
|
|
21
|
-
"or set the 'PAYSTACK_SECRET_KEY' environment variable."
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
self.headers = {
|
|
25
|
-
"Authorization": f"Bearer {self.secret_key}",
|
|
26
|
-
"Content-Type": "application/json",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
def _request(self, method: str, endpoint: str, data: Optional[Dict] = None, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
30
|
-
url = f"{self.BASE_URL}{endpoint}"
|
|
31
|
-
try:
|
|
32
|
-
response = requests.request(
|
|
33
|
-
method, url, json=data, params=params, headers=self.headers, timeout=30
|
|
34
|
-
)
|
|
35
|
-
result = response.json()
|
|
36
|
-
except requests.RequestException as e:
|
|
37
|
-
raise PaystackAPIError(f"Network error: {str(e)}")
|
|
38
|
-
except ValueError:
|
|
39
|
-
raise PaystackAPIError("Invalid JSON response from Paystack.")
|
|
40
|
-
|
|
41
|
-
if not response.ok or not result.get("status"):
|
|
42
|
-
raise PaystackAPIError(result.get("message", "API Request Failed"))
|
|
43
|
-
|
|
44
|
-
return result.get("data", result)
|
|
45
|
-
|
|
46
|
-
def create_charge(
|
|
47
|
-
self,
|
|
48
|
-
email: str,
|
|
49
|
-
amount: Union[int, float, Decimal],
|
|
50
|
-
currency: Currency = Currency.NGN,
|
|
51
|
-
charge_strategy: ChargeStrategy = ChargeStrategy.ABSORB,
|
|
52
|
-
split_ratio: float = 0.5,
|
|
53
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
54
|
-
**kwargs: Any
|
|
55
|
-
) -> Dict[str, Any]:
|
|
56
|
-
"""Initializes a transaction applying the correct fee strategy automatically."""
|
|
57
|
-
amount = Decimal(str(amount))
|
|
58
|
-
fee = PaystackFeeCalculator.calculate_fee(amount, currency)
|
|
59
|
-
|
|
60
|
-
if charge_strategy == ChargeStrategy.PASS:
|
|
61
|
-
customer_amount = PaystackFeeCalculator.gross_up_amount(amount, currency)
|
|
62
|
-
elif charge_strategy == ChargeStrategy.SPLIT:
|
|
63
|
-
customer_amount = amount + (fee * Decimal(str(split_ratio)))
|
|
64
|
-
else:
|
|
65
|
-
customer_amount = amount
|
|
66
|
-
|
|
67
|
-
payload = {
|
|
68
|
-
"email": email,
|
|
69
|
-
"amount": int(customer_amount * 100), # Convert to lowest denomination (kobo/cents)
|
|
70
|
-
"currency": currency.value,
|
|
71
|
-
"reference": kwargs.pop("reference", str(uuid.uuid4())),
|
|
72
|
-
"metadata": {
|
|
73
|
-
"smartpaystack_strategy": charge_strategy.value,
|
|
74
|
-
"merchant_expected": float(amount),
|
|
75
|
-
"customer_amount": float(customer_amount),
|
|
76
|
-
**(metadata or {}),
|
|
77
|
-
},
|
|
78
|
-
**kwargs
|
|
79
|
-
}
|
|
80
|
-
return self._request("POST", "/transaction/initialize", data=payload)
|
|
81
|
-
|
|
82
|
-
def resolve_account_number(self, account_number: str, bank_code: str) -> Dict[str, Any]:
|
|
83
|
-
"""Verifies an account number and bank code before creating a recipient."""
|
|
84
|
-
params = {"account_number": account_number, "bank_code": bank_code}
|
|
85
|
-
return self._request("GET", "/bank/resolve", params=params)
|
|
86
|
-
|
|
87
|
-
def create_transfer_recipient(
|
|
88
|
-
self,
|
|
89
|
-
name: str,
|
|
90
|
-
account_number: str,
|
|
91
|
-
bank_code: str,
|
|
92
|
-
recipient_type: RecipientType = RecipientType.NUBAN,
|
|
93
|
-
currency: Currency = Currency.NGN,
|
|
94
|
-
description: str = "",
|
|
95
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
96
|
-
) -> Dict[str, Any]:
|
|
97
|
-
"""Creates a recipient code needed to initiate a transfer."""
|
|
98
|
-
payload = {
|
|
99
|
-
"type": recipient_type.value,
|
|
100
|
-
"name": name,
|
|
101
|
-
"account_number": account_number,
|
|
102
|
-
"bank_code": bank_code,
|
|
103
|
-
"currency": currency.value,
|
|
104
|
-
"description": description,
|
|
105
|
-
"metadata": metadata or {}
|
|
106
|
-
}
|
|
107
|
-
return self._request("POST", "/transferrecipient", data=payload)
|
|
108
|
-
|
|
109
|
-
def initiate_transfer(
|
|
110
|
-
self,
|
|
111
|
-
amount: Union[int, float, Decimal],
|
|
112
|
-
recipient_code: str,
|
|
113
|
-
currency: Currency = Currency.NGN,
|
|
114
|
-
source: TransferSource = TransferSource.BALANCE,
|
|
115
|
-
reason: str = "",
|
|
116
|
-
reference: Optional[str] = None
|
|
117
|
-
) -> Dict[str, Any]:
|
|
118
|
-
"""Initiates a transfer from your Paystack balance to the recipient."""
|
|
119
|
-
payload = {
|
|
120
|
-
"source": source.value,
|
|
121
|
-
"amount": int(Decimal(str(amount)) * 100), # Convert to kobo/cents
|
|
122
|
-
"currency": currency.value,
|
|
123
|
-
"recipient": recipient_code,
|
|
124
|
-
"reason": reason,
|
|
125
|
-
"reference": reference or str(uuid.uuid4())
|
|
126
|
-
}
|
|
127
|
-
return self._request("POST", "/transfer", data=payload)
|
smartpaystack-0.1.0/config.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from decimal import Decimal
|
|
2
|
-
from .enums import Currency
|
|
3
|
-
|
|
4
|
-
# Official Paystack fee structures per currency
|
|
5
|
-
CURRENCY_RULES = {
|
|
6
|
-
Currency.NGN: {
|
|
7
|
-
"percentage": Decimal("0.015"),
|
|
8
|
-
"flat_fee": Decimal("100"),
|
|
9
|
-
"threshold": Decimal("2500"),
|
|
10
|
-
"cap": Decimal("2000"),
|
|
11
|
-
},
|
|
12
|
-
Currency.GHS: {
|
|
13
|
-
"percentage": Decimal("0.0195"),
|
|
14
|
-
"flat_fee": Decimal("0"),
|
|
15
|
-
"threshold": Decimal("0"),
|
|
16
|
-
"cap": None,
|
|
17
|
-
},
|
|
18
|
-
Currency.ZAR: {
|
|
19
|
-
"percentage": Decimal("0.029"),
|
|
20
|
-
"flat_fee": Decimal("1"),
|
|
21
|
-
"threshold": Decimal("0"),
|
|
22
|
-
"cap": None,
|
|
23
|
-
},
|
|
24
|
-
Currency.KES: {
|
|
25
|
-
"percentage": Decimal("0.029"),
|
|
26
|
-
"flat_fee": Decimal("0"),
|
|
27
|
-
"threshold": Decimal("0"),
|
|
28
|
-
"cap": None,
|
|
29
|
-
},
|
|
30
|
-
}
|
smartpaystack-0.1.0/enums.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
|
|
3
|
-
class ChargeStrategy(str, Enum):
|
|
4
|
-
ABSORB = "absorb"
|
|
5
|
-
PASS = "pass"
|
|
6
|
-
SPLIT = "split"
|
|
7
|
-
|
|
8
|
-
class Currency(str, Enum):
|
|
9
|
-
NGN = "NGN"
|
|
10
|
-
GHS = "GHS"
|
|
11
|
-
ZAR = "ZAR"
|
|
12
|
-
KES = "KES"
|
|
13
|
-
USD = "USD"
|
|
14
|
-
|
|
15
|
-
class RecipientType(str, Enum):
|
|
16
|
-
NUBAN = "nuban"
|
|
17
|
-
MOBILE_MONEY = "mobile_money"
|
|
18
|
-
BASA = "basa"
|
|
19
|
-
|
|
20
|
-
class TransferSource(str, Enum):
|
|
21
|
-
BALANCE = "balance"
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
class PaystackError(Exception):
|
|
2
|
-
"""Base exception for all SmartPaystack errors."""
|
|
3
|
-
pass
|
|
4
|
-
|
|
5
|
-
class PaystackAPIError(PaystackError):
|
|
6
|
-
"""Raised when the Paystack API returns an error or fails."""
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
class WebhookVerificationError(PaystackError):
|
|
10
|
-
"""Raised when a webhook signature fails validation."""
|
|
11
|
-
pass
|
smartpaystack-0.1.0/webhooks.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import hmac
|
|
2
|
-
import hashlib
|
|
3
|
-
import json
|
|
4
|
-
from typing import Dict, Any
|
|
5
|
-
from .exceptions import WebhookVerificationError
|
|
6
|
-
|
|
7
|
-
class WebhookVerifier:
|
|
8
|
-
"""A framework-agnostic utility for validating Paystack webhook signatures."""
|
|
9
|
-
|
|
10
|
-
def __init__(self, secret_key: str):
|
|
11
|
-
self.secret_key = secret_key.encode("utf-8")
|
|
12
|
-
|
|
13
|
-
def verify_and_parse(self, payload: bytes, signature: str) -> Dict[str, Any]:
|
|
14
|
-
if not signature:
|
|
15
|
-
raise WebhookVerificationError("Missing Paystack signature header")
|
|
16
|
-
|
|
17
|
-
# Paystack uses HMAC SHA512 to sign webhooks
|
|
18
|
-
hash_obj = hmac.new(self.secret_key, msg=payload, digestmod=hashlib.sha512)
|
|
19
|
-
|
|
20
|
-
if not hmac.compare_digest(hash_obj.hexdigest(), signature):
|
|
21
|
-
raise WebhookVerificationError("Invalid Paystack webhook signature")
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
return json.loads(payload)
|
|
25
|
-
except json.JSONDecodeError:
|
|
26
|
-
raise WebhookVerificationError("Invalid JSON payload")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|