import hashlib import hmac import json import logging import time import httpx _logger = logging.getLogger(__name__) STRIPE_API_BASE = "https://api.stripe.com/v1" class EncoachStripeService: def __init__(self, env): self.env = env params = env["ir.config_parameter"].sudo() self.api_key = params.get_param("encoach.stripe_secret_key", "") self.webhook_secret = params.get_param("encoach.stripe_webhook_secret", "") def _headers(self): return { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/x-www-form-urlencoded", } def create_checkout_session(self, user, package, success_url, cancel_url, discount_code=None): if not self.api_key: return {"error": "Stripe secret key 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) amount_cents = int(round(amount * 100)) data = { "mode": "payment", "line_items[0][price_data][currency]": (package.currency or "usd").lower(), "line_items[0][price_data][unit_amount]": str(amount_cents), "line_items[0][price_data][product_data][name]": package.name, "line_items[0][quantity]": "1", "success_url": success_url, "cancel_url": cancel_url, "client_reference_id": str(user.id), "customer_email": user.login, "metadata[package_id]": str(package.id), "metadata[user_id]": str(user.id), } try: resp = httpx.post( f"{STRIPE_API_BASE}/checkout/sessions", headers=self._headers(), data=data, timeout=30, ) resp.raise_for_status() session = resp.json() return {"sessionId": session["id"], "url": session["url"]} except httpx.HTTPStatusError as e: _logger.error("Stripe API error: %s", e.response.text) return {"error": f"Stripe error: {e.response.status_code}"} except Exception as e: _logger.exception("Stripe checkout error") return {"error": str(e)} def verify_webhook_signature(self, payload, sig_header): if not self.webhook_secret: _logger.warning("No Stripe webhook secret configured") return False try: elements = {k: v for k, v in ( item.split("=", 1) for item in sig_header.split(",") )} timestamp = elements.get("t", "") signature = elements.get("v1", "") signed_payload = f"{timestamp}.{payload}" expected = hmac.new( self.webhook_secret.encode("utf-8"), signed_payload.encode("utf-8"), hashlib.sha256, ).hexdigest() if not hmac.compare_digest(expected, signature): return False if abs(time.time() - int(timestamp)) > 300: _logger.warning("Stripe webhook timestamp too old") return False return True except Exception: _logger.exception("Stripe webhook signature verification failed") return False def handle_webhook(self, payload, sig_header): if not self.verify_webhook_signature(payload, sig_header): return {"error": "Invalid signature"} event = json.loads(payload) event_type = event.get("type") if event_type == "checkout.session.completed": session = event["data"]["object"] user_id = int(session.get("client_reference_id", 0)) package_id = int(session.get("metadata", {}).get("package_id", 0)) transaction_id = session.get("payment_intent") or session.get("id") if user_id and package_id: self._complete_payment( user_id=user_id, package_id=package_id, provider="stripe", transaction_id=transaction_id, amount=session.get("amount_total", 0) / 100.0, currency=(session.get("currency") or "usd").upper(), ) return {"ok": True} def _complete_payment(self, user_id, package_id, provider, transaction_id, amount, currency): from .subscription_helper import extend_subscription extend_subscription( self.env, user_id, package_id, provider, transaction_id, amount, currency, )