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)}