Skip to content

Commit

Permalink
[ADD] payment_payfip
Browse files Browse the repository at this point in the history
  • Loading branch information
ivantodorovich committed Sep 2, 2021
1 parent fb80129 commit 57eb11e
Show file tree
Hide file tree
Showing 21 changed files with 583 additions and 0 deletions.
2 changes: 2 additions & 0 deletions payment_payfip/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import controllers
from . import models
22 changes: 22 additions & 0 deletions payment_payfip/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2021 Moka Tourisme
# @author: Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Payment PayFIP",
"category": "Payment Acquirer",
"summary": "Payment Acquirer: PayFIP Implementation",
"version": "12.0.1.0.0",
"author": "Moka Tourisme,"
"Odoo Community Association (OCA)",
"license": "AGPL-3",
"maintainers": ["ivantodorovich"],
"depends": ["payment"],
"data": [
"views/assets.xml",
"views/templates.xml",
"views/payment_acquirer.xml",
"data/payment_icon.xml",
"data/payment_acquirer.xml",
],
}
1 change: 1 addition & 0 deletions payment_payfip/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
28 changes: 28 additions & 0 deletions payment_payfip/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright 2021 Moka Tourisme
# @author: Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging
import pprint
import werkzeug

from odoo import http
from odoo.http import request

_logger = logging.getLogger(__name__)


class PayfipController(http.Controller):

@http.route(['/payment/payfip/return'], type='http', auth='none', csrf=False)
def payfip_return(self, **post):
_logger.debug(
"PayFIP: entering form_feedback with post data %s",
pprint.pformat(post),
)
try:
request.env["payment.transaction"].sudo().form_feedback(post, "payfip")
except Exception as e:
_logger.exception(e)
raise e
return werkzeug.utils.redirect("/payment/process")
36 changes: 36 additions & 0 deletions payment_payfip/data/payment_acquirer.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 Moka Tourisme
@author: Iván Todorovich <ivan.todorovich@gmail.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<data noupdate="1">

<record id="payment_acquirer_payfip" model="payment.acquirer">
<field name="name">PayFIP</field>
<field name="provider">payfip</field>
<field name="image" type="base64" file="payment_payfip/static/src/img/icon.png"/>
<field name="company_id" ref="base.main_company"/>
<field name="view_template_id" ref="payfip_form"/>
<field name="environment">test</field>
<field name="description" type="html">
<p>A payment gateway to pay your public invoices.</p>
<ul class="list-inline">
<li class="list-inline-item"><i class="fa fa-check"/>Online Payment</li>
<li class="list-inline-item"><i class="fa fa-check"/>Payment Status Tracking</li>
</ul>
</field>
<field name="pre_msg"><![CDATA[<p>You will be redirected to the PayFIP website after clicking on the payment button.</p>]]></field>
<field name="payment_icon_ids" eval='[(6, 0, [
ref("payment.payment_icon_cc_mastercard"),
ref("payment.payment_icon_cc_visa"),
ref("payment_icon_sepa_direct_debit"),
])]'/>
<field name="specific_countries" eval="True"/>
<field name="country_ids" eval="[(6, 0, [ref('base.fr')])]"/>
<field name="payfip_clientid">99999</field>
</record>

</data>
</odoo>
14 changes: 14 additions & 0 deletions payment_payfip/data/payment_icon.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 Moka Tourisme
@author: Iván Todorovich <ivan.todorovich@gmail.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>

<record id="payment_icon_sepa_direct_debit" model="payment.icon">
<field name="name">SEPA</field>
<field name="image" type="base64" file="payment_payfip/static/src/img/sepa-direct-debit-icon.png"/>
</record>

</odoo>
2 changes: 2 additions & 0 deletions payment_payfip/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import payment_acquirer
from . import payment_transaction
53 changes: 53 additions & 0 deletions payment_payfip/models/payment_acquirer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2021 Moka Tourisme
# @author: Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import re
import uuid
from odoo import api, models, fields
from werkzeug import urls


class PaymentAcquirer(models.Model):
_inherit = "payment.acquirer"

provider = fields.Selection(
selection_add=[('payfip', 'PayFIP')],
)
payfip_clientid = fields.Char(
string="Client ID",
required_if_provider="payfip",
groups="base.group_user",
)

