[ADD] payment_acquirer: first implementation of paypal and ogone acquirers.

paypal: only form is currently implemented. Method name and call order will change.
However the basic mechanism is impelmented. IPN is implemented (paypal contact us
back after the transaction with transaction details).

ogone: form and server2server are implemented. Method names and call order will
change. Based on chs / dle / ... implementation for ogone server2server.

basic addition of tests, still wip.

This commit is not a clean commit but a WIP commit. Please excuse me
but it's time to commit.

bzr revid: tde@openerp.com-20131107172236-chmg198305crgrud
This commit is contained in:
Thibault Delavallée 2013-11-07 18:22:36 +01:00
parent 3d0ac6643e
commit 7fe8885216
15 changed files with 1136 additions and 0 deletions

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import ogone

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="payment_acquirer_ogone" model="payment.acquirer">
<field name="name">ogone</field>
<field name="view_template_id" ref="ogone_acquirer_button"/>
<field name="env">test</field>
<field name='ogone_pspid'>pinky</field>
<field name='ogone_userid'>OOAPI</field>
<field name='ogone_password'>R!ci/6Nu8a</field>
<field name="ogone_shakey_in">tINY4Yv14789gUix1130</field>
<field name="ogone_shakey_out">tINYj885Tfvd4P471464</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="payment_acquirer_paypal" model="payment.acquirer">
<field name="name">paypal</field>
<field name="view_template_id" ref="paypal_acquirer_button"/>
<field name="env">test</field>
<field name="paypal_tx_url">https://www.sandbox.paypal.com/cgi-bin/webscr</field>
<field name="paypal_email_id">tde+paypal-facilitator@openerp.com</field>
<field name="paypal_username">'tde+paypal-facilitator_api1.openerp.com</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,397 @@
# -*- coding: utf-'8' "-*-"
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from hashlib import sha1
import logging
from lxml import etree, objectify
from pprint import pformat
# import requests
import time
from urllib import urlencode
import urllib2
# import urlparse
from openerp.addons.payment_acquirer.data import ogone
from openerp.addons.payment_acquirer.controllers.main import OgoneController
from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
from openerp.osv import osv, fields
from openerp.tools import float_round
_logger = logging.getLogger(__name__)
class PaymentAcquirerOgone(osv.Model):
_inherit = 'payment.acquirer'
def _get_ogone_urls(self, cr, uid, ids, name, args, context=None):
""" Ogone URLS:
- standard order: POST address for form-based
@TDETODO: complete me
"""
res = {}
for acquirer in self.browse(cr, uid, ids, context=context):
qualif = acquirer.env
res[acquirer.id] = {
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % qualif,
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % qualif,
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % qualif,
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % qualif,
}
return res
_columns = {
'ogone_pspid': fields.char(
'PSPID', required_if_provider='ogone'),
'ogone_userid': fields.char(
'API User id', required_if_provider='ogone'),
'ogone_password': fields.char(
'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'),
# store ogone contact URLs -> not necessary IMHO
'ogone_standard_order_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='Stanrd Order URL (form)'),
'ogone_direct_order_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='Direct Order URL (2)'),
'ogone_direct_query_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='Direct Query URL'),
'ogone_afu_agree_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='AFU Agree URL'),
}
def _ogone_generate_shasign(self, acquirer, 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' (openerp contacting ogone) or 'out' (ogone
contacting openerp). 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 acquirer.name == 'ogone'
key = getattr(acquirer, 'ogone_shakey_' + inout)
def filter_key(key):
if inout == 'in':
return True
else:
keys = "ORDERID CURRENCY AMOUNT PM ACCEPTANCE STATUS CARDNO ALIAS ED CN TRXDATE PAYID NCERROR BRAND ECI IP COMPLUS".split()
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))
shasign = sha1(sign).hexdigest()
return shasign
def ogone_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
if partner_values is None:
partner_values = {}
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
acquirer = self.browse(cr, uid, id, context=context)
partner = None
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
tx_values = {
'PSPID': acquirer.ogone_pspid,
'ORDERID': reference,
'AMOUNT': '%d' % int(float_round(amount, 2) * 100),
'CURRENCY': currency and currency.name or 'EUR',
'LANGUAGE': partner and partner.lang or partner_values.get('lang', ''),
'CN': partner and partner.name or partner_values.get('name', ''),
'EMAIL': partner and partner.email or partner_values.get('email', ''),
'OWNERZIP': partner and partner.zip or partner_values.get('zip', ''),
'OWNERADDRESS': partner and ' '.join((partner.street, partner.street2)).strip() or ' '.join((partner_values.get('street', ''), partner_values.get('street2', ''))).strip(),
'OWNERTOWN': partner and partner.city or partner_values.get('city', ''),
'OWNERCTY': partner and partner.country_id and partner.country_id.name or partner_values.get('country_name', ''),
'OWNERTELNO': partner and partner.phone or partner_values.get('phone', ''),
'ACCEPTURL': '%s/%s' % (base_url, OgoneController._accept_url),
'DECLINEURL': '%s/%s' % (base_url, OgoneController._decline_url),
'EXCEPTIONURL': '%s/%s' % (base_url, OgoneController._exception_url),
'CANCELURL': '%s/%s' % (base_url, OgoneController._cancel_url),
}
if tx_custom_values:
tx_values.update(tx_custom_values)
shasign = self._ogone_generate_shasign(acquirer, 'in', tx_values)
tx_values['SHASIGN'] = shasign
return tx_values
class PaymentTxOgone(osv.Model):
_inherit = 'payment.transaction'
_columns = {
'ogone_3ds': fields.dummy('3ds Activated'),
'ogone_3ds_html': fields.html('3DS HTML'),
'ogone_feedback_model': fields.char(),
'ogone_feedback_eval': fields.char(),
'ogone_complus': fields.char('Complus'),
}
# --------------------------------------------------
# FORM RELATED METHODS
# --------------------------------------------------
def _ogone_form_get_tx_from_shasign_out(self, cr, uid, data, context=None):
reference, pay_id, shasign = data.get('orderID'), data.get('PAYID'), data.get('SHASIGN')
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.error(error_msg)
raise ValidationError(error_msg)
# find tx -> @TDENOTE use paytid ?
tx_ids = self.pool['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context)
if not tx_ids or len(tx_ids) > 1:
error_msg = 'Ogone: received data for reference' % (reference)
if not tx_ids:
error_msg += '; no order found'
else:
error_msg += '; multiple order found'
_logger.error(error_msg)
raise ValidationError(error_msg)
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
# verify shasign
shasign_check = self.pool['payment.acquirer']._generate_ogone_shasign(tx.acquirer_id, '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.error(error_msg)
raise ValidationError(error_msg)
return tx
def ogone_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None):
tx = self.browse(cr, uid, id, context=context)
tx_data = {
'LANGUAGE': tx.partner_lang,
'CN': tx.partner_name,
'EMAIL': tx.partner_email,
'OWNERZIP': tx.partner_zip,
'OWNERADDRESS': tx.partner_address,
'OWNERTOWN': tx.partner_city,
'OWNERCTY': tx.partner_country_id and tx.partner_country_id.name or '',
'OWNERTELNO': tx.partner_phone,
}
if tx_custom_values:
tx_data.update(tx_custom_values)
return self.pool['payment.acquirer'].ogone_form_generate_values(
cr, uid, tx.acquirer_id.id,
tx.reference, tx.amount, tx.currency_id,
tx_custom_values=tx_data,
context=context
)
def ogone_form_feedback(self, cr, uid, data, context=None):
print '-- ogone: ogone_form_feedback'
tx = self._ogone_get_tx_from_shasign_out(cr, uid, data, context)
if not tx:
raise ValidationError('Ogone: feedback: tx not found')
status = int(data.get('STATUS', '0'))
print '\togone: statuts %s' % status
if status in [5, 9]:
tx.write({'state': 'done'})
return True
else:
error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
'error_str': data.get('NCERROR'),
'error_code': data.get('NCERRORPLUS'),
'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERRORPLUS')),
}
_logger.info(error)
tx.write({'state': 'error', 'state_message': error})
return False
# --------------------------------------------------
# S2S RELATED METHODS
# --------------------------------------------------
def ogone_s2s_create_alias(self, cr, uid, id, values, context=None):
""" Purpose: create an alias via batch """
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)
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',
}
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.error(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):
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 ?
'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 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
def ogone_s2s_execute(self, cr, uid, id, values, context=None):
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))
result = urllib2.urlopen(request).read()
_logger.info('Contacted Ogone direct order; result %s', result) # debug
tree = objectify.fromstring(result)
payid = tree.get('PAYID')
print 'payid', payid
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.env,)
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
# 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
else:
status = int(status)
if status in VALID_TX:
tx_status = True
tx_done = True
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, orderid, payid)

