Files
encoach_be_odoo19/encoach_subscription/services/paypal_service.py
Talal Sharabi f5b627256f EnCoach Odoo 19 custom modules
Full backend implementation with custom Odoo modules:
- encoach_api: Core API, user management, JWT auth
- encoach_exam: Exam generation (reading, writing, listening, speaking)
- encoach_evaluate: AI-powered evaluation (writing, speaking)
- encoach_training: Training tips and walkthrough
- encoach_storage: File storage management
- encoach_payment: Stripe, PayPal, Paymob integration
- encoach_mail: Email notifications

Made-with: Cursor
2026-03-14 16:46:46 +04:00

129 lines
4.9 KiB
Python

import logging
import httpx
_logger = logging.getLogger(__name__)
PAYPAL_SANDBOX_BASE = "https://api-m.sandbox.paypal.com"
PAYPAL_LIVE_BASE = "https://api-m.paypal.com"
class EncoachPaypalService:
def __init__(self, env):
self.env = env
params = env["ir.config_parameter"].sudo()
self.client_id = params.get_param("encoach.paypal_client_id", "")
self.client_secret = params.get_param("encoach.paypal_client_secret", "")
is_live = params.get_param("encoach.paypal_live", "false").lower() == "true"
self.base_url = PAYPAL_LIVE_BASE if is_live else PAYPAL_SANDBOX_BASE
def _get_access_token(self):
resp = httpx.post(
f"{self.base_url}/v1/oauth2/token",
auth=(self.client_id, self.client_secret),
data={"grant_type": "client_credentials"},
timeout=30,
)
resp.raise_for_status()
return resp.json()["access_token"]
def _headers(self, token):
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
def create_order(self, user, package, discount_code=None):
if not self.client_id or not self.client_secret:
return {"error": "PayPal credentials not configured"}
amount = package.price
if discount_code:
discount = self.env["encoach.discount"].sudo().search([
("code", "=", discount_code),
], limit=1)
if discount and discount.is_valid():
amount = amount * (1 - discount.percentage / 100.0)
currency = (package.currency or "USD").upper()
try:
token = self._get_access_token()
order_data = {
"intent": "CAPTURE",
"purchase_units": [{
"amount": {
"currency_code": currency,
"value": f"{amount:.2f}",
},
"description": package.name,
"custom_id": f"{user.id}:{package.id}",
}],
"application_context": {
"return_url": "https://encoach.ai/payment/success",
"cancel_url": "https://encoach.ai/payment/cancel",
},
}
resp = httpx.post(
f"{self.base_url}/v2/checkout/orders",
headers=self._headers(token),
json=order_data,
timeout=30,
)
resp.raise_for_status()
order = resp.json()
approval_url = ""
for link in order.get("links", []):
if link["rel"] == "approve":
approval_url = link["href"]
break
return {"orderId": order["id"], "approvalUrl": approval_url}
except httpx.HTTPStatusError as e:
_logger.error("PayPal API error: %s", e.response.text)
return {"error": f"PayPal error: {e.response.status_code}"}
except Exception as e:
_logger.exception("PayPal create order error")
return {"error": str(e)}
def capture_order(self, user, order_id):
if not self.client_id or not self.client_secret:
return {"error": "PayPal credentials not configured"}
try:
token = self._get_access_token()
resp = httpx.post(
f"{self.base_url}/v2/checkout/orders/{order_id}/capture",
headers=self._headers(token),
timeout=30,
)
resp.raise_for_status()
capture = resp.json()
if capture.get("status") == "COMPLETED":
purchase_unit = capture.get("purchase_units", [{}])[0]
custom_id = purchase_unit.get("payments", {}).get("captures", [{}])[0].get("custom_id", "")
parts = custom_id.split(":")
user_id = int(parts[0]) if len(parts) >= 1 else user.id
package_id = int(parts[1]) if len(parts) >= 2 else 0
cap_amount = purchase_unit.get("payments", {}).get("captures", [{}])[0]
amount = float(cap_amount.get("amount", {}).get("value", 0))
currency = cap_amount.get("amount", {}).get("currency_code", "USD")
from .subscription_helper import extend_subscription
extend_subscription(
self.env, user_id, package_id, "paypal", order_id, amount, currency,
)
return {"ok": True}
else:
return {"error": f"Order status: {capture.get('status')}"}
except httpx.HTTPStatusError as e:
_logger.error("PayPal capture error: %s", e.response.text)
return {"error": f"PayPal error: {e.response.status_code}"}
except Exception as e:
_logger.exception("PayPal capture error")
return {"error": str(e)}