[WIP] payment_acquirer
bzr revid: chm@openerp.com-20131018111530-tjyhp3cu1qlabhij
This commit is contained in:
parent
36675c3663
commit
666387a274
|
@ -31,7 +31,6 @@
|
|||
'views/acquirer_view.xml',
|
||||
'payment_acquirer_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/ir.rule.xml',
|
||||
],
|
||||
'installable': True,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# -*- coding: utf-'8' "-*-"
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
|
@ -28,73 +28,49 @@ import logging
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class type(osv.osv):
|
||||
_name = 'payment.acquirer.type'
|
||||
|
||||
class Payment(osv.Model):
|
||||
_name = 'payment.transaction'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'id desc'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'create_date': fields.datetime('Creation Date', readonly=True, required=True),
|
||||
'partner_id': fields.related('creditcard_id', 'partner_id', type='many2one', relation='res.partner', readonly=True),
|
||||
'amount': fields.integer('Amount', required=True, help='in cents'),
|
||||
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
|
||||
'reference': fields.char('Order Reference'),
|
||||
'acquirer_ref': fields.char('Payment Acquirer Ref'),
|
||||
'state': fields.selection([("pending", "Pending"),("validated", "Validated"),("refused", "Refused")], 'Status', required=True),
|
||||
'res_model': fields.char('Object Model'),
|
||||
'res_id': fields.char('Object Id'),
|
||||
}
|
||||
def validate_payement(self, cr, uid, id, object, reference, currency, amount, context=None):
|
||||
"""
|
||||
return (payment, retry_time)
|
||||
payment: "validated" or "refused" or "pending"
|
||||
retry_time = False (don't retry validation) or int (seconds for retry validation)
|
||||
"""
|
||||
if isinstance(id, list):
|
||||
id = id[0]
|
||||
pay_type = self.browse(cr, uid, id, context=context)
|
||||
method = getattr(self, '_validate_payement_%s' % pay_type.name)
|
||||
return method(object, reference, currency, amount, context=context)
|
||||
|
||||
def _validate_payement_virement(self, object, reference, currency, amount, context=None):
|
||||
return ("pending", False)
|
||||
|
||||
|
||||
class type_paypal(osv.osv):
|
||||
_inherit = "payment.acquirer.type"
|
||||
|
||||
def _validate_payement_paypal(self, object, reference, currency, amount, context=None):
|
||||
parameters = {}
|
||||
parameters.update(
|
||||
cmd='_notify-validate',
|
||||
business=object.company_id.paypal_account,
|
||||
item_name="%s %s" % (object.company_id.name, reference),
|
||||
item_number=reference,
|
||||
amount=amount,
|
||||
currency_code=currency.name
|
||||
)
|
||||
paypal_url = "https://www.paypal.com/cgi-bin/webscr"
|
||||
paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||
response = urlparse.parse_qsl(requests.post(paypal_url, data=parameters))
|
||||
|
||||
# transaction's unique id
|
||||
# response["txn_id"]
|
||||
|
||||
if response["payment_status"] == "Voided":
|
||||
raise "Paypal authorization has been voided."
|
||||
elif response["payment_status"] in ("Completed", "Processed") and response["item_number"] == reference and response["mc_gross"] == amount:
|
||||
return ("validated", False)
|
||||
elif response["payment_status"] == "Expired":
|
||||
_logger.warn("Paypal Validate Payement status: Expired")
|
||||
return ("pending", 5)
|
||||
elif response["payment_status"] == "Pending":
|
||||
_logger.warn("Paypal Validate Payement status: Pending, reason: %s" % response["pending_reason"])
|
||||
return ("pending", 5)
|
||||
|
||||
# Canceled_Reversal, Denied, Failed, Refunded, Reversed
|
||||
return ("refused", False)
|
||||
|
||||
|
||||
class acquirer(osv.osv):
|
||||
class acquirer(osv.Model):
|
||||
_name = 'payment.acquirer'
|
||||
_description = 'Online Payment Acquirer'
|
||||
|
||||
def list_acquirers(self, cr, uid, context=None):
|
||||
return [("virement", "Virement")]
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'type_id': fields.many2one('payment.acquirer.type', required=True),
|
||||
'acquirer': fields.selection(lambda self, *a, **k: self.list_acquirers(*a, **k), 'Acquirer', required=True),
|
||||
'form_template_id': fields.many2one('ir.ui.view', required=True),
|
||||
'visible': fields.boolean('Visible', help="Make this payment acquirer available (Customer invoices, etc.)"),
|
||||
}
|
||||
|
||||
def _check_required_if_acquirer(self, cr, uid, ids, context=None):
|
||||
for this in self.browse(cr, uid, ids, context=context):
|
||||
if any(c for c, f in self._all_columns.items() if getattr(f.column, 'required_if_acquirer', None) == this.acquirer and not this[c]):
|
||||
return False
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_required_if_acquirer, 'Required fields not filled', ['required for this payment acquirer']),
|
||||
]
|
||||
|
||||
_defaults = {
|
||||
'visible': True,
|
||||
}
|
||||
|
@ -107,6 +83,9 @@ class acquirer(osv.osv):
|
|||
if not context:
|
||||
context = {}
|
||||
|
||||
if isinstance(id, list):
|
||||
id = id[0]
|
||||
|
||||
qweb_context = {}
|
||||
qweb_context.update(
|
||||
object=object,
|
||||
|
@ -126,11 +105,282 @@ class acquirer(osv.osv):
|
|||
|
||||
def validate_payement(self, cr, uid, id, object, reference, currency, amount, context=None):
|
||||
"""
|
||||
return (payment, retry_time)
|
||||
payment: "validated" or "refused" or "pending"
|
||||
return (status, retry_time, log)
|
||||
status: "validated" or "refused" or "pending"
|
||||
retry_time = False (don't retry validation) or int (seconds for retry validation)
|
||||
log = str
|
||||
"""
|
||||
|
||||
if isinstance(id, list):
|
||||
id = id[0]
|
||||
type_id = self.browse(cr, uid, id, context=context).type_id
|
||||
return type_id.validate_payement(object, reference, currency, amount, context=context)
|
||||
|
||||
pay = self.browse(cr, uid, id, context=context)
|
||||
method = getattr(self, '_validate_payement_%s' % pay.acquirer)
|
||||
status, retry_time, log = method(object, reference, currency, amount, context=context)
|
||||
|
||||
|
||||
# log transaction and payment
|
||||
if getattr(object, 'message_post'):
|
||||
object.message_post(cr, uid, False,
|
||||
body=log or "",
|
||||
subject="%s%s" % (status, retry_time and ": %s" % retry_time or ""),
|
||||
type='notification',
|
||||
context=context)
|
||||
|
||||
if status == "validated":
|
||||
_logger.info("Payment Validate for %s:%s" % (object._name, reference) )
|
||||
elif status == "pending":
|
||||
_logger.debug("Payment Pending for %s:%s. Reason: %s" % (object._name, reference, log) )
|
||||
else:
|
||||
_logger.error("Payment Refused for %s:%s. Reason: %s" % (object._name, reference, log) )
|
||||
|
||||
return (status, retry_time, log)
|
||||
|
||||
def _validate_payement_virement(self, object, reference, currency, amount, context=None):
|
||||
return ("pending", False, "")
|
||||
|
||||
def transaction_feedback(self, cr, uid, acquirer, context=None, **values):
|
||||
method = getattr(self, '_transaction_feedback_%s' % acquirer)
|
||||
return method(**values)
|
||||
|
||||
|
||||
# paypal
|
||||
|
||||
|
||||
class acquirer_paypal(osv.osv):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
def list_acquirers(self, cr, uid, context=None):
|
||||
l = super(acquirer_paypal, self).list_acquirers(cr, uid, context)
|
||||
l.append(('paypal', 'Paypal'))
|
||||
return l
|
||||
|
||||
def _validate_payement_paypal(self, object, reference, currency, amount, context=None):
|
||||
|
||||
parameters = {}
|
||||
parameters.update(
|
||||
cmd='_notify-validate',
|
||||
business=object.company_id.paypal_account,
|
||||
item_name="%s %s" % (object.company_id.name, reference),
|
||||
item_number=reference,
|
||||
amount=amount,
|
||||
currency_code=currency.name
|
||||
)
|
||||
paypal_url = "https://www.paypal.com/cgi-bin/webscr"
|
||||
paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||
response = urlparse.parse_qsl(requests.post(paypal_url, data=parameters))
|
||||
|
||||
# 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"] == reference and response["mc_gross"] == amount:
|
||||
status = "validated"
|
||||
elif response["payment_status"] in ("Expired", "Pending"):
|
||||
status = "pending"
|
||||
retry_time = 60
|
||||
|
||||
return (status, retry_time, "payment_status=%s&pending_reason=%s&reason_code=%s" % (
|
||||
response["payment_status"],
|
||||
response.get("pending_reason"),
|
||||
response.get("reason_code")))
|
||||
|
||||
def _transaction_feedback_paypal(self, **values):
|
||||
print values
|
||||
return True
|
||||
|
||||
|
||||
# ogone
|
||||
|
||||
|
||||
class acquirer_ogone(osv.Model):
|
||||
_name = 'payment.payment'
|
||||
|
||||
_columns = {
|
||||
'ogone_3ds': fields.dummy('3ds activated'),
|
||||
'ogone_3ds_html': fields.text(),
|
||||
'ogone_feedback_model': fields.char(),
|
||||
'ogone_feedback_eval': fields.char(),
|
||||
|
||||
# just for info
|
||||
'ogone_accepturl': fields.dummy(),
|
||||
'ogone_declineurl': fields.dummy(),
|
||||
'ogone_exceptionurl': fields.dummy(),
|
||||
|
||||
'ogone_complus': fields.dummy(),
|
||||
}
|
||||
|
||||
def _create_ogone(self, cr, uid, creditcard, values):
|
||||
currency = self.pool['res.currency'].browse(cr, uid, values['currency_id'])
|
||||
orderid = values.get('order_ref') or 'OE-ORDER-%s' % (time.time(),)
|
||||
account = creditcard.provider_account_id
|
||||
|
||||
_logger.debug("Values %s", pformat(values))
|
||||
|
||||
data = {
|
||||
'PSPID': account.ogone_pspid,
|
||||
'USERID': account.ogone_userid,
|
||||
'PSWD': account.ogone_password,
|
||||
'OrderID': orderid,
|
||||
'amount': values['amount'],
|
||||
'CURRENCY': currency.name,
|
||||
'OPERATION': 'SAL',
|
||||
'ECI': 2, # Recurring (from MOTO)
|
||||
'ALIAS': creditcard.provider_ref,
|
||||
'RTIMEOUT': 30,
|
||||
}
|
||||
if creditcard.cvc:
|
||||
data['CVC'] = creditcard.cvc
|
||||
|
||||
if values.pop('ogone_3ds', None):
|
||||
data.update({
|
||||
'FLAG3D': 'Y', # YEAH!!
|
||||
'LANGUAGE': creditcard.partner_id.lang or 'en_US',
|
||||
})
|
||||
|
||||
complus = values.get('ogone_complus')
|
||||
if complus:
|
||||
data['COMPLUS'] = complus
|
||||
|
||||
for url in 'accept decline exception'.split():
|
||||
key = 'ogone_{0}url'.format(url)
|
||||
val = values.pop(key, None)
|
||||
if val:
|
||||
key = '{0}URL'.format(url).upper()
|
||||
data[key] = val
|
||||
|
||||
_logger.debug("data %s", pformat(data))
|
||||
|
||||
data['SHASIGN'] = _generate_ogone_shasign(account, 'in', data)
|
||||
|
||||
direct_order_url = 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % (account.ogone_env,)
|
||||
|
||||
request = urllib2.Request(direct_order_url, urlencode(data))
|
||||
result = urllib2.urlopen(request).read()
|
||||
_logger.debug('result = %s', result)
|
||||
|
||||
try:
|
||||
tree = objectify.fromstring(result)
|
||||
except etree.XMLSyntaxError:
|
||||
# invalid response from ogone
|
||||
_logger.exception('Invalid xml response from ogone')
|
||||
raise
|
||||
|
||||
payid = tree.get('PAYID')
|
||||
|
||||
query_direct_data = dict(
|
||||
PSPID=account.ogone_pspid,
|
||||
USERID=account.ogone_userid,
|
||||
PSWD=account.ogone_password,
|
||||
ID=payid,
|
||||
)
|
||||
query_direct_url = 'https://secure.ogone.com/ncol/%s/querydirect.asp' % (account.ogone_env,)
|
||||
|
||||
def check_status(tree, tries=2):
|
||||
# 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:
|
||||
return True, (orderid, payid)
|
||||
|
||||
if status in PENDING_TX:
|
||||
html = str(tree.HTML_ANSWER)
|
||||
values.update(ogone_3ds_html=html.decode('base64'))
|
||||
return False, (orderid, payid)
|
||||
|
||||
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('result = %s', result)
|
||||
|
||||
try:
|
||||
tree = objectify.fromstring(result)
|
||||
except etree.XMLSyntaxError:
|
||||
# invalid response from ogone
|
||||
pass # retry...
|
||||
|
||||
if tries == 0:
|
||||
raise Exception('Cannot get transaction status...')
|
||||
return check_status(tree, tries - 1)
|
||||
else:
|
||||
error_code = tree.get('NCERROR')
|
||||
if tries and retryable(error_code):
|
||||
return check_status(tree, tries - 1)
|
||||
|
||||
error_str = tree.get('NCERRORPLUS')
|
||||
error_msg = 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)
|
||||
|
||||
return check_status(tree)
|
||||
|
||||
def _ogone_3ds_action(self, cr, uid, ids, context=None):
|
||||
assert len(ids) == 1
|
||||
p = self.browse(cr, uid, ids[0], context=context)
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'ogone_3ds',
|
||||
'params': {
|
||||
'payment_id': p.id,
|
||||
}
|
||||
}
|
||||
|
||||
def _check_sha_sign_out(self, cr, uid, data, context=None):
|
||||
"""Verify the SHA OUT signature of a ogone request.
|
||||
return the linked payment (which must be in pending mode)
|
||||
"""
|
||||
payid = data['PAYID']
|
||||
orderid = data['orderID']
|
||||
p_ids = self.search(cr, uid, [('provider_ref', '=', payid), ('order_ref', '=', orderid)], context=context)
|
||||
if len(p_ids) != 1:
|
||||
raise ValidationError('Unknow order')
|
||||
|
||||
payment = self.browse(cr, uid, p_ids[0], context=context)
|
||||
|
||||
# if payment.state != 'pending':
|
||||
# raise ValidationError('Invalid order')
|
||||
|
||||
shasign = data['SHASIGN'].upper()
|
||||
|
||||
if shasign != _generate_ogone_shasign(payment.creditcard_id.provider_account_id, 'out', data).upper():
|
||||
raise ValidationError('SHASIGN validation error')
|
||||
|
||||
return payment
|
||||
|
||||
def _ogone_transaction_feedback(self, cr, uid, data, context=None):
|
||||
payment = self._check_sha_sign_out(cr, uid, data, context)
|
||||
|
||||
status = int(data.get('STATUS') or '0')
|
||||
if status in [5, 9]:
|
||||
payment.write(dict(state='done'))
|
||||
if payment.ogone_feedback_model and payment.ogone_feedback_eval:
|
||||
model = self.pool.get(payment.ogone_feedback_model)
|
||||
if model:
|
||||
locals_ = {'cr': cr, 'uid': uid, 'model': model}
|
||||
safe_eval(payment.ogone_feedback_eval, locals_)
|
||||
return True
|
||||
else:
|
||||
error_code = data.get('NCERROR')
|
||||
error_str = data.get('NCERRORPLUS')
|
||||
error_msg = OGONE_ERROR_MAP.get(error_code)
|
||||
error = 'ERROR: %s\n\n%s: %s' % (error_str, error_code, error_msg)
|
||||
_logger.info(error)
|
||||
payment.write({'state': 'error', 'error': error})
|
||||
return False
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="0">
|
||||
<record id="paypal" model="payment.acquirer.type">
|
||||
<field name="name">paypal</field>
|
||||
</record>
|
||||
|
||||
<!-- Paypal -->
|
||||
|
||||
|
@ -22,17 +19,13 @@
|
|||
</template>
|
||||
|
||||
<record id="paypal_acquirer" model="payment.acquirer">
|
||||
<field name="name">Paypal</field>
|
||||
<field name="acquirer">paypal</field>
|
||||
<field name="form_template_id" ref="paypal_acquirer_view"/>
|
||||
<field name="type_id" ref="paypal"/>
|
||||
</record>
|
||||
|
||||
<!-- Virement -->
|
||||
|
||||
<record id="virement" model="payment.acquirer.type">
|
||||
<field name="name">virement</field>
|
||||
</record>
|
||||
|
||||
<template id="virement_acquirer_view">
|
||||
<div>
|
||||
<table>
|
||||
|
@ -44,7 +37,7 @@
|
|||
</template>
|
||||
|
||||
<record id="virement_acquirer" model="payment.acquirer">
|
||||
<field name="name">Virement</field>
|
||||
<field name="acquirer">virement</field>
|
||||
<field name="form_template_id" ref="virement_acquirer_view"/>
|
||||
<field name="type_id" ref="virement"/>
|
||||
</record>
|
||||
|
|
|
@ -642,6 +642,7 @@ class product_product(osv.osv):
|
|||
_table = "product_product"
|
||||
_inherits = {'product.template': 'product_tmpl_id'}
|
||||
_inherit = ['mail.thread']
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'default_code,name_template'
|
||||
_columns = {
|
||||
'qty_available': fields.function(_product_qty_available, type='float', string='Quantity On Hand'),
|
||||
|
|
|
@ -230,7 +230,7 @@
|
|||
<div class="col-sm-5">
|
||||
<ol class="breadcrumb">
|
||||
<li><a t-href="/shop">Products</a></li>
|
||||
<li t-if="search.get('category')"><a t-att-href="'/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
|
||||
<li t-if="search.get('category')"><a t-href="/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
|
||||
<li class="active"><span t-field="product.name"></span></li>
|
||||
</ol>
|
||||
</div><div class="col-sm-3">
|
||||
|
|
Loading…
Reference in New Issue