Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions payment_sequra/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
=======================
SeQura Payment Provider
=======================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:9e0ee693a90d9f4b4b2dcb1000aadf108a00e8ae9a080cb33f865ba085a092ad
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--payment-lightgray.png?logo=github
:target: https://github.com/OCA/account-payment/tree/18.0/payment_sequra
:alt: OCA/account-payment
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-payment-18-0/account-payment-18-0-payment_sequra
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/account-payment&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module integrates SeQura using a combination of the Checkout API
and Order Update API. It leverages the generic redirection-based payment
flow provided by the payment module to create and initialize SeQura
transactions using the same request payload that would normally be sent
from SeQura’s embedded checkout.

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-payment/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/account-payment/issues/new?body=module:%20payment_sequra%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Tecnativa

Contributors
------------

- `Tecnativa <https://www.tecnativa.com>`__:

- Juan Carlos Oñate

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

.. |maintainer-juancarlosonate-tecnativa| image:: https://github.com/juancarlosonate-tecnativa.png?size=40px
:target: https://github.com/juancarlosonate-tecnativa
:alt: juancarlosonate-tecnativa

Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:

|maintainer-juancarlosonate-tecnativa|

This module is part of the `OCA/account-payment <https://github.com/OCA/account-payment/tree/18.0/payment_sequra>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
12 changes: 12 additions & 0 deletions payment_sequra/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from . import models
from . import controllers

from odoo.addons.payment import setup_provider, reset_payment_provider


def post_init_hook(env):
setup_provider(env, "sequra")


def uninstall_hook(env):
reset_payment_provider(env, "sequra")
22 changes: 22 additions & 0 deletions payment_sequra/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2025 Juan Carlos Oñate - Tecnativa <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "SeQura Payment Provider",
"summary": "Integrates SeQura as a payment provider",
"version": "18.0.1.0.0",
"category": "Accounting/Payment Providers",
"author": "Tecnativa, Odoo Community Association (OCA)",
"maintainers": ["juancarlosonate-tecnativa"],
"license": "AGPL-3",
"website": "https://github.com/OCA/account-payment",
"depends": ["sale", "stock"],
"data": [
"views/payment_provider_views.xml",
"views/payment_sequra_templates.xml",
"data/payment_provider_data.xml",
"data/stock_picking_actions.xml",
],
"post_init_hook": "post_init_hook",
"uninstall_hook": "uninstall_hook",
"installable": True,
}
6 changes: 6 additions & 0 deletions payment_sequra/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
DEFAULT_PAYMENT_METHOD_CODES = {
"pp3",
}
SUPPORTED_CURRENCIES = [
"EUR",
]
1 change: 1 addition & 0 deletions payment_sequra/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
52 changes: 52 additions & 0 deletions payment_sequra/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright 2025 Juan Carlos Oñate - Tecnativa <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

import logging

from odoo import http
from odoo.http import request

_logger = logging.getLogger(__name__)


class SequraController(http.Controller):
_return_url = "/payment/sequra/return"
_notify_url = "/payment/sequra/notify"
_abort_url = "/payment/sequra/abort"
_webhook_url = "/payment/sequra/webhook"

@http.route(_return_url, type="http", methods=["GET"], auth="public", csrf=False)
def sequra_return(self, **data):
return request.redirect("/payment/status")

@http.route(_notify_url, type="http", methods=["POST"], auth="public", csrf=False)
def sequra_notify(self, **data):
try:
request.env["payment.transaction"].sudo()._handle_notification_data(
"sequra", data
)
except Exception:
_logger.exception("Error processing SeQura IPN")
return request.make_response("Internal Server Error", status=500)
return request.make_response("OK", status=200)

@http.route(
"/payment/sequra/abort/<model('payment.transaction'):transaction>",
type="http",
methods=["GET"],
auth="public",
csrf=False,
)
def sequra_abort(self, transaction=None, **data):
if transaction:
_logger.warning(
"SeQura checkout aborted for transaction %s", transaction.reference
)
transaction._set_canceled()
else:
_logger.warning("No transaction found for SeQura abort call.")
return request.redirect("/payment/status")

@http.route(_webhook_url, type="http", methods=["POST"], auth="public", csrf=False)
def sequra_webhook(self, **data):
return request.make_response("OK", status=200)
33 changes: 33 additions & 0 deletions payment_sequra/data/payment_provider_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="payment_method_pp3" model="payment.method">
<field name="name">Part payments</field>
<field name="code">pp3</field>
<field
name="image"
type="base64"
file="payment_sequra/static/description/pp3.png"
/>
</record>
<record id="payment_provider_sequra" model="payment.provider">
<field name="name">SeQura</field>
<field
name="image_128"
type="base64"
file="payment_sequra/static/description/icon.png"
/>
<field name="code">sequra</field>
<field name="company_id" ref="base.main_company" />
<field name="redirect_form_view_id" ref="sequra_form" />
<field name="sequra_merchant_id">sequra_merchant_id</field>
<field name="sequra_account_key">sequra_account_key</field>
<field name="sequra_account_secret">sequra_account_secret</field>
<field name="module_id" ref="base.module_payment_sequra" />
<field
name="payment_method_ids"
eval="[Command.set([
ref('payment_sequra.payment_method_pp3'),
])]"
/>
</record>
</odoo>
11 changes: 11 additions & 0 deletions payment_sequra/data/stock_picking_actions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="action_server_sequra_notify_shipment" model="ir.actions.server">
<field name="name">Send Shipment Notification to SeQura</field>
<field name="model_id" ref="stock.model_stock_picking" />
<field name="binding_model_id" ref="stock.model_stock_picking" />
<field name="binding_view_types">form,list</field>
<field name="state">code</field>
<field name="code">records._sequra_notify_shipment()</field>
</record>
</odoo>
3 changes: 3 additions & 0 deletions payment_sequra/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import payment_transaction
from . import payment_provider
from . import stock_picking
84 changes: 84 additions & 0 deletions payment_sequra/models/payment_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2025 Juan Carlos Oñate - Tecnativa <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import base64
import logging

import requests

from odoo import fields, models
from odoo.exceptions import UserError

from .. import const

_logger = logging.getLogger(__name__)


class PaymentProvider(models.Model):
_inherit = "payment.provider"

code = fields.Selection(
selection_add=[("sequra", "SeQura")], ondelete={"sequra": "set default"}
)
sequra_merchant_id = fields.Char(
required_if_provider="sequra", groups="base.group_system"
)
sequra_account_key = fields.Char(
required_if_provider="sequra", groups="base.group_system"
)
sequra_account_secret = fields.Char(
required_if_provider="sequra", groups="base.group_system"
)

def _get_supported_currencies(self):
"""Override of `payment` to return the supported currencies."""
supported_currencies = super()._get_supported_currencies()
if self.code == "sequra":
supported_currencies = supported_currencies.filtered(
lambda c: c.name in const.SUPPORTED_CURRENCIES
)
return supported_currencies

def _sequra_get_base_url(self):
self.ensure_one()
return (
"https://sandbox.sequrapi.com"
if self.state == "test"
else "https://api.sequrapi.com"
)

def _sequra_make_request(self, endpoint, payload=None, method="POST"):
self.ensure_one()
base_url = self._sequra_get_base_url()
url = endpoint if endpoint.startswith("http") else f"{base_url}{endpoint}"
credentials = f"{self.sequra_account_key}:{self.sequra_account_secret}"
encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode(
"utf-8"
)
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Sequra-Merchant-Id": self.sequra_merchant_id,
"Authorization": f"Basic {encoded_credentials}",
}
try:
response = requests.request(
method, url, json=payload, headers=headers, timeout=20
)
except requests.RequestException as e:
_logger.exception("Connection error contacting SeQura API: %s", e)
raise UserError(self.env._("Could not reach SeQura API: %s") % e) from e
if response.status_code not in (200, 202, 204, 409):
_logger.error(
"SeQura API error [%s]: %s", response.status_code, response.text
)
raise UserError(
self.env._("SeQura API request failed (%s): %s")
% (response.status_code, response.text)
)
return response

def _get_default_payment_method_codes(self):
default_codes = super()._get_default_payment_method_codes()
if self.code != "sequra":
return default_codes
return const.DEFAULT_PAYMENT_METHOD_CODES
Loading