[IMP] website_sale: improved checkout process

- now going to shop/confirmation when coming back from the acquirer
- added poll on the confirmation page to wait for data from acquirer
- misc cleaning of checkout process, order and transaction management
- added cancel state on payment.transaction, when canceled by the
customer

bzr revid: tde@openerp.com-20131119160129-fkwkhjvk1bh0uarf
This commit is contained in:
Thibault Delavallée 2013-11-19 17:01:29 +01:00
parent ba14bbe339
commit 4f6e792733
8 changed files with 150 additions and 97 deletions

View File

@ -149,11 +149,16 @@ class PaymentTransaction(osv.Model):
string='Type', required=True),
'state': fields.selection(
[('draft', 'Draft'), ('pending', 'Pending'),
('done', 'Done'), ('error', 'Error')],
'Status', required=True,
('done', 'Done'), ('error', 'Error'),
('cancel', 'Canceled')
], 'Status', required=True,
track_visiblity='onchange'),
'state_message': fields.text('Message',
help='Field used to store error and/or validation messages for information'),
# link with a record e.g. sale order
# 'feedback_model': fields.char('Model'),
# 'feedback_res_id': fields.integer('Res Id'),
# 'feedback_method': fields.char('Method'), # use a return url with a dedicated controler ?
# payment
'amount': fields.float('Amount', required=True,
help='Amount in cents',

View File

@ -1,35 +1,8 @@
# -*- 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.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
_logger = logging.getLogger(__name__)
class OgoneController(http.Controller):
_accept_url = '/payment/ogone/test/accept'
@ -39,6 +12,9 @@ class OgoneController(http.Controller):
@website.route([
'/payment/ogone/accept', '/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 ogone_form_feedback(self, **post):
cr, uid, context = request.cr, request.uid, request.context
@ -50,15 +26,3 @@ class OgoneController(http.Controller):
res = Payment.ogone_form_feedback(cr, uid, post, context)
print 'result after feedback', res
return request.redirect(return_url)
@website.route([
'/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 ogone_form_feedback_other(self, **post):
cr, uid, context = request.cr, request.uid, request.context
print 'Entering ogone_form_feedback_other', post
return_url = post.pop('return_url', '/')
print 'return_url', return_url
return request.redirect(return_url)

View File

@ -154,6 +154,11 @@ class PaymentAcquirerOgone(osv.Model):
class PaymentTxOgone(osv.Model):
_inherit = 'payment.transaction'
# ogone status
_ogone_valid_tx_status = [5, 9]
_ogone_wait_tx_status = [41, 50, 51, 52, 55, 56, 91, 92, 99]
_ogone_pending_tx_status = [46] # 3DS HTML response
_ogone_cancel_tx_status = [1]
_columns = {
'ogone_3ds': fields.dummy('3ds Activated'),
@ -231,13 +236,21 @@ class PaymentTxOgone(osv.Model):
return False
status = int(data.get('STATUS', '0'))
if status in [5, 9]:
if status in self._ogone_valid_tx_status:
tx.write({
'state': 'done',
'date_validate': data['TRXDATE'],
'ogone_payid': data['PAYID'],
})
return True
elif status in self._ogone_cancel_tx_status:
tx.write({
'state': 'cancel',
})
elif status in self._ogone_pending_tx_status:
tx.write({
'state': 'pending',
})
else:
error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
'error_str': data.get('NCERROR'),

View File

@ -412,6 +412,8 @@ class Ecommerce(http.Controller):
order_obj = request.registry.get('sale.order')
order = request.registry.get('website').get_current_order(request.cr, request.uid, context=request.context)
if not order:
order = request.registry.get('website')._get_order(request.cr, request.uid, context=request.context)
request.context = dict(request.context, pricelist=self.get_pricelist())
@ -652,45 +654,50 @@ class Ecommerce(http.Controller):
def payment(self, payment_acquirer_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
payment_obj = request.registry.get('payment.acquirer')
order = request.registry['website'].get_current_order(cr, uid, context=context)
# if no sale order at this stage: back to checkout beginning
order = context.get('website_sale_order')
if not order or not order.order_line:
return request.redirect("/shop/checkout/")
if 'website_sale_order' in context:
shipping_pid = context['website_sale_order'].partner_id.id
else:
shipping_pid = False
# alread a transaction: forward to confirmation
tx = context.get('website_sale_transaction')
if tx and not tx.state == 'draft':
return request.redirect('/shop/confirmation')
partner_id = False
shipping_partner_id = False
if order:
if order.partner_id.id:
partner_id = order.partner_id.id
shipping_partner_id = order.partner_id.id
if order.partner_shipping_id.id:
shipping_partner_id = order.partner_shipping_id.id
values = {
'partner': shipping_pid,
'partner': partner_id,
'payment_acquirer_id': payment_acquirer_id,
'order': order
}
values.update(request.registry.get('sale.order')._get_website_data(cr, uid, order, context))
# if payment_acquirer_id:
# values['validation'] = self.payment_validation(payment_acquirer_id)
# if values['validation'][0] == "validated" or (values['validation'][0] == "pending" and values['validation'][1] == False):
# return request.redirect("/shop/confirmation/")
# elif values['validation'][0] == "refused":
# values['payment_acquirer_id'] = None
# fetch all registered payment means
if not values['payment_acquirer_id']:
payment_ids = payment_obj.search(request.cr, SUPERUSER_ID, [('portal_published', '=', True)], context=request.context)
values['payments'] = payment_obj.browse(cr, uid, payment_ids, context=context)
for pay in values['payments']:
pay._content = payment_obj.render(
cr, uid, pay.id,
order.name,
order.amount_total,
order.pricelist_id.currency_id,
partner_id=shipping_pid,
tx_custom_values={
'return_url': '/shop/payment',
},
context=context)
if tx:
payment_ids = [tx.acquirer_id.id]
else:
payment_ids = payment_obj.search(cr, SUPERUSER_ID, [('portal_published', '=', True)], context=context)
values['payments'] = payment_obj.browse(cr, uid, payment_ids, context=context)
for pay in values['payments']:
pay._content = payment_obj.render(
cr, uid, pay.id,
order.name,
order.amount_total,
order.pricelist_id.currency_id,
partner_id=shipping_partner_id,
tx_custom_values={
'return_url': '/shop/confirmation',
},
context=context)
return request.website.render("website_sale.payment", values)
@ -701,7 +708,7 @@ class Ecommerce(http.Controller):
:param int acquirer_id: id of a payment.acquirer record. If not set the
user is redirected to the checkout page
:param dict post: should coutain only post data used by the acquirer
:param dict post: should coutain all post data for the acquirer
"""
# @TDEFIXME: don't know why we received those data, but should not be send to the acquirer
post.pop('submit.x', None)
@ -715,11 +722,9 @@ class Ecommerce(http.Controller):
return request.redirect("/shop/checkout/")
# find an already existing transaction
tx_ids = transaction_obj.search(cr, uid, [
('reference', '=', order.name), ('acquirer_id', '=', acquirer_id)
], context=context)
if not tx_ids:
transaction_obj.create(cr, uid, {
tx = context.get('website_sale_transaction')
if not tx:
tx_id = transaction_obj.create(cr, uid, {
'acquirer_id': acquirer_id,
'type': 'form',
'amount': order.amount_total,
@ -727,32 +732,48 @@ class Ecommerce(http.Controller):
'partner_id': order.partner_id.id,
'reference': order.name,
}, context=context)
request.httprequest.session['website_sale_transaction_id'] = tx_id
elif tx and tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx.write({
'acquirer_id': acquirer_id,
})
acquirer_form_post_url = payment_obj.get_form_action_url(cr, uid, acquirer_id, context=context)
acquirer_total_url = '%s?%s' % (acquirer_form_post_url, urllib.urlencode(post))
return request.redirect(acquirer_total_url)
# @website.route(['/shop/payment_validation/'], type='json', auth="public", multilang=True)
# def payment_validation(self, payment_acquirer_id=None, **post):
# payment_obj = request.registry.get('payment.acquirer')
# order = request.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
# return payment_obj.validate_payement(request.cr, request.uid, int(payment_acquirer_id),
# object=order,
# reference=order.name,
# currency=order.pricelist_id.currency_id,
# amount=order.amount_total,
# context=request.context)
@website.route([
'/shop/payment/transaction/get_status',
'/shop/payment/transaction/get_status/<int:transaction_>'
], type='json', auth="public", multilang=True)
def payment_validation(self, transaction_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
payment_obj = request.registry.get('payment.transaction')
if transaction_id:
tx = payment_obj.browse(cr, uid, transaction_id, context=context)
else:
tx = context.get('website_sale_transaction')
return {
'state': tx.state,
}
@website.route(['/shop/confirmation/'], type='http', auth="public", multilang=True)
def payment_confirmation(self, **post):
order = request.registry['website'].get_current_order(request.cr, request.uid, context=request.context)
context = request.context
# if no sale order at this stage: back to shop
order = context.get('website_sale_order')
if not order or not order.order_line:
return request.redirect("/shop/")
# no transaction: back to payment
tx = context.get('website_sale_transaction')
if not tx:
return request.redirect('/shop/payment')
res = request.website.render("website_sale.confirmation", {'order': order})
request.httprequest.session['ecommerce_order_id'] = False
request.httprequest.session['ecommerce_pricelist'] = False
# request.httprequest.session['ecommerce_order_id'] = False
# request.httprequest.session['ecommerce_pricelist'] = False
return res
@website.route(['/shop/change_sequence/'], type='json', auth="public")

View File

@ -13,9 +13,10 @@ class Website(osv.Model):
order_obj = request.registry.get('sale.order')
# check if order allready exists and have access
if order_id:
if not order_id in order_obj.exists(cr, uid, [order_id], context=context):
return False
try:
order = order_obj.browse(cr, uid, order_id, context=context)
order.pricelist_id
if order:
return order
except:
@ -48,11 +49,25 @@ class Website(osv.Model):
return order
return False
def _get_transaction(self, cr, uid, tx_id=None, context=None):
transaction_obj = request.registry['payment.transaction']
if tx_id:
tx_ids = transaction_obj.search(cr, uid, [('id', '=', tx_id), ('state', 'not in', ['cancel'])], context=context)
if tx_ids:
return transaction_obj.browse(cr, uid, tx_ids[0], context=context)
return False
def get_current_transaction(self, cr, uid, context=None):
pass
if request.httprequest.session.get('website_sale_transaction_id'):
tx = self._get_transaction(cr, uid, tx_id=request.httprequest.session['website_sale_transaction_id'], context=context)
if not tx:
request.httprequest.session['website_sale_transaction_id'] = False
return tx
return False
def preprocess_request(self, cr, uid, ids, request, context=None):
request.context.update({
'website_sale_order': self.get_current_order(cr, uid, context=context),
'website_sale_transaction': self.get_current_transaction(cr, uid, context=context)
})
return super(Website, self).preprocess_request(cr, uid, ids, request, context=None)

View File

@ -11,9 +11,4 @@ $(document).ready(function () {
console.log('cliking on submit for payment - redirecting from', form_action, 'to shop with acqurier_id', acquirer_id);
$(this).closest("form").attr("action", '/shop/payment/transaction/' + acquirer_id + '/');
});
// openerp.jsonRpc('/shop/payment/transaction', 'call', {
// }).then(function (result) {
// console.log(result);
// });
});

View File

@ -0,0 +1,38 @@
$(document).ready(function () {
var _poll_nbr = 0;
function payment_transaction_poll_status() {
return openerp.jsonRpc('/shop/payment/transaction/get_status', 'call', {
}).then(function (result) {
_poll_nbr += 1;
if (result.state == 'done') {
$('div.oe_website_sale_confirmation').html(function() {
return "<h2>Thanks for your order</h2>";
});
}
else if (result.state == 'pending') {
if (_poll_nbr <= 10) {
$('div.oe_website_sale_confirmation').html(function() {
return "<h2>Waiting validation ...</h2>";
});
setTimeout(function () {
return payment_transaction_poll_status();
}, 1000);
}
else {
$('div.oe_website_sale_confirmation').html(function() {
return "<h2>You payment is currently under review. Please come back later.</h2>";
});
}
}
else if (result.state == 'cancel') {
$('div.oe_website_sale_confirmation').html(function() {
return "<h2>The payment seems to have been canceled.</h2>";
});
}
});
}
payment_transaction_poll_status();
});

View File

@ -924,6 +924,7 @@
<t t-call="website.layout">
<t t-set="head">
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_validate.js"></script>
<t t-raw="head or ''"/>
</t>
<t t-set="additional_title">Shop - Confirmed</t>
@ -1011,8 +1012,9 @@
</div>
</div>
<h2>Tanks you for your order.</h2>
<a href="/shop/payment_validate/" class="btn btn-primary mt16">Validate &amp; Pay <span class="icon-long-arrow-right"/></a>
<div class="oe_website_sale_confirmation">
<h2>Tanks you for your order.</h2>
</div>
</div>
<div class="oe_structure"/>