summaryrefslogtreecommitdiff
path: root/addons/payment_ingenico/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/payment_ingenico/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/payment_ingenico/models')
-rw-r--r--addons/payment_ingenico/models/__init__.py3
-rw-r--r--addons/payment_ingenico/models/payment.py597
2 files changed, 600 insertions, 0 deletions
diff --git a/addons/payment_ingenico/models/__init__.py b/addons/payment_ingenico/models/__init__.py
new file mode 100644
index 00000000..ef125336
--- /dev/null
+++ b/addons/payment_ingenico/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import payment
diff --git a/addons/payment_ingenico/models/payment.py b/addons/payment_ingenico/models/payment.py
new file mode 100644
index 00000000..6076088a
--- /dev/null
+++ b/addons/payment_ingenico/models/payment.py
@@ -0,0 +1,597 @@
+# coding: utf-8
+import base64
+import datetime
+import logging
+import time
+from hashlib import sha1
+from pprint import pformat
+from unicodedata import normalize
+
+import requests
+from lxml import etree, objectify
+from werkzeug import urls
+
+from odoo import api, fields, models, _
+from odoo.addons.payment.models.payment_acquirer import ValidationError
+from odoo.addons.payment_ingenico.controllers.main import OgoneController
+from odoo.addons.payment_ingenico.data import ogone
+from odoo.http import request
+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, ustr
+from odoo.tools.float_utils import float_compare, float_repr, float_round
+
+_logger = logging.getLogger(__name__)
+
+
+class PaymentAcquirerOgone(models.Model):
+ _inherit = 'payment.acquirer'
+
+ provider = fields.Selection(selection_add=[
+ ('ogone', 'Ingenico')
+ ], ondelete={'ogone': 'set default'})
+ ogone_pspid = fields.Char('PSPID', required_if_provider='ogone', groups='base.group_user')
+ ogone_userid = fields.Char('API User ID', required_if_provider='ogone', groups='base.group_user')
+ ogone_password = fields.Char('API User Password', required_if_provider='ogone', groups='base.group_user')
+ ogone_shakey_in = fields.Char('SHA Key IN', size=32, required_if_provider='ogone', groups='base.group_user')
+ ogone_shakey_out = fields.Char('SHA Key OUT', size=32, required_if_provider='ogone', groups='base.group_user')
+ ogone_alias_usage = fields.Char('Alias Usage', default="Allow saving my payment data",
+ help="If you want to use Ogone Aliases, this default "
+ "Alias Usage will be presented to the customer as the "
+ "reason you want to keep his payment data")
+
+ def _get_feature_support(self):
+ """Get advanced feature support by provider.
+
+ Each provider should add its technical in the corresponding
+ key for the following features:
+ * fees: support payment fees computations
+ * authorize: support authorizing payment (separates
+ authorization and capture)
+ * tokenize: support saving payment data in a payment.tokenize
+ object
+ """
+ res = super(PaymentAcquirerOgone, self)._get_feature_support()
+ res['tokenize'].append('ogone')
+ return res
+
+ def _get_ogone_urls(self, environment):
+ """ Ogone URLS:
+ - standard order: POST address for form-based """
+ return {
+ 'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard_utf8.asp' % (environment,),
+ 'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect_utf8.asp' % (environment,),
+ 'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect_utf8.asp' % (environment,),
+ 'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % (environment,),
+ }
+
+ def _ogone_generate_shasign(self, inout, values):
+ """ Generate the shasign for incoming or outgoing communications.
+
+ :param string inout: 'in' (odoo contacting ogone) or 'out' (ogone
+ contacting odoo). In this last case only some
+ fields should be contained (see e-Commerce basic)
+ :param dict values: transaction values
+
+ :return string: shasign
+ """
+ assert inout in ('in', 'out')
+ assert self.provider == 'ogone'
+ key = getattr(self, 'ogone_shakey_' + inout)
+
+ def filter_key(key):
+ if inout == 'in':
+ return True
+ else:
+ # SHA-OUT keys
+ # source https://payment-services.ingenico.com/int/en/ogone/support/guides/integration guides/e-commerce/transaction-feedback
+ keys = [
+ 'AAVADDRESS',
+ 'AAVCHECK',
+ 'AAVMAIL',
+ 'AAVNAME',
+ 'AAVPHONE',
+ 'AAVZIP',
+ 'ACCEPTANCE',
+ 'ALIAS',
+ 'AMOUNT',
+ 'BIC',
+ 'BIN',
+ 'BRAND',
+ 'CARDNO',
+ 'CCCTY',
+ 'CN',
+ 'COLLECTOR_BIC',
+ 'COLLECTOR_IBAN',
+ 'COMPLUS',
+ 'CREATION_STATUS',
+ 'CREDITDEBIT',
+ 'CURRENCY',
+ 'CVCCHECK',
+ 'DCC_COMMPERCENTAGE',
+ 'DCC_CONVAMOUNT',
+ 'DCC_CONVCCY',
+ 'DCC_EXCHRATE',
+ 'DCC_EXCHRATESOURCE',
+ 'DCC_EXCHRATETS',
+ 'DCC_INDICATOR',
+ 'DCC_MARGINPERCENTAGE',
+ 'DCC_VALIDHOURS',
+ 'DEVICEID',
+ 'DIGESTCARDNO',
+ 'ECI',
+ 'ED',
+ 'EMAIL',
+ 'ENCCARDNO',
+ 'FXAMOUNT',
+ 'FXCURRENCY',
+ 'IP',
+ 'IPCTY',
+ 'MANDATEID',
+ 'MOBILEMODE',
+ 'NBREMAILUSAGE',
+ 'NBRIPUSAGE',
+ 'NBRIPUSAGE_ALLTX',
+ 'NBRUSAGE',
+ 'NCERROR',
+ 'ORDERID',
+ 'PAYID',
+ 'PAYIDSUB',
+ 'PAYMENT_REFERENCE',
+ 'PM',
+ 'SCO_CATEGORY',
+ 'SCORING',
+ 'SEQUENCETYPE',
+ 'SIGNDATE',
+ 'STATUS',
+ 'SUBBRAND',
+ 'SUBSCRIPTION_ID',
+ 'TICKET',
+ 'TRXDATE',
+ 'VC',
+ ]
+ return key.upper() in keys
+
+ items = sorted((k.upper(), v) for k, v in values.items())
+ sign = ''.join('%s=%s%s' % (k, v, key) for k, v in items if v and filter_key(k))
+ sign = sign.encode("utf-8")
+ shasign = sha1(sign).hexdigest()
+ return shasign
+
+ def ogone_form_generate_values(self, values):
+ base_url = self.get_base_url()
+ ogone_tx_values = dict(values)
+ param_plus = {
+ 'return_url': ogone_tx_values.pop('return_url', False)
+ }
+ temp_ogone_tx_values = {
+ 'PSPID': self.ogone_pspid,
+ 'ORDERID': values['reference'],
+ 'AMOUNT': float_repr(float_round(values['amount'], 2) * 100, 0),
+ 'CURRENCY': values['currency'] and values['currency'].name or '',
+ 'LANGUAGE': values.get('partner_lang'),
+ 'CN': values.get('partner_name'),
+ 'EMAIL': values.get('partner_email'),
+ 'OWNERZIP': values.get('partner_zip'),
+ 'OWNERADDRESS': values.get('partner_address'),
+ 'OWNERTOWN': values.get('partner_city'),
+ 'OWNERCTY': values.get('partner_country') and values.get('partner_country').code or '',
+ 'OWNERTELNO': values.get('partner_phone'),
+ 'ACCEPTURL': urls.url_join(base_url, OgoneController._accept_url),
+ 'DECLINEURL': urls.url_join(base_url, OgoneController._decline_url),
+ 'EXCEPTIONURL': urls.url_join(base_url, OgoneController._exception_url),
+ 'CANCELURL': urls.url_join(base_url, OgoneController._cancel_url),
+ 'PARAMPLUS': urls.url_encode(param_plus),
+ }
+ if self.save_token in ['ask', 'always']:
+ temp_ogone_tx_values.update({
+ 'ALIAS': 'ODOO-NEW-ALIAS-%s' % time.time(), # something unique,
+ 'ALIASUSAGE': values.get('alias_usage') or self.ogone_alias_usage,
+ })
+ shasign = self._ogone_generate_shasign('in', temp_ogone_tx_values)
+ temp_ogone_tx_values['SHASIGN'] = shasign
+ ogone_tx_values.update(temp_ogone_tx_values)
+ return ogone_tx_values
+
+ def ogone_get_form_action_url(self):
+ self.ensure_one()
+ environment = 'prod' if self.state == 'enabled' else 'test'
+ return self._get_ogone_urls(environment)['ogone_standard_order_url']
+
+ def ogone_s2s_form_validate(self, data):
+ error = dict()
+
+ mandatory_fields = ["cc_number", "cc_cvc", "cc_holder_name", "cc_expiry", "cc_brand"]
+ # Validation
+ for field_name in mandatory_fields:
+ if not data.get(field_name):
+ error[field_name] = 'missing'
+
+ return False if error else True
+
+ def ogone_s2s_form_process(self, data):
+ values = {
+ 'cc_number': data.get('cc_number'),
+ 'cc_cvc': int(data.get('cc_cvc')),
+ 'cc_holder_name': data.get('cc_holder_name'),
+ 'cc_expiry': data.get('cc_expiry'),
+ 'cc_brand': data.get('cc_brand'),
+ 'acquirer_id': int(data.get('acquirer_id')),
+ 'partner_id': int(data.get('partner_id'))
+ }
+ pm_id = self.env['payment.token'].sudo().create(values)
+ return pm_id
+
+
+class PaymentTxOgone(models.Model):
+ _inherit = 'payment.transaction'
+ # ogone status
+ _ogone_valid_tx_status = [5, 9, 8]
+ _ogone_wait_tx_status = [41, 50, 51, 52, 55, 56, 91, 92, 99]
+ _ogone_pending_tx_status = [46, 81, 82] # 46 = 3DS HTML response
+ _ogone_cancel_tx_status = [1]
+
+ # --------------------------------------------------
+ # FORM RELATED METHODS
+ # --------------------------------------------------
+
+ @api.model
+ def _ogone_form_get_tx_from_data(self, data):
+ """ Given a data dict coming from ogone, verify it and find the related
+ transaction record. Create a payment token if an alias is returned."""
+ reference, pay_id, shasign, alias = data.get('orderID'), data.get('PAYID'), data.get('SHASIGN'), data.get('ALIAS')
+ if not reference or not pay_id or not shasign:
+ error_msg = _('Ogone: 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)
+
+ # find tx -> @TDENOTE use paytid ?
+ tx = self.search([('reference', '=', reference)])
+ if not tx or len(tx) > 1:
+ error_msg = _('Ogone: 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._ogone_generate_shasign('out', data)
+ if shasign_check.upper() != shasign.upper():
+ error_msg = _('Ogone: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)
+ _logger.info(error_msg)
+ raise ValidationError(error_msg)
+
+ if not tx.acquirer_reference:
+ tx.acquirer_reference = pay_id
+
+ # alias was created on ogone server, store it
+ if alias and tx.type == 'form_save':
+ Token = self.env['payment.token']
+ domain = [('acquirer_ref', '=', alias)]
+ cardholder = data.get('CN')
+ if not Token.search_count(domain):
+ _logger.info('Ogone: saving alias %s for partner %s' % (data.get('CARDNO'), tx.partner_id))
+ ref = Token.create({'name': data.get('CARDNO') + (' - ' + cardholder if cardholder else ''),
+ 'partner_id': tx.partner_id.id,
+ 'acquirer_id': tx.acquirer_id.id,
+ 'acquirer_ref': alias})
+ tx.write({'payment_token_id': ref.id})
+
+ return tx
+
+ def _ogone_form_get_invalid_parameters(self, data):
+ invalid_parameters = []
+
+ # TODO: txn_id: should be false at draft, set afterwards, and verified with txn details
+ if self.acquirer_reference and data.get('PAYID') != self.acquirer_reference:
+ invalid_parameters.append(('PAYID', data.get('PAYID'), self.acquirer_reference))
+ # check what is bought
+ if float_compare(float(data.get('amount', '0.0')), self.amount, 2) != 0:
+ invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount))
+ if data.get('currency') != self.currency_id.name:
+ invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name))
+
+ return invalid_parameters
+
+ def _ogone_form_validate(self, data):
+ if self.state not in ['draft', 'pending']:
+ _logger.info('Ogone: trying to validate an already validated tx (ref %s)', self.reference)
+ return True
+
+ status = int(data.get('STATUS', '0'))
+ if status in self._ogone_valid_tx_status:
+ vals = {
+ 'date': datetime.datetime.strptime(data['TRXDATE'], '%m/%d/%y').strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'acquirer_reference': data['PAYID'],
+ }
+ if data.get('ALIAS') and self.partner_id and \
+ (self.type == 'form_save' or self.acquirer_id.save_token == 'always')\
+ and not self.payment_token_id:
+ pm = self.env['payment.token'].create({
+ 'partner_id': self.partner_id.id,
+ 'acquirer_id': self.acquirer_id.id,
+ 'acquirer_ref': data.get('ALIAS'),
+ 'name': '%s - %s' % (data.get('CARDNO'), data.get('CN'))
+ })
+ vals.update(payment_token_id=pm.id)
+ self.write(vals)
+ if self.payment_token_id:
+ self.payment_token_id.verified = True
+ self._set_transaction_done()
+ self.execute_callback()
+ # if this transaction is a validation one, then we refund the money we just withdrawn
+ if self.type == 'validation':
+ self.s2s_do_refund()
+
+ return True
+ elif status in self._ogone_cancel_tx_status:
+ self.write({'acquirer_reference': data.get('PAYID')})
+ self._set_transaction_cancel()
+ elif status in self._ogone_pending_tx_status or status in self._ogone_wait_tx_status:
+ self.write({'acquirer_reference': data.get('PAYID')})
+ self._set_transaction_pending()
+ else:
+ error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
+ 'error_str': data.get('NCERRORPLUS'),
+ 'error_code': data.get('NCERROR'),
+ 'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERROR')),
+ }
+ _logger.info(error)
+ self.write({
+ 'state_message': error,
+ 'acquirer_reference': data.get('PAYID'),
+ })
+ self._set_transaction_cancel()
+ return False
+
+ # --------------------------------------------------
+ # S2S RELATED METHODS
+ # --------------------------------------------------
+ def ogone_s2s_do_transaction(self, **kwargs):
+ # TODO: create tx with s2s type
+ account = self.acquirer_id
+ reference = self.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%y%m%d_%H%M%S'), self.partner_id.id)
+
+ param_plus = {
+ 'return_url': kwargs.get('return_url', False)
+ }
+
+ data = {
+ 'PSPID': account.ogone_pspid,
+ 'USERID': account.ogone_userid,
+ 'PSWD': account.ogone_password,
+ 'ORDERID': reference,
+ 'AMOUNT': int(self.amount * 100),
+ 'CURRENCY': self.currency_id.name,
+ 'OPERATION': 'SAL',
+ 'ECI': 9, # Recurring (from eCommerce)
+ 'ALIAS': self.payment_token_id.acquirer_ref,
+ 'RTIMEOUT': 30,
+ 'PARAMPLUS': urls.url_encode(param_plus),
+ 'EMAIL': self.partner_id.email or '',
+ 'CN': self.partner_id.name or '',
+ }
+
+ if request:
+ data['REMOTE_ADDR'] = request.httprequest.remote_addr
+
+ if kwargs.get('3d_secure'):
+ data.update({
+ 'FLAG3D': 'Y',
+ 'LANGUAGE': self.partner_id.lang or 'en_US',
+ })
+
+ for url in 'accept decline exception'.split():
+ key = '{0}_url'.format(url)
+ val = kwargs.pop(key, None)
+ if val:
+ key = '{0}URL'.format(url).upper()
+ data[key] = val
+
+ data['SHASIGN'] = self.acquirer_id._ogone_generate_shasign('in', data)
+
+ direct_order_url = 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % ('prod' if self.acquirer_id.state == 'enabled' else 'test')
+
+ logged_data = data.copy()
+ logged_data.pop('PSWD')
+ _logger.info("ogone_s2s_do_transaction: Sending values to URL %s, values:\n%s", direct_order_url, pformat(logged_data))
+ result = requests.post(direct_order_url, data=data).content
+
+ try:
+ tree = objectify.fromstring(result)
+ _logger.info('ogone_s2s_do_transaction: Values received:\n%s', etree.tostring(tree, pretty_print=True, encoding='utf-8'))
+ except etree.XMLSyntaxError:
+ # invalid response from ogone
+ _logger.exception('Invalid xml response from ogone')
+ _logger.info('ogone_s2s_do_transaction: Values received:\n%s', result)
+ raise
+
+ return self._ogone_s2s_validate_tree(tree)
+
+ def ogone_s2s_do_refund(self, **kwargs):
+ account = self.acquirer_id
+ reference = self.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%y%m%d_%H%M%S'), self.partner_id.id)
+
+ data = {
+ 'PSPID': account.ogone_pspid,
+ 'USERID': account.ogone_userid,
+ 'PSWD': account.ogone_password,
+ 'ORDERID': reference,
+ 'AMOUNT': int(self.amount * 100),
+ 'CURRENCY': self.currency_id.name,
+ 'OPERATION': 'RFS',
+ 'PAYID': self.acquirer_reference,
+ }
+ data['SHASIGN'] = self.acquirer_id._ogone_generate_shasign('in', data)
+
+ direct_order_url = 'https://secure.ogone.com/ncol/%s/maintenancedirect.asp' % ('prod' if self.acquirer_id.state == 'enabled' else 'test')
+
+ logged_data = data.copy()
+ logged_data.pop('PSWD')
+ _logger.info("ogone_s2s_do_refund: Sending values to URL %s, values:\n%s", direct_order_url, pformat(logged_data))
+ result = requests.post(direct_order_url, data=data).content
+
+ try:
+ tree = objectify.fromstring(result)
+ _logger.info('ogone_s2s_do_refund: Values received:\n%s', etree.tostring(tree, pretty_print=True, encoding='utf-8'))
+ except etree.XMLSyntaxError:
+ # invalid response from ogone
+ _logger.exception('Invalid xml response from ogone')
+ _logger.info('ogone_s2s_do_refund: Values received:\n%s', result)
+ raise
+
+ return self._ogone_s2s_validate_tree(tree)
+
+ def _ogone_s2s_validate(self):
+ tree = self._ogone_s2s_get_tx_status()
+ return self._ogone_s2s_validate_tree(tree)
+
+ def _ogone_s2s_validate_tree(self, tree, tries=2):
+ if self.state not in ['draft', 'pending']:
+ _logger.info('Ogone: trying to validate an already validated tx (ref %s)', self.reference)
+ return True
+
+ status = int(tree.get('STATUS') or 0)
+ if status in self._ogone_valid_tx_status:
+ self.write({
+ 'date': datetime.date.today().strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'acquirer_reference': tree.get('PAYID'),
+ })
+ if tree.get('ALIAS') and self.partner_id and \
+ (self.type == 'form_save' or self.acquirer_id.save_token == 'always')\
+ and not self.payment_token_id:
+ pm = self.env['payment.token'].create({
+ 'partner_id': self.partner_id.id,
+ 'acquirer_id': self.acquirer_id.id,
+ 'acquirer_ref': tree.get('ALIAS'),
+ 'name': tree.get('CARDNO'),
+ })
+ self.write({'payment_token_id': pm.id})
+ if self.payment_token_id:
+ self.payment_token_id.verified = True
+ self._set_transaction_done()
+ self.execute_callback()
+ # if this transaction is a validation one, then we refund the money we just withdrawn
+ if self.type == 'validation':
+ self.s2s_do_refund()
+ return True
+ elif status in self._ogone_cancel_tx_status:
+ self.write({'acquirer_reference': tree.get('PAYID')})
+ self._set_transaction_cancel()
+ elif status in self._ogone_pending_tx_status:
+ vals = {
+ 'acquirer_reference': tree.get('PAYID'),
+ }
+ if status == 46: # HTML 3DS
+ vals['html_3ds'] = ustr(base64.b64decode(tree.HTML_ANSWER.text))
+ self.write(vals)
+ self._set_transaction_pending()
+ elif status in self._ogone_wait_tx_status and tries > 0:
+ time.sleep(0.5)
+ self.write({'acquirer_reference': tree.get('PAYID')})
+ tree = self._ogone_s2s_get_tx_status()
+ return self._ogone_s2s_validate_tree(tree, tries - 1)
+ else:
+ error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
+ 'error_str': tree.get('NCERRORPLUS'),
+ 'error_code': tree.get('NCERROR'),
+ 'error_msg': ogone.OGONE_ERROR_MAP.get(tree.get('NCERROR')),
+ }
+ _logger.info(error)
+ self.write({
+ 'state_message': error,
+ 'acquirer_reference': tree.get('PAYID'),
+ })
+ self._set_transaction_cancel()
+ return False
+
+ def _ogone_s2s_get_tx_status(self):
+ account = self.acquirer_id
+ #reference = tx.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%Y%m%d_%H%M%S'), tx.partner_id.id)
+
+ data = {
+ 'PAYID': self.acquirer_reference,
+ 'PSPID': account.ogone_pspid,
+ 'USERID': account.ogone_userid,
+ 'PSWD': account.ogone_password,
+ }
+
+ query_direct_url = 'https://secure.ogone.com/ncol/%s/querydirect.asp' % ('prod' if self.acquirer_id.state == 'enabled' else 'test')
+
+ logged_data = data.copy()
+ logged_data.pop('PSWD')
+
+ _logger.info("_ogone_s2s_get_tx_status: Sending values to URL %s, values:\n%s", query_direct_url, pformat(logged_data))
+ result = requests.post(query_direct_url, data=data).content
+
+ try:
+ tree = objectify.fromstring(result)
+ _logger.info('_ogone_s2s_get_tx_status: Values received:\n%s', etree.tostring(tree, pretty_print=True, encoding='utf-8'))
+ except etree.XMLSyntaxError:
+ # invalid response from ogone
+ _logger.exception('Invalid xml response from ogone')
+ _logger.info('_ogone_s2s_get_tx_status: Values received:\n%s', result)
+ raise
+
+ return tree
+
+
+class PaymentToken(models.Model):
+ _inherit = 'payment.token'
+
+ def ogone_create(self, values):
+ if values.get('cc_number'):
+ # create a alias via batch
+ values['cc_number'] = values['cc_number'].replace(' ', '')
+ acquirer = self.env['payment.acquirer'].browse(values['acquirer_id'])
+ alias = 'ODOO-NEW-ALIAS-%s' % time.time()
+
+ expiry = str(values['cc_expiry'][:2]) + str(values['cc_expiry'][-2:])
+ line = 'ADDALIAS;%(alias)s;%(cc_holder_name)s;%(cc_number)s;%(expiry)s;%(cc_brand)s;%(pspid)s'
+ line = line % dict(values, alias=alias, expiry=expiry, pspid=acquirer.ogone_pspid)
+
+ data = {
+ 'FILE_REFERENCE': alias,
+ 'TRANSACTION_CODE': 'MTR',
+ 'OPERATION': 'SAL',
+ 'NB_PAYMENTS': 1, # even if we do not actually have any payment, ogone want it to not be 0
+ 'FILE': normalize('NFKD', line).encode('ascii','ignore'), # Ogone Batch must be ASCII only
+ 'REPLY_TYPE': 'XML',
+ 'PSPID': acquirer.ogone_pspid,
+ 'USERID': acquirer.ogone_userid,
+ 'PSWD': acquirer.ogone_password,
+ 'PROCESS_MODE': 'CHECKANDPROCESS',
+ }
+
+ url = 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % ('prod' if acquirer.state == 'enabled' else 'test')
+ _logger.info("ogone_create: Creating new alias %s via url %s", alias, url)
+ result = requests.post(url, data=data).content
+
+ try:
+ tree = objectify.fromstring(result)
+ except etree.XMLSyntaxError:
+ _logger.exception('Invalid xml response from ogone')
+ return None
+
+ error_code = error_str = None
+ if hasattr(tree, 'PARAMS_ERROR'):
+ error_code = tree.NCERROR.text
+ error_str = 'PARAMS ERROR: %s' % (tree.PARAMS_ERROR.text or '',)
+ else:
+ node = tree.FORMAT_CHECK
+ error_node = getattr(node, 'FORMAT_CHECK_ERROR', None)
+ if error_node is not None:
+ error_code = error_node.NCERROR.text
+ error_str = 'CHECK ERROR: %s' % (error_node.ERROR.text or '',)
+
+ if error_code:
+ error_msg = tree.get(error_code)
+ error = '%s\n\n%s: %s' % (error_str, error_code, error_msg)
+ _logger.error(error)
+ raise Exception(error)
+
+ return {
+ 'acquirer_ref': alias,
+ 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name'])
+ }
+ return {}