[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:
committed by
Christophe Simonis
parent
4647778758
commit
dbd3efef1f
@@ -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',
|
||||
],
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
import payment_acquirer
|
||||
import res_config
|
||||
import res_partner
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user