[REF] payment_buckaroo: migrate Buckaroo to the new payment API
See the merge commit for more details. task-2333032
This commit is contained in:
committed by
Antoine Vandevenne (anv)
parent
660dc0ebaf
commit
356c93a42d
@@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
from odoo.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers
|
||||
from odoo.addons.payment import reset_payment_provider
|
||||
from . import models
|
||||
|
||||
from odoo.addons.payment import reset_payment_acquirer
|
||||
from odoo.addons.payment.models.payment_acquirer import create_missing_journals # post-init hook
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
reset_payment_provider(cr, registry, 'buckaroo')
|
||||
reset_payment_acquirer(cr, registry, 'buckaroo')
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Buckaroo Payment Acquirer',
|
||||
'version': '2.0',
|
||||
'category': 'Accounting/Payment Acquirers',
|
||||
'sequence': 355,
|
||||
'summary': 'Payment Acquirer: Buckaroo Implementation',
|
||||
'version': '1.0',
|
||||
'description': """Buckaroo Payment Acquirer""",
|
||||
'depends': ['payment'],
|
||||
'data': [
|
||||
@@ -13,8 +13,7 @@
|
||||
'views/payment_buckaroo_templates.xml',
|
||||
'data/payment_acquirer_data.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'post_init_hook': 'create_missing_journal_for_acquirers',
|
||||
'post_init_hook': 'create_missing_journals',
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
import werkzeug
|
||||
|
||||
from odoo import http
|
||||
@@ -12,20 +13,13 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
class BuckarooController(http.Controller):
|
||||
_return_url = '/payment/buckaroo/return'
|
||||
_cancel_url = '/payment/buckaroo/cancel'
|
||||
_exception_url = '/payment/buckaroo/error'
|
||||
_reject_url = '/payment/buckaroo/reject'
|
||||
|
||||
@http.route([
|
||||
'/payment/buckaroo/return',
|
||||
'/payment/buckaroo/cancel',
|
||||
'/payment/buckaroo/error',
|
||||
'/payment/buckaroo/reject',
|
||||
], type='http', auth='public', csrf=False)
|
||||
def buckaroo_return(self, **post):
|
||||
""" Buckaroo."""
|
||||
_logger.info('Buckaroo: entering form_feedback with post data %s', pprint.pformat(post)) # debug
|
||||
request.env['payment.transaction'].sudo().form_feedback(post, 'buckaroo')
|
||||
post = {key.upper(): value for key, value in post.items()}
|
||||
return_url = post.get('ADD_RETURNDATA') or '/'
|
||||
return werkzeug.utils.redirect('/payment/process')
|
||||
@http.route(_return_url, type='http', auth='public', methods=['POST'], csrf=False)
|
||||
def buckaroo_return_from_redirect(self, **data):
|
||||
""" Process the data returned by Buckaroo after redirection.
|
||||
|
||||
:param dict data: The feedback data
|
||||
"""
|
||||
_logger.info("received notification data:\n%s", pprint.pformat(data))
|
||||
request.env['payment.transaction'].sudo()._handle_feedback_data('buckaroo', data)
|
||||
return werkzeug.utils.redirect('/payment/status')
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_buckaroo" model="payment.acquirer">
|
||||
<field name="name">Buckaroo</field>
|
||||
<field name="image_128" type="base64" file="payment_buckaroo/static/src/img/buckaroo_icon.png"/>
|
||||
<field name="provider">buckaroo</field>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
<field name="view_template_id" ref="buckaroo_form"/>
|
||||
</record>
|
||||
<record id="payment.payment_acquirer_buckaroo" model="payment.acquirer">
|
||||
<field name="provider">buckaroo</field>
|
||||
<field name="redirect_form_view_id" ref="redirect_form"/>
|
||||
<field name="support_authorization">False</field>
|
||||
<field name="support_fees_computation">False</field>
|
||||
<field name="support_tokenization">False</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import payment
|
||||
from . import payment_acquirer
|
||||
from . import payment_transaction
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# coding: utf-8
|
||||
from hashlib import sha1
|
||||
import logging
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.addons.payment.models.payment_acquirer import ValidationError
|
||||
from odoo.addons.payment_buckaroo.controllers.main import BuckarooController
|
||||
|
||||
from odoo.tools.float_utils import float_compare
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def normalize_keys_upper(data):
|
||||
"""Set all keys of a dictionnary to uppercase
|
||||
|
||||
Buckaroo parameters names are case insensitive
|
||||
convert everything to upper case to be able to easily detected the presence
|
||||
of a parameter by checking the uppercase key only
|
||||
"""
|
||||
return {key.upper(): val for key, val in data.items()}
|
||||
|
||||
|
||||
class AcquirerBuckaroo(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(selection_add=[
|
||||
('buckaroo', 'Buckaroo')
|
||||
], ondelete={'buckaroo': 'set default'})
|
||||
brq_websitekey = fields.Char('WebsiteKey', required_if_provider='buckaroo', groups='base.group_user')
|
||||
brq_secretkey = fields.Char('SecretKey', required_if_provider='buckaroo', groups='base.group_user')
|
||||
|
||||
def _get_buckaroo_urls(self, environment):
|
||||
""" Buckaroo URLs
|
||||
"""
|
||||
if environment == 'prod':
|
||||
return {
|
||||
'buckaroo_form_url': 'https://checkout.buckaroo.nl/html/',
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'buckaroo_form_url': 'https://testcheckout.buckaroo.nl/html/',
|
||||
}
|
||||
|
||||
def _buckaroo_generate_digital_sign(self, inout, values):
|
||||
""" Generate the shasign for incoming or outgoing communications.
|
||||
|
||||
:param browse acquirer: the payment.acquirer browse record. It should
|
||||
have a shakey in shaky out
|
||||
:param string inout: 'in' (odoo contacting buckaroo) or 'out' (buckaroo
|
||||
contacting odoo).
|
||||
:param dict values: transaction values
|
||||
|
||||
:return string: shasign
|
||||
"""
|
||||
assert inout in ('in', 'out')
|
||||
assert self.provider == 'buckaroo'
|
||||
|
||||
keys = "add_returndata Brq_amount Brq_culture Brq_currency Brq_invoicenumber Brq_return Brq_returncancel Brq_returnerror Brq_returnreject brq_test Brq_websitekey".split()
|
||||
|
||||
def get_value(key):
|
||||
if values.get(key):
|
||||
return values[key]
|
||||
return ''
|
||||
|
||||
values = dict(values or {})
|
||||
|
||||
if inout == 'out':
|
||||
for key in list(values):
|
||||
# case insensitive keys
|
||||
if key.upper() == 'BRQ_SIGNATURE':
|
||||
del values[key]
|
||||
break
|
||||
|
||||
items = sorted(values.items(), key=lambda pair: pair[0].lower())
|
||||
sign = ''.join('%s=%s' % (k, urls.url_unquote_plus(v)) for k, v in items)
|
||||
else:
|
||||
sign = ''.join('%s=%s' % (k, get_value(k)) for k in keys)
|
||||
# Add the pre-shared secret key at the end of the signature
|
||||
sign = sign + self.brq_secretkey
|
||||
shasign = sha1(sign.encode('utf-8')).hexdigest()
|
||||
return shasign
|
||||
|
||||
def buckaroo_form_generate_values(self, values):
|
||||
base_url = self.get_base_url()
|
||||
buckaroo_tx_values = dict(values)
|
||||
buckaroo_tx_values.update({
|
||||
'Brq_websitekey': self.brq_websitekey,
|
||||
'Brq_amount': values['amount'],
|
||||
'Brq_currency': values['currency'] and values['currency'].name or '',
|
||||
'Brq_invoicenumber': values['reference'],
|
||||
'brq_test': True if self.state == 'test' else False,
|
||||
'Brq_return': urls.url_join(base_url, BuckarooController._return_url),
|
||||
'Brq_returncancel': urls.url_join(base_url, BuckarooController._cancel_url),
|
||||
'Brq_returnerror': urls.url_join(base_url, BuckarooController._exception_url),
|
||||
'Brq_returnreject': urls.url_join(base_url, BuckarooController._reject_url),
|
||||
'Brq_culture': (values.get('partner_lang') or 'en_US').replace('_', '-'),
|
||||
'add_returndata': buckaroo_tx_values.pop('return_url', '') or '',
|
||||
})
|
||||
buckaroo_tx_values['Brq_signature'] = self._buckaroo_generate_digital_sign('in', buckaroo_tx_values)
|
||||
return buckaroo_tx_values
|
||||
|
||||
def buckaroo_get_form_action_url(self):
|
||||
self.ensure_one()
|
||||
environment = 'prod' if self.state == 'enabled' else 'test'
|
||||
return self._get_buckaroo_urls(environment)['buckaroo_form_url']
|
||||
|
||||
|
||||
class TxBuckaroo(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
# buckaroo status
|
||||
_buckaroo_valid_tx_status = [190]
|
||||
_buckaroo_pending_tx_status = [790, 791, 792, 793]
|
||||
_buckaroo_cancel_tx_status = [890, 891]
|
||||
_buckaroo_error_tx_status = [490, 491, 492]
|
||||
_buckaroo_reject_tx_status = [690]
|
||||
|
||||
# --------------------------------------------------
|
||||
# FORM RELATED METHODS
|
||||
# --------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _buckaroo_form_get_tx_from_data(self, data):
|
||||
""" Given a data dict coming from buckaroo, verify it and find the related
|
||||
transaction record. """
|
||||
origin_data = dict(data)
|
||||
data = normalize_keys_upper(data)
|
||||
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
|
||||
if not reference or not pay_id or not shasign:
|
||||
error_msg = _('Buckaroo: received data with missing reference (%s) or pay_id (%s) or shasign (%s)') % (reference, pay_id, shasign)
|
||||
_logger.info(error_msg)
|
||||
raise ValidationError(error_msg)
|
||||
|
||||
tx = self.search([('reference', '=', reference)])
|
||||
if not tx or len(tx) > 1:
|
||||
error_msg = _('Buckaroo: 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)
|
||||
|
||||
# verify shasign
|
||||
shasign_check = tx.acquirer_id._buckaroo_generate_digital_sign('out', origin_data)
|
||||
if shasign_check.upper() != shasign.upper():
|
||||
error_msg = _('Buckaroo: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)
|
||||
_logger.info(error_msg)
|
||||
raise ValidationError(error_msg)
|
||||
|
||||
return tx
|
||||
|
||||
def _buckaroo_form_get_invalid_parameters(self, data):
|
||||
invalid_parameters = []
|
||||
data = normalize_keys_upper(data)
|
||||
if self.acquirer_reference and data.get('BRQ_TRANSACTIONS') != self.acquirer_reference:
|
||||
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), self.acquirer_reference))
|
||||
# check what is buyed
|
||||
if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), self.amount, 2) != 0:
|
||||
invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % self.amount))
|
||||
if data.get('BRQ_CURRENCY') != self.currency_id.name:
|
||||
invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), self.currency_id.name))
|
||||
|
||||
return invalid_parameters
|
||||
|
||||
def _buckaroo_form_validate(self, data):
|
||||
data = normalize_keys_upper(data)
|
||||
status_code = int(data.get('BRQ_STATUSCODE', '0'))
|
||||
if status_code in self._buckaroo_valid_tx_status:
|
||||
self.write({'acquirer_reference': data.get('BRQ_TRANSACTIONS')})
|
||||
self._set_transaction_done()
|
||||
return True
|
||||
elif status_code in self._buckaroo_pending_tx_status:
|
||||
self.write({'acquirer_reference': data.get('BRQ_TRANSACTIONS')})
|
||||
self._set_transaction_pending()
|
||||
return True
|
||||
elif status_code in self._buckaroo_cancel_tx_status:
|
||||
self.write({'acquirer_reference': data.get('BRQ_TRANSACTIONS')})
|
||||
self._set_transaction_cancel()
|
||||
return True
|
||||
else:
|
||||
error = 'Buckaroo: feedback error'
|
||||
_logger.info(error)
|
||||
self.write({
|
||||
'state_message': error,
|
||||
'acquirer_reference': data.get('BRQ_TRANSACTIONS'),
|
||||
})
|
||||
self._set_transaction_cancel()
|
||||
return False
|
||||
@@ -0,0 +1,65 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from hashlib import sha1
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class PaymentAcquirer(models.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
provider = fields.Selection(
|
||||
selection_add=[('buckaroo', "Buckaroo")], ondelete={'buckaroo': 'set default'})
|
||||
buckaroo_website_key = fields.Char(
|
||||
string="Website Key", help="The key solely used to identify the website with Buckaroo",
|
||||
required_if_provider='buckaroo')
|
||||
buckaroo_secret_key = fields.Char(
|
||||
string="Buckaroo Secret Key", required_if_provider='buckaroo', groups='base.group_system')
|
||||
|
||||
def _buckaroo_get_api_url(self):
|
||||
""" Return the API URL according to the state.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: The API URL
|
||||
:rtype: str
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.state == 'enabled':
|
||||
return 'https://checkout.buckaroo.nl/html/'
|
||||
else:
|
||||
return 'https://testcheckout.buckaroo.nl/html/'
|
||||
|
||||
def _buckaroo_generate_digital_sign(self, values, incoming=True):
|
||||
""" Generate the shasign for incoming or outgoing communications.
|
||||
|
||||
:param dict values: The values used to generate the signature
|
||||
:param bool incoming: Whether the signature must be generate for an incoming (Buckaroo to
|
||||
Odoo) or outgoing (Odoo to Buckaroo) communication.
|
||||
:return: The shasign
|
||||
:rtype: str
|
||||
"""
|
||||
if incoming:
|
||||
# Remove the signature from the values used to check the signature
|
||||
for key in values.keys():
|
||||
if key.upper() == 'BRQ_SIGNATURE': # Keys are case-insensitive
|
||||
del values[key]
|
||||
break
|
||||
# Incoming communication values must be URL-decoded before checking the signature
|
||||
items = [(k, urls.url_unquote_plus(v)) for k, v in values.items()]
|
||||
else:
|
||||
# Only use items whose key starts with 'add_', 'brq_', or 'cust_' (case insensitive)
|
||||
items = [
|
||||
(k, v) for k, v in values.items()
|
||||
if any(k.upper().startswith(key_prefix) for key_prefix in ('ADD_', 'BRQ_', 'CUST_'))
|
||||
]
|
||||
# Sort parameters by upper-cased key name
|
||||
sorted_items = sorted(items, key=lambda pair: pair[0].upper())
|
||||
# Build the signing string by concatenating all parameters
|
||||
sign_string = ''.join(f'{k}={v or ""}' for k, v in sorted_items)
|
||||
# Append the pre-shared secret key to the signing string
|
||||
sign_string += self.buckaroo_secret_key
|
||||
# Calculate the SHA-1 hash over the signing string
|
||||
return sha1(sign_string.encode('utf-8')).hexdigest()
|
||||
@@ -0,0 +1,144 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from odoo.addons.payment_buckaroo.controllers.main import BuckarooController
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _normalize_dataset(data):
|
||||
""" Set all keys of a dictionary to uppercase.
|
||||
|
||||
As Buckaroo parameters names are case insensitive, we can convert everything to upper case to
|
||||
easily detected the presence of a parameter by checking the uppercase key only.
|
||||
|
||||
:param dict data: The dictionary whose keys must be set to uppercase
|
||||
:return: A copy of the original data with all keys set to uppercase
|
||||
:rtype: dict
|
||||
"""
|
||||
return {key.upper(): val for key, val in data.items()}
|
||||
|
||||
|
||||
class PaymentTransaction(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
# Buckaroo status codes
|
||||
_pending_tx_status = [790, 791, 792, 793]
|
||||
_valid_tx_status = [190]
|
||||
_cancel_tx_status = [890, 891]
|
||||
|
||||
def _get_specific_rendering_values(self, processing_values):
|
||||
""" Override of payment to return Buckaroo-specific rendering values.
|
||||
|
||||
Note: self.ensure_one() from `_get_processing_values`
|
||||
|
||||
:param dict processing_values: The generic and specific processing values of the transaction
|
||||
:return: The dict of acquirer-specific processing values
|
||||
:rtype: dict
|
||||
"""
|
||||
res = super()._get_specific_rendering_values(processing_values)
|
||||
if self.provider != 'buckaroo':
|
||||
return res
|
||||
|
||||
return_url = urls.url_join(self.acquirer_id._get_base_url(), BuckarooController._return_url)
|
||||
rendering_values = {
|
||||
'api_url': self.acquirer_id._buckaroo_get_api_url(),
|
||||
'Brq_websitekey': self.acquirer_id.buckaroo_website_key,
|
||||
'Brq_amount': self.amount,
|
||||
'Brq_currency': self.currency_id.name,
|
||||
'Brq_invoicenumber': self.reference,
|
||||
# Include all 4 URL keys despite they share the same value as they are part of the sig.
|
||||
'Brq_return': return_url,
|
||||
'Brq_returncancel': return_url,
|
||||
'Brq_returnerror': return_url,
|
||||
'Brq_returnreject': return_url,
|
||||
}
|
||||
if self.partner_lang:
|
||||
rendering_values['Brq_culture'] = self.partner_lang.replace('_', '-')
|
||||
rendering_values['Brq_signature'] = self.acquirer_id._buckaroo_generate_digital_sign(
|
||||
rendering_values, incoming=False
|
||||
)
|
||||
return rendering_values
|
||||
|
||||
@api.model
|
||||
def _get_tx_from_feedback_data(self, provider, data):
|
||||
""" Override of payment to find the transaction based on Buckaroo data.
|
||||
|
||||
:param str provider: The provider of the acquirer that handled the transaction
|
||||
:param dict data: The feedback data sent by the provider
|
||||
:return: The transaction if found
|
||||
:rtype: recordset of `payment.transaction`
|
||||
:raise: ValidationError if inconsistent data were received
|
||||
:raise: ValidationError if the data match no transaction
|
||||
:raise: ValidationError if the signature can not be verified
|
||||
"""
|
||||
tx = super()._get_tx_from_feedback_data(provider, data)
|
||||
if provider != 'buckaroo':
|
||||
return tx
|
||||
|
||||
normalized_data = _normalize_dataset(data)
|
||||
reference = normalized_data.get('BRQ_INVOICENUMBER')
|
||||
shasign = normalized_data.get('BRQ_SIGNATURE')
|
||||
if not reference or not shasign:
|
||||
raise ValidationError(
|
||||
"Buckaroo: " + _(
|
||||
"Received data with missing reference (%(ref)s) or shasign (%(sign))",
|
||||
ref=reference, sign=shasign
|
||||
)
|
||||
)
|
||||
|
||||
tx = self.search([('reference', '=', reference), ('provider', '=', 'buckaroo')])
|
||||
if not tx:
|
||||
raise ValidationError(
|
||||
"Buckaroo: " + _("No transaction found matching reference %s.", reference)
|
||||
)
|
||||
|
||||
# Verify signature
|
||||
shasign_check = tx.acquirer_id._buckaroo_generate_digital_sign(data, incoming=True)
|
||||
if shasign_check != shasign:
|
||||
raise ValidationError(
|
||||
"Buckaroo: " + _(
|
||||
"Invalid shasign: received %(sign)s, computed %(check)s",
|
||||
sign=shasign, check=shasign_check
|
||||
)
|
||||
)
|
||||
|
||||
return tx
|
||||
|
||||
def _process_feedback_data(self, data):
|
||||
""" Override of payment to process the transaction based on Buckaroo data.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:param dict data: The feedback data sent by the provider
|
||||
:return: None
|
||||
:raise: ValidationError if inconsistent data were received
|
||||
"""
|
||||
super()._process_feedback_data(data)
|
||||
if self.provider != 'buckaroo':
|
||||
return
|
||||
|
||||
normalized_data = _normalize_dataset(data)
|
||||
transaction_keys = normalized_data.get('BRQ_TRANSACTIONS')
|
||||
if not transaction_keys:
|
||||
raise ValidationError("Buckaroo: " + _("Received data with missing transaction keys"))
|
||||
# BRQ_TRANSACTIONS can hold multiple, comma-separated, tx keys. In practice, it holds only
|
||||
# one reference. So we split for semantic correctness and keep the first transaction key.
|
||||
self.acquirer_reference = transaction_keys.split(',')[0]
|
||||
|
||||
status_code = int(normalized_data.get('BRQ_STATUSCODE') or 0)
|
||||
if status_code in self._pending_tx_status:
|
||||
self._set_pending()
|
||||
elif status_code in self._valid_tx_status:
|
||||
self._set_done()
|
||||
elif status_code in self._cancel_tx_status:
|
||||
self._set_canceled()
|
||||
else:
|
||||
_logger.warning("Buckaroo: received unknown status code: %s", status_code)
|
||||
self._set_error("Buckaroo: " + _("Unknown status code: %s", status_code))
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_buckaroo
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
class BuckarooCommon(PaymentCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.buckaroo = cls._prepare_acquirer('buckaroo', update_values={
|
||||
'buckaroo_website_key': 'dummy',
|
||||
'buckaroo_secret_key': 'dummy',
|
||||
})
|
||||
|
||||
# Override defaults
|
||||
cls.acquirer = cls.buckaroo
|
||||
cls.currency = cls.currency_euro
|
||||
@@ -1,120 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from lxml import objectify
|
||||
from werkzeug import urls
|
||||
|
||||
import odoo
|
||||
from odoo.addons.payment.models.payment_acquirer import ValidationError
|
||||
from odoo.addons.payment.tests.common import PaymentAcquirerCommon
|
||||
from odoo.addons.payment_buckaroo.controllers.main import BuckarooController
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from .common import BuckarooCommon
|
||||
from ..controllers.main import BuckarooController
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install', 'external', '-standard')
|
||||
class BuckarooCommon(PaymentAcquirerCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
# get the buckaroo account
|
||||
cls.buckaroo = cls.env.ref('payment.payment_acquirer_buckaroo')
|
||||
cls.buckaroo.write({
|
||||
'brq_websitekey': 'dummy',
|
||||
'brq_secretkey': 'dummy',
|
||||
'state': 'test',
|
||||
})
|
||||
@tagged('post_install', '-at_install')
|
||||
class BuckarooTest(BuckarooCommon):
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install', 'external', '-standard')
|
||||
class BuckarooForm(BuckarooCommon):
|
||||
|
||||
def test_10_Buckaroo_form_render(self):
|
||||
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
# be sure not to do stupid things
|
||||
self.assertEqual(self.buckaroo.state, 'test', 'test without test environment')
|
||||
|
||||
# ----------------------------------------
|
||||
# Test: button direct rendering
|
||||
# ----------------------------------------
|
||||
|
||||
form_values = {
|
||||
'add_returndata': '/payment/process',
|
||||
'Brq_websitekey': self.buckaroo.brq_websitekey,
|
||||
'Brq_amount': '2240.0',
|
||||
'Brq_currency': 'EUR',
|
||||
'Brq_invoicenumber': 'SO004',
|
||||
'Brq_signature': 'f09bb79451a7ddef3178462ea52b07e55d34e99e', # update me
|
||||
'brq_test': 'True',
|
||||
'Brq_return': urls.url_join(base_url, BuckarooController._return_url),
|
||||
'Brq_returncancel': urls.url_join(base_url, BuckarooController._cancel_url),
|
||||
'Brq_returnerror': urls.url_join(base_url, BuckarooController._exception_url),
|
||||
'Brq_returnreject': urls.url_join(base_url, BuckarooController._reject_url),
|
||||
def test_redirect_form_values(self):
|
||||
return_url = self._build_url(BuckarooController._return_url)
|
||||
expected_values = {
|
||||
'Brq_websitekey': self.buckaroo.buckaroo_website_key,
|
||||
'Brq_amount': str(self.amount),
|
||||
'Brq_currency': self.currency.name,
|
||||
'Brq_invoicenumber': self.reference,
|
||||
'Brq_signature': '04c26578116990496770687a8bf225200e0f56e6',
|
||||
'Brq_return': return_url,
|
||||
'Brq_returncancel': return_url,
|
||||
'Brq_returnerror': return_url,
|
||||
'Brq_returnreject': return_url,
|
||||
'Brq_culture': 'en-US',
|
||||
}
|
||||
|
||||
# render the button
|
||||
res = self.buckaroo.render(
|
||||
'SO004', 2240.0, self.currency_euro.id,
|
||||
partner_id=None,
|
||||
values=self.buyer_values)
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
data_set = tree.xpath("//input[@name='data_set']")
|
||||
self.assertEqual(len(data_set), 1, 'paypal: Found %d "data_set" input instead of 1' % len(data_set))
|
||||
self.assertEqual(data_set[0].get('data-action-url'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['submit', 'data_set']:
|
||||
continue
|
||||
self.assertEqual(
|
||||
form_input.get('value'),
|
||||
form_values[form_input.get('name')],
|
||||
'Buckaroo: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
|
||||
)
|
||||
tx_sudo = self.create_transaction(flow='redirect')
|
||||
with mute_logger('odoo.addons.payment.models.payment_transaction'):
|
||||
processing_values = tx_sudo._get_processing_values()
|
||||
form_info = self._extract_values_from_html_form(processing_values['redirect_form_html'])
|
||||
|
||||
# ----------------------------------------
|
||||
# Test2: button using tx + validation
|
||||
# ----------------------------------------
|
||||
self.assertEqual(form_info['action'], "https://testcheckout.buckaroo.nl/html/")
|
||||
self.assertDictEqual(expected_values, form_info['inputs'],
|
||||
"Buckaroo: invalid inputs specified in the redirect form.")
|
||||
|
||||
# create a new draft tx
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'amount': 2240.0,
|
||||
'acquirer_id': self.buckaroo.id,
|
||||
'currency_id': self.currency_euro.id,
|
||||
'reference': 'SO004',
|
||||
'partner_id': self.buyer_id,
|
||||
})
|
||||
|
||||
# render the button
|
||||
res = self.buckaroo.render(
|
||||
'should_be_erased', 2240.0, self.currency_euro,
|
||||
partner_id=None,
|
||||
values=self.buyer_values)
|
||||
|
||||
# check form result
|
||||
tree = objectify.fromstring(res)
|
||||
data_set = tree.xpath("//input[@name='data_set']")
|
||||
self.assertEqual(len(data_set), 1, 'paypal: Found %d "data_set" input instead of 1' % len(data_set))
|
||||
self.assertEqual(data_set[0].get('data-action-url'), 'https://testcheckout.buckaroo.nl/html/', 'Buckaroo: wrong form POST url')
|
||||
for form_input in tree.input:
|
||||
if form_input.get('name') in ['submit', 'data_set']:
|
||||
continue
|
||||
self.assertEqual(
|
||||
form_input.get('value'),
|
||||
form_values[form_input.get('name')],
|
||||
'Buckaroo: wrong value for form input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.payment_buckaroo.models.payment', 'ValidationError')
|
||||
def test_20_buckaroo_form_management(self):
|
||||
# be sure not to do stupid thing
|
||||
self.assertEqual(self.buckaroo.state, 'test', 'test without test environment')
|
||||
def test_feedback_processing(self):
|
||||
self.amount = 2240.0
|
||||
self.reference = 'SO004'
|
||||
|
||||
# typical data posted by buckaroo after client has successfully paid
|
||||
buckaroo_post_data = {
|
||||
'BRQ_RETURNDATA': u'',
|
||||
'BRQ_AMOUNT': u'2240.00',
|
||||
'BRQ_CURRENCY': u'EUR',
|
||||
'BRQ_AMOUNT': str(self.amount),
|
||||
'BRQ_CURRENCY': self.currency.name,
|
||||
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
|
||||
'BRQ_INVOICENUMBER': u'SO004',
|
||||
'BRQ_INVOICENUMBER': self.reference,
|
||||
'brq_payment': u'573311D081B04069BD6336001611DBD4',
|
||||
'BRQ_PAYMENT_METHOD': u'paypal',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
|
||||
@@ -123,52 +54,43 @@ class BuckarooForm(BuckarooCommon):
|
||||
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
|
||||
'Brq_signature': u'9e75e51cbcb6fbc798b40d1787cc5b7c36557d56',
|
||||
'Brq_signature': u'e439f3af06b9752197631715628d6a198a25900f',
|
||||
'BRQ_STATUSCODE': u'190',
|
||||
'BRQ_STATUSCODE_DETAIL': u'S001',
|
||||
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
|
||||
'BRQ_TEST': u'true',
|
||||
'BRQ_TIMESTAMP': u'2014-05-08 12:41:21',
|
||||
'BRQ_TRANSACTIONS': u'D6106678E1D54EEB8093F5B3AC42EA7B',
|
||||
'BRQ_WEBSITEKEY': u'5xTGyGyPyl',
|
||||
}
|
||||
|
||||
# should raise error about unknown tx
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['payment.transaction'].form_feedback(buckaroo_post_data, 'buckaroo')
|
||||
with self.assertRaises(ValidationError): # unknown transaction
|
||||
self.env['payment.transaction']._handle_feedback_data('buckaroo', buckaroo_post_data)
|
||||
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'amount': 2240.0,
|
||||
'acquirer_id': self.buckaroo.id,
|
||||
'currency_id': self.currency_euro.id,
|
||||
'reference': 'SO004',
|
||||
'partner_name': 'Norbert Buyer',
|
||||
'partner_country_id': self.country_france.id})
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
|
||||
# validate it
|
||||
tx.form_feedback(buckaroo_post_data, 'buckaroo')
|
||||
# check state
|
||||
tx._handle_feedback_data('buckaroo', buckaroo_post_data)
|
||||
self.assertEqual(tx.state, 'done', 'Buckaroo: validation did not put tx into done state')
|
||||
self.assertEqual(tx.acquirer_reference, buckaroo_post_data.get('BRQ_TRANSACTIONS'), 'Buckaroo: validation did not update tx payid')
|
||||
self.assertEqual(tx.acquirer_reference, buckaroo_post_data.get('BRQ_TRANSACTIONS'),
|
||||
'Buckaroo: validation did not update tx payid')
|
||||
|
||||
# reset tx
|
||||
tx = self.env['payment.transaction'].create({
|
||||
'amount': 2240.0,
|
||||
'acquirer_id': self.buckaroo.id,
|
||||
'currency_id': self.currency_euro.id,
|
||||
'reference': 'SO004-2',
|
||||
'partner_name': 'Norbert Buyer',
|
||||
'partner_country_id': self.country_france.id})
|
||||
# New reference for new tx
|
||||
self.reference = 'SO004-2'
|
||||
tx = self.create_transaction(flow='redirect')
|
||||
|
||||
buckaroo_post_data['BRQ_INVOICENUMBER'] = 'SO004-2'
|
||||
buckaroo_post_data['BRQ_INVOICENUMBER'] = self.reference
|
||||
# now buckaroo post is ok: try to modify the SHASIGN
|
||||
buckaroo_post_data['Brq_signature'] = '54d928810e343acf5fb0c3ee75fd747ff159ef7a'
|
||||
with self.assertRaises(ValidationError):
|
||||
tx.form_feedback(buckaroo_post_data, 'buckaroo')
|
||||
self.env['payment.transaction']._handle_feedback_data('buckaroo', buckaroo_post_data)
|
||||
|
||||
# simulate an error
|
||||
buckaroo_post_data['BRQ_STATUSCODE'] = '2'
|
||||
buckaroo_post_data['Brq_signature'] = '9138e2bf09a708a4eb485ce7777e5406898b689d'
|
||||
tx.form_feedback(buckaroo_post_data, 'buckaroo')
|
||||
buckaroo_post_data['Brq_signature'] = 'b8e54e26b2b5a5e697b8ed5085329ea712fd48b2'
|
||||
|
||||
# check state
|
||||
self.assertEqual(tx.state, 'cancel', 'Buckaroo: erroneous validation did not put tx into error state')
|
||||
# Avoid warning log bc of unknown status code
|
||||
with mute_logger('odoo.addons.payment_buckaroo.models.payment_transaction'):
|
||||
self.env['payment.transaction']._handle_feedback_data('buckaroo', buckaroo_post_data)
|
||||
|
||||
self.assertEqual(tx.state, 'error',
|
||||
'Buckaroo: unexpected status code should put tx in error state')
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<template id="buckaroo_form">
|
||||
<div>
|
||||
<input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/>
|
||||
<input type="hidden" name="Brq_websitekey" t-att-value="Brq_websitekey"/>
|
||||
<input type="hidden" name="Brq_amount" t-att-value="Brq_amount"/>
|
||||
<input type="hidden" name="Brq_currency" t-att-value="Brq_currency"/>
|
||||
<input type="hidden" name="Brq_invoicenumber" t-att-value="Brq_invoicenumber"/>
|
||||
<input type="hidden" name="Brq_signature" t-att-value="Brq_signature"/>
|
||||
<input type="hidden" name="brq_test" t-att-value="brq_test"/>
|
||||
<input type="hidden" name="Brq_culture" t-att-value="Brq_culture"/>
|
||||
<!-- URLs -->
|
||||
<input t-if="Brq_return" type="hidden" name='Brq_return'
|
||||
t-att-value="Brq_return"/>
|
||||
<input t-if="Brq_returncancel" type="hidden" name='Brq_returncancel'
|
||||
t-att-value="Brq_returncancel"/>
|
||||
<input t-if="Brq_returnerror" type="hidden" name='Brq_returnerror'
|
||||
t-att-value="Brq_returnerror"/>
|
||||
<input t-if="Brq_returnreject" type="hidden" name='Brq_returnreject'
|
||||
t-att-value="Brq_returnreject"/>
|
||||
<input type="hidden" name='add_returndata' t-att-value="add_returndata"/>
|
||||
</div>
|
||||
</template>
|
||||
</data>
|
||||
|
||||
<template id="redirect_form">
|
||||
<form t-att-action="api_url" method="post">
|
||||
<input type="hidden" name="Brq_websitekey" t-att-value="Brq_websitekey"/>
|
||||
<input type="hidden" name="Brq_amount" t-att-value="Brq_amount"/>
|
||||
<input type="hidden" name="Brq_currency" t-att-value="Brq_currency"/>
|
||||
<input type="hidden" name="Brq_invoicenumber" t-att-value="Brq_invoicenumber"/>
|
||||
<input type="hidden" name="Brq_signature" t-att-value="Brq_signature"/>
|
||||
<input t-if="Brq_culture" type="hidden" name="Brq_culture" t-att-value="Brq_culture"/>
|
||||
<!-- URLs -->
|
||||
<input type="hidden" name="Brq_return" t-att-value="Brq_return"/>
|
||||
<input type="hidden" name="Brq_returncancel" t-att-value="Brq_returncancel"/>
|
||||
<input type="hidden" name="Brq_returnerror" t-att-value="Brq_returnerror"/>
|
||||
<input type="hidden" name="Brq_returnreject" t-att-value="Brq_returnreject"/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_buckaroo" model="ir.ui.view">
|
||||
<field name="name">acquirer.form.buckaroo</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', '!=', 'buckaroo')]}">
|
||||
<field name="brq_websitekey" attrs="{'required':[ ('provider', '=', 'buckaroo'), ('state', '!=', 'disabled')]}"/>
|
||||
<field name="brq_secretkey" attrs="{'required':[ ('provider', '=', 'buckaroo'), ('state', '!=', 'disabled')]}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="payment_acquirer_form" model="ir.ui.view">
|
||||
<field name="name">Buckaroo Acquirer Form</field>
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="inherit_id" ref="payment.payment_acquirer_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//group[@name="acquirer"]' position='inside'>
|
||||
<group attrs="{'invisible': [('provider', '!=', 'buckaroo')]}">
|
||||
<field name="buckaroo_website_key" attrs="{'required':[ ('provider', '=', 'buckaroo'), ('state', '!=', 'disabled')]}"/>
|
||||
<field name="buckaroo_secret_key" string="Secret Key" attrs="{'required':[ ('provider', '=', 'buckaroo'), ('state', '!=', 'disabled')]}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user