[IMP] payment_acquirer: continuing base model.
Two main models : payment.acquirer and payment.transaction. payment.acquirer models the acquirer: paypal, ogone. Each specific acquirer will inherit from the class and add specific fields. payment.transaction models the transaction itself. It has basic fields for a transaction: date, partner, partner fields (to hold data at transaction time), reference, state and its message, amount, currency. Class methods are not finished and still quite WIP. payment.acqurier has a render method that is used to render its form. The form is the 'pay now' button with the specific sementic of each acquirer. This model is supposed to work on form-based and server-to-server implementation methods. bzr revid: tde@openerp.com-20131107171558-jrwrj3ll9kol6bav
This commit is contained in:
parent
420cdb13a2
commit
3d0ac6643e
|
@ -26,10 +26,14 @@
|
|||
'version': '0.1',
|
||||
'description': """Payment acquirer module, use to display payment method and validate the payments.""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['decimal_precision'],
|
||||
'depends': ['decimal_precision', 'mail'],
|
||||
'data': [
|
||||
'views/acquirer_view.xml',
|
||||
'views/payment_acquirer_views.xml',
|
||||
'views/ogone.xml',
|
||||
'views/paypal.xml',
|
||||
'data/payment_acquirer_data.xml',
|
||||
'data/ogone.xml',
|
||||
'data/paypal.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
|
|
|
@ -21,12 +21,76 @@
|
|||
|
||||
from openerp.addons.web import http
|
||||
from openerp.addons.web.http import request
|
||||
# from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
|
||||
from openerp.addons.website.models import website
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from urllib import urlencode
|
||||
|
||||
class website_project(http.Controller):
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@website.route(['/payment_acquirer/<path:acquirer>/'], type='http', auth="public")
|
||||
def project(self, acquirer=None, **post):
|
||||
obj = request.registry['payment.acquirer']
|
||||
return obj.transaction_feedback(request.cr, request.uid, acquirer, context=request.context, **post)
|
||||
|
||||
class PaypalController(http.Controller):
|
||||
_notify_url = '/payment/paypal/ipn/'
|
||||
_return_url = '/payment/paypal/dpn/'
|
||||
_cancel_url = '/payment/paypal/cancel/'
|
||||
# _ipn_url2 = '/payment/paypal/<int:acquirer_id>/ipn/'
|
||||
|
||||
@website.route('/payment/paypal/<int:acquirer_id>/ipn/', type='http', auth='admin')
|
||||
def paypal_ipn(self, **post):
|
||||
print 'Entering paypal_ipn with post', post
|
||||
# step 1: return an empty HTTP 200 response -> will be done at the end by returning ''
|
||||
|
||||
# step 2: POST the complete, unaltered message back to Paypal (preceded by cmd=_notify-validate), with same encoding
|
||||
paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr"
|
||||
post_url = '%s?cmd=_notify-validate&%s' % (paypal_url, urlencode(post))
|
||||
resp = requests.post(post_url)
|
||||
print '\tReceived response', resp, resp.text
|
||||
|
||||
# step 3: paypal send either VERIFIED or INVALID (single word)
|
||||
if resp.text == 'VERIFIED':
|
||||
# _logger.warning('')
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
# payment_transaction = request.registry['payment.transaction']
|
||||
# payment_transaction.validate()
|
||||
elif resp.text == 'INVALID':
|
||||
# _logger.warning('')
|
||||
pass
|
||||
else:
|
||||
# _logger.warning('') -> something went wrong
|
||||
pass
|
||||
|
||||
return ''
|
||||
|
||||
@website.route([
|
||||
'/payment/paypal/test/dpn',
|
||||
], type='http', auth="public")
|
||||
def paypal_test_success(self, **post):
|
||||
""" TODO
|
||||
"""
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
print post
|
||||
return ''
|
||||
|
||||
|
||||
class OgoneController(http.Controller):
|
||||
_accept_url = '/payment/ogone/test/accept'
|
||||
_decline_url = '/payment/ogone/test/decline'
|
||||
_exception_url = '/payment/ogone/test/exception'
|
||||
_cancel_url = '/payment/ogone/test/cancel'
|
||||
|
||||
@website.route([
|
||||
'/payment/ogone/feedback', '/payment/ogone/test/accept',
|
||||
'/payment/ogone/decline', '/payment/ogone/test/decline',
|
||||
'/payment/ogone/exception', '/payment/ogone/test/exception',
|
||||
'/payment/ogone/cancel', '/payment/ogone/test/cancel',
|
||||
], type='http', auth='admin')
|
||||
def feedback(self, **post):
|
||||
cr, uid, context = request.cr, request.uid, request.context
|
||||
Payment = request.registry['payment.transaction']
|
||||
print 'Entering ogone feedback with', post
|
||||
|
||||
res = Payment.tx_ogone_feedback(cr, uid, post, context)
|
||||
print res
|
||||
return ''
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<data noupdate="0">
|
||||
|
||||
<!-- Paypal -->
|
||||
|
||||
<!--
|
||||
<template id="paypal_acquirer_view">
|
||||
<form t-if="object.company_id.paypal_account" action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" target="_self">
|
||||
<input type="hidden" name="cmd" value="_xclick"/>
|
||||
|
@ -16,17 +16,36 @@
|
|||
<input t-if="cancel_url" type="hidden" name="cancel_url" t-att-value="cancel_url"/>
|
||||
<input type="image" name="submit" src="https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif"/>
|
||||
</form>
|
||||
</template>
|
||||
</template> -->
|
||||
|
||||
<record id="paypal_acquirer" model="payment.acquirer">
|
||||
<!-- <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_xclick">
|
||||
<input type="hidden" name="business" value="alfred@gmail.com">
|
||||
<input type="hidden" name="lc" value="US">
|
||||
<input type="hidden" name="item_name" value="object_name">
|
||||
<input type="hidden" name="item_number" value="object_id">
|
||||
<input type="hidden" name="amount" value="23.00">
|
||||
<input type="hidden" name="currency_code" value="USD">
|
||||
<input type="hidden" name="button_subtype" value="services">
|
||||
<input type="hidden" name="no_note" value="0">
|
||||
<input type="hidden" name="tax_rate" value="21.000">
|
||||
<input type="hidden" name="shipping" value="2.00">
|
||||
<input type="hidden" name="bn" value="PP-BuyNowBF:btn_buynowCC_LG.gif:NonHostedGuest">
|
||||
<input type="image" src="https://www.paypalobjects.com/fr_XC/i/btn/btn_buynowCC_LG.gif" border="0" name="submit" alt="PayPal - la solution de paiement en ligne la plus simple et la plus sécurisée !">
|
||||
<img alt="" border="0" src="https://www.paypalobjects.com/fr_XC/i/scr/pixel.gif" width="1" height="1">
|
||||
</form> -->
|
||||
|
||||
|
||||
|
||||
<!-- <record id="paypal_acquirer" model="payment.acquirer">
|
||||
<field name="acquirer">paypal</field>
|
||||
<field name="form_template_id" ref="paypal_acquirer_view"/>
|
||||
<field name="type_id" ref="paypal"/>
|
||||
</record>
|
||||
</record> -->
|
||||
|
||||
<!-- Virement -->
|
||||
|
||||
<template id="virement_acquirer_view">
|
||||
<!-- <template id="virement_acquirer_view">
|
||||
<div>
|
||||
<table>
|
||||
<tr><td>Numero de compte: </td><td></td></tr>
|
||||
|
@ -40,6 +59,6 @@
|
|||
<field name="acquirer">virement</field>
|
||||
<field name="form_template_id" ref="virement_acquirer_view"/>
|
||||
<field name="type_id" ref="virement"/>
|
||||
</record>
|
||||
</record> -->
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -20,3 +20,5 @@
|
|||
##############################################################################
|
||||
|
||||
import payment_acquirer
|
||||
import paypal
|
||||
import ogone
|
||||
|
|
|
@ -19,399 +19,223 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp
|
||||
from openerp.addons.payment_acquirer.models import ogone_errors
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools import float_repr
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
|
||||
from hashlib import sha1
|
||||
from lxml import etree, objectify
|
||||
import logging
|
||||
from pprint import pformat
|
||||
import requests
|
||||
import urlparse
|
||||
import time
|
||||
from urllib import urlencode
|
||||
import urllib2
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _generate_ogone_shasign(acc, inout, values):
|
||||
assert inout in ('in', 'out')
|
||||
assert acc.provider == 'ogone'
|
||||
key = acc['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
|
||||
|
||||
|
||||
class ValidationError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class Payment(osv.Model):
|
||||
class PaymentAcquirer(osv.Model):
|
||||
_name = 'payment.acquirer'
|
||||
_description = 'Online Payment Acquirer'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'view_template_id': fields.many2one('ir.ui.view', required=True),
|
||||
'env': fields.selection(
|
||||
[('test', 'Test'), ('prod', 'Production')],
|
||||
string='Environment'),
|
||||
'portal_published': fields.boolean('Visible in Portal',
|
||||
help="Make this payment acquirer available (Customer invoices, etc.)"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'portal_published': True,
|
||||
'env': 'test',
|
||||
}
|
||||
|
||||
def _check_required_if_provider(self, cr, uid, ids, context=None):
|
||||
for acquirer 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_provider', None) == acquirer.name and not acquirer[c]):
|
||||
return False
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_required_if_provider, 'Required fields not filled', ['required for this provider']),
|
||||
]
|
||||
|
||||
def render(self, cr, uid, id, reference, amount, currency, tx_id=None, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
|
||||
""" Renders the form template of the given acquirer as a qWeb template.
|
||||
All templates should handle:
|
||||
|
||||
- acquirer: the payment.acquirer browse record
|
||||
- user: the current user browse record
|
||||
- currency: currency browse record
|
||||
- amount: amount of the transaction
|
||||
- reference: reference of the transaction
|
||||
- partner: the current partner browse record, if any (not necessarily set)
|
||||
- partner_values: a dictionary of partner-related values
|
||||
- tx_values: a dictionary of transaction related values that depends on
|
||||
on the acquirer
|
||||
- context: OpenERP context dictionary
|
||||
|
||||
:param string reference: the transaction reference
|
||||
:param float amount: the amount the buyer has to pay
|
||||
:param res.currency browse record currency: currency
|
||||
:param int tx_id: id of a transaction; if set, bypasses all other given
|
||||
values and only render the already-stored transaction
|
||||
:param res.partner browse record partner_id: the buyer
|
||||
:param dict partner_values: a dictionary of values for the buyer (see above)
|
||||
:param dict tx_custom_values: a dictionary of values for the transction
|
||||
that is given to the acquirer-specific method
|
||||
generating the form values
|
||||
:param dict context: OpenERP context
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
partner = None
|
||||
if partner_id:
|
||||
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
|
||||
acquirer = self.browse(cr, uid, id, context=context)
|
||||
method_name = '%s_form_generate_values' % (acquirer.name)
|
||||
|
||||
tx_values = {}
|
||||
if tx_id and hasattr(self.pool['payment.transaction'], method_name):
|
||||
method = getattr(self.pool['payment.transaction'], method_name)
|
||||
tx_values = method(cr, uid, tx_id, tx_custom_values, context=context)
|
||||
elif hasattr(self, method_name):
|
||||
method = getattr(self, method_name)
|
||||
tx_values = method(cr, uid, id, reference, amount, currency, partner_id, partner_values, tx_custom_values, context=context)
|
||||
|
||||
qweb_context = {
|
||||
'acquirer': acquirer,
|
||||
'user': self.pool.get("res.users").browse(cr, uid, uid, context=context),
|
||||
'reference': reference,
|
||||
'amount': amount,
|
||||
'currency': currency,
|
||||
'partner': partner,
|
||||
'partner_values': partner_values,
|
||||
'tx_values': tx_values,
|
||||
'context': context,
|
||||
}
|
||||
return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.id, qweb_context, engine='ir.qweb', context=context)
|
||||
|
||||
|
||||
class PaymentTransaction(osv.Model):
|
||||
_name = 'payment.transaction'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'id desc'
|
||||
|
||||
_columns = {
|
||||
'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'),
|
||||
'date_create': fields.datetime('Creation Date', readonly=True, required=True),
|
||||
'date_validate': fields.datetime('Validation Date'),
|
||||
'acquirer_id': fields.many2one(
|
||||
'payment.acquirer', 'Acquirer',
|
||||
required=True,
|
||||
),
|
||||
'type': fields.selection(
|
||||
[('server2server', 'Server To Server'), ('form', 'Form')],
|
||||
string='Type', required=True),
|
||||
'state': fields.selection(
|
||||
[('draft', 'Draft'), ('pending', 'Pending'),
|
||||
('done', 'Done'), ('error', 'Error')],
|
||||
'Status', required=True,
|
||||
track_visiblity='onchange'),
|
||||
'state_message': fields.text('Message',
|
||||
help='Field used to store error and/or validation messages for information'),
|
||||
# payment
|
||||
'amount': fields.float('Amount', required=True,
|
||||
help='Amount in cents',
|
||||
track_visibility='always'),
|
||||
'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'),
|
||||
'reference': fields.char('Order Reference', required=True),
|
||||
'name': fields.char('Item name'),
|
||||
# duplicate partner / transaction data to store the values at transaction time
|
||||
'partner_id': fields.many2one('res.partner', 'Partner'),
|
||||
'partner_name': fields.char('Partner Name'),
|
||||
'partner_lang': fields.char('Lang'),
|
||||
'partner_email': fields.char('Email'),
|
||||
'partner_zip': fields.char('Zip'),
|
||||
'partner_address': fields.char('Address'),
|
||||
'partner_city': fields.char('City'),
|
||||
'partner_country_id': fields.many2one('res.country', 'Country'),
|
||||
'partner_phone': fields.char('Phone'),
|
||||
'partner_reference': fields.char('Buyer Reference'),
|
||||
}
|
||||
|
||||
|
||||
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),
|
||||
'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,
|
||||
'date_create': fields.datetime.now,
|
||||
'type': 'form',
|
||||
'state': 'draft',
|
||||
'partner_lang': 'en_US',
|
||||
}
|
||||
|
||||
def render(self, cr, uid, id, object, reference, currency, amount, cancel_url=None, return_url=None, context=None):
|
||||
""" Renders the form template of the given acquirer as a qWeb template """
|
||||
user = self.pool.get("res.users")
|
||||
precision = self.pool.get("decimal.precision").precision_get(cr, openerp.SUPERUSER_ID, 'Account')
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if not 'name' in values and 'reference' in values:
|
||||
values['name'] = values['reference']
|
||||
if values.get('partner_id'):
|
||||
values.update(self.on_change_partner_id(cr, uid, None, values.get('partner_id'), context=context)['values'])
|
||||
|
||||
if not context:
|
||||
context = {}
|
||||
if values.get('acquirer_id'):
|
||||
acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context)
|
||||
custom_method_name = '%s_create' % acquirer.name
|
||||
if hasattr(self, custom_method_name):
|
||||
values.update(getattr(self, custom_method_name)(cr, uid, values, context=context))
|
||||
|
||||
if isinstance(id, list):
|
||||
id = id[0]
|
||||
return super(PaymentTransaction, self).create(cr, uid, values, context=context)
|
||||
|
||||
qweb_context = {}
|
||||
qweb_context.update(
|
||||
object=object,
|
||||
reference=reference,
|
||||
currency=currency,
|
||||
amount=amount,
|
||||
amount_str=float_repr(amount, precision),
|
||||
user_id=user.browse(cr, uid, uid),
|
||||
context=context,
|
||||
cancel_url=cancel_url,
|
||||
return_url=return_url
|
||||
)
|
||||
def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
|
||||
if partner_id:
|
||||
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
|
||||
values = {
|
||||
'partner_name': partner.name,
|
||||
'partner_lang': partner.lang,
|
||||
'partner_email': partner.email,
|
||||
'partner_zip': partner.zip,
|
||||
'partner_address': ' '.join((partner.street, partner.street2)).strip(),
|
||||
'partner_city': partner.city,
|
||||
'partner_country_id': partner.country_id.id,
|
||||
'partner_phone': partner.phone,
|
||||
}
|
||||
else:
|
||||
values = {
|
||||
'partner_name': False,
|
||||
'partner_lang': 'en_US',
|
||||
'partner_email': False,
|
||||
'partner_zip': False,
|
||||
'partner_address': False,
|
||||
'partner_city': False,
|
||||
'partner_country_id': False,
|
||||
'partner_phone': False,
|
||||
}
|
||||
return {'values': values}
|
||||
|
||||
return self.browse(cr, uid, id, context=context) \
|
||||
.form_template_id.render(qweb_context, engine='ir.qweb', context=context) \
|
||||
.strip()
|
||||
|
||||
def validate_payement(self, cr, uid, id, object, reference, currency, amount, context=None):
|
||||
def validate(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
res = []
|
||||
for tx in self.browse(cr, uid, ids, context=context):
|
||||
method = getattr(self, '%s_validate')
|
||||
status, retry_time, log = method(cr, uid, [tx.id], context=context)[0]
|
||||
|
||||
if isinstance(id, list):
|
||||
id = id[0]
|
||||
|
||||
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,
|
||||
# log validation on transaction
|
||||
self.message_post(
|
||||
cr, uid, tx.id,
|
||||
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
|
||||
if status == "validated":
|
||||
_logger.info("Tx Validated for %s:%s" % (tx.acquirer_id.name, tx.reference))
|
||||
elif status == "pending":
|
||||
_logger.debug("Tx Pending for %s:%s. Reason: %s" % (tx.acquirer_id.name, tx.reference))
|
||||
else:
|
||||
status = int(status)
|
||||
_logger.error("Tx Refused for %s:%s. Reason: %s" % (tx.acquirer_id.name, tx.reference))
|
||||
|
||||
if status in VALID_TX:
|
||||
return True, (orderid, payid)
|
||||
res.append((status, retry_time, log))
|
||||
|
||||
if status in PENDING_TX:
|
||||
html = str(tree.HTML_ANSWER)
|
||||
values.update(ogone_3ds_html=html.decode('base64'))
|
||||
return False, (orderid, payid)
|
||||
return res
|
||||
|
||||
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 ogone_errors.retryable(error_code):
|
||||
return check_status(tree, tries - 1)
|
||||
|
||||
error_str = tree.get('NCERRORPLUS')
|
||||
error_msg = ogone_errors.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_errors.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
|
||||
def create_s2s(self, cr, uid, context=None):
|
||||
pass
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="acquirer_form" model="ir.ui.view">
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payment Acquirer" version="7.0">
|
||||
<group col="1">
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/><h1><field name="name"/></h1>
|
||||
<div class="oe_edit_only"><field name="visible"/><label for="visible"/></div>
|
||||
</div>
|
||||
<group string="Form Template">
|
||||
<div>
|
||||
<p>
|
||||
This is an HTML form template to submit a payment through this acquirer.
|
||||
The template will be rendered with qWeb, so it may use qWeb expressions.
|
||||
The qWeb evaluation context provides:
|
||||
<ul>
|
||||
<li>reference: the reference number of the document to pay</li>
|
||||
<li>currency: the currency record in which the document is issued (e.g. currency.name could be EUR)</li>
|
||||
<li>amount: the total amount to pay, as a float</li>
|
||||
<li>amount_str: the total amount to pay, as a string with the account precision</li>
|
||||
<li>object: the browse record on which the payment form is rendered (usually an invoice or sales order record)</li>
|
||||
<li>user_id: the current user browse record</li>
|
||||
<li>context: the current context dictionary</li>
|
||||
</ul>
|
||||
If the template renders to an empty result in a certain context it will be ignored, as if it was inactive.
|
||||
</p>
|
||||
</div>
|
||||
<field name="form_template" nolabel="1" colspan="2"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="acquirer_list" model="ir.ui.view">
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payment Acquirers">
|
||||
<field name="name"/>
|
||||
<field name="visible"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="acquirer_search" model="ir.ui.view">
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Acquirers list action is visible in Invoicing Settings -->
|
||||
<record model="ir.actions.act_window" id="action_acquirer_list">
|
||||
<field name="name">Payment Acquirers</field>
|
||||
<field name="res_model">payment.acquirer</field>
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<menuitem
|
||||
name='Payments'
|
||||
id='root_payment_menu'
|
||||
parent='base.menu_administration'/>
|
||||
|
||||
<record id="acquirer_form" model="ir.ui.view">
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payment Acquirer" version="7.0">
|
||||
<group col="1">
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/><h1><field name="name"/></h1>
|
||||
<div class="oe_edit_only"><field name="portal_published"/><label for="portal_published"/></div>
|
||||
<field name="env"/>
|
||||
</div>
|
||||
<group string="Form Template">
|
||||
<div>
|
||||
<p>
|
||||
This is an HTML form template to submit a payment through this acquirer.
|
||||
The template will be rendered with qWeb, so it may use qWeb expressions.
|
||||
The qWeb evaluation context provides:
|
||||
<ul>
|
||||
<li>reference: the reference number of the document to pay</li>
|
||||
<li>currency: the currency record in which the document is issued (e.g. currency.name could be EUR)</li>
|
||||
<li>amount: the total amount to pay, as a float</li>
|
||||
<li>amount_str: the total amount to pay, as a string with the account precision</li>
|
||||
<li>object: the browse record on which the payment form is rendered (usually an invoice or sales order record)</li>
|
||||
<li>user_id: the current user browse record</li>
|
||||
<li>context: the current context dictionary</li>
|
||||
</ul>
|
||||
If the template renders to an empty result in a certain context it will be ignored, as if it was inactive.
|
||||
</p>
|
||||
</div>
|
||||
<field name="view_template_id" nolabel="1" colspan="2"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="acquirer_list" model="ir.ui.view">
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payment Acquirers">
|
||||
<field name="name"/>
|
||||
<field name="portal_published"/>
|
||||
<field name="env"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="acquirer_search" model="ir.ui.view">
|
||||
<field name="model">payment.acquirer</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_payment_acquirer">
|
||||
<field name="name">Payment Acquirers</field>
|
||||
<field name="res_model">payment.acquirer</field>
|
||||
<field name='view_type'>form</field>
|
||||
<field name='view_mode'>tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
action='action_payment_acquirer'
|
||||
id='payment_acquirer_menu'
|
||||
parent='root_payment_menu'
|
||||
sequence='10' />
|
||||
|
||||
<record model="ir.ui.view" id="transaction_form">
|
||||
<field name="model">payment.transaction</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payment Transactions" version="7.0">
|
||||
<group>
|
||||
<field name="date_create"/>
|
||||
<field name="date_validate"/>
|
||||
<field name="acquirer_id"/>
|
||||
<field name="state"/>
|
||||
<field name="state_message"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id"/>
|
||||
<field name="reference"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="partner_name"/>
|
||||
<field name="partner_email"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="transaction_list">
|
||||
<field name="model">payment.transaction</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payment Transactions">
|
||||
<field name="partner_id"/>
|
||||
<field name="partner_name"/>
|
||||
<field name="state"/>
|
||||
<field name="reference"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="transaction">
|
||||
<field name="model">payment.transaction</field>
|
||||
<field name="arch" type="xml">
|
||||
<search>
|
||||
<field name="partner_id"/>
|
||||
<field name="partner_name"/>
|
||||
<field name="reference"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_payment_transaction">
|
||||
<field name="name">Payment Transactions</field>
|
||||
<field name="res_model">payment.transaction</field>
|
||||
<field name='view_type'>form</field>
|
||||
<field name='view_mode'>tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
action='action_payment_transaction'
|
||||
id='payment_transaction_menu'
|
||||
parent='root_payment_menu'
|
||||
sequence='20' />
|
||||
</data>
|
||||
</openerp>
|
Loading…
Reference in New Issue