payplus-python 0.1.2__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.
- payplus_python-0.2.0/PKG-INFO +485 -0
- payplus_python-0.2.0/README.md +441 -0
- payplus_python-0.2.0/examples/subscription_saas.py +151 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/__init__.py +4 -10
- payplus_python-0.2.0/payplus/api/customers.py +114 -0
- payplus_python-0.2.0/payplus/api/payment_pages.py +299 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/api/recurring.py +83 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/client.py +3 -1
- payplus_python-0.2.0/payplus/models/__init__.py +16 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/models/subscription.py +3 -1
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/subscriptions/__init__.py +0 -2
- payplus_python-0.2.0/payplus/subscriptions/manager.py +600 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/subscriptions/storage.py +100 -251
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/webhooks/handler.py +154 -162
- payplus_python-0.2.0/payplus_python.egg-info/PKG-INFO +485 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus_python.egg-info/SOURCES.txt +1 -3
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus_python.egg-info/top_level.txt +1 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/pyproject.toml +1 -1
- {payplus_python-0.1.2 → payplus_python-0.2.0}/tests/test_models.py +116 -171
- payplus_python-0.1.2/PKG-INFO +0 -446
- payplus_python-0.1.2/README.md +0 -402
- payplus_python-0.1.2/examples/subscription_saas.py +0 -206
- payplus_python-0.1.2/payplus/api/payment_pages.py +0 -176
- payplus_python-0.1.2/payplus/models/__init__.py +0 -23
- payplus_python-0.1.2/payplus/models/invoice.py +0 -242
- payplus_python-0.1.2/payplus/models/payment.py +0 -179
- payplus_python-0.1.2/payplus/subscriptions/billing.py +0 -231
- payplus_python-0.1.2/payplus/subscriptions/manager.py +0 -600
- payplus_python-0.1.2/payplus_python.egg-info/PKG-INFO +0 -446
- {payplus_python-0.1.2 → payplus_python-0.2.0}/LICENSE +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/examples/basic_payment.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/examples/fastapi_webhooks.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/api/__init__.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/api/base.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/api/payments.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/api/transactions.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/exceptions.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/models/customer.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/models/tier.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus/webhooks/__init__.py +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus_python.egg-info/dependency_links.txt +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/payplus_python.egg-info/requires.txt +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/setup.cfg +0 -0
- {payplus_python-0.1.2 → payplus_python-0.2.0}/tests/__init__.py +0 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: payplus-python
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python SDK for PayPlus payment gateway with subscription management for SaaS apps
|
|
5
|
+
Author-email: Two Solutions <dev@two-solutions.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Two-Solutions/payplus-python
|
|
8
|
+
Project-URL: Documentation, https://github.com/Two-Solutions/payplus-python#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/Two-Solutions/payplus-python
|
|
10
|
+
Project-URL: Issues, https://github.com/Two-Solutions/payplus-python/issues
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.25.0
|
|
23
|
+
Requires-Dist: email-validator>=2.0.0
|
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
|
25
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
|
+
Provides-Extra: fastapi
|
|
34
|
+
Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
|
|
35
|
+
Provides-Extra: flask
|
|
36
|
+
Requires-Dist: flask>=2.0.0; extra == "flask"
|
|
37
|
+
Provides-Extra: postgres
|
|
38
|
+
Requires-Dist: psycopg2-binary>=2.9.0; extra == "postgres"
|
|
39
|
+
Requires-Dist: asyncpg>=0.28.0; extra == "postgres"
|
|
40
|
+
Provides-Extra: mongodb
|
|
41
|
+
Requires-Dist: motor>=3.0.0; extra == "mongodb"
|
|
42
|
+
Requires-Dist: pymongo>=4.0.0; extra == "mongodb"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# PayPlus Python SDK
|
|
46
|
+
|
|
47
|
+
A Python SDK for [PayPlus](https://www.payplus.co.il/) payment gateway with built-in subscription management for SaaS applications.
|
|
48
|
+
|
|
49
|
+
[](https://badge.fury.io/py/payplus-python)
|
|
50
|
+
[](https://www.python.org/downloads/)
|
|
51
|
+
[](https://opensource.org/licenses/MIT)
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- Full PayPlus API coverage — payment pages, transactions, recurring payments, customers
|
|
56
|
+
- Subscription management — payment-link-based recurring billing for SaaS apps
|
|
57
|
+
- Database integration — MongoDB and SQLAlchemy storage backends
|
|
58
|
+
- Webhook handling — IPN/webhook integration with HMAC signature verification
|
|
59
|
+
- Async support — full async/await for modern Python apps
|
|
60
|
+
- Type safe — Pydantic models with full type hints
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install payplus-python
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
With optional dependencies:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install payplus-python[fastapi] # FastAPI webhook integration
|
|
72
|
+
pip install payplus-python[postgres] # PostgreSQL storage
|
|
73
|
+
pip install payplus-python[mongodb] # MongoDB storage
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Implementation Steps
|
|
77
|
+
|
|
78
|
+
A step-by-step guide covering the full subscription lifecycle in your app.
|
|
79
|
+
|
|
80
|
+
### Step 1: Initialize the SDK
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from decimal import Decimal
|
|
84
|
+
from payplus import PayPlus, SubscriptionManager
|
|
85
|
+
from payplus.models.subscription import BillingCycle
|
|
86
|
+
from payplus.subscriptions.storage import MongoDBStorage
|
|
87
|
+
from payplus.webhooks import WebhookHandler
|
|
88
|
+
from motor.motor_asyncio import AsyncIOMotorClient
|
|
89
|
+
|
|
90
|
+
client = PayPlus(
|
|
91
|
+
api_key="your_api_key",
|
|
92
|
+
secret_key="your_secret_key",
|
|
93
|
+
sandbox=True,
|
|
94
|
+
)
|
|
95
|
+
mongo = AsyncIOMotorClient("mongodb://localhost:27017")
|
|
96
|
+
storage = MongoDBStorage(mongo.your_database)
|
|
97
|
+
manager = SubscriptionManager(client, storage)
|
|
98
|
+
webhook_handler = WebhookHandler(client)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 2: Define your plans (run once on app setup)
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
await manager.create_tier(
|
|
105
|
+
tier_id="basic",
|
|
106
|
+
name="Basic",
|
|
107
|
+
price=Decimal("29"),
|
|
108
|
+
billing_cycle=BillingCycle.MONTHLY,
|
|
109
|
+
trial_days=7,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
await manager.create_tier(
|
|
113
|
+
tier_id="pro",
|
|
114
|
+
name="Pro",
|
|
115
|
+
price=Decimal("79"),
|
|
116
|
+
billing_cycle=BillingCycle.MONTHLY,
|
|
117
|
+
trial_days=14,
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Step 3: User signs up
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
customer = await manager.create_customer(
|
|
125
|
+
email="user@example.com",
|
|
126
|
+
name="John Doe",
|
|
127
|
+
phone="050-1234567",
|
|
128
|
+
)
|
|
129
|
+
# Save customer.id in your user record
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Step 4: User subscribes to a plan
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
subscription = await manager.create_subscription(
|
|
136
|
+
customer_id=customer.id,
|
|
137
|
+
tier_id="pro",
|
|
138
|
+
callback_url="https://yourapp.com/webhooks/payplus",
|
|
139
|
+
success_url="https://yourapp.com/subscription/success",
|
|
140
|
+
failure_url="https://yourapp.com/subscription/failure",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Redirect user to complete payment
|
|
144
|
+
redirect(subscription.payment_page_link)
|
|
145
|
+
|
|
146
|
+
# Save subscription.id in your user record
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Behind the scenes this:
|
|
150
|
+
|
|
151
|
+
1. Creates the customer on PayPlus (`POST /Customers/Add`) if not already created
|
|
152
|
+
2. Generates a payment link with `charge_method=3` and `recurring_settings` derived from the tier
|
|
153
|
+
3. Saves the subscription locally with `status=INCOMPLETE`
|
|
154
|
+
|
|
155
|
+
The user fills in their card details on the PayPlus hosted page. You never touch card data.
|
|
156
|
+
|
|
157
|
+
### Step 5: Set up the webhook endpoint
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from fastapi import FastAPI, Request, HTTPException
|
|
161
|
+
from payplus.webhooks import WebhookSignatureError
|
|
162
|
+
|
|
163
|
+
app = FastAPI()
|
|
164
|
+
|
|
165
|
+
@app.post("/webhooks/payplus")
|
|
166
|
+
async def payplus_webhook(request: Request):
|
|
167
|
+
payload = await request.body()
|
|
168
|
+
signature = request.headers.get("X-PayPlus-Signature", "")
|
|
169
|
+
try:
|
|
170
|
+
event = await webhook_handler.handle_async(payload, signature)
|
|
171
|
+
await manager.handle_webhook_event(event)
|
|
172
|
+
return {"received": True}
|
|
173
|
+
except WebhookSignatureError:
|
|
174
|
+
raise HTTPException(status_code=400, detail="Invalid signature")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This single endpoint handles every subscription event automatically:
|
|
178
|
+
|
|
179
|
+
| Webhook event | What happens |
|
|
180
|
+
|---|---|
|
|
181
|
+
| First payment succeeds | `INCOMPLETE` -> `ACTIVE`, `recurring_uid` stored |
|
|
182
|
+
| Recurring charge succeeds | Billing period advanced, status stays `ACTIVE` |
|
|
183
|
+
| Recurring charge fails | Status -> `PAST_DUE` (-> `UNPAID` after 4 failures) |
|
|
184
|
+
| Recurring canceled | Status -> `CANCELED` |
|
|
185
|
+
| Cancel at period end flagged | After last charge, cancels on PayPlus and sets `CANCELED` |
|
|
186
|
+
|
|
187
|
+
### Step 6: Check access in your app
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
sub = await manager.get_subscription(subscription_id)
|
|
191
|
+
if sub and sub.is_active:
|
|
192
|
+
# User has access
|
|
193
|
+
...
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Step 7: User upgrades plan
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
await manager.change_tier(subscription.id, new_tier_id="enterprise")
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This updates the recurring payment on PayPlus with the new tier's price and billing cycle. The card token is saved automatically from the first payment webhook.
|
|
203
|
+
|
|
204
|
+
### Step 8: User pauses subscription
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
await manager.pause_subscription(subscription.id)
|
|
208
|
+
|
|
209
|
+
# Later, resume it
|
|
210
|
+
await manager.resume_subscription(subscription.id)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Step 9: User cancels subscription
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
# Cancel at end of billing period (user keeps access until then)
|
|
217
|
+
await manager.cancel_subscription(
|
|
218
|
+
subscription.id,
|
|
219
|
+
at_period_end=True,
|
|
220
|
+
reason="Customer requested",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Or cancel immediately
|
|
224
|
+
await manager.cancel_subscription(subscription.id, at_period_end=False)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Step 10: React to lifecycle events (optional)
|
|
228
|
+
|
|
229
|
+
Register hooks to trigger your own business logic:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
manager.on("subscription.activated", lambda sub: send_welcome_email(sub))
|
|
233
|
+
manager.on("subscription.renewed", lambda sub: log_renewal(sub))
|
|
234
|
+
manager.on("subscription.payment_failed", lambda sub: send_dunning_email(sub))
|
|
235
|
+
manager.on("subscription.canceled", lambda sub: handle_offboarding(sub))
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Trials
|
|
239
|
+
|
|
240
|
+
If a tier has `trial_days` set, the subscription flow changes:
|
|
241
|
+
|
|
242
|
+
- `create_subscription()` sets `jump_payments` in `recurring_settings`, telling PayPlus to wait N days before the first charge
|
|
243
|
+
- The subscription starts as `INCOMPLETE` (waiting for the user to enter card details on the payment page)
|
|
244
|
+
- When the user completes the payment page, PayPlus validates the card but doesn't charge yet
|
|
245
|
+
- The webhook activates the subscription as `TRIALING` (since `trial_end` is in the future)
|
|
246
|
+
- After the trial period, PayPlus charges automatically and sends a `recurring.charged` webhook
|
|
247
|
+
- `is_active` returns `True` for both `ACTIVE` and `TRIALING` statuses
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
# Tier with a 14-day trial
|
|
251
|
+
await manager.create_tier(
|
|
252
|
+
tier_id="pro",
|
|
253
|
+
name="Pro",
|
|
254
|
+
price=Decimal("79"),
|
|
255
|
+
trial_days=14, # 14 free days before first charge
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# After subscription is created and user completes payment page:
|
|
259
|
+
# sub.status == "trialing"
|
|
260
|
+
# sub.is_active == True
|
|
261
|
+
# sub.trial_end == ~14 days from now
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### How it all fits together
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
User clicks "Subscribe to Pro"
|
|
268
|
+
|
|
|
269
|
+
v
|
|
270
|
+
create_subscription()
|
|
271
|
+
- Creates customer on PayPlus
|
|
272
|
+
- Generates payment link with recurring settings
|
|
273
|
+
- Subscription status: INCOMPLETE
|
|
274
|
+
|
|
|
275
|
+
v
|
|
276
|
+
User redirected to PayPlus payment page
|
|
277
|
+
User enters card details and pays
|
|
278
|
+
|
|
|
279
|
+
v
|
|
280
|
+
PayPlus sends webhook to callback_url
|
|
281
|
+
|
|
|
282
|
+
v
|
|
283
|
+
handle_webhook_event()
|
|
284
|
+
- Matches webhook to subscription via page_request_uid
|
|
285
|
+
- Saves card token and recurring_uid
|
|
286
|
+
- Sets status: ACTIVE (or TRIALING if trial_days > 0)
|
|
287
|
+
|
|
|
288
|
+
v
|
|
289
|
+
Every billing cycle, PayPlus charges automatically
|
|
290
|
+
- recurring.charged -> period advanced, still ACTIVE
|
|
291
|
+
- recurring.failed -> PAST_DUE (-> UNPAID after 4 failures)
|
|
292
|
+
|
|
293
|
+
Lifecycle actions (from your app):
|
|
294
|
+
- change_tier() -> updates amount on PayPlus
|
|
295
|
+
- pause/resume -> updates local status
|
|
296
|
+
- cancel(at_period_end=True) -> flags locally, cancels on PayPlus after last charge
|
|
297
|
+
- cancel(at_period_end=False) -> cancels on PayPlus immediately, status: CANCELED
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Direct API Usage
|
|
301
|
+
|
|
302
|
+
You can also use the PayPlus API directly without the subscription manager:
|
|
303
|
+
|
|
304
|
+
### Payment Link
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
result = client.payment_pages.generate_link(
|
|
308
|
+
amount=100.00,
|
|
309
|
+
currency="ILS",
|
|
310
|
+
description="One-time payment",
|
|
311
|
+
customer_email="customer@example.com",
|
|
312
|
+
success_url="https://yourapp.com/success",
|
|
313
|
+
callback_url="https://yourapp.com/webhooks/payplus",
|
|
314
|
+
)
|
|
315
|
+
print(result["data"]["payment_page_link"])
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Payment Link with Recurring
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from payplus.api.payment_pages import build_recurring_settings
|
|
322
|
+
|
|
323
|
+
result = client.payment_pages.generate_link(
|
|
324
|
+
amount=79.00,
|
|
325
|
+
currency="ILS",
|
|
326
|
+
charge_method=3, # Recurring
|
|
327
|
+
customer_uid="payplus-customer-uid",
|
|
328
|
+
callback_url="https://yourapp.com/webhooks/payplus",
|
|
329
|
+
recurring_settings=build_recurring_settings(
|
|
330
|
+
billing_cycle="monthly",
|
|
331
|
+
trial_days=14,
|
|
332
|
+
number_of_charges=0, # Unlimited
|
|
333
|
+
),
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Create Customer
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
result = client.customers.add(
|
|
341
|
+
customer_name="John Doe",
|
|
342
|
+
email="john@example.com",
|
|
343
|
+
phone="050-1234567",
|
|
344
|
+
)
|
|
345
|
+
customer_uid = result["data"]["customer_uid"]
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Transactions
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
# Charge a saved card token
|
|
352
|
+
result = client.transactions.charge(
|
|
353
|
+
token="card_token",
|
|
354
|
+
amount=99.00,
|
|
355
|
+
currency="ILS",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Refund
|
|
359
|
+
client.transactions.refund(
|
|
360
|
+
transaction_uid=result["data"]["transaction_uid"],
|
|
361
|
+
amount=99.00,
|
|
362
|
+
)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Recurring Payments
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
# Create recurring from token
|
|
369
|
+
result = client.recurring.add(
|
|
370
|
+
token="card_token",
|
|
371
|
+
amount=49.00,
|
|
372
|
+
interval="month",
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Cancel
|
|
376
|
+
client.recurring.cancel(result["data"]["recurring_uid"])
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Storage Backends
|
|
380
|
+
|
|
381
|
+
### MongoDB
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
from motor.motor_asyncio import AsyncIOMotorClient
|
|
385
|
+
from payplus.subscriptions.storage import MongoDBStorage
|
|
386
|
+
|
|
387
|
+
mongo = AsyncIOMotorClient("mongodb://localhost:27017")
|
|
388
|
+
storage = MongoDBStorage(mongo.your_database)
|
|
389
|
+
await storage.create_indexes() # Run once
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### SQLAlchemy (PostgreSQL, MySQL, SQLite)
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
396
|
+
from payplus.subscriptions.storage import SQLAlchemyStorage
|
|
397
|
+
|
|
398
|
+
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
|
|
399
|
+
storage = SQLAlchemyStorage(engine)
|
|
400
|
+
await storage.create_tables() # Run once
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### In-Memory (development/testing)
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
# Used automatically when no storage is provided
|
|
407
|
+
manager = SubscriptionManager(client)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## API Reference
|
|
411
|
+
|
|
412
|
+
### PayPlus Client
|
|
413
|
+
|
|
414
|
+
| Module | Methods |
|
|
415
|
+
|--------|---------|
|
|
416
|
+
| `client.customers` | `add()` |
|
|
417
|
+
| `client.payment_pages` | `generate_link()`, `get_status()` |
|
|
418
|
+
| `client.transactions` | `charge()`, `get()`, `refund()`, `list()` |
|
|
419
|
+
| `client.recurring` | `add()`, `update()`, `charge()`, `cancel()`, `get()`, `list()` |
|
|
420
|
+
| `client.payments` | `check_card()`, `tokenize()`, `get_token()`, `delete_token()` |
|
|
421
|
+
|
|
422
|
+
### Subscription Manager
|
|
423
|
+
|
|
424
|
+
| Method | Description |
|
|
425
|
+
|--------|-------------|
|
|
426
|
+
| `create_customer()` | Create a new customer |
|
|
427
|
+
| `get_customer()` | Get a customer by ID |
|
|
428
|
+
| `create_tier()` | Create a pricing tier |
|
|
429
|
+
| `get_tier()` | Get a tier by ID |
|
|
430
|
+
| `list_tiers()` | List all tiers |
|
|
431
|
+
| `create_subscription()` | Create subscription and generate payment link |
|
|
432
|
+
| `get_subscription()` | Get a subscription by ID |
|
|
433
|
+
| `change_tier()` | Upgrade/downgrade (updates PayPlus recurring) |
|
|
434
|
+
| `pause_subscription()` | Pause a subscription |
|
|
435
|
+
| `resume_subscription()` | Resume a paused subscription |
|
|
436
|
+
| `cancel_subscription()` | Cancel immediately or at period end |
|
|
437
|
+
| `handle_webhook_event()` | Process webhook and update subscription state |
|
|
438
|
+
|
|
439
|
+
## Configuration
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
PAYPLUS_API_KEY=your_api_key
|
|
443
|
+
PAYPLUS_SECRET_KEY=your_secret_key
|
|
444
|
+
PAYPLUS_TERMINAL_UID=your_terminal_uid # Optional
|
|
445
|
+
PAYPLUS_SANDBOX=true # For testing
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```python
|
|
449
|
+
# Sandbox (restapidev.payplus.co.il)
|
|
450
|
+
client = PayPlus(api_key="...", secret_key="...", sandbox=True)
|
|
451
|
+
|
|
452
|
+
# Production (restapi.payplus.co.il)
|
|
453
|
+
client = PayPlus(api_key="...", secret_key="...", sandbox=False)
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Error Handling
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
from payplus.exceptions import (
|
|
460
|
+
PayPlusError,
|
|
461
|
+
PayPlusAPIError,
|
|
462
|
+
PayPlusAuthError,
|
|
463
|
+
SubscriptionError,
|
|
464
|
+
WebhookSignatureError,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
result = client.transactions.charge(token="...", amount=100)
|
|
469
|
+
except PayPlusAuthError:
|
|
470
|
+
print("Invalid API credentials")
|
|
471
|
+
except PayPlusAPIError as e:
|
|
472
|
+
print(f"API error [{e.status_code}]: {e.message}")
|
|
473
|
+
except PayPlusError as e:
|
|
474
|
+
print(f"General error: {e}")
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## License
|
|
478
|
+
|
|
479
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
480
|
+
|
|
481
|
+
## Links
|
|
482
|
+
|
|
483
|
+
- [PayPlus Documentation](https://docs.payplus.co.il/)
|
|
484
|
+
- [GitHub Repository](https://github.com/Two-Solutions/payplus-python)
|
|
485
|
+
- [Issue Tracker](https://github.com/Two-Solutions/payplus-python/issues)
|