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
129 lines
4.9 KiB
Python
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)}
|