payplus-python 0.1.2__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
@@ -8,7 +8,7 @@ from decimal import Decimal
8
8
 
9
9
  from payplus import PayPlus, SubscriptionManager
10
10
  from payplus.subscriptions.storage import MongoDBStorage
11
- from payplus.subscriptions import BillingService
11
+ from payplus.webhooks import WebhookHandler
12
12
 
13
13
 
14
14
  async def main():
@@ -18,7 +18,7 @@ async def main():
18
18
  secret_key=os.environ.get("PAYPLUS_SECRET_KEY", "your_secret_key"),
19
19
  sandbox=True,
20
20
  )
21
-
21
+
22
22
  # Setup MongoDB storage
23
23
  try:
24
24
  from motor.motor_asyncio import AsyncIOMotorClient
@@ -28,33 +28,19 @@ async def main():
28
28
  except ImportError:
29
29
  print("MongoDB not available, using in-memory storage")
30
30
  storage = None
31
-
31
+
32
32
  # Create subscription manager
33
33
  manager = SubscriptionManager(client, storage)
34
-
34
+
35
35
  # Register event handlers
36
- manager.on("subscription.created", lambda s: print(f"Subscription created: {s.id}"))
37
- manager.on("subscription.activated", lambda s: print(f"🎉 Subscription activated: {s.id}"))
38
- manager.on("payment.succeeded", lambda p: print(f"💰 Payment received: {p.amount} {p.currency}"))
39
- manager.on("payment.failed", lambda p: print(f"Payment failed: {p.failure_message}"))
40
-
36
+ manager.on("subscription.created", lambda s: print(f"Subscription created: {s.id}"))
37
+ manager.on("subscription.activated", lambda s: print(f"Subscription activated: {s.id}"))
38
+ manager.on("subscription.renewed", lambda s: print(f"Subscription renewed: {s.id}"))
39
+ manager.on("subscription.payment_failed", lambda s: print(f"Payment failed for: {s.id}"))
40
+
41
41
  # ==================== Setup Pricing Tiers ====================
42
- print("\n📦 Creating pricing tiers...")
43
-
44
- # Free tier
45
- free_tier = await manager.create_tier(
46
- tier_id="free",
47
- name="Free",
48
- price=Decimal("0"),
49
- features=[
50
- {"feature_id": "projects", "name": "Projects", "included_quantity": 3},
51
- {"feature_id": "storage_gb", "name": "Storage (GB)", "included_quantity": 1},
52
- {"feature_id": "team_members", "name": "Team Members", "included_quantity": 1},
53
- ],
54
- )
55
- print(f" - {free_tier.name}: ₪{free_tier.price}/month")
56
-
57
- # Basic tier
42
+ print("\nCreating pricing tiers...")
43
+
58
44
  basic_tier = await manager.create_tier(
59
45
  tier_id="basic",
60
46
  name="Basic",
@@ -64,141 +50,100 @@ async def main():
64
50
  {"feature_id": "projects", "name": "Projects", "included_quantity": 10},
65
51
  {"feature_id": "storage_gb", "name": "Storage (GB)", "included_quantity": 10},
66
52
  {"feature_id": "team_members", "name": "Team Members", "included_quantity": 5},
67
- {"feature_id": "api_access", "name": "API Access"},
68
53
  ],
69
54
  )
