From 8a6e859c2bc964693a1fb809f3df7ac027e04b78 Mon Sep 17 00:00:00 2001 From: Ajay javiya Date: Thu, 24 Jul 2014 12:58:18 +0530 Subject: [PATCH] [ADD] New payment acquirer Authorize.net. --- addons/payment/models/res_config.py | 3 + addons/payment/views/res_config_view.xml | 4 + addons/payment_authorize/__init__.py | 23 +++ addons/payment_authorize/__openerp__.py | 18 ++ .../payment_authorize/controllers/__init__.py | 3 + addons/payment_authorize/controllers/main.py | 32 ++++ addons/payment_authorize/data/authorize.xml | 18 ++ addons/payment_authorize/models/__init__.py | 3 + addons/payment_authorize/models/authorize.py | 165 +++++++++++++++++ .../static/description/icon.png | Bin 0 -> 6281 bytes .../static/src/img/authorize_icon.png | Bin 0 -> 2966 bytes addons/payment_authorize/tests/__init__.py | 3 + .../payment_authorize/tests/test_authorize.py | 173 ++++++++++++++++++ addons/payment_authorize/views/authorize.xml | 42 +++++ .../views/payment_acquirer.xml | 33 ++++ .../views/payment_authorize_template.xml | 10 + 16 files changed, 530 insertions(+) create mode 100644 addons/payment_authorize/__init__.py create mode 100644 addons/payment_authorize/__openerp__.py create mode 100644 addons/payment_authorize/controllers/__init__.py create mode 100644 addons/payment_authorize/controllers/main.py create mode 100644 addons/payment_authorize/data/authorize.xml create mode 100644 addons/payment_authorize/models/__init__.py create mode 100644 addons/payment_authorize/models/authorize.py create mode 100644 addons/payment_authorize/static/description/icon.png create mode 100644 addons/payment_authorize/static/src/img/authorize_icon.png create mode 100644 addons/payment_authorize/tests/__init__.py create mode 100644 addons/payment_authorize/tests/test_authorize.py create mode 100644 addons/payment_authorize/views/authorize.xml create mode 100644 addons/payment_authorize/views/payment_acquirer.xml create mode 100644 addons/payment_authorize/views/payment_authorize_template.xml diff --git a/addons/payment/models/res_config.py b/addons/payment/models/res_config.py index a74e595b52e..b64b698c83d 100644 --- a/addons/payment/models/res_config.py +++ b/addons/payment/models/res_config.py @@ -19,4 +19,7 @@ class AccountPaymentConfig(osv.TransientModel): 'module_payment_buckaroo': fields.boolean( 'Manage Payments Using Buckaroo', help='-It installs the module payment_buckaroo.'), + 'module_payment_authorize': fields.boolean( + 'Manage Payments Using Authorize.Net', + help='-It installs the module payment_authorize.'), } diff --git a/addons/payment/views/res_config_view.xml b/addons/payment/views/res_config_view.xml index e4e6fc96652..8f5e5f31321 100644 --- a/addons/payment/views/res_config_view.xml +++ b/addons/payment/views/res_config_view.xml @@ -24,6 +24,10 @@ diff --git a/addons/payment_authorize/__init__.py b/addons/payment_authorize/__init__.py new file mode 100644 index 00000000000..9332521b0ee --- /dev/null +++ b/addons/payment_authorize/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, Open Source Management Solution +# Copyright (C) 2014-TODAY Odoo SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import models +import controllers diff --git a/addons/payment_authorize/__openerp__.py b/addons/payment_authorize/__openerp__.py new file mode 100644 index 00000000000..57bf2c7e711 --- /dev/null +++ b/addons/payment_authorize/__openerp__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Authorize.Net Payment Acquirer', + 'category': 'Hidden', + 'summary': 'Payment Acquirer: Authorize.net Implementation', + 'version': '1.0', + 'description': """Authorize.Net Payment Acquirer""", + 'author': 'Odoo SA', + 'depends': ['payment'], + 'data': [ + 'views/authorize.xml', + 'views/payment_acquirer.xml', + 'data/authorize.xml', + 'views/payment_authorize_template.xml', + ], + 'installable': True, +} diff --git a/addons/payment_authorize/controllers/__init__.py b/addons/payment_authorize/controllers/__init__.py new file mode 100644 index 00000000000..bbd183e955b --- /dev/null +++ b/addons/payment_authorize/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import main diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py new file mode 100644 index 00000000000..1279c23ce26 --- /dev/null +++ b/addons/payment_authorize/controllers/main.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +import pprint +import logging +import urlparse + +from openerp import http +from openerp.http import request + +_logger = logging.getLogger(__name__) + + +class AuthorizeController(http.Controller): + _return_url = '/payment/authorize/return/' + _cancel_url = '/payment/authorize/cancel/' + + @http.route([ + '/payment/authorize/return/', + '/payment/authorize/cancel/', + ], type='http', auth='public') + def authorize_form_feedback(self, **post): + _logger.info('Authorize: entering form_feedback with post data %s', pprint.pformat(post)) + return_url = '/' + if post: + request.env['payment.transaction'].sudo().form_feedback(post, 'authorize') + return_url = post.pop('return_url', '/') + base_url = request.env['ir.config_parameter'].get_param('web.base.url') + # Authorize.Net is expecting a response to the POST sent by their server. + # This response is in the form of a URL that Authorize.Net will pass on to the + # client's browser to redirect them to the desired location need javascript. + return request.render('payment_authorize.payment_authorize_redirect', { + 'return_url': '%s' % urlparse.urljoin(base_url, return_url) + }) diff --git a/addons/payment_authorize/data/authorize.xml b/addons/payment_authorize/data/authorize.xml new file mode 100644 index 00000000000..d1d38106252 --- /dev/null +++ b/addons/payment_authorize/data/authorize.xml @@ -0,0 +1,18 @@ + + + + + + Authorize.Net + authorize + + + test + You will be redirected to the Authorize website after clicking on the payment button.

]]>
+ dummy + dummy +
+ +
+
diff --git a/addons/payment_authorize/models/__init__.py b/addons/payment_authorize/models/__init__.py new file mode 100644 index 00000000000..92e55eeff24 --- /dev/null +++ b/addons/payment_authorize/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import authorize diff --git a/addons/payment_authorize/models/authorize.py b/addons/payment_authorize/models/authorize.py new file mode 100644 index 00000000000..2708daa37a6 --- /dev/null +++ b/addons/payment_authorize/models/authorize.py @@ -0,0 +1,165 @@ +# -*- coding: utf-'8' "-*-" + +import hashlib +import hmac +import logging +import time +import urlparse + +from openerp import api, fields, models +from openerp.addons.payment.models.payment_acquirer import ValidationError +from openerp.addons.payment_authorize.controllers.main import AuthorizeController +from openerp.tools.float_utils import float_compare + +_logger = logging.getLogger(__name__) + + +class PaymentAcquirerAuthorize(models.Model): + _inherit = 'payment.acquirer' + + def _get_authorize_urls(self, environment): + """ Authorize URLs """ + if environment == 'prod': + return {'authorize_form_url': 'https://secure.authorize.net/gateway/transact.dll'} + else: + return {'authorize_form_url': 'https://test.authorize.net/gateway/transact.dll'} + + @api.model + def _get_providers(self): + providers = super(PaymentAcquirerAuthorize, self)._get_providers() + providers.append(['authorize', 'Authorize.Net']) + return providers + + authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize') + authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize') + + def _authorize_generate_hashing(self, values): + data = '^'.join([ + values['x_login'], + values['x_fp_sequence'], + values['x_fp_timestamp'], + values['x_amount'], + values['x_currency_code']]) + return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest() + + @api.multi + def authorize_form_generate_values(self, partner_values, tx_values): + self.ensure_one() + base_url = self.env['ir.config_parameter'].get_param('web.base.url') + authorize_tx_values = dict(tx_values) + temp_authorize_tx_values = { + 'x_login': self.authorize_login, + 'x_trans_key': self.authorize_transaction_key, + 'x_amount': str(tx_values['amount']), + 'x_show_form': 'PAYMENT_FORM', + 'x_type': 'AUTH_CAPTURE', + 'x_method': 'CC', + 'x_fp_sequence': '%s%s' % (self.id, int(time.time())), + 'x_version': '3.1', + 'x_relay_response': 'TRUE', + 'x_fp_timestamp': str(int(time.time())), + 'x_relay_url': '%s' % urlparse.urljoin(base_url, AuthorizeController._return_url), + 'x_cancel_url': '%s' % urlparse.urljoin(base_url, AuthorizeController._cancel_url), + 'x_currency_code': tx_values['currency'] and tx_values['currency'].name or '', + 'address': partner_values['address'], + 'city': partner_values['city'], + 'country': partner_values['country'] and partner_values['country'].name or '', + 'email': partner_values['email'], + 'zip': partner_values['zip'], + 'first_name': partner_values['first_name'], + 'last_name': partner_values['last_name'], + 'phone': partner_values['phone'], + 'state': partner_values.get('state') and partner_values['state'].name or '', + } + temp_authorize_tx_values['returndata'] = authorize_tx_values.pop('return_url', '') + temp_authorize_tx_values['x_fp_hash'] = self._authorize_generate_hashing(temp_authorize_tx_values) + authorize_tx_values.update(temp_authorize_tx_values) + return partner_values, authorize_tx_values + + @api.multi + def authorize_get_form_action_url(self): + self.ensure_one() + return self._get_authorize_urls(self.environment)['authorize_form_url'] + + +class TxAuthorize(models.Model): + _inherit = 'payment.transaction' + + authorize_txnid = fields.Char(string='Transaction ID') + + _authorize_valid_tx_status = 1 + _authorize_pending_tx_status = 4 + _authorize_cancel_tx_status = 2 + + # -------------------------------------------------- + # FORM RELATED METHODS + # -------------------------------------------------- + + @api.model + def _authorize_form_get_tx_from_data(self, data): + """ Given a data dict coming from authorize, verify it and find the related + transaction record. """ + reference, trans_id, fingerprint = data.get('x_invoice_num'), data.get('x_trans_id'), data.get('x_MD5_Hash') + if not reference or not trans_id or not fingerprint: + error_msg = 'Authorize: received data with missing reference (%s) or trans_id (%s) or fingerprint (%s)' % (reference, trans_id, fingerprint) + _logger.error(error_msg) + raise ValidationError(error_msg) + tx = self.search([('reference', '=', reference)]) + if not tx or len(tx) > 1: + error_msg = 'Authorize: received data for reference %s' % (reference) + if not tx: + error_msg += '; no order found' + else: + error_msg += '; multiple order found' + _logger.error(error_msg) + raise ValidationError(error_msg) + return tx[0] + + @api.model + def _authorize_form_get_invalid_parameters(self, tx, data): + invalid_parameters = [] + + if tx.authorize_txnid and data.get('x_trans_id') != tx.authorize_txnid: + invalid_parameters.append(('Transaction Id', data.get('x_trans_id'), tx.authorize_txnid)) + # check what is buyed + if float_compare(float(data.get('x_amount', '0.0')), tx.amount, 2) != 0: + invalid_parameters.append(('Amount', data.get('x_amount'), '%.2f' % tx.amount)) + return invalid_parameters + + @api.model + def _authorize_form_validate(self, tx, data): + if tx.state == 'done': + _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % tx.reference) + return True + status_code = int(data.get('x_response_code', '0')) + if status_code == self._authorize_valid_tx_status: + tx.write({ + 'state': 'done', + 'authorize_txnid': data.get('x_trans_id'), + 'acquirer_reference': data['x_invoice_num'], + }) + return True + elif status_code == self._authorize_pending_tx_status: + tx.write({ + 'state': 'pending', + 'authorize_txnid': data.get('x_trans_id'), + 'acquirer_reference': data['x_invoice_num'], + }) + return True + elif status_code == self._authorize_cancel_tx_status: + tx.write({ + 'state': 'cancel', + 'authorize_txnid': data.get('x_trans_id'), + 'acquirer_reference': data['x_invoice_num'], + }) + return True + else: + error = data.get('x_response_reason_text') + _logger.info(error) + tx.write({ + 'state': 'error', + 'state_message': error, + 'authorize_txnid': data.get('x_trans_id'), + 'acquirer_reference': data['x_invoice_num'], + }) + return False diff --git a/addons/payment_authorize/static/description/icon.png b/addons/payment_authorize/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c2bab4f7a07daccbbd2ec348788d6e9ce437db9e GIT binary patch literal 6281 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 literal 0 HcmV?d00001 diff --git a/addons/payment_authorize/tests/__init__.py b/addons/payment_authorize/tests/__init__.py new file mode 100644 index 00000000000..7c3bcfce559 --- /dev/null +++ b/addons/payment_authorize/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from openerp.addons.payment_authorize.tests import test_authorize diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py new file mode 100644 index 00000000000..f49aefaee40 --- /dev/null +++ b/addons/payment_authorize/tests/test_authorize.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- + +import hashlib +import hmac +import time +import urlparse +from lxml import objectify + +import openerp +from openerp.addons.payment.models.payment_acquirer import ValidationError +from openerp.addons.payment.tests.common import PaymentAcquirerCommon +from openerp.addons.payment_authorize.controllers.main import AuthorizeController +from openerp.tools import mute_logger + + +@openerp.tests.common.at_install(True) +@openerp.tests.common.post_install(True) +class AuthorizeCommon(PaymentAcquirerCommon): + + def setUp(self): + super(AuthorizeCommon, self).setUp() + self.base_url = self.env['ir.config_parameter'].get_param('web.base.url') + # authorize only support USD in test environment + self.currency_usd = self.env['res.currency'].search([('name', '=', 'USD')], limit=1)[0] + # get the authorize account + model, self.authorize_id = self.env['ir.model.data'].get_object_reference('payment_authorize', 'payment_acquirer_authorize') + + +@openerp.tests.common.at_install(True) +@openerp.tests.common.post_install(True) +class AuthorizeForm(AuthorizeCommon): + + def _authorize_generate_hashing(self, values): + data = '^'.join([ + values['x_login'], + values['x_fp_sequence'], + values['x_fp_timestamp'], + values['x_amount'], + ]) + '^' + return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest() + + def test_10_Authorize_form_render(self): + authorize = self.env['payment.acquirer'].browse(self.authorize_id) + self.assertEqual(authorize.environment, 'test', 'test without test environment') + + # ---------------------------------------- + # Test: button direct rendering + # ---------------------------------------- + form_values = { + 'x_login': authorize.authorize_login, + 'x_trans_key': authorize.authorize_transaction_key, + 'x_amount': '320.0', + 'x_show_form': 'PAYMENT_FORM', + 'x_type': 'AUTH_CAPTURE', + 'x_method': 'CC', + 'x_fp_sequence': '%s%s' % (authorize.id, int(time.time())), + 'x_version': '3.1', + 'x_relay_response': 'TRUE', + 'x_fp_timestamp': str(int(time.time())), + 'x_relay_url': '%s' % urlparse.urljoin(self.base_url, AuthorizeController._return_url), + 'x_cancel_url': '%s' % urlparse.urljoin(self.base_url, AuthorizeController._cancel_url), + 'return_url': None, + 'x_currency_code': 'USD', + 'x_invoice_num': 'SO004', + 'x_first_name': 'Buyer', + 'x_last_name': 'Norbert', + 'x_address': 'Huge Street 2/543', + 'x_city': 'Sin City', + 'x_zip': '1000', + 'x_country': 'Belgium', + 'x_phone': '0032 12 34 56 78', + 'x_email': 'norbert.buyer@example.com', + 'x_state': None, + } + + form_values['x_fp_hash'] = self._authorize_generate_hashing(form_values) + # render the button + cr, uid, context = self.env.cr, self.env.uid, {} + res = self.payment_acquirer.render( + cr, uid, self.authorize_id, 'SO004', 320.0, self.currency_usd.id, + partner_id=None, partner_values=self.buyer_values, context=context) + # check form result + tree = objectify.fromstring(res) + self.assertEqual(tree.get('action'), 'https://test.authorize.net/gateway/transact.dll', 'Authorize: wrong form POST url') + for form_input in tree.input: + # Generated and received 'x_fp_hash' are always different so skeep it. + if form_input.get('name') in ['submit', 'x_fp_hash']: + continue + self.assertEqual( + form_input.get('value'), + form_values[form_input.get('name')], + 'Authorize: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')]) + ) + + @mute_logger('openerp.addons.payment_authorize.models.authorize', 'ValidationError') + def test_20_authorize_form_management(self): + cr, uid, context = self.env.cr, self.env.uid, {} + # be sure not to do stupid thing + authorize = self.env['payment.acquirer'].browse(self.authorize_id) + self.assertEqual(authorize.environment, 'test', 'test without test environment') + + # typical data posted by authorize after client has successfully paid + authorize_post_data = { + 'return_url': u'/shop/payment/validate', + 'x_MD5_Hash': u'7934485E1C105940BE854208D10FAB4F', + 'x_account_number': u'XXXX0027', + 'x_address': u'Huge Street 2/543', + 'x_amount': u'320.00', + 'x_auth_code': u'E4W7IU', + 'x_avs_code': u'Y', + 'x_card_type': u'Visa', + 'x_cavv_response': u'2', + 'x_city': u'Sun City', + 'x_company': u'', + 'x_country': u'Belgium', + 'x_cust_id': u'', + 'x_cvv2_resp_code': u'', + 'x_description': u'', + 'x_duty': u'0.00', + 'x_email': u'norbert.buyer@exampl', + 'x_fax': u'', + 'x_first_name': u'Norbert', + 'x_freight': u'0.00', + 'x_invoice_num': u'SO004', + 'x_last_name': u'Buyer', + 'x_method': u'CC', + 'x_phone': u'0032 12 34 56 78', + 'x_po_num': u'', + 'x_response_code': u'1', + 'x_response_reason_code': u'1', + 'x_response_reason_text': u'This transaction has been approved.', + 'x_ship_to_address': u'Huge Street 2/543', + 'x_ship_to_city': u'Sun City', + 'x_ship_to_company': u'', + 'x_ship_to_country': u'Belgium', + 'x_ship_to_first_name': u'Norbert', + 'x_ship_to_last_name': u'Buyer', + 'x_ship_to_state': u'', + 'x_ship_to_zip': u'1000', + 'x_state': u'', + 'x_tax': u'0.00', + 'x_tax_exempt': u'FALSE', + 'x_test_request': u'false', + 'x_trans_id': u'2217460311', + 'x_type': u'auth_capture', + 'x_zip': u'1000' + } + + # should raise error about unknown tx + with self.assertRaises(ValidationError): + self.payment_transaction.form_feedback(cr, uid, authorize_post_data, 'authorize', context=context) + + tx = self.env['payment.transaction'].create({ + 'amount': 320.0, + 'acquirer_id': self.authorize_id, + 'currency_id': self.currency_usd.id, + 'reference': 'SO004', + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france_id}) + # validate it + self.payment_transaction.form_feedback(cr, uid, authorize_post_data, 'authorize', context=context) + # check state + self.assertEqual(tx.state, 'done', 'Authorize: validation did not put tx into done state') + self.assertEqual(tx.authorize_txnid, authorize_post_data.get('x_trans_id'), 'Authorize: validation did not update tx payid') + + # reset tx + tx.write({'state': 'draft', 'date_validate': False, 'authorize_txnid': False}) + + # simulate an error + authorize_post_data['x_response_code'] = u'3' + self.payment_transaction.form_feedback(cr, uid, authorize_post_data, 'authorize', context=context) + # check state + self.assertEqual(tx.state, 'error', 'Authorize: erroneous validation did not put tx into error state') diff --git a/addons/payment_authorize/views/authorize.xml b/addons/payment_authorize/views/authorize.xml new file mode 100644 index 00000000000..7285ccb860a --- /dev/null +++ b/addons/payment_authorize/views/authorize.xml @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/addons/payment_authorize/views/payment_acquirer.xml b/addons/payment_authorize/views/payment_acquirer.xml new file mode 100644 index 00000000000..a370f9f7a2e --- /dev/null +++ b/addons/payment_authorize/views/payment_acquirer.xml @@ -0,0 +1,33 @@ + + + + + acquirer.form.authorize + payment.acquirer + + + + + + + + + + + + + acquirer.transaction.form.authorize + payment.transaction + + + + + + + + + + + + + diff --git a/addons/payment_authorize/views/payment_authorize_template.xml b/addons/payment_authorize/views/payment_authorize_template.xml new file mode 100644 index 00000000000..58b1ee85e67 --- /dev/null +++ b/addons/payment_authorize/views/payment_authorize_template.xml @@ -0,0 +1,10 @@ + + + + + +