reevit 0.9.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reevit-0.9.1/LICENSE +21 -0
- reevit-0.9.1/PKG-INFO +452 -0
- reevit-0.9.1/README.md +425 -0
- reevit-0.9.1/reevit/__init__.py +11 -0
- reevit-0.9.1/reevit/_version.py +6 -0
- reevit-0.9.1/reevit/client.py +92 -0
- reevit-0.9.1/reevit/services/__init__.py +2 -0
- reevit-0.9.1/reevit/services/checkout_sessions.py +10 -0
- reevit-0.9.1/reevit/services/connections.py +55 -0
- reevit-0.9.1/reevit/services/customers.py +39 -0
- reevit-0.9.1/reevit/services/fraud.py +11 -0
- reevit-0.9.1/reevit/services/invoices.py +30 -0
- reevit-0.9.1/reevit/services/payment_links.py +42 -0
- reevit-0.9.1/reevit/services/payments.py +54 -0
- reevit-0.9.1/reevit/services/routing_rules.py +30 -0
- reevit-0.9.1/reevit/services/subscriptions.py +27 -0
- reevit-0.9.1/reevit/services/webhooks.py +43 -0
- reevit-0.9.1/reevit/webhooks.py +53 -0
- reevit-0.9.1/reevit.egg-info/PKG-INFO +452 -0
- reevit-0.9.1/reevit.egg-info/SOURCES.txt +24 -0
- reevit-0.9.1/reevit.egg-info/dependency_links.txt +1 -0
- reevit-0.9.1/reevit.egg-info/requires.txt +1 -0
- reevit-0.9.1/reevit.egg-info/top_level.txt +1 -0
- reevit-0.9.1/setup.cfg +4 -0
- reevit-0.9.1/setup.py +36 -0
- reevit-0.9.1/tests/test_webhooks.py +50 -0
reevit-0.9.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Reevit
|
|
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.
|
reevit-0.9.1/PKG-INFO
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reevit
|
|
3
|
+
Version: 0.9.1
|
|
4
|
+
Summary: Reevit Python SDK
|
|
5
|
+
Home-page: https://github.com/Reevit-Platform/python-sdk
|
|
6
|
+
Author: Reevit
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Documentation, https://docs.reevit.io
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.6
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests>=2.25.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
Dynamic: project-url
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# Reevit Python SDK
|
|
29
|
+
|
|
30
|
+
The official Python SDK for [Reevit](https://reevit.io) — a unified payment orchestration platform for Africa.
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/reevit/)
|
|
33
|
+
[](https://pypi.org/project/reevit/)
|
|
34
|
+
[](https://opensource.org/licenses/MIT)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install reevit==0.9.1
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from reevit import Reevit
|
|
46
|
+
|
|
47
|
+
client = Reevit(api_key="pfk_live_xxx", org_id="org_123")
|
|
48
|
+
|
|
49
|
+
# Create a payment
|
|
50
|
+
try:
|
|
51
|
+
payment = client.payments.create_intent({
|
|
52
|
+
"amount": 5000, # 50.00 GHS
|
|
53
|
+
"currency": "GHS",
|
|
54
|
+
"method": "momo",
|
|
55
|
+
"country": "GH",
|
|
56
|
+
"customer_id": "cust_123",
|
|
57
|
+
"metadata": {
|
|
58
|
+
"order_id": "12345"
|
|
59
|
+
}
|
|
60
|
+
}, idempotency_key="order_12345")
|
|
61
|
+
print(f"Payment created: {payment['id']}")
|
|
62
|
+
except Exception as e:
|
|
63
|
+
print(f"Error: {e}")
|
|
64
|
+
|
|
65
|
+
# List payments
|
|
66
|
+
payments = client.payments.list()
|
|
67
|
+
print(payments)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Server-created checkout sessions
|
|
71
|
+
|
|
72
|
+
Create checkout sessions on your server, then pass `session["session_secret"]` to a browser SDK — [`@reevit/react`](https://www.npmjs.com/package/@reevit/react), [`@reevit/vue`](https://www.npmjs.com/package/@reevit/vue), or [`@reevit/svelte`](https://www.npmjs.com/package/@reevit/svelte) — to render the checkout UI.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
session = client.checkout_sessions.create(
|
|
76
|
+
{
|
|
77
|
+
"amount": 5000,
|
|
78
|
+
"currency": "GHS",
|
|
79
|
+
"method": "mobile_money",
|
|
80
|
+
"country": "GH",
|
|
81
|
+
},
|
|
82
|
+
idempotency_key="order_12345",
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Idempotency
|
|
87
|
+
|
|
88
|
+
Pass `idempotency_key` to safely retry intent creation without duplicates.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
payment = client.payments.create_intent(
|
|
92
|
+
{
|
|
93
|
+
"amount": 5000,
|
|
94
|
+
"currency": "GHS",
|
|
95
|
+
"method": "momo",
|
|
96
|
+
"country": "GH",
|
|
97
|
+
},
|
|
98
|
+
idempotency_key="order_12345",
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Features
|
|
103
|
+
|
|
104
|
+
- **Payments**: Create intents, update intents, confirm, confirm intent, cancel, retry, refund, stats
|
|
105
|
+
- **Connections**: Manage PSP integrations, validation, labels, status, audit
|
|
106
|
+
- **Subscriptions**: Manage recurring billing lifecycle
|
|
107
|
+
- **Fraud**: Configure fraud rules
|
|
108
|
+
- **Customers / Payment Links / Checkout Sessions / Webhooks / Routing Rules / Invoices**: Additional backend services
|
|
109
|
+
|
|
110
|
+
`org_id` is supported directly on the client. Omitting it for authenticated requests still works for backward compatibility, but that mode is deprecated.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Webhook Verification
|
|
115
|
+
|
|
116
|
+
Reevit sends webhooks to notify your application of payment events. Always verify webhook signatures.
|
|
117
|
+
|
|
118
|
+
### Understanding Webhooks
|
|
119
|
+
|
|
120
|
+
There are **two types of webhooks** in Reevit:
|
|
121
|
+
|
|
122
|
+
1. **Inbound Webhooks (PSP → Reevit)**: Webhooks from payment providers (Paystack, Flutterwave, etc.) to Reevit. Configure these in the PSP dashboard. Reevit handles them automatically.
|
|
123
|
+
|
|
124
|
+
2. **Outbound Webhooks (Reevit → Your App)**: Webhooks from Reevit to your application. Configure in Reevit Dashboard and create a handler in your app.
|
|
125
|
+
|
|
126
|
+
### Signature Format
|
|
127
|
+
|
|
128
|
+
- **Header**: `X-Reevit-Signature: sha256=<hex-signature>`
|
|
129
|
+
- **Signature**: `HMAC-SHA256(request_body, signing_secret)`
|
|
130
|
+
|
|
131
|
+
### Getting Your Signing Secret
|
|
132
|
+
|
|
133
|
+
1. Go to **Reevit Dashboard > Developers > Webhooks**
|
|
134
|
+
2. Configure your webhook endpoint URL
|
|
135
|
+
3. Copy the signing secret (starts with `whsec_`)
|
|
136
|
+
4. Set environment variable: `REEVIT_WEBHOOK_SECRET=whsec_xxx...`
|
|
137
|
+
|
|
138
|
+
### Flask Webhook Handler
|
|
139
|
+
|
|
140
|
+
The SDK ships a constant-time verifier — `verify_webhook_signature(payload, signature, secret)` — so you do not have to reimplement HMAC. Pass the **raw** request body (not parsed-and-reserialized JSON), the `X-Reevit-Signature` header, and your signing secret.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import os
|
|
144
|
+
import logging
|
|
145
|
+
from dataclasses import dataclass
|
|
146
|
+
from typing import Optional, Dict, Any
|
|
147
|
+
from flask import Flask, request, jsonify
|
|
148
|
+
from reevit import verify_webhook_signature
|
|
149
|
+
|
|
150
|
+
app = Flask(__name__)
|
|
151
|
+
logging.basicConfig(level=logging.INFO)
|
|
152
|
+
logger = logging.getLogger(__name__)
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class PaymentData:
|
|
156
|
+
id: str
|
|
157
|
+
status: str
|
|
158
|
+
amount: int
|
|
159
|
+
currency: str
|
|
160
|
+
provider: str
|
|
161
|
+
customer_id: Optional[str] = None
|
|
162
|
+
metadata: Optional[Dict[str, str]] = None
|
|
163
|
+
|
|
164
|
+
@dataclass
|
|
165
|
+
class SubscriptionData:
|
|
166
|
+
id: str
|
|
167
|
+
customer_id: str
|
|
168
|
+
plan_id: str
|
|
169
|
+
status: str
|
|
170
|
+
amount: int
|
|
171
|
+
currency: str
|
|
172
|
+
interval: str
|
|
173
|
+
next_renewal_at: Optional[str] = None
|
|
174
|
+
|
|
175
|
+
@app.route('/webhooks/reevit', methods=['POST'])
|
|
176
|
+
def webhook():
|
|
177
|
+
payload = request.get_data() # raw bytes — do not re-serialize
|
|
178
|
+
signature = request.headers.get('X-Reevit-Signature', '')
|
|
179
|
+
secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
|
|
180
|
+
|
|
181
|
+
# Verify signature (required in production)
|
|
182
|
+
if not verify_webhook_signature(payload, signature, secret):
|
|
183
|
+
logger.warning('[Webhook] Invalid signature')
|
|
184
|
+
return jsonify({'error': 'Invalid signature'}), 401
|
|
185
|
+
|
|
186
|
+
event = request.get_json()
|
|
187
|
+
event_type = event.get('type')
|
|
188
|
+
event_id = event.get('id')
|
|
189
|
+
|
|
190
|
+
logger.info(f'[Webhook] Received: {event_type} ({event_id})')
|
|
191
|
+
|
|
192
|
+
# Handle different event types
|
|
193
|
+
if event_type == 'reevit.webhook.test':
|
|
194
|
+
logger.info(f'[Webhook] Test received: {event.get("message")}')
|
|
195
|
+
|
|
196
|
+
# Payment events
|
|
197
|
+
elif event_type == 'payment.succeeded':
|
|
198
|
+
data = PaymentData(**event.get('data', {}))
|
|
199
|
+
handle_payment_succeeded(data)
|
|
200
|
+
|
|
201
|
+
elif event_type == 'payment.failed':
|
|
202
|
+
data = PaymentData(**event.get('data', {}))
|
|
203
|
+
handle_payment_failed(data)
|
|
204
|
+
|
|
205
|
+
elif event_type == 'payment.refunded':
|
|
206
|
+
data = PaymentData(**event.get('data', {}))
|
|
207
|
+
handle_payment_refunded(data)
|
|
208
|
+
|
|
209
|
+
elif event_type == 'payment.pending':
|
|
210
|
+
data = PaymentData(**event.get('data', {}))
|
|
211
|
+
logger.info(f'[Webhook] Payment pending: {data.id}')
|
|
212
|
+
|
|
213
|
+
# Subscription events
|
|
214
|
+
elif event_type == 'subscription.created':
|
|
215
|
+
data = SubscriptionData(**event.get('data', {}))
|
|
216
|
+
handle_subscription_created(data)
|
|
217
|
+
|
|
218
|
+
elif event_type == 'subscription.renewed':
|
|
219
|
+
data = SubscriptionData(**event.get('data', {}))
|
|
220
|
+
handle_subscription_renewed(data)
|
|
221
|
+
|
|
222
|
+
elif event_type == 'subscription.canceled':
|
|
223
|
+
data = SubscriptionData(**event.get('data', {}))
|
|
224
|
+
handle_subscription_canceled(data)
|
|
225
|
+
|
|
226
|
+
else:
|
|
227
|
+
logger.info(f'[Webhook] Unhandled event: {event_type}')
|
|
228
|
+
|
|
229
|
+
return jsonify({'received': True})
|
|
230
|
+
|
|
231
|
+
# Payment handlers
|
|
232
|
+
def handle_payment_succeeded(data: PaymentData):
|
|
233
|
+
order_id = data.metadata.get('order_id') if data.metadata else None
|
|
234
|
+
logger.info(f'[Webhook] Payment succeeded: {data.id} for order {order_id}')
|
|
235
|
+
|
|
236
|
+
# TODO: Implement your business logic
|
|
237
|
+
# - Update order status to "paid"
|
|
238
|
+
# - Send confirmation email to customer
|
|
239
|
+
# - Trigger fulfillment process
|
|
240
|
+
|
|
241
|
+
def handle_payment_failed(data: PaymentData):
|
|
242
|
+
logger.info(f'[Webhook] Payment failed: {data.id}')
|
|
243
|
+
|
|
244
|
+
# TODO: Implement your business logic
|
|
245
|
+
# - Update order status to "payment_failed"
|
|
246
|
+
# - Send notification to customer
|
|
247
|
+
# - Allow retry
|
|
248
|
+
|
|
249
|
+
def handle_payment_refunded(data: PaymentData):
|
|
250
|
+
order_id = data.metadata.get('order_id') if data.metadata else None
|
|
251
|
+
logger.info(f'[Webhook] Payment refunded: {data.id} for order {order_id}')
|
|
252
|
+
|
|
253
|
+
# TODO: Implement your business logic
|
|
254
|
+
# - Update order status to "refunded"
|
|
255
|
+
# - Restore inventory if applicable
|
|
256
|
+
|
|
257
|
+
# Subscription handlers
|
|
258
|
+
def handle_subscription_created(data: SubscriptionData):
|
|
259
|
+
logger.info(f'[Webhook] Subscription created: {data.id} for customer {data.customer_id}')
|
|
260
|
+
|
|
261
|
+
# TODO: Implement your business logic
|
|
262
|
+
# - Grant access to subscription features
|
|
263
|
+
# - Send welcome email
|
|
264
|
+
|
|
265
|
+
def handle_subscription_renewed(data: SubscriptionData):
|
|
266
|
+
logger.info(f'[Webhook] Subscription renewed: {data.id}')
|
|
267
|
+
|
|
268
|
+
# TODO: Implement your business logic
|
|
269
|
+
# - Extend access period
|
|
270
|
+
# - Send renewal confirmation
|
|
271
|
+
|
|
272
|
+
def handle_subscription_canceled(data: SubscriptionData):
|
|
273
|
+
logger.info(f'[Webhook] Subscription canceled: {data.id}')
|
|
274
|
+
|
|
275
|
+
# TODO: Implement your business logic
|
|
276
|
+
# - Revoke access at end of billing period
|
|
277
|
+
# - Send cancellation confirmation
|
|
278
|
+
|
|
279
|
+
if __name__ == '__main__':
|
|
280
|
+
app.run(port=8080)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Django Webhook Handler
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# views.py
|
|
287
|
+
import json
|
|
288
|
+
import os
|
|
289
|
+
import logging
|
|
290
|
+
from django.http import JsonResponse
|
|
291
|
+
from django.views.decorators.csrf import csrf_exempt
|
|
292
|
+
from django.views.decorators.http import require_POST
|
|
293
|
+
from reevit import verify_webhook_signature
|
|
294
|
+
|
|
295
|
+
logger = logging.getLogger(__name__)
|
|
296
|
+
|
|
297
|
+
@csrf_exempt
|
|
298
|
+
@require_POST
|
|
299
|
+
def reevit_webhook(request):
|
|
300
|
+
payload = request.body # raw bytes — do not re-serialize
|
|
301
|
+
signature = request.headers.get('X-Reevit-Signature', '')
|
|
302
|
+
secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
|
|
303
|
+
|
|
304
|
+
if not verify_webhook_signature(payload, signature, secret):
|
|
305
|
+
return JsonResponse({'error': 'Invalid signature'}, status=401)
|
|
306
|
+
|
|
307
|
+
event = json.loads(payload)
|
|
308
|
+
event_type = event.get('type')
|
|
309
|
+
|
|
310
|
+
logger.info(f'[Webhook] Received: {event_type}')
|
|
311
|
+
|
|
312
|
+
if event_type == 'payment.succeeded':
|
|
313
|
+
data = event.get('data', {})
|
|
314
|
+
order_id = data.get('metadata', {}).get('order_id')
|
|
315
|
+
# Fulfill order, send confirmation email
|
|
316
|
+
logger.info(f'Payment succeeded for order {order_id}')
|
|
317
|
+
|
|
318
|
+
elif event_type == 'payment.failed':
|
|
319
|
+
# Notify customer, allow retry
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
elif event_type == 'subscription.renewed':
|
|
323
|
+
# Extend access
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
elif event_type == 'subscription.canceled':
|
|
327
|
+
# Revoke access
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
return JsonResponse({'received': True})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### FastAPI Webhook Handler
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
337
|
+
from pydantic import BaseModel
|
|
338
|
+
from typing import Optional, Dict
|
|
339
|
+
import os
|
|
340
|
+
import logging
|
|
341
|
+
from reevit import verify_webhook_signature
|
|
342
|
+
|
|
343
|
+
app = FastAPI()
|
|
344
|
+
logger = logging.getLogger(__name__)
|
|
345
|
+
|
|
346
|
+
class PaymentData(BaseModel):
|
|
347
|
+
id: str
|
|
348
|
+
status: str
|
|
349
|
+
amount: int
|
|
350
|
+
currency: str
|
|
351
|
+
provider: str
|
|
352
|
+
customer_id: Optional[str] = None
|
|
353
|
+
metadata: Optional[Dict[str, str]] = None
|
|
354
|
+
|
|
355
|
+
class SubscriptionData(BaseModel):
|
|
356
|
+
id: str
|
|
357
|
+
customer_id: str
|
|
358
|
+
plan_id: str
|
|
359
|
+
status: str
|
|
360
|
+
amount: int
|
|
361
|
+
currency: str
|
|
362
|
+
interval: str
|
|
363
|
+
next_renewal_at: Optional[str] = None
|
|
364
|
+
|
|
365
|
+
@app.post('/webhooks/reevit')
|
|
366
|
+
async def webhook(request: Request):
|
|
367
|
+
payload = await request.body() # raw bytes — do not re-serialize
|
|
368
|
+
signature = request.headers.get('X-Reevit-Signature', '')
|
|
369
|
+
secret = os.environ.get('REEVIT_WEBHOOK_SECRET', '')
|
|
370
|
+
|
|
371
|
+
if not verify_webhook_signature(payload, signature, secret):
|
|
372
|
+
raise HTTPException(status_code=401, detail='Invalid signature')
|
|
373
|
+
|
|
374
|
+
event = await request.json()
|
|
375
|
+
event_type = event.get('type')
|
|
376
|
+
|
|
377
|
+
logger.info(f'[Webhook] Received: {event_type}')
|
|
378
|
+
|
|
379
|
+
# Payment events
|
|
380
|
+
if event_type == 'payment.succeeded':
|
|
381
|
+
data = PaymentData(**event.get('data', {}))
|
|
382
|
+
order_id = data.metadata.get('order_id') if data.metadata else None
|
|
383
|
+
logger.info(f'Payment succeeded: {data.id} for order {order_id}')
|
|
384
|
+
# Fulfill order, send confirmation email
|
|
385
|
+
|
|
386
|
+
elif event_type == 'payment.failed':
|
|
387
|
+
# Notify customer, allow retry
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
# Subscription events
|
|
391
|
+
elif event_type == 'subscription.renewed':
|
|
392
|
+
data = SubscriptionData(**event.get('data', {}))
|
|
393
|
+
logger.info(f'Subscription renewed: {data.id}')
|
|
394
|
+
# Extend access
|
|
395
|
+
|
|
396
|
+
elif event_type == 'subscription.canceled':
|
|
397
|
+
data = SubscriptionData(**event.get('data', {}))
|
|
398
|
+
logger.info(f'Subscription canceled: {data.id}')
|
|
399
|
+
# Revoke access
|
|
400
|
+
|
|
401
|
+
return {'received': True}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Supported PSPs
|
|
405
|
+
|
|
406
|
+
| Provider | Countries | Payment Methods |
|
|
407
|
+
|----------|-----------|-----------------|
|
|
408
|
+
| Paystack | NG, GH, ZA, KE | Card, Mobile Money, Bank Transfer |
|
|
409
|
+
| Flutterwave | NG, GH, KE, ZA + | Card, Mobile Money, Bank Transfer |
|
|
410
|
+
| Hubtel | GH | Mobile Money |
|
|
411
|
+
| Stripe | Global (50+) | Card, Apple Pay, Google Pay |
|
|
412
|
+
| Monnify | NG | Card, Bank Transfer, USSD |
|
|
413
|
+
| M-Pesa | KE, TZ | Mobile Money (STK Push) |
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Release Notes
|
|
418
|
+
|
|
419
|
+
### v0.9.1
|
|
420
|
+
|
|
421
|
+
- Added `verify_webhook_signature` / `sign_webhook_payload` helpers (constant-time HMAC-SHA256 verification of the `X-Reevit-Signature` header)
|
|
422
|
+
- Version is now sourced from `reevit._version` and sent as the `X-Reevit-Client-Version` header
|
|
423
|
+
|
|
424
|
+
### v0.9.0
|
|
425
|
+
|
|
426
|
+
- Added server-created checkout sessions
|
|
427
|
+
- Version alignment across all Reevit SDKs
|
|
428
|
+
- Updated documentation and webhook examples
|
|
429
|
+
- Added support for Apple Pay and Google Pay
|
|
430
|
+
- Updated supported PSPs and payment methods documentation
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Environment Variables
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
export REEVIT_API_KEY=pfk_live_xxx
|
|
438
|
+
export REEVIT_ORG_ID=org_xxx
|
|
439
|
+
export REEVIT_WEBHOOK_SECRET=whsec_xxx # Get from Dashboard > Developers > Webhooks
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Support
|
|
445
|
+
|
|
446
|
+
- **Documentation**: [https://docs.reevit.io](https://docs.reevit.io)
|
|
447
|
+
- **GitHub Issues**: [https://github.com/Reevit-Platform/backend/issues](https://github.com/Reevit-Platform/backend/issues)
|
|
448
|
+
- **Email**: support@reevit.io
|
|
449
|
+
|
|
450
|
+
## License
|
|
451
|
+
|
|
452
|
+
MIT License - see [LICENSE](LICENSE) for details.
|