70
- print(f" - {basic_tier.name}: {basic_tier.price}/month (7-day trial)")
71
-
72
- # Pro tier
55
+ print(f" - {basic_tier.name}: ILS {basic_tier.price}/month (7-day trial)")
56
+
73
57
  pro_tier = await manager.create_tier(
74
58
  tier_id="pro",
75
59
  name="Pro",
76
60
  price=Decimal("79"),
77
61
  trial_days=14,
78
62
  is_popular=True,
79
- annual_discount_percent=Decimal("20"),
80
63
  features=[
81
- {"feature_id": "projects", "name": "Projects", "included_quantity": None}, # Unlimited
64
+ {"feature_id": "projects", "name": "Projects", "included_quantity": None},
82
65
  {"feature_id": "storage_gb", "name": "Storage (GB)", "included_quantity": 100},
83
66
  {"feature_id": "team_members", "name": "Team Members", "included_quantity": 20},
84
67
  {"feature_id": "api_access", "name": "API Access"},
85
68
  {"feature_id": "priority_support", "name": "Priority Support"},
86
- {"feature_id": "custom_domain", "name": "Custom Domain"},
87
- ],
88
- )
89
- print(f" - {pro_tier.name}: ₪{pro_tier.price}/month (14-day trial, 20% annual discount)")
90
-
91
- # Enterprise tier
92
- enterprise_tier = await manager.create_tier(
93
- tier_id="enterprise",
94
- name="Enterprise",
95
- price=Decimal("199"),
96
- annual_discount_percent=Decimal("25"),
97
- features=[
98
- {"feature_id": "projects", "name": "Projects", "included_quantity": None},
99
- {"feature_id": "storage_gb", "name": "Storage (GB)", "included_quantity": None},
100
- {"feature_id": "team_members", "name": "Team Members", "included_quantity": None},
101
- {"feature_id": "api_access", "name": "API Access"},
102
- {"feature_id": "priority_support", "name": "Priority Support"},
103
- {"feature_id": "custom_domain", "name": "Custom Domain"},
104
- {"feature_id": "sso", "name": "SSO Integration"},
105
- {"feature_id": "dedicated_support", "name": "Dedicated Account Manager"},
106
69
  ],
107
70
  )
108
- print(f" - {enterprise_tier.name}: {enterprise_tier.price}/month (25% annual discount)")
109
-
71
+ print(f" - {pro_tier.name}: ILS {pro_tier.price}/month (14-day trial)")
72
+
110
73
  # ==================== Create Customer ====================
111
- print("\n👤 Creating customer...")
112
-
74
+ print("\nCreating customer...")
75
+
113
76
  customer = await manager.create_customer(
114
77
  email="demo@example.com",
115
78
  name="Demo User",
116
- metadata={"source": "signup_form", "campaign": "launch_2024"},
117
79
  )
118
80
  print(f" Customer ID: {customer.id}")
119
- print(f" Email: {customer.email}")
120
-
121
- # ==================== Add Payment Method ====================
122
- print("\n💳 Adding payment method...")
123
-
124
- # In production, this token comes from a PayPlus payment page
125
- # For demo, we'll simulate with a fake token
126
- demo_token = "demo_card_token_xxx"
127
-
128
- payment_method = await manager.add_payment_method(
129
- customer_id=customer.id,
130
- token=demo_token,
131
- card_brand="Visa",
132
- last_four="4242",
133
- expiry_month="12",
134
- expiry_year="2028",
135
- )
136
- print(f" Payment Method: {payment_method.card_brand} ****{payment_method.last_four}")
137
-
81
+
138
82
  # ==================== Create Subscription ====================
139
- print("\n📋 Creating Pro subscription...")
140
-
83
+ print("\nCreating Pro subscription (generates payment link)...")
84
+
141
85
  subscription = await manager.create_subscription(
142
86
  customer_id=customer.id,
143
87
  tier_id="pro",
88
+ callback_url="https://example.com/webhooks/payplus",
89
+ success_url="https://example.com/success",
90
+ failure_url="https://example.com/failure",
144
91
  )
145
-
92
+
146
93
  print(f" Subscription ID: {subscription.id}")
147
- print(f" Status: {subscription.status}")
148
- print(f" Amount: {subscription.amount}/month")
149
- print(f" Trial ends: {subscription.trial_end}")
150
- print(f" Current period: {subscription.current_period_start} - {subscription.current_period_end}")
151
-
94
+ print(f" Status: {subscription.status}") # INCOMPLETE until customer pays
95
+ print(f" Amount: ILS {subscription.amount}/month")
96
+ print(f" Payment link: {subscription.payment_page_link}")
97
+ print(f" -> Redirect customer to this link to complete payment")
98
+
99
+ # ==================== Webhook Handling ====================
100
+ # In production, this happens when PayPlus sends a webhook after the customer pays.
101
+ # The webhook handler + manager.handle_webhook_event() updates the subscription:
102
+ #
103
+ # webhook_handler = WebhookHandler(client)
104
+ #
105
+ # @webhook_handler.on("payment.success")
106
+ # async def on_payment(event):
107
+ # sub = await manager.handle_webhook_event(event)
108
+ # if sub:
109
+ # print(f"Subscription {sub.id} is now {sub.status}")
110
+ #
111
+ # @webhook_handler.on("recurring.charged")
112
+ # async def on_renewal(event):
113
+ # await manager.handle_webhook_event(event)
114
+ #
115
+ # @webhook_handler.on("recurring.failed")
116
+ # async def on_failure(event):
117
+ # await manager.handle_webhook_event(event)
118
+
152
119
  # ==================== Subscription Operations ====================