View File

@ -0,0 +1,241 @@
# -*- coding: utf-'8' "-*-"
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.payment_acquirer.controllers.main import PaypalController
from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
from openerp.osv import osv, fields
import logging
import requests
import urlparse
_logger = logging.getLogger(__name__)
class AcquirerPaypal(osv.Model):
_inherit = 'payment.acquirer'
_columns = {
'paypal_email_id': fields.char('Email ID', required_if_provider='paypal'),
'paypal_username': fields.char('Username', required_if_provider='paypal'),
'paypal_password': fields.char('Password'),
'paypal_signature': fields.char('Signature'),
'paypal_tx_url': fields.char('Transaction URL', required_if_provider='paypal'),
'paypal_use_dpn': fields.boolean('Use DPN'),
'paypal_use_ipn': fields.boolean('Use IPN'),
}
_defaults = {
'paypal_use_dpn': True,
'paypal_use_ipn': True,
'paypal_tx_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr',
}
def paypal_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
if partner_values is None:
partner_values = {}
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
acquirer = self.browse(cr, uid, id, context=context)
partner = None
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
tx_values = {
'cmd': '_xclick',
'business': acquirer.paypal_email_id,
'item_name': reference,
'item_number': reference,
'amount': amount,
'currency_code': currency and currency.name or 'EUR',
'address1': partner and ' '.join((partner.street, partner.street2)).strip() or ' '.join((partner_values.get('street', ''), partner_values.get('street2', ''))).strip(),
'city': partner and partner.city or partner_values.get('city', ''),
'country': partner and partner.country_id and partner.country_id.name or partner_values.get('country_name', ''),
'email': partner and partner.email or partner_values.get('email', ''),
'zip': partner and partner.zip or partner_values.get('zip', ''),
'first_name': partner and partner.name or partner_values.get('name', '').split()[-1:],
'last_name': partner and partner.name or partner_values.get('name', '').split()[:-1],
'return': '%s/%s' % (base_url, PaypalController._return_url),
'notify_url': '%s/%s' % (base_url, PaypalController._notify_url),
'cancel_return': '%s/%s' % (base_url, PaypalController._cancel_url),
}
if tx_custom_values:
tx_values.update(tx_custom_values)
return tx_values
class TxPaypal(osv.Model):
_inherit = 'payment.transaction'
_columns = {
'paypal_txn_id': fields.char('Transaction ID'),
}
# --------------------------------------------------
# FORM RELATED METHODS
# --------------------------------------------------
def paypal_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None):
tx = self.browse(cr, uid, id, context=context)
tx_data = {
'item_name': tx.name,
'first_name': tx.partner_name and tx.partner_name.split()[-1:],
'last_name': tx.partner_name and tx.partner_name.split()[:-1],
'email': tx.partner_email,
'zip': tx.partner_zip,
'address1': tx.partner_address,
'city': tx.partner_city,
'country': tx.partner_country_id and tx.partner_country_id.name or '',
}
if tx_custom_values:
tx_data.update(tx_custom_values)
return self.pool['payment.acquirer'].paypal_form_generate_values(
cr, uid, tx.acquirer_id.id,
tx.reference, tx.amount, tx.currency_id,
tx_custom_values=tx_data,
context=context
)
def validate_paypal_notification(self, cr, uid, url, context=None):
parsed_url = urlparse.urlparse(url)
query_parameters = parsed_url.query
parameters = urlparse.parse_qs(query_parameters)
invalid_parameters = []
# check tx effectively exists
txn_id = parameters.get('txn_id')[0]
tx_ids = self.search(cr, uid, [('paypal_txn_id', '=', txn_id)], context=context)
if not tx_ids:
_logger.warning(
'Received a notification from Paypal for a tx %s that does not exists in database.' %
txn_id
)
return False
elif len(tx_ids) > 1:
_logger.warning(
'Received a notification from Paypal for a tx %s that is duplicated in database.' %
txn_id
)
tx = self.browse(cr, uid, tx_ids[0], context=context)
if parameters.get('notify_version')[0] != '2.6':
_logger.warning(
'Received a notification from Paypal with version %s instead of 2.6. This could lead to issues when managing it.' %
parameters.get('notify_version')
)
if parameters.get('test_ipn')[0]:
_logger.warning(
'Received a notification from Paypal using sandbox'
),
# check transaction
if parameters.get('payment_status')[0] != 'Completed':
invalid_parameters.append(('payment_status', 'Completed'))
# check what is buyed
if parameters.get('mc_gross')[0] != tx.amount:
invalid_parameters.append(('mc_gross', tx.amount))
if parameters.get('mc_currency')[0] != tx.currency_id.name:
invalid_parameters.append(('mc_currency', tx.currency_id.name))
# if parameters.get('payment_fee') != tx.payment_fee:
# invalid_parameters.append(('payment_fee', tx.payment_fee))
# if parameters.get('quantity') != tx.quantity:
# invalid_parameters.append(('mc_currency', tx.quantity))
# if parameters.get('shipping') != tx.shipping:
# invalid_parameters.append(('shipping', tx.shipping))
# check buyer
# if parameters.get('payer_id') != tx.payer_id:
# invalid_parameters.append(('mc_gross', tx.payer_id))
# if parameters.get('payer_email') != tx.payer_email:
# invalid_parameters.append(('payer_email', tx.payer_email))
# check seller
# if parameters.get('receiver_email') != tx.receiver_email:
# invalid_parameters.append(('receiver_email', tx.receiver_email))
# if parameters.get('receiver_id') != tx.receiver_id:
# invalid_parameters.append(('receiver_id', tx.receiver_id))
if not invalid_parameters:
self.write(cr, uid, [tx.id], {
'payment_type': parameters.get('payment_type')[0],
'date_validate': parameters.get('payment_date', [fields.datetime.now()])[0],
'txn_type': parameters.get('express_checkout')[0],
}, context=context)
return tx.id
else:
_warn_message = 'The following transaction parameters are incorrect:\n'
for item in invalid_parameters:
_warn_message += '\t%s: received %s instead of %s\n' % (item[0], parameters.get(item[0])[0], item[1])
_logger.warning(_warn_message)
return False
def create_paypal_command(self, cr, uid, cmd, parameters):
parameters.update(cmd=cmd)
return requests.post(self._paypal_url, data=parameters)
def _validate_paypal(self, cr, uid, ids, context=None):
res = []
for tx in self.browse(cr, uid, ids, context=context):
parameters = {}
parameters.update(
cmd='_notify-validate',
business='tdelavallee-facilitator@gmail.com',
item_name="%s %s" % ('cacapoutch', tx.reference),
item_number=tx.reference,
amount=tx.amount,
currency_code=tx.currency_id.name,
)
print '\t', parameters
# paypal_url = "https://www.paypal.com/cgi-bin/webscr"
paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr"
resp = requests.post(paypal_url, data=parameters)
print resp
print resp.url
print resp.text
response = urlparse.parse_qsl(resp)
print response
# transaction's unique id
# response["txn_id"]
# "Failed", "Reversed", "Refunded", "Canceled_Reversal", "Denied"
status = "refused"
retry_time = False
if response["payment_status"] == "Voided":
status = "refused"
elif response["payment_status"] in ("Completed", "Processed") and response["item_number"] == tx.reference and response["mc_gross"] == tx.amount:
status = "validated"
elif response["payment_status"] in ("Expired", "Pending"):
status = "pending"
retry_time = 60
res.append(
(status, retry_time, "payment_status=%s&pending_reason=%s&reason_code=%s" % (
response["payment_status"],
response.get("pending_reason"),
response.get("reason_code")))
)
return response
def _transaction_feedback_paypal(self, **values):
print values
return True

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_payment_acquirer
checks = [
test_payment_acquirer,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 20123TODAY OpenERP S.A. <http://www.openerp.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests import common
class TestPaymentAcquirer(common.TransactionCase):
def setUp(self):
super(TestPaymentAcquirer, self).setUp()
self.payment_acquirer = self.registry('payment.acquirer')
self.payment_transaction = self.registry('payment.transaction')
self.currency_euro_id = self.registry('res.currency').search(
self.cr, self.uid, [('name', '=', 'EUR')], limit=1)[0]
self.currency_euro = self.registry('res.currency').browse(
self.cr, self.uid, self.currency_euro_id)
country_belgium_id = self.registry('res.country').search(
self.cr, self.uid, [('code', 'like', 'BE')], limit=1)[0]
# dict partner values
self.buyer_values = {
'name': 'Norbert Buyer',
'lang': 'en_US',
'email': 'norbert.buyer@example.com',
'street': 'Huge Street',
'street2': '2/543',
'phone': '0032 12 34 56 78',
'city': 'Sin City',
'zip': '1000',
'country_id': country_belgium_id,
'country_name': 'Belgium',
}
# test partner
self.buyer_id = self.registry('res.partner').create(
self.cr, self.uid, {
'name': 'Norbert Buyer',
'lang': 'en_US',
'email': 'norbert.buyer@example.com',
'street': 'Huge Street',
'street2': '2/543',
'phone': '0032 12 34 56 78',
'city': 'Sin City',
'zip': '1000',
'country_id': country_belgium_id,
}
)

View File

@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.payment_acquirer.controllers.main import OgoneController
from openerp.addons.payment_acquirer.tests.common import TestPaymentAcquirer
from openerp.osv.orm import except_orm
from lxml import objectify
# import requests
# import urlparse
class BasicPayment(TestPaymentAcquirer):
def test_10_paypal_basic(self):
pass
def test_11_paypal_form(self):
cr, uid = self.cr, self.uid
context = {}
base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
# ogone_url = self.payment_acquirer._get_ogone_urls(cr, uid, [ogone_id], None, None)[ogone_id]['ogone_standard_order_url']
model, paypal_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer', 'paypal_acquirer_button')
# forgot some mandatory fields: should crash
with self.assertRaises(except_orm):
paypal_id = self.payment_acquirer.create(
cr, uid, {
'name': 'paypal',
'env': 'test',
'view_template_id': paypal_view_id,
'paypal_email_id': 'tde+paypal-facilitator@openerp.com',
}, context=context
)
# tde+buyer@openerp.com
# create a new paypal account
paypal_id = self.payment_acquirer.create(
cr, uid, {
'name': 'paypal',
'env': 'test',
'view_template_id': paypal_view_id,
'paypal_email_id': 'tde+paypal-facilitator@openerp.com',
'paypal_username': 'tde+paypal-facilitator_api1.openerp.com',
}, context=context
)
# verify acquirer data
paypal = self.payment_acquirer.browse(cr, uid, paypal_id, context)
self.assertEqual(paypal.env, 'test', 'test without test env')
# render the button
res = self.payment_acquirer.render(
cr, uid, paypal_id,
'test_ref0', 0.01, self.currency_euro,
partner_id=None,
partner_values=self.buyer_values,
context=context)
print res
# # check some basic paypal methods
# res = self.payment_transaction.validate_paypal_notification(
# cr, uid,
# 'http://localhost/payment?mc_gross=19.95&protection_eligibility=Eligible&address_status=confirmed&payer_id=LPLWNMTBWMFAY&tax=0.00&address_street=1+Main+St&payment_date=20%3A12%3A59+Jan+13%2C+2009+PST&payment_status=Completed&charset=windows-1252&address_zip=95131&first_name=Test&mc_fee=0.88&address_country_code=US&address_name=Test+User&notify_version=2.6&custom=&payer_status=verified&address_country=United+States&address_city=San+Jose&quantity=1&verify_sign=AtkOfCXbDm2hu0ZELryHFjY-Vb7PAUvS6nMXgysbElEn9v-1XcmSoGtf&payer_email=gpmac_1231902590_per%40paypal.com&txn_id=61E67681CH3238416&payment_type=instant&last_name=User&address_state=CA&receiver_email=gpmac_1231902686_biz%40paypal.com&payment_fee=0.88&receiver_id=S8XGHLYDW9T3S&txn_type=express_checkout&item_name=&mc_currency=USD&item_number=&residence_country=US&test_ipn=1&handling_amount=0.00&transaction_subject=&payment_gross=19.95&shipping=0.00')
# self.assertEqual(res, False, 'payment: paypal validation on a txn_id that does not exists should return False')
# txn_id = self.payment_transaction.create(
# cr, uid, {
# 'amount': 0.01,
# 'acquirer_id': paypal_id,
# 'currency_id': currency_id,
# 'reference': 'test_reference',
# 'paypal_txn_id': '61E67681CH3238416',
# }, context=context
# )
# res = self.payment_transaction.validate_paypal_notification(
# cr, uid,
# 'http://localhost/payment?mc_gross=19.95&protection_eligibility=Eligible&address_status=confirmed&payer_id=LPLWNMTBWMFAY&tax=0.00&address_street=1+Main+St&payment_date=20%3A12%3A59+Jan+13%2C+2009+PST&payment_status=Completed&charset=windows-1252&address_zip=95131&first_name=Test&mc_fee=0.88&address_country_code=US&address_name=Test+User&notify_version=2.6&custom=&payer_status=verified&address_country=United+States&address_city=San+Jose&quantity=1&verify_sign=AtkOfCXbDm2hu0ZELryHFjY-Vb7PAUvS6nMXgysbElEn9v-1XcmSoGtf&payer_email=gpmac_1231902590_per%40paypal.com&txn_id=61E67681CH3238416&payment_type=instant&last_name=User&address_state=CA&receiver_email=gpmac_1231902686_biz%40paypal.com&payment_fee=0.88&receiver_id=S8XGHLYDW9T3S&txn_type=express_checkout&item_name=&mc_currency=USD&item_number=&residence_country=US&test_ipn=1&handling_amount=0.00&transaction_subject=&payment_gross=19.95&shipping=0.00')
# print res
# # user pays using Paypal
# resp = self.payment_transaction.create_paypal_command(
# cr, uid, cmd='_xclick', parameters={
# 'business': 'tdelavallee-facilitator@gmail.com',
# 'amount': 50,
# 'item_name': 'test_item',
# 'quantity': 1,
# 'currency_code': 'USD',
# 'return': 'http://www.example.com',
# })
# print resp
# print resp.url
# print resp.text
# self.payment_transaction.validate(cr, uid, [tx_id], context=context)
def test_20_ogone_form(self):
cr, uid = self.cr, self.uid
context = {}
base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
# ogone_url = self.payment_acquirer._get_ogone_urls(cr, uid, [ogone_id], None, None)[ogone_id]['ogone_standard_order_url']
model, ogone_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer', 'ogone_acquirer_button')
# create a new ogone account
ogone_id = self.payment_acquirer.create(
cr, uid, {
'name': 'ogone',
'env': 'test',
'view_template_id': ogone_view_id,
'ogone_pspid': 'pinky',
'ogone_userid': 'OOAPI',
'ogone_password': 'R!ci/6Nu8a',
'ogone_shakey_in': 'tINY4Yv14789gUix1130',
'ogone_shakey_out': 'tINYj885Tfvd4P471464',
}, context=context
)
# verify acquirer data
ogone = self.payment_acquirer.browse(cr, uid, ogone_id, context)
self.assertEqual(ogone.env, 'test', 'test without test env')
form_values = {
'PSPID': 'pinky',
'ORDERID': 'test_ref0',
'AMOUNT': '1',
'CURRENCY': 'EUR',
'LANGUAGE': 'en_US',
'CN': 'Norbert Buyer',
'EMAIL': 'norbert.buyer@example.com',
'OWNERZIP': '1000',
'OWNERADDRESS': 'Huge Street 2/543',
'OWNERCTY': 'Belgium',
'OWNERTOWN': 'Sin City',
'OWNERTELNO': '0032 12 34 56 78',
'SHASIGN': 'ea74bb42d4f25746279cdd44a737aaddc71e7f9f',
'ACCEPTURL': '%s/%s' % (base_url, OgoneController._accept_url),
'DECLINEURL': '%s/%s' % (base_url, OgoneController._decline_url),
'EXCEPTIONURL': '%s/%s' % (base_url, OgoneController._exception_url),
'CANCELURL': '%s/%s' % (base_url, OgoneController._cancel_url),
}
# render the button
res = self.payment_acquirer.render(
cr, uid, ogone_id,
'test_ref0', 0.01, self.currency_euro,
partner_id=None,
partner_values=self.buyer_values,
context=context)
# check form result
tree = objectify.fromstring(res)
self.assertEqual(tree.get('action'), 'https://secure.ogone.com/ncol/test/orderstandard.asp', 'ogone: wrong form POST url')
for form_input in tree.input:
if form_input.get('name') in ['submit']:
continue
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'ogone: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')])
)
# resp = requests.post(tree.get('action'), data=form_values)
# create a new draft tx
tx_id = self.payment_transaction.create(
cr, uid, {
'amount': 0.01,
'acquirer_id': ogone_id,
'currency_id': self.currency_euro_id,
'reference': 'test_ref0',
'partner_id': self.buyer_id,
}, context=context
)
# render the button
res = self.payment_acquirer.render(
cr, uid, ogone_id,
'test_ref0', 0.01, self.currency_euro,
tx_id=tx_id,
partner_id=None,
partner_values=self.buyer_values,
context=context)
# check form result
tree = objectify.fromstring(res)
self.assertEqual(tree.get('action'), 'https://secure.ogone.com/ncol/test/orderstandard.asp', 'ogone: wrong form POST url')
for form_input in tree.input:
if form_input.get('name') in ['submit']:
continue
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'ogone: 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')])
)
def test_21_ogone_s2s(self):
cr, uid = self.cr, self.uid
context = {}
model, ogone_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer', 'ogone_acquirer_button')
# create a new ogone account
ogone_id = self.payment_acquirer.create(
cr, uid, {
'name': 'ogone',
'env': 'test',
'view_template_id': ogone_view_id,
'ogone_pspid': 'pinky',
'ogone_userid': 'OOAPI',
'ogone_password': 'R!ci/6Nu8a',
'ogone_shakey_in': 'tINY4Yv14789gUix1130',
'ogone_shakey_out': 'tINYj885Tfvd4P471464',
}, context=context
)
# create a new draft tx
tx_id = self.payment_transaction.create(
cr, uid, {
'amount': 0.01,
'acquirer_id': ogone_id,
'currency_id': self.currency_euro_id,
'reference': 'test_ogone_0',
'partner_id': self.buyer_id,
'type': 'server2server',
}, context=context
)
res = self.payment_transaction.ogone_s2s_create_alias(
cr, uid, tx_id, {
'expiry_date_mm': '01',
'expiry_date_yy': '2015',
'holder_name': 'Norbert Poilu',
'number': '4000000000000002',
'brand': 'VISA',
}, context=context)
print res
res = self.payment_transaction.ogone_s2s_execute(cr, uid, tx_id, {}, context=context)
print res
# {
# 'orderID': u'reference',
# 'STATUS': u'9',
# 'CARDNO': u'XXXXXXXXXXXX0002',
# 'PAYID': u'24998692',
# 'CN': u'Norbert Poilu',
# 'NCERROR': u'0',
# 'TRXDATE': u'11/05/13',
# 'IP': u'85.201.233.72',
# 'BRAND': u'VISA',
# 'ACCEPTANCE': u'test123',
# 'currency': u'EUR',
# 'amount': u'1.95',
# 'SHASIGN': u'EFDC56879EF7DE72CCF4B397076B5C9A844CB0FA',
# 'ED': u'0314',
# 'PM': u'CreditCard'
# }

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<template id="ogone_acquirer_button">
<form t-if="acquirer" t-att-action="acquirer.ogone_standard_order_url" method="post" target="_self">
<!-- seller -->
<input type='hidden' name='PSPID' t-att-value='tx_values["PSPID"]'/>
<input type='hidden' name='ORDERID' t-att-value='tx_values["ORDERID"]'/>
<!-- cart -->
<input type='hidden' name='AMOUNT' t-att-value='tx_values["AMOUNT"]'/>
<input type='hidden' name='CURRENCY' t-att-value='tx_values["CURRENCY"]'/>
<!-- buyer -->
<input type='hidden' name='LANGUAGE' t-att-value='tx_values["LANGUAGE"]'/>
<input type='hidden' name='CN' t-att-value='tx_values["CN"]'/>
<input type='hidden' name='EMAIL' t-att-value='tx_values["EMAIL"]'/>
<input type='hidden' name='OWNERZIP' t-att-value='tx_values["OWNERZIP"]'/>
<input type='hidden' name='OWNERADDRESS' t-att-value='tx_values["OWNERADDRESS"]'/>
<input type='hidden' name='OWNERCTY' t-att-value='tx_values["OWNERCTY"]'/>
<input type='hidden' name='OWNERTOWN' t-att-value='tx_values["OWNERTOWN"]'/>
<input type='hidden' name='OWNERTELNO' t-att-value='tx_values["OWNERTELNO"]'/>
<!-- before payment verification -->
<input type='hidden' name='SHASIGN' t-att-value='tx_values["SHASIGN"]'/>
<!-- look and print -->
<!-- <input type='hidden' name='TITLE' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='BGCOLOR' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='TXTCOLOR' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='TBLBGCOLOR' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='TBLTXTCOLOR' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='BUTTONBGCOLOR' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='BUTTONTXTCOLOR' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='LOGO' t-att-value='tx_dict["currency_name"]'/>
<input type='hidden' name='FONTTYPE' t-att-value='tx_dict["currency_name"]'/> -->
<!-- redirection -->
<input type='hidden' name='ACCEPTURL' t-att-value='tx_values["ACCEPTURL"]'/>
<input type='hidden' name='DECLINEURL' t-att-value='tx_values["DECLINEURL"]'/>
<input type='hidden' name='EXCEPTIONURL' t-att-value='tx_values["EXCEPTIONURL"]'/>
<input type='hidden' name='CANCELURL' t-att-value='tx_values["CANCELURL"]'/>
<input type="image" name="submit" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
</form>
</template>
</data>
</openerp>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<template id="paypal_acquirer_button">
<form t-if="acquirer.paypal_email_id" t-att-action="acquirer.paypal_tx_url" method="post" target="_self">
<input type="hidden" name="cmd" value="tx_values['cmd']"/>
<input type="hidden" name="business" t-att-value="tx_values['business']"/>
<input type="hidden" name="item_name" t-attf-value="tx_values['item_name']"/>
<input type="hidden" name="item_number" t-att-value="tx_values['item_number']"/>
<input type="hidden" name="amount" t-att-value="tx_values['amount']"/>
<input type="hidden" name="currency_code" t-att-value="tx_values['currency_code']"/>
<!-- partner / address data -->
<input type="hidden" name="address1" t-att-value="tx_values['address1']"/>
<input type="hidden" name="city" t-att-value="tx_values['city']"/>
<input type="hidden" name="country" t-att-value="tx_values['country']"/>
<input type="hidden" name="email" t-att-value="tx_values['email']"/>
<input type="hidden" name="first_name" t-att-value="tx_values['first_name']"/>
<input type="hidden" name="last_name" t-att-value="tx_values['last_name']"/>
<input type="hidden" name="zip" t-att-value="tx_values['zip']"/>
<!-- URLs -->
<input t-if="acquirer.paypal_use_dpn" type='hidden' name='return'
t-att-value="tx_values['return']"/>
<input t-if="acquirer.paypal_use_ipn" type='hidden' name='notify_url'
t-att-value="tx_values['notify_url']"/>
<input t-if="tx_values['cancel_return']" type="hidden" name="cancel_return"
t-att-value="tx_values['cancel_return']"/>
<!-- button -->
<input type="image" name="submit" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
</form>
</template>
</data>
</openerp>