diff options
| author | Antoine Prieels <anp@odoo.com> | 2020-09-26 12:53:29 +0000 |
|---|---|---|
| committer | Pierre Masereel <pim@odoo.com> | 2020-09-29 11:44:46 +0000 |
| commit | b12b6eb91995fc8b9580fc97e4b49ac83d3e761a (patch) | |
| tree | 70c1dbd403b61fddf53ed19c7db3e37e8d4f8f32 /addons | |
| parent | e6dc4ea2e6c321829ed2fe8c1c7b0e58db354935 (diff) | |
[ADD] payment_odoo_by_adyen: Adyen for Platforms in eCommerce
Adyen for Platforms integration in eCommerce.
closes odoo/odoo#44582
Taskid: 2129218
Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/payment/data/payment_acquirer_data.xml | 29 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/__init__.py | 5 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/__manifest__.py | 19 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/controllers/__init__.py | 4 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/controllers/main.py | 22 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml | 14 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/models/__init__.py | 4 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/models/payment.py | 179 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/static/src/img/odoo_icon.png | bin | 0 -> 12863 bytes | |||
| -rw-r--r-- | addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml | 9 | ||||
| -rw-r--r-- | addons/payment_odoo_by_adyen/views/payment_views.xml | 22 |
11 files changed, 307 insertions, 0 deletions
diff --git a/addons/payment/data/payment_acquirer_data.xml b/addons/payment/data/payment_acquirer_data.xml index 6175435b0c2..63447edcc06 100644 --- a/addons/payment/data/payment_acquirer_data.xml +++ b/addons/payment/data/payment_acquirer_data.xml @@ -12,6 +12,35 @@ <input type="hidden" name="currency" t-att-value="currency.name"/> </template> + <record id="payment_acquirer_odoo_by_adyen" model="payment.acquirer"> + <field name="name">Odoo Payments by Adyen</field> + <field name="display_as">Credit Card (powered by Odoo Payments)</field> + <field name="view_template_id" ref="default_acquirer_button"/> + <field name="image_128" type="base64" file="payment_odoo_by_adyen/static/src/img/odoo_icon.png"/> + <field name="module_id" ref="base.module_payment_odoo_by_adyen"/> + <field name="sequence">1</field> + <field name="description" type="html"> + <p> + A payment gateway to accept online payments via credit cards, debit cards and bank transfers. + </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"/>Subscriptions</li> + <li class="list-inline-item"><i class="fa fa-check"/>Save Cards</li> + <li class="list-inline-item"><i class="fa fa-check"/>Payment Status Tracking</li> + </ul> + </field> + <!-- https://www.adyen.com/payment-methods --> + <field name="payment_icon_ids" eval='[(6, 0, [ref("payment.payment_icon_cc_bancontact"), + ref("payment.payment_icon_cc_maestro"), + ref("payment.payment_icon_cc_mastercard"), + ref("payment.payment_icon_cc_visa"), + ref("payment.payment_icon_cc_discover"), + ref("payment.payment_icon_cc_diners_club_intl"), + ref("payment.payment_icon_cc_jcb"), + ref("payment.payment_icon_cc_unionpay")])]'/> + </record> + <record id="payment_acquirer_buckaroo" model="payment.acquirer"> <field name="name">Buckaroo</field> <field name="display_as">Credit Card (powered by Buckaroo)</field> diff --git a/addons/payment_odoo_by_adyen/__init__.py b/addons/payment_odoo_by_adyen/__init__.py new file mode 100644 index 00000000000..7d34c7c054a --- /dev/null +++ b/addons/payment_odoo_by_adyen/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import controllers +from . import models diff --git a/addons/payment_odoo_by_adyen/__manifest__.py b/addons/payment_odoo_by_adyen/__manifest__.py new file mode 100644 index 00000000000..b3d7b76a9ea --- /dev/null +++ b/addons/payment_odoo_by_adyen/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Odoo Payments by Adyen Payment Acquirer', + 'category': 'Accounting/Payment Acquirers', + 'sequence': 330, + 'summary': 'Payment Acquirer: Odoo Payments by Adyen', + 'version': '1.0', + 'description': """Odoo Payments by Adyen""", + 'depends': ['payment', 'adyen_platforms'], + 'data': [ + 'views/payment_views.xml', + 'views/payment_odoo_by_adyen_templates.xml', + 'data/payment_acquirer_data.xml', + ], + 'installable': True, + 'application': True, +} diff --git a/addons/payment_odoo_by_adyen/controllers/__init__.py b/addons/payment_odoo_by_adyen/controllers/__init__.py new file mode 100644 index 00000000000..5d4b25db9c0 --- /dev/null +++ b/addons/payment_odoo_by_adyen/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import main diff --git a/addons/payment_odoo_by_adyen/controllers/main.py b/addons/payment_odoo_by_adyen/controllers/main.py new file mode 100644 index 00000000000..cb2182e81b8 --- /dev/null +++ b/addons/payment_odoo_by_adyen/controllers/main.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import logging +import pprint + +from odoo import http +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class OdooByAdyenController(http.Controller): + _notification_url = '/payment/odoo_adyen/notification' + + @http.route('/payment/odoo_adyen/notification', type='json', auth='public', csrf=False) + def odoo_adyen_notification(self): + data = json.loads(request.httprequest.data) + _logger.info('Beginning Odoo by Adyen form_feedback with data %s', pprint.pformat(data)) + if data.get('authResult') not in ['CANCELLED']: + request.env['payment.transaction'].sudo().form_feedback(data, 'odoo_adyen') diff --git a/addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml b/addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml new file mode 100644 index 00000000000..f6572c102c6 --- /dev/null +++ b/addons/payment_odoo_by_adyen/data/payment_acquirer_data.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data noupdate="1"> + + <record id="payment.payment_acquirer_odoo_by_adyen" model="payment.acquirer"> + <field name="name">Odoo Payments by Adyen</field> + <field name="image_128" type="base64" file="payment_odoo_by_adyen/static/src/img/odoo_icon.png"/> + <field name="provider">odoo_adyen</field> + <field name="company_id" ref="base.main_company"/> + <field name="view_template_id" ref="odoo_by_adyen_form"/> + </record> + + </data> +</odoo> diff --git a/addons/payment_odoo_by_adyen/models/__init__.py b/addons/payment_odoo_by_adyen/models/__init__.py new file mode 100644 index 00000000000..2ec5b9cd2f6 --- /dev/null +++ b/addons/payment_odoo_by_adyen/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import payment diff --git a/addons/payment_odoo_by_adyen/models/payment.py b/addons/payment_odoo_by_adyen/models/payment.py new file mode 100644 index 00000000000..9e7a4dc2afa --- /dev/null +++ b/addons/payment_odoo_by_adyen/models/payment.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import hashlib +import hmac +import json +import logging +from werkzeug import urls + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError +from odoo.addons.payment_odoo_by_adyen.controllers.main import OdooByAdyenController + +_logger = logging.getLogger(__name__) + + +class AcquirerOdooByAdyen(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[ + ('odoo_adyen', 'Odoo Payments by Adyen') + ], ondelete={'odoo_adyen': 'set default'}) + odoo_adyen_account_id = fields.Many2one('adyen.account', required_if_provider='odoo_adyen', related='company_id.adyen_account_id') + odoo_adyen_payout_id = fields.Many2one('adyen.payout', required_if_provider='odoo_adyen', string='Adyen Payout', domain="[('adyen_account_id', '=', odoo_adyen_account_id)]") + + def _get_feature_support(self): + res = super(AcquirerOdooByAdyen, self)._get_feature_support() + res['tokenize'].append('odoo_adyen') + return res + + @api.model + def _odoo_adyen_format_amount(self, amount, currency_id): + return { + 'value': int(amount * (10 ** currency_id.decimal_places)), + 'currency': currency_id.name, + } + + @api.model + def _odoo_adyen_compute_signature(self, amount, currency_id, reference): + secret = self.env['ir.config_parameter'].sudo().get_param('database.secret') + token_str = '%s%s%s' % ( + int(amount * (10 ** currency_id.decimal_places)), + currency_id.name, + reference + ) + return hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest() + + def odoo_adyen_form_generate_values(self, values): + # Don't use the value returned by `self.get_base_url` for the notification_url as + # `request.httprequest.url_root` could be forged to retrieve the signature and + # fake a payment update + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + data = { + 'adyen_uuid': self.odoo_adyen_account_id.adyen_uuid, + 'payout': self.odoo_adyen_payout_id.code, + 'amount': self._odoo_adyen_format_amount(values['amount'], values['currency']), + 'reference': values['reference'], + 'shopperLocale': values.get('partner_lang'), + 'metadata': { + 'merchant_signature': self._odoo_adyen_compute_signature(values['amount'],values['currency'],values['reference']), + 'notification_url': urls.url_join(base_url, OdooByAdyenController._notification_url), + }, + 'returnUrl': urls.url_join(self.get_base_url(), '/payment/process'), + } + + if self.save_token in ['ask', 'always']: + data.update({ + 'shopperReference': '%s_%s' % (self.odoo_adyen_account_id.adyen_uuid, values['partner_id']), + 'storePaymentMethod': True, + 'recurringProcessingModel': 'CardOnFile', + }) + + values.update({ + 'data': json.dumps(data), + }) + return values + + def odoo_adyen_get_form_action_url(self): + self.ensure_one() + proxy_url = self.env['ir.config_parameter'].sudo().get_param('adyen_platforms.proxy_url') + return urls.url_join(proxy_url, 'pay_by_link') + + def odoo_adyen_create_account(self): + return self.env['adyen.account'].action_create_redirect() + +class TxOdooByAdyen(models.Model): + _inherit = 'payment.transaction' + + def odoo_adyen_s2s_do_transaction(self, **kwargs): + self.ensure_one() + # Don't use the value returned by `self.get_base_url` for the notification_url as + # `request.httprequest.url_root` could be forged to retrieve the signature and + # fake a payment update + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + data = { + 'payout': self.acquirer_id.odoo_adyen_payout_id.code, + 'amount': self.acquirer_id._odoo_adyen_format_amount(self.amount, self.currency_id), + 'reference': self.reference, + 'paymentMethod': { + 'type': self.payment_token_id.odoo_adyen_payment_method_type, + 'storedPaymentMethodId': self.payment_token_id.acquirer_ref, + }, + 'shopperReference': '%s_%s' % (self.acquirer_id.odoo_adyen_account_id.adyen_uuid, self.partner_id.id), + 'shopperInteraction': 'ContAuth', + 'metadata': { + 'merchant_signature': self.acquirer_id._odoo_adyen_compute_signature(self.amount, self.currency_id, self.reference), + 'notification_url': urls.url_join(base_url, OdooByAdyenController._notification_url), + }, + 'returnUrl': urls.url_join(self.get_base_url(), '/payment/process'), + } + self.acquirer_id.odoo_adyen_account_id._adyen_rpc('payments', data) + + @api.model + def _odoo_adyen_form_get_tx_from_data(self, data): + reference = data.get('merchantReference') + if not reference: + error_msg = _('Odoo Payments by Adyen: received data with missing reference (%s)', reference) + _logger.info(error_msg) + raise ValidationError(error_msg) + + tx = self.env['payment.transaction'].search([('reference', '=', reference)]) + if not tx or len(tx) > 1: + error_msg = _('Odoo Payments by Adyen: received data for reference %s') % (reference) + if not tx: + error_msg += _('; no order found') + else: + error_msg += _('; multiple order found') + _logger.info(error_msg) + raise ValidationError(error_msg) + + return tx + + def _odoo_adyen_form_get_invalid_parameters(self, data): + invalid_parameters = [] + + if self.acquirer_reference and data.get('pspReference') != self.acquirer_reference: + invalid_parameters.append(('pspReference', data.get('pspReference'), self.acquirer_reference)) + + return invalid_parameters + + def _odoo_adyen_form_validate(self, data): + merchant_signature = self.acquirer_id._odoo_adyen_compute_signature(self.amount, self.currency_id, self.reference) + if merchant_signature != data['additionalData']['metadata.merchant_signature']: + return False + + # Save token + if self.partner_id and not self.payment_token_id and \ + (self.type == 'form_save' or self.acquirer_id.save_token == 'always') \ + and 'recurring.shopperReference' in data['additionalData']: + res = self.acquirer_id.odoo_adyen_account_id._adyen_rpc('payment_methods', { + 'shopperReference': data['additionalData']['recurring.shopperReference'] + }) + stored_payment_methods = res['storedPaymentMethods'] + pm_id = data['additionalData']['recurring.recurringDetailReference'] + token_id = self.env['payment.token'].create({ + 'name': _("Card No XXXXXXXXXXXX%s", data['additionalData']['cardSummary']), + 'acquirer_ref': pm_id, + 'acquirer_id': self.acquirer_id.id, + 'partner_id': self.partner_id.id, + 'odoo_adyen_payment_method_type': next(pm['type'] for pm in stored_payment_methods if pm['id'] == pm_id) + }) + self.payment_token_id = token_id + + # Update status + if data['success']: + self.write({'acquirer_reference': data.get('pspReference')}) + self._set_transaction_done() + return True + else: + error = _('Odoo Payment by Adyen: feedback error') + _logger.info(error) + self.write({'state_message': error}) + self._set_transaction_cancel() + return False + +class PaymentToken(models.Model): + _inherit = 'payment.token' + + odoo_adyen_payment_method_type = fields.Char(string='PaymentMethod Type') diff --git a/addons/payment_odoo_by_adyen/static/src/img/odoo_icon.png b/addons/payment_odoo_by_adyen/static/src/img/odoo_icon.png Binary files differnew file mode 100644 index 00000000000..5701852c578 --- /dev/null +++ b/addons/payment_odoo_by_adyen/static/src/img/odoo_icon.png diff --git a/addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml b/addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml new file mode 100644 index 00000000000..1aaa50f40ba --- /dev/null +++ b/addons/payment_odoo_by_adyen/views/payment_odoo_by_adyen_templates.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data noupdate="1"> + <template id="odoo_by_adyen_form"> + <input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/> + <input type="hidden" name="data" t-att-value="data"/> + </template> + </data> +</odoo> diff --git a/addons/payment_odoo_by_adyen/views/payment_views.xml b/addons/payment_odoo_by_adyen/views/payment_views.xml new file mode 100644 index 00000000000..715aefac291 --- /dev/null +++ b/addons/payment_odoo_by_adyen/views/payment_views.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + + <record id="acquirer_form_odoo_by_adyen" model="ir.ui.view"> + <field name="name">acquirer.form.odoo.by.adyen</field> + <field name="model">payment.acquirer</field> + <field name="inherit_id" ref="payment.acquirer_form"/> + <field name="arch" type="xml"> + <xpath expr='//group[@name="acquirer"]' position='inside'> + <group attrs="{'invisible': [('provider', '!=', 'odoo_adyen')]}"> + <button name="odoo_adyen_create_account" icon="fa-arrow-right" type="object" class="btn-link" string="Create an account in 1 minute" + attrs="{'invisible': [('odoo_adyen_account_id', '!=', False)]}"/> + <field name="odoo_adyen_account_id" readonly="1" attrs="{'invisible': [('odoo_adyen_account_id', '=', False)]}"/> + <field name="odoo_adyen_payout_id" attrs="{'invisible': [('odoo_adyen_account_id', '=', False)], 'required':[('provider', '=', 'odoo_adyen'), ('state', '!=', 'disabled')]}"/> + </group> + </xpath> + </field> + </record> + + </data> +</odoo> |
