oca-ocb-core/odoo-bringout-oca-ocb-payment_flutterwave/payment_flutterwave/controllers/main.py
Ernad Husremovic aee3ee8bf7 add missing payment providers and iot modules for 19.0
Add 19 payment provider modules needed by the sale module:
payment_adyen, payment_aps, payment_asiapay, payment_authorize,
payment_buckaroo, payment_demo, payment_dpo, payment_flutterwave,
payment_iyzico, payment_mercado_pago, payment_mollie, payment_nuvei,
payment_paymob, payment_paypal, payment_razorpay, payment_redsys,
payment_stripe, payment_worldline, payment_xendit

Add 3 IoT modules needed for point_of_sale:
iot_base, iot_box_image, iot_drivers

Note: Stripe test API keys replaced with placeholders.

🤖 assisted by claude
2026-03-09 15:45:22 +01:00

110 lines
4.2 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import hmac
import json
import pprint
from werkzeug.exceptions import Forbidden
from odoo import http
from odoo.exceptions import ValidationError
from odoo.http import request
from odoo.addons.payment.logging import get_payment_logger
_logger = get_payment_logger(__name__)
class FlutterwaveController(http.Controller):
_return_url = '/payment/flutterwave/return'
_auth_return_url = '/payment/flutterwave/auth_return'
_webhook_url = '/payment/flutterwave/webhook'
@http.route(_return_url, type='http', methods=['GET'], auth='public')
def flutterwave_return_from_checkout(self, **data):
"""Process the payment data sent by Flutterwave after redirection from checkout.
:param dict data: The payment data.
"""
_logger.info("Handling redirection from Flutterwave with data:\n%s", pprint.pformat(data))
if data.get('status') != 'cancelled':
self._verify_and_process(data)
else: # The customer cancelled the payment by clicking on the close button.
pass # Don't try to process this case because the transaction id was not provided.
# Redirect the user to the status page.
return request.redirect('/payment/status')
@http.route(_auth_return_url, type='http', methods=['GET'], auth='public')
def flutterwave_return_from_authorization(self, response=None):
""" Process the response sent by Flutterwave after authorization.
:param str response: The stringified JSON response.
"""
data = json.loads(response) if response else {}
return self.flutterwave_return_from_checkout(**data)
@http.route(_webhook_url, type='http', methods=['POST'], auth='public', csrf=False)
def flutterwave_webhook(self):
"""Process the payment data sent by Flutterwave to the webhook.
:return: An empty string to acknowledge the notification.
:rtype: str
"""
data = request.get_json_data()
_logger.info("Notification received from Flutterwave with data:\n%s", pprint.pformat(data))
if data['event'] == 'charge.completed':
payment_data = data['data']
tx_sudo = request.env['payment.transaction'].sudo()._search_by_reference(
'flutterwave', payment_data
)
if tx_sudo:
signature = request.httprequest.headers.get('verif-hash')
self._verify_signature(signature, tx_sudo)
tx_sudo._process('flutterwave', payment_data)
return request.make_json_response('')
@staticmethod
def _verify_signature(received_signature, tx_sudo):
"""Check that the received signature matches the expected one.
:param dict received_signature: The signature received with the payment data.
:param payment.transaction tx_sudo: The sudoed transaction referenced by the payment data.
:return: None
:raise Forbidden: If the signatures don't match.
"""
# Check for the received signature.
if not received_signature:
_logger.warning("Received payment data with missing signature.")
raise Forbidden()
# Compare the received signature with the expected signature.
expected_signature = tx_sudo.provider_id.flutterwave_webhook_secret
if not hmac.compare_digest(received_signature, expected_signature):
_logger.warning("Received payment data with invalid signature.")
raise Forbidden()
@staticmethod
def _verify_and_process(data):
"""Verify and process the payment data sent by Flutterwave.
:param dict data: The payment data.
:return: None
"""
tx_sudo = request.env['payment.transaction'].sudo()._search_by_reference(
'flutterwave', data
)
if not tx_sudo:
return
try:
verified_data = tx_sudo._send_api_request(
'GET', 'transactions/verify_by_reference', params={'tx_ref': tx_sudo.reference},
)
except ValidationError:
_logger.error("Unable to verify the payment data")
else:
tx_sudo._process('flutterwave', verified_data)