153
- print("\n⚙️ Subscription operations...")
154
-
155
- # Check if active
156
- print(f" Is active: {subscription.is_active}")
157
- print(f" Is trialing: {subscription.is_trialing}")
158
-
159
- # Upgrade to enterprise
160
- print("\n 📈 Upgrading to Enterprise...")
161
- subscription = await manager.change_tier(subscription.id, "enterprise")
162
- print(f" New tier: {subscription.tier_id}")
163
- print(f" New amount: ₪{subscription.amount}/month")
164
-
165
- # Pause subscription
166
- print("\n ⏸️ Pausing subscription...")
120
+ print("\nSubscription operations...")
121
+
122
+ # Upgrade to basic (while still incomplete, for demo)
123
+ print("\n Upgrading tier...")
124
+ subscription = await manager.change_tier(subscription.id, "basic")
125
+ print(f" New tier: {subscription.tier_id}, amount: ILS {subscription.amount}")
126
+
127
+ # Pause
128
+ print("\n Pausing subscription...")
167
129
  subscription = await manager.pause_subscription(subscription.id)
168
130
  print(f" Status: {subscription.status}")
169
-
170
- # Resume subscription
171
- print("\n ▶️ Resuming subscription...")
131
+
132
+ # Resume
133
+ print("\n Resuming subscription...")
172
134
  subscription = await manager.resume_subscription(subscription.id)
173
135
  print(f" Status: {subscription.status}")
174
-
136
+
175
137
  # Cancel at period end
176
- print("\n 🚫 Scheduling cancellation...")
138
+ print("\n Scheduling cancellation...")
177
139
  subscription = await manager.cancel_subscription(
178
140
  subscription.id,
179
141
  at_period_end=True,
180
142
  reason="Demo completed",
181
143
  )
182
- print(f" Will cancel at: {subscription.current_period_end}")
183
144
  print(f" Cancellation reason: {subscription.cancellation_reason}")
184
-
185
- # ==================== Billing Service ====================
186
- print("\n💵 Billing service...")
187
-
188
- billing = BillingService(manager)
189
-
190
- # In production, these would run on a schedule
191
- print(" Processing due renewals...")
192
- renewed = await billing.process_due_renewals()
193
- print(f" Renewed: {len(renewed)} subscriptions")
194
-
195
- print(" Processing trial endings...")
196
- converted = await billing.process_trial_endings()
197
- print(f" Converted: {len(converted)} trials")
198
-
199
- print("\n✅ Demo completed!")
200
-
201
- # Cleanup
145
+
146
+ print("\nDemo completed!")
202
147
  client.close()
203
148
 
204
149
 
