Skip to content

Commit 3af57c7

Browse files
committed
feat(release): bump
♬ Morgan Willis - Set Me Free
1 parent 1ed9063 commit 3af57c7

File tree

2 files changed

+50
-13
lines changed

2 files changed

+50
-13
lines changed

paypal_subscription/paypal.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@ class PayPalAPI:
1515
def __init__(self):
1616
self.client_id = os.getenv("PAYPAL_CLIENT_ID")
1717
self.client_secret = os.getenv("PAYPAL_CLIENT_SECRET")
18-
self.base_url = "https://api.sandbox.paypal.com" if os.getenv("PAYPAL_SANDBOX", "True") == "True" else "https://api.paypal.com"
18+
self.base_url = "https://api-m.sandbox.paypal.com" if os.getenv("PAYPAL_SANDBOX", "True") == "True" else "https://api.paypal.com"
1919
if self.client_id != "" and self.client_secret != "":
2020
self.access_token = self._get_access_token()
2121
else:
2222
raise ValueError("Missing Paypal Secrets")
23-
self.headers = {"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json"}
23+
self.headers = {"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json", "Accept": "application/json"}
2424
self.plan_id = ""
2525

2626
def _make_request(self, url: str, method: str, **kwargs) -> Any:
2727
response = requests.request(method, url, **kwargs)
28-
response.raise_for_status()
28+
if not response.ok:
29+
print(f"PayPal API Error: {response.status_code} - {response.text}")
30+
response.raise_for_status()
2931
return response.json()
3032

3133
def _get_access_token(self) -> str:
@@ -139,6 +141,26 @@ def verify_payment(self, order_id: str) -> Dict[str, Any]:
139141
"order_details": order_details
140142
}
141143

144+
def get_product_by_name(self, name: str) -> Optional[Dict[str, Any]]:
145+
"""
146+
Search for a product by name.
147+
148+
Args:
149+
name (str): Name of the product.
150+
151+
Returns:
152+
Optional[Dict[str, Any]]: Product details if found, else None.
153+
"""
154+
url = f"{self.base_url}/v1/catalogs/products?page_size=20"
155+
response = requests.get(url, headers=self.headers)
156+
response.raise_for_status()
157+
products = response.json().get("products", [])
158+
159+
for product in products:
160+
if product.get("name") == name:
161+
return product
162+
return None
163+
142164
def create_product(self, name: str, description: str, type_: str = "SERVICE", category: str = "SOFTWARE") -> Dict[str, Any]:
143165
"""
144166
Create a product for subscription.
@@ -161,21 +183,27 @@ def create_product(self, name: str, description: str, type_: str = "SERVICE", ca
161183
url = f"{self.base_url}/v1/catalogs/products"
162184
return self._make_request(url=url, method="POST", json=product_data, headers=self.headers)
163185

164-
def create_plan(self, product_id: str, name: str, description: str, price: str, currency: str = "EUR", cycles: int = 1) -> Dict[str, Any]:
186+
def create_plan(self, name: str, description: str, price: str, currency: str = "EUR", cycles: int = 1) -> Dict[str, Any]:
165187
"""
166-
Create a subscription plan.
188+
Create a subscription plan, and create the product if it doesn't exist.
167189
168190
Args:
169-
product_id (str): Product ID.
170-
name (str): Plan name.
171-
description (str): Plan description.
191+
name (str): Plan and product name.
192+
description (str): Plan and product description.
172193
price (str): Plan price.
173194
currency (str): Currency code (default is "EUR").
174-
cycles (int): Number of payment cycles (default is 1 for one-time subscription, 0 infinite).
195+
cycles (int): Number of billing cycles (default is 1).
175196
176197
Returns:
177198
Dict[str, Any]: API response with plan details.
178199
"""
200+
product = self.get_product_by_name(name)
201+
if product:
202+
product_id = product["id"]
203+
else:
204+
product = self.create_product(name=name, description=description)
205+
product_id = product["id"]
206+
179207
data = {
180208
"product_id": product_id,
181209
"name": name,
@@ -186,15 +214,25 @@ def create_plan(self, product_id: str, name: str, description: str, price: str,
186214
"tenure_type": "REGULAR",
187215
"sequence": 1,
188216
"total_cycles": cycles,
189-
"pricing_scheme": {"fixed_price": {"value": price, "currency_code": currency}}
217+
"pricing_scheme": {
218+
"fixed_price": {
219+
"value": price,
220+
"currency_code": currency
221+
}
222+
}
190223
}
191224
],
192225
"payment_preferences": {
193226
"auto_bill_outstanding": True,
227+
"setup_fee": {
228+
"value": "0",
229+
"currency_code": currency
230+
},
194231
"setup_fee_failure_action": "CONTINUE",
195232
"payment_failure_threshold": 3
196233
}
197234
}
235+
198236
url = f"{self.base_url}/v1/billing/plans"
199237
return self._make_request(url=url, method="POST", json=data, headers=self.headers)
200238

@@ -372,7 +410,6 @@ def create_or_update_subscription(self, identifier: str, name: str = "", descrip
372410
else:
373411
if os.getenv("DEBUG", False):
374412
print(f"Subscription {identifier} not found. Creating a new subscription.")
375-
product = self.create_product(name=name, description=description)
376-
plan = self.create_plan(product_id=product["id"], name=name, description=description, price=price, currency=currency)
413+
plan = self.create_plan(name=name, description=description, price=price, currency=currency)
377414
new_subscription = self.create_subscription(plan_id=plan["id"], subscriber_email=subscriber_email, return_url=return_url, cancel_url=cancel_url)
378415
return new_subscription

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='paypal-subscription',
5-
version='0.1.2',
5+
version='0.1.3',
66
packages=find_packages(),
77
author='Codeat',
88
author_email='mte90net@gmail.com',

0 commit comments

Comments
 (0)