diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py
index f93c913ed1d..42947d7a4b9 100644
--- a/addons/payment_ogone/controllers/main.py
+++ b/addons/payment_ogone/controllers/main.py
@@ -5,6 +5,7 @@ import werkzeug
from openerp import http, SUPERUSER_ID
from openerp.http import request
+from openerp.addons.payment.models.payment_acquirer import ValidationError
_logger = logging.getLogger(__name__)
@@ -27,3 +28,30 @@ class OgoneController(http.Controller):
cr, uid, context = request.cr, SUPERUSER_ID, request.context
request.registry['payment.transaction'].form_feedback(cr, uid, post, 'ogone', context=context)
return werkzeug.utils.redirect(post.pop('return_url', '/'))
+
+ @http.route(['/payment/ogone/s2s/create_json'], type='json', auth='public')
+ def ogone_s2s_create_json(self, **kwargs):
+ data = kwargs['params']
+ acquirer_id = int(data.get('acquirer_id'))
+ acquirer = request.env['payment.acquirer'].browse(acquirer_id)
+ new_id = acquirer.s2s_process(data)
+ return new_id
+
+ @http.route(['/payment/ogone/s2s/create'], type='http', auth='public')
+ def ogone_s2s_create(self, **post):
+ acquirer_id = int(post.get('acquirer_id'))
+ acquirer = request.env['payment.acquirer'].browse(acquirer_id)
+ acquirer.s2s_process(post)
+ return werkzeug.utils.redirect(post.get('return_url', '/'))
+
+ @http.route(['/payment/ogone/s2s/feedback'])
+ def feedback(self, **kwargs):
+ cr, uid, context = request.cr, SUPERUSER_ID, request.context
+ payment = request.registry.get('payment.transaction')
+ try:
+ tx = payment._ogone_form_get_tx_from_data(cr, uid, kwargs, context=context)
+ payment._ogone_s2s_validate(tx)
+ except ValidationError:
+ return 'ko'
+
+ return 'ok'
diff --git a/addons/payment_ogone/data/ogone.xml b/addons/payment_ogone/data/ogone.xml
index bb9c9a59eea..3f0f0844640 100644
--- a/addons/payment_ogone/data/ogone.xml
+++ b/addons/payment_ogone/data/ogone.xml
@@ -3,10 +3,11 @@
- Credit Card
+ Ogone
ogone
+
test
You will be redirected to the Ogone website after clicking on the payment button.
]]>
diff --git a/addons/payment_ogone/models/ogone.py b/addons/payment_ogone/models/ogone.py
index 4276d8d3dcd..f6610544113 100644
--- a/addons/payment_ogone/models/ogone.py
+++ b/addons/payment_ogone/models/ogone.py
@@ -1,5 +1,6 @@
# -*- coding: utf-'8' "-*-"
+import datetime
from hashlib import sha1
import logging
from lxml import etree, objectify
@@ -10,12 +11,14 @@ from urllib import urlencode
import urllib2
import urlparse
+from openerp import SUPERUSER_ID
from openerp.addons.payment.models.payment_acquirer import ValidationError
from openerp.addons.payment_ogone.controllers.main import OgoneController
from openerp.addons.payment_ogone.data import ogone
from openerp.osv import osv, fields
-from openerp.tools import float_round
+from openerp.tools import float_round, DEFAULT_SERVER_DATE_FORMAT
from openerp.tools.float_utils import float_compare
+from openerp.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@@ -48,6 +51,10 @@ class PaymentAcquirerOgone(osv.Model):
'ogone_password': fields.char('API User Password', required_if_provider='ogone'),
'ogone_shakey_in': fields.char('SHA Key IN', size=32, required_if_provider='ogone'),
'ogone_shakey_out': fields.char('SHA Key OUT', size=32, required_if_provider='ogone'),
+ 'ogone_alias_usage': fields.char('Alias Usage', 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 _ogone_generate_shasign(self, acquirer, inout, values):
@@ -161,6 +168,11 @@ class PaymentAcquirerOgone(osv.Model):
'EXCEPTIONURL': '%s' % urlparse.urljoin(base_url, OgoneController._exception_url),
'CANCELURL': '%s' % urlparse.urljoin(base_url, OgoneController._cancel_url),
}
+ if tx_values.get('type') == 'form_save':
+ temp_ogone_tx_values.update({
+ 'ALIAS': 'ODOO-NEW-ALIAS-%s' % time.time(), # something unique,
+ 'ALIASUSAGE': tx_values.get('alias_usage') or acquirer.ogone_alias_usage,
+ })
if ogone_tx_values.get('return_url'):
temp_ogone_tx_values['PARAMPLUS'] = 'return_url=%s' % ogone_tx_values.pop('return_url')
shasign = self._ogone_generate_shasign(acquirer, 'in', temp_ogone_tx_values)
@@ -172,6 +184,31 @@ class PaymentAcquirerOgone(osv.Model):
acquirer = self.browse(cr, uid, id, context=context)
return self._get_ogone_urls(cr, uid, acquirer.environment, context=context)['ogone_standard_order_url']
+ def ogone_s2s_form_validate(self, cr, uid, id, data, context=None):
+ error = dict()
+ error_message = []
+
+ 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, cr, uid, data, context=None):
+ 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.pool['payment.method'].create(cr, SUPERUSER_ID, values, context=context)
+ return pm_id
+
class PaymentTxOgone(osv.Model):
_inherit = 'payment.transaction'
@@ -181,21 +218,14 @@ class PaymentTxOgone(osv.Model):
_ogone_pending_tx_status = [46] # 3DS HTML response
_ogone_cancel_tx_status = [1]
- _columns = {
- 'ogone_3ds': fields.boolean('3DS Activated'),
- 'ogone_3ds_html': fields.html('3DS HTML'),
- 'ogone_complus': fields.char('Complus'),
- 'ogone_payid': fields.char('PayID', help='Payment ID, generated by Ogone')
- }
-
# --------------------------------------------------
# FORM RELATED METHODS
# --------------------------------------------------
def _ogone_form_get_tx_from_data(self, cr, uid, data, context=None):
""" Given a data dict coming from ogone, verify it and find the related
- transaction record. """
- reference, pay_id, shasign = data.get('orderID'), data.get('PAYID'), data.get('SHASIGN')
+ transaction record. Create a payment method 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 shashign (%s)') % (reference, pay_id, shasign)
_logger.info(error_msg)
@@ -220,6 +250,20 @@ class PaymentTxOgone(osv.Model):
_logger.info(error_msg)
raise ValidationError(error_msg)
+ # alias was created on ogone server, store it
+ if alias:
+ method_obj = self.pool['payment.method']
+ domain = [('acquirer_ref', '=', alias)]
+ cardholder = data.get('CN')
+ if not method_obj.search_count(cr, uid, domain, context=context):
+ _logger.info('Ogone: saving alias %s for partner %s' % (data.get('CARDNO'), tx.partner_id))
+ ref = method_obj.create(cr, uid, {'name': data.get('CARDNO') + (' - ' + cardholder if cardholder else ''),
+ 'partner_id': tx.partner_id.id,
+ 'acquirer_id': tx.acquirer_id.id,
+ 'acquirer_ref': alias
+ })
+ tx.partner_reference = alias
+
return tx
def _ogone_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
@@ -276,195 +320,189 @@ class PaymentTxOgone(osv.Model):
# --------------------------------------------------
# S2S RELATED METHODS
# --------------------------------------------------
-
- def ogone_s2s_create_alias(self, cr, uid, id, values, context=None):
- """ Create an alias at Ogone via batch.
-
- .. versionadded:: pre-v8 saas-3
- .. warning::
-
- Experimental code. You should not use it before OpenERP v8 official
- release.
- """
+ def ogone_s2s_do_transaction(self, cr, uid, id, context=None, **kwargs):
+ # TODO: create tx with s2s type
tx = self.browse(cr, uid, id, context=context)
- assert tx.type == 'server2server', 'Calling s2s dedicated method for a %s acquirer' % tx.type
- alias = 'OPENERP-%d-%d' % (tx.partner_id.id, tx.id)
+ account = tx.acquirer_id
+ reference = tx.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%y%m%d_%H%M%S'), tx.partner_id.id)
- expiry_date = '%s%s' % (values['expiry_date_mm'], values['expiry_date_yy'][2:])
- line = 'ADDALIAS;%(alias)s;%(holder_name)s;%(number)s;%(expiry_date)s;%(brand)s;%(pspid)s'
- line = line % dict(values, alias=alias, expiry_date=expiry_date, pspid=tx.acquirer_id.ogone_pspid)
-
- tx_data = {
- 'FILE_REFERENCE': 'OPENERP-NEW-ALIAS-%s' % time.time(), # something unique,
- 'TRANSACTION_CODE': 'ATR',
- 'OPERATION': 'SAL',
- 'NB_PAYMENTS': 1, # even if we do not actually have any payment, ogone want it to not be 0
- 'FILE': line,
- 'REPLY_TYPE': 'XML',
- 'PSPID': tx.acquirer_id.ogone_pspid,
- 'USERID': tx.acquirer_id.ogone_userid,
- 'PSWD': tx.acquirer_id.ogone_password,
- 'PROCESS_MODE': 'CHECKANDPROCESS',
- }
-
- # TODO: fix URL computation
- request = urllib2.Request(tx.acquirer_id.ogone_afu_agree_url, urlencode(tx_data))
- result = urllib2.urlopen(request).read()
-
- 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 = ogone.OGONE_ERROR_MAP.get(error_code)
- error = '%s\n\n%s: %s' % (error_str, error_code, error_msg)
- _logger.info(error)
- raise Exception(error) # TODO specific exception
-
- tx.write({'partner_reference': alias})
- return True
-
- def ogone_s2s_generate_values(self, cr, uid, id, custom_values, context=None):
- """ Generate valid Ogone values for a s2s tx.
-
- .. versionadded:: pre-v8 saas-3
- .. warning::
-
- Experimental code. You should not use it before OpenERP v8 official
- release.
- """
- tx = self.browse(cr, uid, id, context=context)
- tx_data = {
- 'PSPID': tx.acquirer_id.ogone_pspid,
- 'USERID': tx.acquirer_id.ogone_userid,
- 'PSWD': tx.acquirer_id.ogone_password,
- 'OrderID': tx.reference,
- 'amount': '%d' % int(float_round(tx.amount, 2) * 100), # tde check amount or str * 100 ?
+ data = {
+ 'PSPID': account.ogone_pspid,
+ 'USERID': account.ogone_userid,
+ 'PSWD': account.ogone_password,
+ 'ORDERID': reference,
+ 'AMOUNT': long(tx.amount * 100),
'CURRENCY': tx.currency_id.name,
- 'LANGUAGE': tx.partner_lang,
'OPERATION': 'SAL',
'ECI': 2, # Recurring (from MOTO)
'ALIAS': tx.partner_reference,
'RTIMEOUT': 30,
}
- if custom_values.get('ogone_cvc'):
- tx_data['CVC'] = custom_values.get('ogone_cvc')
- if custom_values.pop('ogone_3ds', None):
- tx_data.update({
- 'FLAG3D': 'Y', # YEAH!!
+
+ if kwargs.get('3d_secure'):
+ data.update({
+ 'FLAG3D': 'Y',
+ 'LANGUAGE': tx.partner_id.lang or 'en_US',
})
- if custom_values.get('ogone_complus'):
- tx_data['COMPLUS'] = custom_values.get('ogone_complus')
- if custom_values.get('ogone_accept_url'):
- pass
- shasign = self.pool['payment.acquirer']._ogone_generate_shasign(tx.acquirer_id, 'in', tx_data)
- tx_data['SHASIGN'] = shasign
- return tx_data
+ 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
- def ogone_s2s_feedback(self, cr, uid, data, context=None):
- """
- .. versionadded:: pre-v8 saas-3
- .. warning::
+ data['SHASIGN'] = self.pool['payment.acquirer']._ogone_generate_shasign(tx.acquirer_id, 'in', data)
- Experimental code. You should not use it before OpenERP v8 official
- release.
- """
- pass
+ direct_order_url = 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % (tx.acquirer_id.environment)
- def ogone_s2s_execute(self, cr, uid, id, values, context=None):
- """
- .. versionadded:: pre-v8 saas-3
- .. warning::
-
- Experimental code. You should not use it before OpenERP v8 official
- release.
- """
- tx = self.browse(cr, uid, id, context=context)
-
- tx_data = self.ogone_s2s_generate_values(cr, uid, id, values, context=context)
- _logger.info('Generated Ogone s2s data %s', pformat(tx_data)) # debug
-
- request = urllib2.Request(tx.acquirer_id.ogone_direct_order_url, urlencode(tx_data))
+ _logger.debug("Ogone data %s", pformat(data))
+ request = urllib2.Request(direct_order_url, urlencode(data))
result = urllib2.urlopen(request).read()
- _logger.info('Contacted Ogone direct order; result %s', result) # debug
+ _logger.debug('Ogone response = %s', result)
- tree = objectify.fromstring(result)
- payid = tree.get('PAYID')
+ try:
+ tree = objectify.fromstring(result)
+ except etree.XMLSyntaxError:
+ # invalid response from ogone
+ _logger.exception('Invalid xml response from ogone')
+ raise
- query_direct_data = dict(
- PSPID=tx.acquirer_id.ogone_pspid,
- USERID=tx.acquirer_id.ogone_userid,
- PSWD=tx.acquirer_id.ogone_password,
- ID=payid,
- )
- query_direct_url = 'https://secure.ogone.com/ncol/%s/querydirect.asp' % (tx.acquirer_id.environment,)
+ return self._ogone_s2s_validate_tree(tx, tree)
+
+ def _ogone_s2s_validate(self, tx):
+ tree = self._ogone_s2s_get_tx_status(tx)
+ return self.ogone_s2s_validate(tx, tree)
+
+ def _ogone_s2s_validate_tree(self, tx, tree, tries=2):
+ if tx.state not in ('draft', 'pending'):
+ return True # already validated
+
+ status = int(tree.get('STATUS', '0'))
+ if status in self._ogone_valid_tx_status:
+ tx.write({
+ 'state': 'done',
+ 'date_validate': datetime.date.today().strftime(DEFAULT_SERVER_DATE_FORMAT),
+ 'acquirer_reference': tree.get('PAYID'),
+ })
+ if tx.s2s_cb_eval:
+ safe_eval(tx.s2s_cb_eval, {'self': tx})
+ return True
+ elif status in self._ogone_cancel_tx_status:
+ tx.write({
+ 'state': 'cancel',
+ 'acquirer_reference': tree.get('PAYID'),
+ })
+ elif status in self._ogone_pending_tx_status:
+ tx.write({
+ 'state': 'pending',
+ 'acquirer_reference': tree.get('PAYID'),
+ 'html_3ds': str(tree.HTML_ANSWER).decode('base64')
+ })
+ elif status in self._ogone_wait_tx_status and tries > 0:
+ time.sleep(1500)
+ tx.write({'acquirer_reference': tree.get('PAYID')})
+ tree = self._ogone_s2s_get_tx_status(tx)
+ return self.ogone_s2s_validate(tx, tree, tries - 1)
+ else:
+ error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
+ 'error_str': tree.get('NCERROR'),
+ 'error_code': tree.get('NCERRORPLUS'),
+ 'error_msg': ogone.OGONE_ERROR_MAP.get(tree.get('NCERRORPLUS')),
+ }
+ _logger.info(error)
+ tx.write({
+ 'state': 'error',
+ 'state_message': error,
+ 'acquirer_reference': tree.get('PAYID'),
+ })
+ return False
+
+ def _ogone_s2s_get_tx_status(self, tx):
+ account = tx.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': tx.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' % (tx.acquirer_id.environment)
+
+ _logger.debug("Ogone data %s", pformat(data))
+ request = urllib2.Request(query_direct_url, urlencode(data))
+ result = urllib2.urlopen(request).read()
+ _logger.debug('Ogone response = %s', result)
+
+ try:
+ tree = objectify.fromstring(result)
+ except etree.XMLSyntaxError:
+ # invalid response from ogone
+ _logger.exception('Invalid xml response from ogone')
+ raise
+
+ return tree
+
+
+class PaymentMethod(osv.Model):
+ _inherit = 'payment.method'
+
+ def ogone_create(self, cr, uid, values, context=None):
+ if values.get('cc_number'):
+ # create a alias via batch
+ values['cc_number'] = values['cc_number'].replace(' ', '')
+ acquirer = self.pool['payment.acquirer'].browse(cr, uid, 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': 'ATR',
+ 'OPERATION': 'SAL',
+ 'NB_PAYMENTS': 1, # even if we do not actually have any payment, ogone want it to not be 0
+ 'FILE': line,
+ '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' % (acquirer.environment,)
+ request = urllib2.Request(url, urlencode(data))
+
+ result = urllib2.urlopen(request).read()
- tries = 2
- tx_done = False
- tx_status = False
- while not tx_done or tries > 0:
try:
tree = objectify.fromstring(result)
except etree.XMLSyntaxError:
- # invalid response from ogone
_logger.exception('Invalid xml response from ogone')
- raise
+ return None
- # see https://secure.ogone.com/ncol/paymentinfos1.asp
- VALID_TX = [5, 9]
- WAIT_TX = [41, 50, 51, 52, 55, 56, 91, 92, 99]
- PENDING_TX = [46] # 3DS HTML response
- # other status are errors...
-
- status = tree.get('STATUS')
- if status == '':
- status = 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:
- status = int(status)
+ 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 status in VALID_TX:
- tx_status = True
- tx_done = True
+ 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)
- elif status in PENDING_TX:
- html = str(tree.HTML_ANSWER)
- tx_data.update(ogone_3ds_html=html.decode('base64'))
- tx_status = False
- tx_done = True
-
- elif status in WAIT_TX:
- time.sleep(1500)
-
- request = urllib2.Request(query_direct_url, urlencode(query_direct_data))
- result = urllib2.urlopen(request).read()
- _logger.debug('Contacted Ogone query direct; result %s', result)
-
- else:
- error_code = tree.get('NCERROR')
- if not ogone.retryable(error_code):
- error_str = tree.get('NCERRORPLUS')
- error_msg = ogone.OGONE_ERROR_MAP.get(error_code)
- error = 'ERROR: %s\n\n%s: %s' % (error_str, error_code, error_msg)
- _logger.info(error)
- raise Exception(error)
-
- tries = tries - 1
-
- if not tx_done and tries == 0:
- raise Exception('Cannot get transaction status...')
-
- return tx_status
+ return {
+ 'acquirer_ref': alias,
+ 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name'])
+ }
+ return {}
diff --git a/addons/payment_ogone/views/ogone.xml b/addons/payment_ogone/views/ogone.xml
index 48480da23eb..a6dcd4f5624 100644
--- a/addons/payment_ogone/views/ogone.xml
+++ b/addons/payment_ogone/views/ogone.xml
@@ -1,6 +1,6 @@
-
+
+
+
+
+
diff --git a/addons/payment_ogone/views/payment_acquirer.xml b/addons/payment_ogone/views/payment_acquirer.xml
index f297eb9431a..83eebb91a01 100644
--- a/addons/payment_ogone/views/payment_acquirer.xml
+++ b/addons/payment_ogone/views/payment_acquirer.xml
@@ -14,29 +14,11 @@
+
-
- acquirer.transaction.form.ogone
- payment.transaction
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-