payplus/__init__.py CHANGED
@@ -3,18 +3,16 @@ PayPlus Python SDK - Payment gateway integration with subscription management fo
3
3
  """
4
4
 
5
5
  from payplus.client import PayPlus
6
- from payplus.subscriptions.manager import SubscriptionManager
6
+ from payplus.models.customer import Customer
7
7
  from payplus.models.subscription import (
8
+ BillingCycle,
8
9
  Subscription,
9
10
  SubscriptionStatus,
10
- BillingCycle,
11
11
  )
12
- from payplus.models.customer import Customer
13
- from payplus.models.payment import Payment, PaymentStatus
14
- from payplus.models.invoice import Invoice, InvoiceStatus
15
12
  from payplus.models.tier import Tier
13
+ from payplus.subscriptions.manager import SubscriptionManager
16
14
 
17
- __version__ = "0.1.0"
15
+ __version__ = "0.2.1"
18
16
  __all__ = [
19
17
  "PayPlus",
20
18
  "SubscriptionManager",
@@ -22,9 +20,5 @@ __all__ = [
22
20
  "SubscriptionStatus",
23
21
  "BillingCycle",
24
22
  "Customer",
25
- "Payment",
26
- "PaymentStatus",
27
- "Invoice",
28
- "InvoiceStatus",
29
23
  "Tier",
30
24
  ]
@@ -0,0 +1,114 @@
1
+ """
2
+ PayPlus Customers API.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any, Optional
8
+
9
+ from payplus.api.base import BaseAPI
10
+
11
+
12
+ class CustomersAPI(BaseAPI):
13
+ """
14
+ Customers API for creating and managing customers on PayPlus.
15
+ """
16
+
17
+ def add(
18
+ self,
19
+ customer_name: str,
20
+ email: str,
21
+ phone: Optional[str] = None,
22
+ vat_number: Optional[int] = None,
23
+ paying_vat: bool = True,
24
+ customer_number: Optional[str] = None,
25
+ notes: Optional[str] = None,
26
+ contacts: Optional[list[dict[str, Any]]] = None,
27
+ business_address: Optional[str] = None,
28
+ business_city: Optional[str] = None,
29
+ business_postal_code: Optional[str] = None,
30
+ business_country_iso: str = "IL",
31
+ subject_code: Optional[str] = None,
32
+ communication_email: Optional[str] = None,
33
+ **kwargs: Any,
34
+ ) -> dict[str, Any]:
35
+ """
36
+ Create a new customer on PayPlus.
37
+
38
+ Args:
39
+ customer_name: Customer name (required)
40
+ email: Customer email (required)
41
+ phone: Customer phone number
42
+ vat_number: Customer or company VAT number
43
+ paying_vat: Whether customer pays VAT
44
+ customer_number: Internal customer number
45
+ notes: Notes about the customer
46
+ contacts: List of contact dicts
47
+ business_address: Business address
48
+ business_city: Business city
49
+ business_postal_code: Business postal code
50
+ business_country_iso: Business country code (default: IL)
51
+ subject_code: External customer number from ERP
52
+ communication_email: Email for communication
53
+
54
+ Returns:
55
+ API response with customer_uid in data
56
+ """
57
+ data: dict[str, Any] = {
58
+ "customer_name": customer_name,
59
+ "email": email,
60
+ "paying_vat": paying_vat,
61
+ }
62
+
63
+ if phone is not None:
64
+ data["phone"] = phone
65
+ if vat_number is not None:
66
+ data["vat_number"] = vat_number
67
+ if customer_number is not None:
68
+ data["customer_number"] = customer_number
69
+ if notes is not None:
70
+ data["notes"] = notes
71
+ if contacts is not None:
72
+ data["contacts"] = contacts
73
+ if business_address is not None:
74
+ data["business_address"] = business_address
75
+ if business_city is not None:
76
+ data["business_city"] = business_city
77
+ if business_postal_code is not None:
78
+ data["business_postal_code"] = business_postal_code
79
+ if business_country_iso != "IL":
80
+ data["business_country_iso"] = business_country_iso
81
+ if subject_code is not None:
82
+ data["subject_code"] = subject_code
83
+ if communication_email is not None:
84
+ data["communication_email"] = communication_email
85
+
86
+ for key, value in kwargs.items():
87
+ if key not in data and value is not None:
88
+ data[key] = value
89
+
90
+ return self._request("POST", "Customers/Add", data)
91
+
92
+ async def async_add(
93
+ self,
94
+ customer_name: str,
95
+ email: str,
96
+ **kwargs: Any,
97
+ ) -> dict[str, Any]:
98
+ """Async version of add."""
99
+ data: dict[str, Any] = {
100
+ "customer_name": customer_name,
101
+ "email": email,
102
+ "paying_vat": kwargs.pop("paying_vat", True),
103
+ }
104
+
105
+ if kwargs.get("phone") is not None:
106
+ data["phone"] = kwargs.pop("phone")
107
+ if kwargs.get("vat_number") is not None:
108
+ data["vat_number"] = kwargs.pop("vat_number")
109
+
110
+ for key, value in kwargs.items():
111
+ if key not in data and value is not None:
112
+ data[key] = value
113
+
114
+ return await self._async_request("POST", "Customers/Add", data)