@api.multi
def payfip_form_generate_values(self, values):
# PayFIP won't allow special characters in their refdet field
# So we change the tx.reference to something PayFIP can handle
# It's also limited to 30 characters only
tx = self.env['payment.transaction'].search(
[('reference', '=', values.get('reference'))],
)
if tx.state not in ['done', 'pending']:
tx.reference = str(uuid.uuid4()).replace("-", "")[:30]
# Prepare values
payfip_values = dict(values)
payfip_values.update({
"numcli": self.payfip_clientid,
"exer": fields.Date.today().year,
"refdet": tx.reference,
"objet": re.sub('[^a-zA-Z0-9]', '', values.get("reference"))[:100],
"montant": int(values.get("amount", 0.00) * 100),
"mel": (
values.get('partner_email')
or values.get('billing_partner_email')
or ''
),
"urlcl": urls.url_join(self.get_base_url(), "/payment/payfip/return"),
"saisie": "X" if self.environment == "prod" else "T",
})
return payfip_values

@api.multi
def payfip_get_form_action_url(self):
return "https://www.tipi.budget.gouv.fr/tpa/paiement.web"
82 changes: 82 additions & 0 deletions payment_payfip/models/payment_transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2021 Moka Tourisme
# @author: Iván Todorovich <ivan.todorovich@gmail.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from datetime import datetime
from odoo import api, models, fields
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare

import logging
_logger = logging.getLogger(__name__)


class PaymentTransaction(models.Model):
_inherit = "payment.transaction"

@api.model
def _payfip_form_get_tx_from_data(self, data):
"""
Given a data dict coming from PayFIP, verify it and find the related
transaction record.
"""
reference = data.get("refdet")
# Sanity Checks
if not reference:
error_msg = (
"PayFIP: Received data with missing reference (%s)" % reference
)
_logger.info(error_msg)
_logger.debug(data)
raise ValidationError(error_msg)
# Try to find tx
tx = self.search([("reference", "=", reference)])
if not tx or len(tx) > 1:
error_msg = (
"PayFIP: Received data for reference %s" % reference
)
if not tx:
error_msg += "; No order found"
else:
error_msg += "; Multiple orders found"
_logger.info(error_msg)
_logger.debug(data)
raise ValidationError(error_msg)
return tx

@api.multi
def _payfip_form_get_invalid_parameters(self, data):
invalid_parameters = []
# Check acquirer reference
if self.acquirer_reference and data.get('numauto') != self.acquirer_reference:
invalid_parameters.append(
("Transaction Id", data.get("numauto"), self.acquirer_reference)
)
# Check amount
amount = data.get("montant", "000")
amount = "%s.%s" % (amount[:-2], amount[-2:])
amount = float(amount)
if float_compare(amount, self.amount, 2) != 0:
invalid_parameters.append(
("Amount", data.get("montant"), "%.2f" % self.amount)
)
return invalid_parameters

@api.multi
def _payfip_form_validate(self, data):
status = data.get("resultrans")
res = {
"acquirer_reference": data.get("numauto"),
"date": fields.Datetime.now(),
}
# P: Payée CB
# V: Payée Prélèvement
if status in ["P", "V"]:
res["date"] = datetime.strptime(
"%s %s" % (data.get("dattrans", ""), data.get("heurtrans", "")),
"%d%m%Y %H%M"
)
self._set_transaction_done()
else:
self._set_transaction_cancel()
return self.write(res)
10 changes: 10 additions & 0 deletions payment_payfip/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
To use this module you'll need to get your **Nº Client Payfip**

You can do so by following the steps described on
`the official website <https://www.payfip.gouv.fr/>`_.

Once you get the **Nº Client Payfip**, go to *Accounting > Configuration > Payment acquirers*
and use it to configure the PayFIP payment acquirer.

Note that the Payfip implementation process is not so straightforward, you will
need to contact them to validate your environment SSL certificates and URLs.
3 changes: 3 additions & 0 deletions payment_payfip/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* `Moka Tourisme <https://www.mokatourisme.fr>`_:

* Iván Todorovich <ivan.todorovich@gmail.com>
3 changes: 3 additions & 0 deletions payment_payfip/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This module adds the payment acquirer PayFIP.

* Official site: https://www.collectivites-locales.gouv.fr/payfip
4 changes: 4 additions & 0 deletions payment_payfip/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
According to PayFIP documentation, there's an activation delay in return url domains.
The module may not receive the payment return the first times you use it.
It may take 24 hours or more, this is a limitation on PayFIP side.
In our case it took several days for our domain to be activated.
Binary file added payment_payfip/static/src/img/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 57eb11e

Please sign in to comment.