[IMP] payment: add support for server2server payments

This commit adds a new model, Payment Method, which stores
a reference to the payment acquirer's database and a reference to
a partner. Each payment module must have its own implementation.

The implementation is completely abstract but may not suit every
provider's way of implementing recurring payments.
This commit is contained in:
Damien Bouvy
2015-06-15 14:57:14 +02:00
committed by Christophe Simonis
parent 4647778758
commit dbd3efef1f
8 changed files with 157 additions and 55 deletions
+1
View File
@@ -11,6 +11,7 @@
'data': [
'views/payment_acquirer.xml',
'views/res_config_view.xml',
'views/res_partner_view.xml',
'security/ir.model.access.csv',
'security/payment_security.xml',
],
+1
View File
@@ -2,3 +2,4 @@
import payment_acquirer
import res_config
import res_partner
+74 -54
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-'8' "-*-"
import logging
from openerp.osv import osv, fields
@@ -64,13 +63,16 @@ class PaymentAcquirer(osv.Model):
'provider': fields.selection(_provider_selection, string='Provider', required=True),
'company_id': fields.many2one('res.company', 'Company', required=True),
'pre_msg': fields.html('Message', translate=True,
help='Message displayed to explain and help the payment process.'),
help='Message displayed to explain and help the payment process.'),
'post_msg': fields.html('Thanks Message', help='Message displayed after having done the payment process.'),
'validation': fields.selection(
[('manual', 'Manual'), ('automatic', 'Automatic')],
string='Process Method',
help='Static payments are payments like transfer, that require manual steps.'),
'view_template_id': fields.many2one('ir.ui.view', 'Form Button Template', required=True),
'registration_view_template_id': fields.many2one('ir.ui.view', 'S2S Form Template',
domain=[('type', '=', 'qweb')],
help="Template for method registration"),
'environment': fields.selection(
[('test', 'Test'), ('prod', 'Production')],
string='Environment', oldname='env'),
@@ -273,11 +275,41 @@ class PaymentAcquirer(osv.Model):
'partner_values': partner_values,
'tx_values': tx_values,
'context': context,
'type': tx_values.get('type') or 'form',
}
# because render accepts view ids but not qweb -> need to use the xml_id
return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context)
def _registration_render(self, cr, uid, id, partner_id, qweb_context=None, context=None):
acquirer = self.browse(cr, uid, id, context=context)
if qweb_context is None:
qweb_context = {}
qweb_context.update(id=id, partner_id=partner_id)
method_name = '_%s_registration_form_generate_values' % (acquirer.provider,)
if hasattr(self, method_name):
method = getattr(self, method_name)
qweb_context.update(method(cr, uid, id, qweb_context, context=context))
return self.pool['ir.ui.view'].render(cr, uid, acquirer.registration_view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context)
def s2s_process(self, cr, uid, id, data, context=None):
acquirer = self.browse(cr, uid, id, context=context)
cust_method_name = '%s_s2s_form_process' % (acquirer.provider)
if not self.s2s_validate(cr, uid, id, data, context=context):
return False
if hasattr(self, cust_method_name):
method = getattr(self, cust_method_name)
return method(cr, uid, data, context=context)
return True
def s2s_validate(self, cr, uid, id, data, context=None):
acquirer = self.browse(cr, uid, id, context=context)
cust_method_name = '%s_s2s_form_validate' % (acquirer.provider)
if hasattr(self, cust_method_name):
method = getattr(self, cust_method_name)
return method(cr, uid, id, data, context=context)
return True
def _wrap_payment_block(self, cr, uid, html_block, amount, currency_id, context=None):
payment_header = _('Pay safely online')
amount_str = float_repr(amount, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
@@ -342,7 +374,7 @@ class PaymentTransaction(osv.Model):
required=True,
),
'type': fields.selection(
[('server2server', 'Server To Server'), ('form', 'Form')],
[('server2server', 'Server To Server'), ('form', 'Form'), ('form_save', 'Form with credentials storage')],
string='Type', required=True),
'state': fields.selection(
[('draft', 'Draft'), ('pending', 'Pending'),
@@ -377,6 +409,11 @@ class PaymentTransaction(osv.Model):
'partner_phone': fields.char('Phone'),
'partner_reference': fields.char('Partner Reference',
help='Reference of the customer in the acquirer database'),
'html_3ds': fields.char('3D Secure HTML'),
's2s_cb_eval': fields.char('S2S Callback', help="""\
Will be safe_eval with `self` being the current transaction. i.e.:
self.env['my.model'].payment_validated(self)"""),
}
def _check_reference(self, cr, uid, ids, context=None):
@@ -467,62 +504,16 @@ class PaymentTransaction(osv.Model):
# --------------------------------------------------
# SERVER2SERVER RELATED METHODS
# --------------------------------------------------
def s2s_create(self, cr, uid, values, cc_values, context=None):
tx_id, tx_result = self.s2s_send(cr, uid, values, cc_values, context=context)
self.s2s_feedback(cr, uid, tx_id, tx_result, context=context)
return tx_id
def s2s_send(self, cr, uid, values, cc_values, context=None):
""" Create and send server-to-server transaction.
:param dict values: transaction values
:param dict cc_values: credit card values that are not stored into the
payment.transaction object. Acquirers should
handle receiving void or incorrect cc values.
Should contain :
- holder_name
- number
- cvc
- expiry_date
- brand
- expiry_date_yy
- expiry_date_mm
"""
tx_id, result = None, None
if values.get('acquirer_id'):
acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context)
custom_method_name = '_%s_s2s_send' % acquirer.provider
if hasattr(self, custom_method_name):
tx_id, result = getattr(self, custom_method_name)(cr, uid, values, cc_values, context=context)
if tx_id is None and result is None:
tx_id = super(PaymentTransaction, self).create(cr, uid, values, context=context)
return (tx_id, result)
def s2s_feedback(self, cr, uid, tx_id, data, context=None):
""" Handle the feedback of a server-to-server transaction. """
tx = self.browse(cr, uid, tx_id, context=context)
invalid_parameters = None
invalid_param_method_name = '_%s_s2s_get_invalid_parameters' % tx.acquirer_id.provider
if hasattr(self, invalid_param_method_name):
invalid_parameters = getattr(self, invalid_param_method_name)(cr, uid, tx, data, context=context)
if invalid_parameters:
_error_message = '%s: incorrect tx data:\n' % (tx.acquirer_id.name)
for item in invalid_parameters:
_error_message += '\t%s: received %s instead of %s\n' % (item[0], item[1], item[2])
_logger.error(_error_message)
return False
feedback_method_name = '_%s_s2s_validate' % tx.acquirer_id.provider
if hasattr(self, feedback_method_name):
return getattr(self, feedback_method_name)(cr, uid, tx, data, context=context)
return True
def s2s_do_transaction(self, cr, uid, id, context=None, **kwargs):
tx = self.browse(cr, uid, id, context=context)
custom_method_name = '%s_s2s_do_transaction' % tx.acquirer_id.provider
if hasattr(self, custom_method_name):
return getattr(self, custom_method_name)(cr, uid, id, context=context, **kwargs)
def s2s_get_tx_status(self, cr, uid, tx_id, context=None):
""" Get the tx status. """
@@ -533,3 +524,32 @@ class PaymentTransaction(osv.Model):
return getattr(self, invalid_param_method_name)(cr, uid, tx, context=context)
return True
class PaymentMethod(osv.Model):
_name = 'payment.method'
_order = 'partner_id'
_columns = {
'name': fields.char('Name', help='Name of the payment method'),
'partner_id': fields.many2one('res.partner', 'Partner', required=True),
'acquirer_id': fields.many2one('payment.acquirer', 'Acquirer Account', required=True),
'acquirer_ref': fields.char('Acquirer Ref.', required=True),
'active': fields.boolean('Active')
}
_defaults = {
'active': True
}
def create(self, cr, uid, values, context=None):
# call custom create method if defined (i.e. ogone_create for ogone)
if values.get('acquirer_id'):
acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context)
# custom create
custom_method_name = '%s_create' % acquirer.provider
if hasattr(self, custom_method_name):
values.update(getattr(self, custom_method_name)(cr, uid, values, context=context))
return super(PaymentMethod, self).create(cr, uid, values, context=context)
+9
View File
@@ -0,0 +1,9 @@
from openerp.osv import fields, osv
class res_partner(osv.osv):
_name = 'res.partner'
_inherit = 'res.partner'
_columns = {
'payment_method_ids': fields.one2many('payment.method', 'partner_id', 'Payment Methods'),
}
+5 -1
View File
@@ -4,4 +4,8 @@ payment_acquirer_user,payment.acquirer.user,model_payment_acquirer,base.group_us
payment_acquirer_system,payment.acquirer.system,model_payment_acquirer,base.group_system,1,1,1,1
payment_transaction_all,payment.transaction.all,model_payment_transaction,,1,0,0,0
payment_transaction_user,payment.transaction.user,model_payment_transaction,base.group_user,1,1,1,0
payment_transaction_system,payment.transaction.system,model_payment_transaction,base.group_system,1,1,1,1
payment_transaction_system,payment.transaction.system,model_payment_transaction,base.group_system,1,1,1,1
payment_method_all,payment.method.all,model_payment_method,,1,0,0,0
payment_method_user,payment.method.user,model_payment_method,base.group_user,1,1,1,0
payment_method_portal,payment.method.portal,model_payment_method,base.group_portal,1,1,1,0
payment_method_system,payment.method.system,model_payment_method,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 payment_acquirer_system payment.acquirer.system model_payment_acquirer base.group_system 1 1 1 1
5 payment_transaction_all payment.transaction.all model_payment_transaction 1 0 0 0
6 payment_transaction_user payment.transaction.user model_payment_transaction base.group_user 1 1 1 0
7 payment_transaction_system payment.transaction.system model_payment_transaction base.group_system 1 1 1 1
8 payment_method_all payment.method.all model_payment_method 1 0 0 0
9 payment_method_user payment.method.user model_payment_method base.group_user 1 1 1 0
10 payment_method_portal payment.method.portal model_payment_method base.group_portal 1 1 1 0
11 payment_method_system payment.method.system model_payment_method base.group_system 1 1 1 1
@@ -20,5 +20,18 @@
<field name="groups" eval="[(4, ref('base.group_sale_salesman'))]"/>
</record>
<record id="payment_method_user_rule" model="ir.rule">
<field name="name">Access own payment methods only</field>
<field name="model_id" ref="payment.model_payment_method"/>
<field name="domain_force">[('partner_id','=',user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user')), (4, ref('base.group_portal')), (4, ref('base.group_public'))]"/>
</record>
<record id="payment_method_salesman_rule" model="ir.rule">
<field name="name">Access every payment method</field>
<field name="model_id" ref="payment.model_payment_method"/>
<field name="groups" eval="[(4, ref('base.group_sale_salesman'))]"/>
</record>
</data>
</openerp>
+29
View File
@@ -58,6 +58,7 @@
</ul>
</div>
</div>
<field name="registration_view_template_id"/>
</group>
</sheet>
</form>
@@ -189,5 +190,33 @@
parent='root_payment_menu'
sequence='20' />
<!-- Payment Methods -->
<record model='ir.ui.view' id='payment_method_tree_view'>
<field name='name'>payment.method.tree</field>
<field name='model'>payment.method</field>
<field name='arch' type='xml'>
<tree string='Payment Methods' create='false' editable='bottom'>
<field name="name"/>
<field name="active"/>
<field name='partner_id' />
<field name='acquirer_id' readonly='1'/>
<field name='acquirer_ref' readonly='1'/>
</tree>
</field>
</record>
<record model='ir.actions.act_window' id='payment_method_act'>
<field name='name'>Saved Payment Data</field>
<field name='res_model'>payment.method</field>
<field name='view_type'>form</field>
<field name='view_mode'>tree</field>
</record>
<menuitem
action='payment_method_act'
id='payment_method_menu'
parent='root_payment_menu' />
</data>
</openerp>
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- Add creditcard to res.partner -->
<record id="view_partners_form_payment_defaultcreditcard" model="ir.ui.view">
<field name="name">view.res.partner.form.payment.defaultcreditcard</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr='//group[@name="purchase"]' position="after">
<group string="Payment" colspan="2">
<field name="payment_method_ids" groups="base.group_sale_salesman">
<tree editable="bottom" create="false">
<field name="name"/>
<field name="acquirer_id" readonly="1"/>
<field name="acquirer_ref" readonly="1"/>
</tree>
</field>
</group>
</xpath>
</field>
</record>
</data>
</openerp>