import hashlib import hmac import json import logging import httpx _logger = logging.getLogger(__name__) PAYMOB_BASE = "https://accept.paymob.com/api" class EncoachPaymobService: def __init__(self, env): self.env = env params = env["ir.config_parameter"].sudo() self.api_key = params.get_param("encoach.paymob_api_key", "") self.hmac_secret = params.get_param("encoach.paymob_hmac_secret", "") def _authenticate(self): resp = httpx.post( f"{PAYMOB_BASE}/auth/tokens", json={"api_key": self.api_key}, timeout=30, ) resp.raise_for_status() return resp.json()["token"] def create_intention(self, user, package, discount_code=None): if not self.api_key: return {"error": "Paymob API 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)) currency = (package.currency or "EGP").upper() try: auth_token = self._authenticate() order_resp = httpx.post( f"{PAYMOB_BASE}/ecommerce/orders", json={ "auth_token": auth_token, "delivery_needed": False, "amount_cents": amount_cents, "currency": currency, "merchant_order_id": f"encoach_{user.id}_{package.id}", "items": [{ "name": package.name, "amount_cents": amount_cents, "quantity": 1, }], }, timeout=30, ) order_resp.raise_for_status() order = order_resp.json() integration_id = self.env["ir.config_parameter"].sudo().get_param( "encoach.paymob_integration_id", "" ) key_resp = httpx.post( f"{PAYMOB_BASE}/acceptance/payment_keys", json={ "auth_token": auth_token, "amount_cents": amount_cents, "expiration": 3600, "order_id": order["id"], "billing_data": { "email": user.login, "first_name": (user.name or "").split()[0] if user.name else "N/A", "last_name": (user.name or "").split()[-1] if user.name else "N/A", "phone_number": "N/A", "apartment": "N/A", "floor": "N/A", "street": "N/A", "building": "N/A", "shipping_method": "N/A", "postal_code": "N/A", "city": "N/A", "country": "N/A", "state": "N/A", }, "currency": currency, "integration_id": int(integration_id) if integration_id else 0, "lock_order_when_paid": True, }, timeout=30, ) key_resp.raise_for_status() payment_key = key_resp.json()["token"] return { "intentionId": str(order["id"]), "clientSecret": payment_key, } except httpx.HTTPStatusError as e: _logger.error("Paymob API error: %s", e.response.text) return {"error": f"Paymob error: {e.response.status_code}"} except Exception as e: _logger.exception("Paymob create intention error") return {"error": str(e)} def verify_transaction(self, payload, hmac_header): if not self.hmac_secret: _logger.warning("No Paymob HMAC secret configured") return None try: data = payload if isinstance(payload, dict) else json.loads(payload) obj = data.get("obj", {}) concat_str = ( str(obj.get("amount_cents", "")) + str(obj.get("created_at", "")) + str(obj.get("currency", "")) + str(obj.get("error_occured", "")).lower() + str(obj.get("has_parent_transaction", "")).lower() + str(obj.get("id", "")) + str(obj.get("integration_id", "")) + str(obj.get("is_3d_secure", "")).lower() + str(obj.get("is_auth", "")).lower() + str(obj.get("is_capture", "")).lower() + str(obj.get("is_refunded", "")).lower() + str(obj.get("is_standalone_payment", "")).lower() + str(obj.get("is_voided", "")).lower() + str(obj.get("order", {}).get("id", "")) + str(obj.get("owner", "")) + str(obj.get("pending", "")).lower() + str(obj.get("source_data", {}).get("pan", "")) + str(obj.get("source_data", {}).get("sub_type", "")) + str(obj.get("source_data", {}).get("type", "")) + str(obj.get("success", "")).lower() ) computed = hmac.new( self.hmac_secret.encode("utf-8"), concat_str.encode("utf-8"), hashlib.sha512, ).hexdigest() if not hmac.compare_digest(computed, hmac_header): return None if obj.get("success") is True: merchant_order_id = str(obj.get("order", {}).get("merchant_order_id", "")) parts = merchant_order_id.replace("encoach_", "").split("_") user_id = int(parts[0]) if len(parts) >= 1 else 0 package_id = int(parts[1]) if len(parts) >= 2 else 0 amount = obj.get("amount_cents", 0) / 100.0 currency = obj.get("currency", "EGP") transaction_id = str(obj.get("id", "")) from .subscription_helper import extend_subscription extend_subscription( self.env, user_id, package_id, "paymob", transaction_id, amount, currency, ) return {"ok": True} return {"error": "Transaction not successful"} except Exception: _logger.exception("Paymob verify transaction error") return None