Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
14.0 l10n ch import payment return (#66)
Browse files Browse the repository at this point in the history
* [ADD] l10n_ch_import_payment_return module to handle return from payment orders
Add functionalities to handle the payment return from the switzerland pain generated from payment orders
simgonzalez authored and ecino committed Jan 18, 2024
1 parent 94e5278 commit 20ed733
Showing 14 changed files with 290 additions and 0 deletions.
86 changes: 86 additions & 0 deletions l10n_ch_import_payment_return/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
=============================
l10n_ch_import_payment_return
=============================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:74050c117e767ff97c56412206efd3981573666cdf9205e8b4c1b71ae7b09a8f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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-CompassionCH%2Fl10n_switzerland-lightgray.png?logo=github
:target: https://github.com/CompassionCH/l10n_switzerland/tree/14.0/l10n_ch_import_payment_return
:alt: CompassionCH/l10n_switzerland

|badge1| |badge2| |badge3|

This module add the functionality of processing the return of the payment orders

**Table of contents**

.. contents::
:local:

Usage
=====

To use this module, you need to:

#. Go to EDI and configure a backend and an exchange type

#. Create a payment order and choose an EDI exchange type

#. When generating the file an edi exchange should be created

#. When a return come back it should be processed by the parser

Known issues / Roadmap
======================

* Automate a generic treatment process of the files

Changelog
=========

14.0.1.0.0 (2023-06-23)
~~~~~~~~~~~~~~~~~~~~~~~

* [ADD] l10n_ch_import_payment_return

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

Bugs are tracked on `GitHub Issues <https://github.com/CompassionCH/l10n_switzerland/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/CompassionCH/l10n_switzerland/issues/new?body=module:%20l10n_ch_import_payment_return%0Aversion:%2014.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
~~~~~~~

* Compassion CH

Contributors
~~~~~~~~~~~~

* Simon Gonzalez <simon.gonzalez@bluewin.ch> (https://compassion.ch)

Maintainers
~~~~~~~~~~~

This module is part of the `CompassionCH/l10n_switzerland <https://github.com/CompassionCH/l10n_switzerland/tree/14.0/l10n_ch_import_payment_return>`_ project on GitHub.

You are welcome to contribute.
2 changes: 2 additions & 0 deletions l10n_ch_import_payment_return/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import wizard
from . import components
16 changes: 16 additions & 0 deletions l10n_ch_import_payment_return/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2023 Compassion CH (<https://www.compassion.ch>)
# @author: Simon Gonzalez <simon.gonzalez@bluewin.ch>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "l10n_ch_import_payment_return",
"version": "14.0.1.0.0",
"development_status": "Beta",
"license": "AGPL-3",
"author": "Compassion CH,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/l10n-switzerland",
"category": "Banking addons",
"depends": ["account_payment_export_sftp"], # OCA/bank-payment
"data": ["data/edi_data.xml"],
"installable": True,
}
1 change: 1 addition & 0 deletions l10n_ch_import_payment_return/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import edi_import_payment_return
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2023 Compassion CH (https://www.compassion.ch)
# @author: Simon Gonzalez <simon.gonzalez@bluewin.ch>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import base64

from odoo.addons.component.core import Component

from ..wizard.pain_parser_ch import PainParserCH


class EDIExchangeProcessPaymentReturn(Component):
"""ACK for payment order pain002.01.03.CH"""

_name = "edi.input.payment.ch.process"
_inherit = "edi.component.input.mixin"
_backend_type = "sftp_pay_ord_ack"
_usage = "input.process"

def process(self):
origin_output = self.exchange_record.parent_id
payment_order = self.env[origin_output.model].browse(
self.exchange_record.parent_id.res_id
)
try:
PainParserCH().validate_initial_return(
base64.b64decode(self.exchange_record.exchange_file)
)
except Exception as e:
payment_order.action_cancel()
raise e
payment_order.generated2uploaded()
7 changes: 7 additions & 0 deletions l10n_ch_import_payment_return/data/edi_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="edi_ack_backend" model="edi.backend.type">
<field name="name">SFTP for payment orders with ack</field>
<field name="code">sftp_pay_ord_ack</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions l10n_ch_import_payment_return/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Simon Gonzalez <simon.gonzalez@bluewin.ch> (https://compassion.ch)
1 change: 1 addition & 0 deletions l10n_ch_import_payment_return/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This module add the functionality of processing the return of the payment orders
4 changes: 4 additions & 0 deletions l10n_ch_import_payment_return/readme/HISTORY.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
14.0.1.0.0 (2023-06-23)
~~~~~~~~~~~~~~~~~~~~~~~

* [ADD] l10n_ch_import_payment_return
1 change: 1 addition & 0 deletions l10n_ch_import_payment_return/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Automate a generic treatment process of the files
9 changes: 9 additions & 0 deletions l10n_ch_import_payment_return/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
To use this module, you need to:

#. Go to EDI and configure a backend and an exchange type

#. Create a payment order and choose an EDI exchange type

#. When generating the file an edi exchange should be created

#. When a return come back it should be processed by the parser
1 change: 1 addition & 0 deletions l10n_ch_import_payment_return/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import payment_return_import
97 changes: 97 additions & 0 deletions l10n_ch_import_payment_return/wizard/pain_parser_ch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright 2023 Compassion CH - Simon Gonzalez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging
import re

from lxml import etree

from odoo.addons.account_payment_return_import_iso20022.wizard.pain_parser import (
PainParser,
)

_logger = logging.getLogger(__name__)

ACCEPTANCE_STATUS = ["ACCP", "ACWC", "ACTC"]


class AcceptedEmptyByBankException(ValueError):
pass


class PainParserCH(PainParser):
"""Parser for SEPA Direct Debit Unpaid Report import files."""

def _get_root_ns(self, data):
try:
root = etree.fromstring(data, parser=etree.XMLParser(recover=True))
except etree.XMLSyntaxError:
# ABNAmro is known to mix up encodings
root = etree.fromstring(data.decode("iso-8859-15").encode("utf-8"))
if root is None:
raise ValueError("Not a valid xml file, or not an xml file at all.")
ns = root.tag[1 : root.tag.index("}")]
self.check_version(ns, root)
return root, ns

def get_origin_msg(self, file):
root, ns = self._get_root_ns(file)
ORIGIN_MSG_XML_NODE = (
"./ns:CstmrPmtStsRpt/ns:OrgnlGrpInfAndSts/ns:OrgnlMsgId",
)
found_node = root.xpath(ORIGIN_MSG_XML_NODE, namespaces={"ns": ns})
return found_node[0].text

def check_version(self, ns, root):
"""Validate validity of SEPA Direct Debit Unpaid Report file."""
# Check wether it is SEPA Direct Debit Unpaid Report at all:
re_pain = re.compile(r"^http://www.six-interbank-clearing.com/.*/pain")
if not re_pain.search(ns):
raise ValueError("no pain: " + ns)
# Check wether version 002.001.03.ch:
re_pain_version = re.compile(r"|pain.002.001.03.ch.02.xsd")
if not re_pain_version.search(ns):
raise ValueError("no PAIN.002.001.03: " + ns)
# Check GrpHdr element:
root_0_0 = root[0][0].tag[len(ns) + 2 :] # strip namespace
if root_0_0 != "GrpHdr":
raise ValueError("expected GrpHdr, got: " + root_0_0)

def validate_grp_status(self, ns, root):
"""Validate the status of the pain 002"""
STATUS_XML_NODE = "./ns:CstmrPmtStsRpt/ns:OrgnlGrpInfAndSts/ns:GrpSts"
found_node = root.xpath(STATUS_XML_NODE, namespaces={"ns": ns})
if found_node:
return_status = found_node[0].text
_logger.info(f"File has this status {return_status}")
if return_status in ACCEPTANCE_STATUS:
return True
elif return_status == "PART":
return True
elif return_status == "RJCT":
info_status = root.xpath(
"./ns:CstmrPmtStsRpt/ns:OrgnlGrpInfAndSts/ns:StsRsnInf/ns:AddtlInf",
namespaces={"ns": ns},
)
raise ValueError(f"File rejected !\nReason: {info_status[0].text}")
else:
raise ValueError("Status not known by Pain Parser CH")
else:
raise ValueError("Status Node not found")

def validate_initial_return(self, data):
root, ns = self._get_root_ns(data)
self.validate_grp_status(ns, root)
return root, ns

def parse(self, data):
"""Parse a pain.002.001.03 file."""
root, ns = self.validate_initial_return(data)
payment_returns = []
for node in root:
payment_return = super().parse_payment_return(ns, node)
if payment_return["transactions"]:
if not payment_return.get("account_number"):
payment_return["account_number"] = False
payment_returns.append(payment_return)
return payment_returns
33 changes: 33 additions & 0 deletions l10n_ch_import_payment_return/wizard/payment_return_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2016 Tecnativa - Carlos Dauden
# Copyright 2019 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import api, models

from .pain_parser_ch import PainParserCH

_logger = logging.getLogger(__name__)


class PaymentReturnImport(models.TransientModel):
_inherit = "payment.return.import"

@api.model
def _parse_single_document(self, data_file):
"""
Try to parse the file as the following format or fall back on next
parser:
- pain.002.001.03.CH
"""
pain_parser = PainParserCH()
try:
_logger.debug("Try parsing as a PAIN Direct Debit Unpaid CH " "Report.")
return pain_parser.parse(data_file)
except ValueError:
_logger.debug(
"Payment return file is not a ISO20022 CH " "supported file.",
exc_info=True,
)
return super()._parse_single_document(data_file)

0 comments on commit 20ed733

Please sign in to comment.