[MERGE] Merge parents

bzr revid: rim@openerp.com-20131206091907-r9zo0z32zgewj0mx
This commit is contained in:
Richard Mathot (OpenERP) 2013-12-06 10:19:07 +01:00
commit 6768bb96ed
72 changed files with 1069 additions and 1159 deletions

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
<record id="res_partner_grade_bronze" model="res.partner.grade">
<field name="name">Bronze</field>
<field name="sequence">1</field>
</record>
<record id="res_partner_grade_silver" model="res.partner.grade">
<field name="name">Silver</field>
<field name="sequence">2</field>
<record id="res_partner_grade_platinium" model="res.partner.grade">
<field name="name">Platinum</field>
<field name="sequence">4</field>
</record>
<record id="res_partner_grade_gold" model="res.partner.grade">
<field name="name">Gold</field>
<field name="sequence">3</field>
</record>
<record id="res_partner_grade_platinium" model="res.partner.grade">
<field name="name">Platinum</field>
<field name="sequence">4</field>
<record id="res_partner_grade_silver" model="res.partner.grade">
<field name="name">Silver</field>
<field name="sequence">2</field>
</record>
<record id="res_partner_grade_bronze" model="res.partner.grade">
<field name="name">Bronze</field>
<field name="sequence">1</field>
</record>
<record id="base.res_partner_15" model="res.partner">

View File

@ -1,14 +1,15 @@
# -*- coding: utf-'8' "-*-"
from openerp.osv import osv, fields
import logging
from openerp.osv import osv, fields
from openerp.tools import float_round
_logger = logging.getLogger(__name__)
def _partner_format_address(address1=False, address2=False):
return ' '.join((address1 or '', address2 or ''))
return ' '.join((address1 or '', address2 or '')).strip()
def _partner_split_name(partner_name):
@ -22,8 +23,9 @@ class ValidationError(ValueError):
class PaymentAcquirer(osv.Model):
""" Acquirer Model. Each specific acquirer can extend the model by adding
its own fields. Using the required_if_provider='<name>' attribute on fields
it is possible to have required fields that depend on a specific acquirer.
its own fields, using the acquirer_name as a prefix for the new fields.
Using the required_if_provider='<name>' attribute on fields it is possible
to have required fields that depend on a specific acquirer.
Each acquirer has a link to an ir.ui.view record that is a template of
a button used to display the payment form. See examples in ``payment_acquirer_ogone``
@ -36,7 +38,10 @@ class PaymentAcquirer(osv.Model):
method that generates the values used to render the form button template.
- ``<name>_get_form_action_url(self, cr, uid, id, context=None):``: method
that returns the url of the button form. It is used for example in
ecommerce application, if you want to repost some data to the acquirer.
ecommerce application, if you want to post some data to the acquirer.
- ``<name>_compute_fees(self, cr, uid, id, amount, currency_id, country_id,
context=None)``: computed the fees of the acquirer, using generic fields
defined on the acquirer model (see fields definition).
Each acquirer should also define controllers to handle communication between
OpenERP and the acquirer. It generally consists in return urls given to the
@ -54,8 +59,9 @@ class PaymentAcquirer(osv.Model):
'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.)"),
'portal_published': fields.boolean(
'Visible in Portal',
help="Make this payment acquirer available (Customer invoices, etc.)"),
# Fees
'fees_active': fields.boolean('Compute fees'),
'fees_dom_fixed': fields.float('Fixed domestic fees'),
@ -89,27 +95,91 @@ class PaymentAcquirer(osv.Model):
return getattr(self, '%s_get_form_action_url' % acquirer.name)(cr, uid, id, context=context)
return False
def form_preprocess_values(self, cr, uid, id, reference, amount, currency_id, tx_id, partner_id, partner_values, tx_custom_values, context=None):
acquirer = self.browse(cr, uid, id, context=context)
tx_values = tx_custom_values
def form_preprocess_values(self, cr, uid, id, reference, amount, currency_id, tx_id, partner_id, partner_values, tx_values, context=None):
""" Pre process values before giving them to the acquirer-specific render
methods. Those methods will receive:
# compute country
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
tx_values['country_id'] = partner.country_id.id
elif partner_values and partner_values.get('country_name'):
country_ids = self.pool['res.country'].search(cr, uid, [('name', '=', partner_values.get('country_name'))], context=context)
tx_values['country_id'] = country_ids and country_ids[0] or None
- partner_values: will contain name, lang, email, zip, address, city,
country_id (int or False), country (browse or False), phone, reference
- tx_values: will contain refernece, amount, currency_id (int or False),
currency (browse or False), partner (browse or False)
"""
acquirer = self.browse(cr, uid, id, context=context)
if tx_id:
tx = self.browse(cr, uid, id, context=context)
tx_data = {
'reference': tx.reference,
'amount': tx.amount,
'currency_id': tx.currency_id.id,
'currency': tx.currency_id,
'partner': tx.partner_id,
}
partner_data = {
'name': tx.partner_name,
'lang': tx.partner_lang,
'email': tx.partner_email,
'zip': tx.partner_zip,
'address': tx.partner_address,
'city': tx.partner_city,
'country_id': tx.partner_country_id.id,
'country': tx.partner_country_id,
'phone': tx.partner_phone,
'reference': tx.partner_reference,
}
else:
tx_values['country_id'] = False
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
partner_data = {
'name': partner.name,
'lang': partner.lang,
'email': partner.email,
'zip': partner.zip,
'city': partner.city,
'address': _partner_format_address(partner.street, partner.street2),
'country_id': partner.country_id.id,
'country': partner.country_id,
'phone': partner.phone,
}
else:
partner, partner_data = False, {}
partner_data.update(partner_values)
if currency_id:
currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
else:
currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
tx_data = {
'reference': reference,
'amount': amount,
'currency_id': currency.id,
'currency': currency,
'partner': partner,
}
# update tx values
tx_data.update(tx_values)
# update partner values
if not partner_data.get('address'):
partner_data['address'] = _partner_format_address(partner_data.get('street', ''), partner_data.get('street2', ''))
if not partner_data.get('country') and partner_data.get('country_id'):
partner_data['country'] = self.pool['res.country'].browse(cr, uid, partner_data.get('country_id'), context=context)
partner_data.update({
'first_name': _partner_split_name(partner_data['name'])[0],
'last_name': _partner_split_name(partner_data['name'])[1],
})
# compute fees
if hasattr(self, '%s_compute_fees' % acquirer.name):
tx_values['fees'] = getattr(self, '%s_compute_fees' % acquirer.name)(cr, uid, id, amount, currency_id, tx_values.get('country_id'), context=None)
fees_method_name = '%s_compute_fees' % acquirer.name
if hasattr(self, fees_method_name):
fees = getattr(self, fees_method_name)(
cr, uid, id, tx_data['amount'], tx_data['currency_id'], partner_data['country_id'], context=None)
tx_data['fees'] = float_round(fees, 2)
return tx_values
return (partner_data, tx_data)
def render(self, cr, uid, id, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
def render(self, cr, uid, id, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, context=None):
""" Renders the form template of the given acquirer as a qWeb template.
All templates will receive:
@ -120,9 +190,9 @@ class PaymentAcquirer(osv.Model):
- 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_custom_values: a dictionary of transaction related values that depends
on the acquirer. Some specific keys should be managed
in each provider, depending on the features it offers:
- tx_values: a dictionary of transaction related values that depends on
the acquirer. Some specific keys should be managed in each
provider, depending on the features it offers:
- 'feedback_url': feedback URL, controler that manage answer of the acquirer
(without base url) -> FIXME
@ -147,38 +217,40 @@ class PaymentAcquirer(osv.Model):
"""
if context is None:
context = {}
if tx_custom_values is None:
tx_custom_values = {}
partner = None
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
if tx_values is None:
tx_values = {}
if partner_values is None:
partner_values = {}
acquirer = self.browse(cr, uid, id, context=context)
currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
# pre-process values
tx_values = self.form_preprocess_values(cr, uid, id, reference, amount, currency, tx_id, partner_id, partner_values, tx_custom_values, context=context)
amount = float_round(amount, 2)
partner_values, tx_values = self.form_preprocess_values(
cr, uid, id, reference, amount, currency_id, tx_id, partner_id,
partner_values, tx_values, context=context)
# call <name>_form_generate_values to update the tx dict with acqurier specific values
cust_method_name = '%s_form_generate_values' % (acquirer.name)
if tx_id and hasattr(self.pool['payment.transaction'], cust_method_name):
method = getattr(self.pool['payment.transaction'], cust_method_name)
tx_values = method(cr, uid, tx_id, tx_values, context=context)
elif hasattr(self, cust_method_name):
if hasattr(self, cust_method_name):
method = getattr(self, cust_method_name)
tx_values = method(cr, uid, id, reference, amount, currency, partner_id, partner_values, tx_values, context=context)
partner_values, tx_values = method(cr, uid, id, partner_values, tx_values, context=context)
qweb_context = {
'tx_url': context.get('tx_url', self.get_form_action_url(cr, uid, id, context=context)),
'submit_class': context.get('submit_class', 'btn btn-link'),
'submit_txt': context.get('submit_txt'),
'acquirer': acquirer,
'user': self.pool.get("res.users").browse(cr, uid, uid, context=context),
'reference': reference,
'amount': amount,
'currency': currency,
'partner': partner,
'reference': tx_values['reference'],
'amount': tx_values['amount'],
'currency': tx_values['currency'],
'partner': tx_values.get('partner'),
'partner_values': partner_values,
'tx_values': tx_values,
'context': context,
}
# because render accepts view ids but not qweb -> need to find the xml_id
# 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)
@ -186,12 +258,6 @@ class PaymentTransaction(osv.Model):
""" Transaction Model. Each specific acquirer can extend the model by adding
its own fields.
Methods that should be added in an acquirer-specific implementation:
- ``<name>_form_generate_values(self, cr, uid, id, tx_custom_values=None,
context=None)``: method that generates the values used to render the
form button template.
Methods that can be added in an acquirer-specific implementation:
- ``<name>_create``: method receiving values used when creating a new
@ -230,22 +296,29 @@ class PaymentTransaction(osv.Model):
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'),
'fees': fields.float('Fees', help='Fees amount; set by the system because depends on the acquirer'),
digits=(16, 2),
track_visibility='always',
help='Amount in cents'),
'fees': fields.float('Fees',
digits=(16, 2),
track_visibility='always',
help='Fees amount; set by the system because depends on the acquirer'),
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
'reference': fields.char('Order Reference', required=True),
'acquirer_reference': fields.char('Acquirer Order Reference',
help='Reference of the TX as stored in the acquirer database'),
# duplicate partner / transaction data to store the values at transaction time
'partner_id': fields.many2one('res.partner', 'Partner'),
'partner_id': fields.many2one('res.partner', 'Partner', track_visibility='onchange',),
'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_country_id': fields.many2one('res.country', 'Country', required=True),
'partner_phone': fields.char('Phone'),
'partner_reference': fields.char('Buyer Reference'),
'partner_reference': fields.char('Partner Reference',
help='Reference of the customer in the acquirer database'),
}
_sql_constraints = [
@ -272,8 +345,9 @@ class PaymentTransaction(osv.Model):
# compute fees
custom_method_name = '%s_compute_fees' % acquirer.name
if hasattr(Acquirer, custom_method_name):
values['fees'] = getattr(Acquirer, custom_method_name)(
fees = getattr(Acquirer, custom_method_name)(
cr, uid, acquirer.id, values.get('amount', 0.0), values.get('currency_id'), values.get('country_id'), context=None)
values['fees'] = float_round(fees, 2)
# custom create
custom_method_name = '%s_create' % acquirer.name
@ -283,30 +357,19 @@ class PaymentTransaction(osv.Model):
return super(PaymentTransaction, self).create(cr, uid, values, context=context)
def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
partner = 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': _partner_format_address(partner.street, partner.street2),
'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 {'values': {
'partner_name': partner and partner.name or False,
'partner_lang': partner and partner.lang or 'en_US',
'partner_email': partner and partner.email or False,
'partner_zip': partner and partner.zip or False,
'partner_address': _partner_format_address(partner and partner.street or '', partner and partner.street2 or ''),
'partner_city': partner and partner.city or False,
'partner_country_id': partner and partner.country_id.id or False,
'partner_phone': partner and partner.phone or False,
}}
# --------------------------------------------------
# FORM RELATED METHODS

View File

@ -14,8 +14,10 @@ class PaymentAcquirerCommon(common.TransactionCase):
self.cr, self.uid, [('name', '=', 'EUR')], limit=1)[0]
self.currency_euro = self.registry('res.currency').browse(
self.cr, self.uid, self.currency_euro_id)
country_belgium_id = self.registry('res.country').search(
self.country_belgium_id = self.registry('res.country').search(
self.cr, self.uid, [('code', 'like', 'BE')], limit=1)[0]
self.country_france_id = self.registry('res.country').search(
self.cr, self.uid, [('code', 'like', 'FR')], limit=1)[0]
# dict partner values
self.buyer_values = {
@ -27,7 +29,7 @@ class PaymentAcquirerCommon(common.TransactionCase):
'phone': '0032 12 34 56 78',
'city': 'Sin City',
'zip': '1000',
'country_id': country_belgium_id,
'country_id': self.country_belgium_id,
'country_name': 'Belgium',
}
@ -42,6 +44,6 @@ class PaymentAcquirerCommon(common.TransactionCase):
'phone': '0032 12 34 56 78',
'city': 'Sin City',
'zip': '1000',
'country_id': country_belgium_id,
'country_id': self.country_belgium_id,
}
)

View File

@ -38,10 +38,10 @@
<div>
<field name="view_template_id" nolabel="1"/>
<div>
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:
This template renders the acquirer button with all necessary values.
It is be rendered with qWeb with the following evaluation context:
<ul>
<li>tx_url: transaction URL to post the form</li>
<li>acquirer: payment.acquirer browse record</li>
<li>user: current user browse record</li>
<li>reference: the transaction reference number</li>
@ -49,7 +49,7 @@
<li>amount: the transaction amount, a float</li>
<li>partner: the buyer partner browse record, not necessarily set</li>
<li>partner_values: specific values about the buyer, for example coming from a shipping form</li>
<li>tx_values: specific transaction values</li>
<li>tx_values: transaction values</li>
<li>context: the current context dictionary</li>
</ul>
</div>
@ -98,23 +98,32 @@
<field name="arch" type="xml">
<form string="Payment Transactions" version="7.0">
<sheet>
<group>
<group col="6">
<group>
<field name="reference"/>
<field name="amount"/>
<field name="date_create"/>
<field name="fees"/>
<field name="currency_id"/>
<field name="partner_id"/>
<field name="partner_name"/>
<field name="partner_email"/>
<field name="partner_reference"/>
</group>
<group>
<field name="date_create"/>
<field name="date_validate"/>
<field name="acquirer_id"/>
<field name="acquirer_reference"/>
<field name="date_validate"/>
<field name="state"/>
<field name="state_message"/>
</group>
<group>
<field name="partner_name"/>
<field name="partner_address"/>
<field name="partner_email"/>
<field name="partner_lang"/>
<field name="partner_zip"/>
<field name="partner_city"/>
<field name="partner_country_id"/>
</group>
</group>
<notebook>
</notebook>

View File

@ -19,7 +19,7 @@ class AdyenController(http.Controller):
@website.route([
'/payment/adyen/return/',
], type='http', auth='public', methods=['POST'])
], type='http', auth='public')
def adyen_return(self, **post):
""" Paypal IPN. """
_logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug

View File

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<data noupdate="1">
<record id="payment_acquirer_adyen" model="payment.acquirer">
<field name="name">adyen</field>
<field name="view_template_id" ref="adyen_acquirer_button"/>
<field name="env">test</field>
<field name="adyen_merchant_account">OpenERPCOM</field>
<field name="adyen_skin_code">cbqYWvVL</field>
<field name="adyen_skin_hmac_key">cbqYWvVL</field>
<field name="adyen_merchant_account">dummy</field>
<field name="adyen_skin_code">dummy</field>
<field name="adyen_skin_hmac_key">dummy</field>
</record>
</data>

View File

@ -21,26 +21,24 @@ _logger = logging.getLogger(__name__)
class AcquirerAdyen(osv.Model):
_inherit = 'payment.acquirer'
def _get_adyen_urls(self, cr, uid, ids, name, args, context=None):
def _get_adyen_urls(self, cr, uid, env, context=None):
""" Adyen URLs
- yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple
"""
res = {}
for acquirer in self.browse(cr, uid, ids, context=context):
qualif = acquirer.env
res[acquirer.id] = {
'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % qualif,
if env == 'prod':
return {
'adyen_form_url': 'https://prod.adyen.com/hpp/pay.shtml',
}
else:
return {
'adyen_form_url': 'https://test.adyen.com/hpp/pay.shtml',
}
return res
_columns = {
'adyen_merchant_account': fields.char('Merchant Account', required_if_provider='adyen'),
'adyen_skin_code': fields.char('Skin Code', required_if_provider='adyen'),
'adyen_skin_hmac_key': fields.char('Skin HMAC Key', required_if_provider='adyen'),
'adyen_form_url': fields.function(
_get_adyen_urls, multi='_get_adyen_urls',
type='char', string='Transaction URL', required_if_provider='adyen'),
}
def _adyen_generate_merchant_sig(self, acquirer, inout, values):
@ -72,9 +70,7 @@ class AcquirerAdyen(osv.Model):
key = acquirer.adyen_skin_hmac_key.encode('ascii')
return base64.b64encode(hmac.new(key, sign, sha1).digest())
def adyen_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
if partner_values is None:
partner_values = {}
def adyen_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
acquirer = self.browse(cr, uid, id, context=context)
# tmp
@ -82,31 +78,26 @@ class AcquirerAdyen(osv.Model):
from dateutil import relativedelta
tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1)
partner = None
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
tx_values = {
'merchantReference': reference,
'paymentAmount': '%d' % int(float_round(amount, 2) * 100),
'currencyCode': currency and currency.name or 'EUR',
adyen_tx_values = dict(tx_values)
adyen_tx_values.update({
'merchantReference': tx_values['reference'],
'paymentAmount': '%d' % int(float_round(tx_values['amount'], 2) * 100),
'currencyCode': tx_values['currency'] and tx_values['currency'].name or '',
'shipBeforeDate': tmp_date,
'skinCode': acquirer.adyen_skin_code,
'merchantAccount': acquirer.adyen_merchant_account,
'shopperLocale': partner and partner.lang or partner_values.get('lang', 'en_US'),
'shopperLocale': partner_values['lang'],
'sessionValidity': tmp_date,
'merchantSig': 'oij',
'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url),
}
if tx_custom_values and tx_custom_values.get('return_url'):
tx_values['merchantReturnData'] = json.dumps({'return_url': '%s' % tx_custom_values.pop('return_url')})
if tx_custom_values:
tx_values.update(tx_custom_values)
tx_values['merchantSig'] = self._adyen_generate_merchant_sig(acquirer, 'in', tx_values)
return tx_values
})
if adyen_tx_values.get('return_url'):
adyen_tx_values['merchantReturnData'] = json.dumps({'return_url': '%s' % adyen_tx_values.pop('return_url')})
adyen_tx_values['merchantSig'] = self._adyen_generate_merchant_sig(acquirer, 'in', adyen_tx_values)
return partner_values, adyen_tx_values
def adyen_get_form_action_url(self, cr, uid, id, context=None):
acquirer = self.browse(cr, uid, id, context=context)
return acquirer.adyen_form_url
return self._get_adyen_urls(cr, uid, acquirer.env, context=context)['adyen_form_url']
class TxAdyen(osv.Model):
@ -120,21 +111,6 @@ class TxAdyen(osv.Model):
# FORM RELATED METHODS
# --------------------------------------------------
def adyen_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None):
tx = self.browse(cr, uid, id, context=context)
tx_data = {
'shopperLocale': tx.partner_lang,
}
if tx_custom_values:
tx_data.update(tx_custom_values)
return self.pool['payment.acquirer'].paypal_form_generate_values(
cr, uid, tx.acquirer_id.id,
tx.reference, tx.amount, tx.currency_id,
tx_custom_values=tx_data,
context=context
)
def _adyen_form_get_tx_from_data(self, cr, uid, data, context=None):
reference, pspReference = data.get('merchantReference'), data.get('pspReference')
if not reference or not pspReference:
@ -164,12 +140,18 @@ class TxAdyen(osv.Model):
return tx
def _adyen_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
# TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details
invalid_parameters = []
# reference at acquirer: pspReference
if tx.acquirer_reference and data.get('pspReference') != tx.acquirer_reference:
invalid_parameters.append(('pspReference', data.get('pspReference'), tx.acquirer_reference))
# seller
if data.get('skinCode') != tx.acquirer_id.adyen_skin_code:
invalid_parameters.append(('skinCode', data.get('skinCode'), tx.acquirer_id.adyen_skin_code))
# result
if not data.get('authResult'):
invalid_parameters.append(('authResult', data.get('authResult'), 'something'))
return invalid_parameters
def _adyen_form_validate(self, cr, uid, tx, data, context=None):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from . import test_adyen
# from . import test_adyen
checks = [
test_adyen,
# test_adyen,
]

View File

@ -1,34 +1,24 @@
# -*- coding: utf-8 -*-
from lxml import objectify
import urlparse
from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
from openerp.addons.payment_acquirer.tests.common import PaymentAcquirerCommon
from openerp.addons.payment_acquirer_adyen.controllers.main import AdyenController
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
from lxml import objectify
import urlparse
class AdyenCommon(PaymentAcquirerCommon):
def setUp(self):
super(AdyenCommon, self).setUp()
cr, uid = self.cr, self.uid
self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
model, self.paypal_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_adyen', 'adyen_acquirer_button')
# create a new ogone account
self.adyen_id = self.payment_acquirer.create(
cr, uid, {
'name': 'adyen',
'env': 'test',
'view_template_id': self.paypal_view_id,
'adyen_merchant_account': 'OpenERPCOM',
'adyen_skin_code': 'cbqYWvVL',
'adyen_skin_hmac_key': 'cbqYWvVL',
})
# get the adyen account
model, self.adyen_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_adyen', 'payment_acquirer_adyen')
# some CC (always use expiration date 06 / 2016, cvc 737, cid 7373 (amex))
self.amex = (('370000000000002', '7373'))
@ -57,6 +47,9 @@ class AdyenForm(AdyenCommon):
def test_10_adyen_form_render(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid things
adyen = self.payment_acquirer.browse(self.cr, self.uid, self.adyen_id, None)
self.assertEqual(adyen.env, 'test', 'test without test env')
# ----------------------------------------
# Test: button direct rendering
@ -74,7 +67,7 @@ class AdyenForm(AdyenCommon):
# render the button
res = self.payment_acquirer.render(
cr, uid, self.adyen_id,
'test_ref0', 0.01, self.currency_euro,
'test_ref0', 0.01, self.currency_euro_id,
partner_id=None,
partner_values=self.buyer_values,
context=context)
@ -88,12 +81,15 @@ class AdyenForm(AdyenCommon):
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'adyen: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')])
'adyen: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
)
@mute_logger('openerp.addons.payment_acquirer_paypal.models.paypal', 'ValidationError')
def test_20_paypal_form_management(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid things
adyen = self.payment_acquirer.browse(self.cr, self.uid, self.adyen_id, None)
self.assertEqual(adyen.env, 'test', 'test without test env')
# {'authResult': u'AUTHORISED',
# 'merchantReference': u'SO014',

View File

@ -3,7 +3,7 @@
<data noupdate="0">
<template id="adyen_acquirer_button">
<form t-if="acquirer.adyen_merchant_account" t-att-action="acquirer.adyen_form_url" method="post" target="_self">
<form t-if="acquirer.adyen_merchant_account" t-att-action="tx_url" method="post" target="_self">
<input type="hidden" name="merchantReference" t-att-value="tx_values['merchantReference']"/>
<input type="hidden" name="paymentAmount" t-att-value="tx_values['paymentAmount']"/>
<input type="hidden" name="currencyCode" t-att-value="tx_values['currencyCode']"/>
@ -19,10 +19,12 @@
<!-- custom -->
<input t-if="tx_values.get('merchantReturnData')" type='hidden' name='merchantReturnData'
t-att-value="tx_values.get('merchantReturnData')"/>
<!-- button -->
<input type="image" name="submit" id="payment_submit"
width="100px"
src="/payment_acquirer_adyen/static/src/img/logo.jpg"/>
<!-- submit -->
<button type="image" name="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_acquirer_adyen/static/src/img/adyen_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/></span>
</button>
</form>
</template>

View File

@ -7,10 +7,11 @@
<field name="model">payment.acquirer</field>
<field name="inherit_id" ref="payment_acquirer.acquirer_form"/>
<field name="arch" type="xml">
<xpath expr='//group[@name="acquirer_base"]' position='after'>
<group string="Adyen Details"
attrs="{'invisible': [('name', '!=', 'adyen')]}">
<xpath expr='//group[@name="acquirer_display"]' position='after'>
<group attrs="{'invisible': [('name', '!=', 'adyen')]}">
<field name="adyen_merchant_account"/>
<field name="adyen_skin_code"/>
<field name="adyen_skin_hmac_key"/>
</group>
</xpath>
</field>

View File

@ -22,7 +22,8 @@ class OgoneController(http.Controller):
'/payment/ogone/cancel', '/payment/ogone/test/cancel',
], type='http', auth='admin')
def ogone_form_feedback(self, **post):
""" Ogone contacts using GET, at least for accept """
_logger.info('Ogone: entering form_feedback with post data %s', pprint.pformat(post)) # debug
cr, uid, context = request.cr, request.uid, request.context
request.registry['payment.transaction'].ogone_form_feedback(cr, uid, post, context)
request.registry['payment.transaction'].form_feedback(cr, uid, post, 'ogone', context=context)
return request.redirect(post.pop('return_url', '/'))

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<data noupdate="1">
<record id="payment_acquirer_ogone" model="payment.acquirer">
<field name="name">ogone</field>
@ -9,8 +9,8 @@
<field name='ogone_pspid'>dummy</field>
<field name='ogone_userid'>dummy</field>
<field name='ogone_password'>dummy</field>
<field name="ogone_shakey_in">tINY4Yv14789gUix1130</field>
<field name="ogone_shakey_out">tINYj885Tfvd4P471464</field>
<field name="ogone_shakey_in">dummy</field>
<field name="ogone_shakey_out">dummy</field>
</record>
</data>

View File

@ -14,6 +14,7 @@ from openerp.addons.payment_acquirer_ogone.controllers.main import OgoneControll
from openerp.addons.payment_acquirer_ogone.data import ogone
from openerp.osv import osv, fields
from openerp.tools import float_round
from openerp.tools.float_utils import float_compare
_logger = logging.getLogger(__name__)
@ -21,48 +22,26 @@ _logger = logging.getLogger(__name__)
class PaymentAcquirerOgone(osv.Model):
_inherit = 'payment.acquirer'
def _get_ogone_urls(self, cr, uid, ids, name, args, context=None):
def _get_ogone_urls(self, cr, uid, env, context=None):
""" Ogone URLS:
- standard order: POST address for form-based
@TDETODO: complete me
"""
res = {}
for acquirer in self.browse(cr, uid, ids, context=context):
qualif = acquirer.env
res[acquirer.id] = {
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % qualif,
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % qualif,
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % qualif,
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % qualif,
}
return res
return {
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % env,
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % env,
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % env,
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % env,
}
_columns = {
'ogone_pspid': fields.char(
'PSPID', required_if_provider='ogone'),
'ogone_userid': fields.char(
'API User id', required_if_provider='ogone'),
'ogone_password': fields.char(
'Password', required_if_provider='ogone'),
'ogone_shakey_in': fields.char(
'SHA Key IN', size=32, required_if_provider='ogone'),
'ogone_shakey_out': fields.char(
'SHA Key OUT', size=32, required_if_provider='ogone'),
# store ogone contact URLs -> not necessary IMHO
'ogone_standard_order_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='Stanrd Order URL (form)'),
'ogone_direct_order_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='Direct Order URL (2)'),
'ogone_direct_query_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='Direct Query URL'),
'ogone_afu_agree_url': fields.function(
_get_ogone_urls, type='char', multi='_get_ogone_urls',
string='AFU Agree URL'),
'ogone_pspid': fields.char('PSPID', required_if_provider='ogone'),
'ogone_userid': fields.char('API User ID', required_if_provider='ogone'),
'ogone_password': fields.char('API User Password', required_if_provider='ogone'),
'ogone_shakey_in': fields.char('SHA Key IN', size=32, required_if_provider='ogone'),
'ogone_shakey_out': fields.char('SHA Key OUT', size=32, required_if_provider='ogone'),
}
def _ogone_generate_shasign(self, acquirer, inout, values):
@ -93,43 +72,39 @@ class PaymentAcquirerOgone(osv.Model):
shasign = sha1(sign).hexdigest()
return shasign
def ogone_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
if partner_values is None:
partner_values = {}
def ogone_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
acquirer = self.browse(cr, uid, id, context=context)
partner = None
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
tx_values = {
ogone_tx_values = dict(tx_values)
temp_ogone_tx_values = {
'PSPID': acquirer.ogone_pspid,
'ORDERID': reference,
'AMOUNT': '%d' % int(float_round(amount, 2) * 100),
'CURRENCY': currency and currency.name or 'EUR',
'LANGUAGE': partner and partner.lang or partner_values.get('lang', ''),
'CN': partner and partner.name or partner_values.get('name', ''),
'EMAIL': partner and partner.email or partner_values.get('email', ''),
'OWNERZIP': partner and partner.zip or partner_values.get('zip', ''),
'OWNERADDRESS': partner and ' '.join((partner.street or '', partner.street2 or '')).strip() or ' '.join((partner_values.get('street', ''), partner_values.get('street2', ''))).strip(),
'OWNERTOWN': partner and partner.city or partner_values.get('city', ''),
'OWNERCTY': partner and partner.country_id and partner.country_id.name or partner_values.get('country_name', ''),
'OWNERTELNO': partner and partner.phone or partner_values.get('phone', ''),
'ORDERID': tx_values['reference'],
'AMOUNT': '%d' % int(float_round(tx_values['amount'], 2) * 100),
'CURRENCY': tx_values['currency'] and tx_values['currency'].name or '',
'LANGUAGE': partner_values['lang'],
'CN': partner_values['name'],
'EMAIL': partner_values['email'],
'OWNERZIP': partner_values['zip'],
'OWNERADDRESS': partner_values['address'],
'OWNERTOWN': partner_values['city'],
'OWNERCTY': partner_values['country'] and partner_values['country'].name or '',
'OWNERTELNO': partner_values['phone'],
'ACCEPTURL': '%s' % urlparse.urljoin(base_url, OgoneController._accept_url),
'DECLINEURL': '%s' % urlparse.urljoin(base_url, OgoneController._decline_url),
'EXCEPTIONURL': '%s' % urlparse.urljoin(base_url, OgoneController._exception_url),
'CANCELURL': '%s' % urlparse.urljoin(base_url, OgoneController._cancel_url),
}
if tx_custom_values and tx_custom_values.get('return_url'):
tx_values['PARAMPLUS'] = 'return_url=%s' % tx_custom_values.pop('return_url')
if tx_custom_values:
tx_values.update(tx_custom_values)
shasign = self._ogone_generate_shasign(acquirer, 'in', tx_values)
tx_values['SHASIGN'] = shasign
return tx_values
if ogone_tx_values.get('return_url'):
temp_ogone_tx_values['PARAMPLUS'] = 'return_url=%s' % ogone_tx_values.pop('return_url')
shasign = self._ogone_generate_shasign(acquirer, 'in', temp_ogone_tx_values)
temp_ogone_tx_values['SHASIGN'] = shasign
ogone_tx_values.update(temp_ogone_tx_values)
return partner_values, ogone_tx_values
def ogone_get_form_action_url(self, cr, uid, id, context=None):
acquirer = self.browse(cr, uid, id, context=context)
return acquirer.ogone_standard_order_url
return self._get_ogone_urls(cr, uid, acquirer.env, context=context)['ogone_standard_order_url']
class PaymentTxOgone(osv.Model):
@ -141,10 +116,8 @@ class PaymentTxOgone(osv.Model):
_ogone_cancel_tx_status = [1]
_columns = {
'ogone_3ds': fields.dummy('3ds Activated'),
'ogone_3ds': fields.boolean('3DS Activated'),
'ogone_3ds_html': fields.html('3DS HTML'),
'ogone_feedback_model': fields.char(),
'ogone_feedback_eval': fields.char(),
'ogone_complus': fields.char('Complus'),
'ogone_payid': fields.char('PayID', help='Payment ID, generated by Ogone')
}
@ -153,30 +126,6 @@ class PaymentTxOgone(osv.Model):
# FORM RELATED METHODS
# --------------------------------------------------
def ogone_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None):
""" Ogone-specific value generation for rendering a transaction-based
form button. """
tx = self.browse(cr, uid, id, context=context)
tx_data = {
'LANGUAGE': tx.partner_lang,
'CN': tx.partner_name,
'EMAIL': tx.partner_email,
'OWNERZIP': tx.partner_zip,
'OWNERADDRESS': tx.partner_address,
'OWNERTOWN': tx.partner_city,
'OWNERCTY': tx.partner_country_id and tx.partner_country_id.name or '',
'OWNERTELNO': tx.partner_phone,
}
if tx_custom_values:
tx_data.update(tx_custom_values)
return self.pool['payment.acquirer'].ogone_form_generate_values(
cr, uid, tx.acquirer_id.id,
tx.reference, tx.amount, tx.currency_id,
tx_custom_values=tx_data,
context=context
)
def _ogone_form_get_tx_from_data(self, cr, uid, data, context=None):
""" Given a data dict coming from ogone, verify it and find the related
transaction record. """
@ -207,29 +156,42 @@ class PaymentTxOgone(osv.Model):
return tx
def ogone_form_feedback(self, cr, uid, data, context=None):
tx = self._ogone_form_get_tx_from_data(cr, uid, data, context)
if not tx:
raise ValidationError('Ogone: feedback: tx not found')
def _ogone_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
invalid_parameters = []
# TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details
if tx.acquirer_reference and data.get('PAYID') != tx.acquirer_reference:
invalid_parameters.append(('PAYID', data.get('PAYID'), tx.acquirer_reference))
# check what is buyed
if float_compare(float(data.get('amount', '0.0')), tx.amount, 2) != 0:
invalid_parameters.append(('amount', data.get('amount'), '%.2f' % tx.amount))
if data.get('currency') != tx.currency_id.name:
invalid_parameters.append(('currency', data.get('currency'), tx.currency_id.name))
return invalid_parameters
def _ogone_form_validate(self, cr, uid, tx, data, context=None):
if tx.state == 'done':
_logger.warning('Ogone: trying to validate an already validated tx (ref %s' % tx.reference)
return False
_logger.warning('Ogone: trying to validate an already validated tx (ref %s)' % tx.reference)
return True
status = int(data.get('STATUS', '0'))
if status in self._ogone_valid_tx_status:
tx.write({
'state': 'done',
'date_validate': data['TRXDATE'],
'ogone_payid': data['PAYID'],
'acquirer_reference': data['PAYID'],
})
return True
elif status in self._ogone_cancel_tx_status:
tx.write({
'state': 'cancel',
'acquirer_reference': data.get('PAYID'),
})
elif status in self._ogone_pending_tx_status:
tx.write({
'state': 'pending',
'acquirer_reference': data.get('PAYID'),
})
else:
error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % {
@ -238,7 +200,11 @@ class PaymentTxOgone(osv.Model):
'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERRORPLUS')),
}
_logger.info(error)
tx.write({'state': 'error', 'state_message': error})
tx.write({
'state': 'error',
'state_message': error,
'acquirer_reference': data.get('PAYID'),
})
return False
# --------------------------------------------------
@ -268,6 +234,7 @@ class PaymentTxOgone(osv.Model):
'PROCESS_MODE': 'CHECKANDPROCESS',
}
# TODO: fix URL computation
request = urllib2.Request(tx.acquirer_id.ogone_afu_agree_url, urlencode(tx_data))
result = urllib2.urlopen(request).read()

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,45 +1,30 @@
# -*- coding: utf-8 -*-
from lxml import objectify
import time
import urlparse
from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
from openerp.addons.payment_acquirer.tests.common import PaymentAcquirerCommon
from openerp.addons.payment_acquirer_ogone.controllers.main import OgoneController
from openerp.tools import mute_logger
from lxml import objectify
# import requests
import time
import urlparse
class OgonePayment(PaymentAcquirerCommon):
def setUp(self):
super(OgonePayment, self).setUp()
cr, uid = self.cr, self.uid
self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
model, self.ogone_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_ogone', 'ogone_acquirer_button')
# create a new ogone account
self.ogone_id = self.payment_acquirer.create(
cr, uid, {
'name': 'ogone',
'env': 'test',
'view_template_id': self.ogone_view_id,
'ogone_pspid': 'dummy',
'ogone_userid': 'dummy',
'ogone_password': 'dummy',
'ogone_shakey_in': 'tINY4Yv14789gUix1130',
'ogone_shakey_out': 'tINYj885Tfvd4P471464',
})
self.ogone_url = self.payment_acquirer._get_ogone_urls(cr, uid, [self.ogone_id], None, None)[self.ogone_id]['ogone_standard_order_url']
def test_00_ogone_acquirer(self):
ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None)
self.assertEqual(ogone.env, 'test', 'test without test env')
# get the adyen account
model, self.ogone_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_ogone', 'payment_acquirer_ogone')
def test_10_ogone_form_render(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid thing
ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None)
self.assertEqual(ogone.env, 'test', 'test without test env')
# ----------------------------------------
# Test: button direct rendering + shasign
@ -68,7 +53,7 @@ class OgonePayment(PaymentAcquirerCommon):
# render the button
res = self.payment_acquirer.render(
cr, uid, self.ogone_id,
'test_ref0', 0.01, self.currency_euro,
'test_ref0', 0.01, self.currency_euro_id,
partner_id=None,
partner_values=self.buyer_values,
context=context)
@ -82,7 +67,7 @@ class OgonePayment(PaymentAcquirerCommon):
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'ogone: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')])
'ogone: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
)
# ----------------------------------------
@ -123,6 +108,9 @@ class OgonePayment(PaymentAcquirerCommon):
@mute_logger('openerp.addons.payment_acquirer_ogone.models.ogone', 'ValidationError')
def test_20_ogone_form_management(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid thing
ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None)
self.assertEqual(ogone.env, 'test', 'test without test env')
# typical data posted by ogone after client has successfully paid
ogone_post_data = {
@ -155,6 +143,7 @@ class OgonePayment(PaymentAcquirerCommon):
'currency_id': self.currency_euro_id,
'reference': 'test_ref_2',
'partner_name': 'Norbert Buyer',
'partner_country_id': self.country_france_id,
}, context=context
)
# validate it
@ -183,6 +172,9 @@ class OgonePayment(PaymentAcquirerCommon):
def test_30_ogone_s2s(self):
test_ref = 'test_ref_%.15f' % time.time()
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid thing
ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None)
self.assertEqual(ogone.env, 'test', 'test without test env')
# create a new draft tx
tx_id = self.payment_transaction.create(

View File

@ -3,7 +3,7 @@
<data noupdate="0">
<template id="ogone_acquirer_button">
<form t-if="acquirer" t-att-action="acquirer.ogone_standard_order_url" method="post" target="_self">
<form t-if="acquirer" t-att-action="tx_url" method="post" target="_self">
<!-- seller -->
<input type='hidden' name='PSPID' t-att-value='tx_values["PSPID"]'/>
<input type='hidden' name='ORDERID' t-att-value='tx_values["ORDERID"]'/>
@ -40,8 +40,12 @@
<input type='hidden' name='DECLINEURL' t-att-value='tx_values["DECLINEURL"]'/>
<input type='hidden' name='EXCEPTIONURL' t-att-value='tx_values["EXCEPTIONURL"]'/>
<input type='hidden' name='CANCELURL' t-att-value='tx_values["CANCELURL"]'/>
<input type="image" name="submit" id="payment_submit"
src="/payment_acquirer_ogone/static/src/img/ogone_logo_plain.gif"/>
<!-- submit -->
<button type="image" name="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_acquirer_ogone/static/src/img/ogone_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/></span>
</button>
</form>
</template>

View File

@ -7,9 +7,8 @@
<field name="model">payment.acquirer</field>
<field name="inherit_id" ref="payment_acquirer.acquirer_form"/>
<field name="arch" type="xml">
<xpath expr='//group[@name="acquirer_base"]' position='after'>
<group string="Ogone Details"
attrs="{'invisible': [('name', '!=', 'ogone')]}">
<xpath expr='//group[@name="acquirer_display"]' position='after'>
<group attrs="{'invisible': [('name', '!=', 'ogone')]}">
<field name="ogone_pspid"/>
<field name="ogone_userid"/>
<field name="ogone_password"/>
@ -29,8 +28,10 @@
<page string="Ogone TX Details">
<group>
<field name="ogone_payid"/>
<!-- <field name="ogone_3ds"/>
<field name="ogone_3ds_html"/> -->
<field name="ogone_3ds"/>
<field name="ogone_3ds_html"/>
<field name="ogone_complus"/>
<field name="ogone_payid"/>
</group>
</page>
</xpath>

View File

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
try:
import simplejson as json
except ImportError:
import json
import logging
import pprint
import requests
from urllib import urlencode
import urllib
import urllib2
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
_logger = logging.getLogger(__name__)
@ -21,6 +21,14 @@ class PaypalController(http.Controller):
_return_url = '/payment/paypal/dpn/'
_cancel_url = '/payment/paypal/cancel/'
def _get_return_url(self, **post):
""" Extract the return URL from the data coming from paypal. """
return_url = post.pop('return_url', '')
if not return_url:
custom = json.loads(post.pop('custom', '{}'))
return_url = custom.get('return_url', '/')
return return_url
def paypal_validate_data(self, **post):
""" Paypal IPN: three steps validation to ensure data correctness
@ -32,14 +40,15 @@ class PaypalController(http.Controller):
Once data is validated, process it. """
res = False
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)
if resp.text == 'VERIFIED':
new_post = dict(post, cmd='_notify-validate')
urequest = urllib2.Request("https://www.sandbox.paypal.com/cgi-bin/webscr", urllib.urlencode(new_post))
uopen = urllib2.urlopen(urequest)
resp = uopen.read()
if resp == 'VERIFIED':
_logger.info('Paypal: validated data')
cr, uid, context = request.cr, request.uid, request.context
res = request.registry['payment.transaction'].form_feedback(cr, uid, post, 'paypal', context=context)
elif resp.text == 'INVALID':
elif resp == 'INVALID':
_logger.warning('Paypal: answered INVALID on data verification')
else:
_logger.warning('Paypal: unrecognized paypal answer, received %s instead of VERIFIED or INVALID' % resp.text)
@ -47,7 +56,7 @@ class PaypalController(http.Controller):
@website.route([
'/payment/paypal/ipn/',
], type='http', auth='public')
], type='http', auth='public', methods=['POST'])
def paypal_ipn(self, **post):
""" Paypal IPN. """
_logger.info('Beginning Paypal IPN form_feedback with post data %s', pprint.pformat(post)) # debug
@ -56,14 +65,11 @@ class PaypalController(http.Controller):
@website.route([
'/payment/paypal/dpn',
], type='http', auth="public")
], type='http', auth="public", methods=['POST'])
def paypal_dpn(self, **post):
""" Paypal DPN """
_logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post)) # debug
return_url = post.pop('return_url', '')
if not return_url:
custom = json.loads(post.pop('custom', '{}'))
return_url = custom.pop('return_url', '/')
return_url = self._get_return_url(**post)
self.paypal_validate_data(**post)
return request.redirect(return_url)
@ -71,11 +77,8 @@ class PaypalController(http.Controller):
'/payment/paypal/cancel',
], type='http', auth="public")
def paypal_cancel(self, **post):
""" TODO
"""
""" When the user cancels its Paypal payment: GET on this route """
cr, uid, context = request.cr, request.uid, request.context
print 'Entering paypal_cancel with post', post
return_url = post.pop('return_url', '/')
print 'return_url', return_url
_logger.info('Beginning Paypal cancel with post data %s', pprint.pformat(post)) # debug
return_url = self._get_return_url(**post)
return request.redirect(return_url)

View File

@ -6,9 +6,11 @@
<field name="name">paypal</field>
<field name="view_template_id" ref="paypal_acquirer_button"/>
<field name="env">test</field>
<field name="message"><![CDATA[
<p>You will be redirected to the Paypal website after cliking on the payment button.</p>]]></field>
<field name="paypal_tx_url">https://www.sandbox.paypal.com/cgi-bin/webscr</field>
<field name="paypal_email_id">dummy</field>
<field name="paypal_username">dummy</field>
<field name="paypal_seller_id">dummy</field>
<field name="paypal_api_username">dummy</field>
<field name="paypal_api_password">dummy</field>
</record>

View File

@ -1,11 +1,5 @@
# -*- coding: utf-'8' "-*-"
from openerp.addons.payment_acquirer.models import payment_acquirer
from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
from openerp.addons.payment_acquirer_paypal.controllers.main import PaypalController
from openerp.osv import osv, fields
from openerp.tools.float_utils import float_compare
import base64
try:
import simplejson as json
@ -16,16 +10,33 @@ import urlparse
import urllib
import urllib2
from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
from openerp.addons.payment_acquirer_paypal.controllers.main import PaypalController
from openerp.osv import osv, fields
from openerp.tools.float_utils import float_compare
_logger = logging.getLogger(__name__)
class AcquirerPaypal(osv.Model):
_inherit = 'payment.acquirer'
def _get_paypal_urls(self, cr, uid, env, context=None):
""" Paypal URLS """
if env == 'prod':
return {
'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr',
'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token',
}
else:
return {
'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr',
'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token',
}
_columns = {
'paypal_email_id': fields.char('Email ID', required_if_provider='paypal'),
'paypal_username': fields.char('Username', required_if_provider='paypal'),
'paypal_tx_url': fields.char('Transaction URL', required_if_provider='paypal'),
'paypal_seller_id': fields.char('Seller ID', required_if_provider='paypal'),
'paypal_use_ipn': fields.boolean('Use IPN'),
# Server 2 server
'paypal_api_enabled': fields.boolean('Use Rest API'),
@ -36,7 +47,6 @@ class AcquirerPaypal(osv.Model):
}
_defaults = {
'paypal_tx_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr',
'paypal_use_ipn': True,
'fees_active': False,
'fees_dom_fixed': 0.35,
@ -65,44 +75,38 @@ class AcquirerPaypal(osv.Model):
fees = amount * (1 + acquirer.fees_int_var / 100.0) + acquirer.fees_int_fixed - amount
return fees
def paypal_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None):
if partner_values is None:
partner_values = {}
def paypal_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
acquirer = self.browse(cr, uid, id, context=context)
partner = None
if partner_id:
partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
tx_values = {
paypal_tx_values = dict(tx_values)
paypal_tx_values.update({
'cmd': '_xclick',
'business': acquirer.paypal_email_id,
'item_name': reference,
'item_number': reference,
'amount': amount,
'currency_code': currency and currency.name or 'EUR',
'address1': payment_acquirer._partner_format_address(partner and partner.street or partner_values.get('street', ''), partner and partner.street2 or partner_values.get('street2', '')),
'city': partner and partner.city or partner_values.get('city', ''),
'country': partner and partner.country_id and partner.country_id.name or partner_values.get('country_name', ''),
'email': partner and partner.email or partner_values.get('email', ''),
'zip': partner and partner.zip or partner_values.get('zip', ''),
'first_name': payment_acquirer._partner_split_name(partner and partner.name or partner_values.get('name', ''))[0],
'last_name': payment_acquirer._partner_split_name(partner and partner.name or partner_values.get('name', ''))[1],
'item_name': tx_values['reference'],
'item_number': tx_values['reference'],
'amount': tx_values['amount'],
'currency_code': tx_values['currency'] and tx_values['currency'].name or '',
'address1': partner_values['address'],
'city': partner_values['city'],
'country': partner_values['country'] and partner_values['country'].name or '',
'email': partner_values['email'],
'zip': partner_values['zip'],
'first_name': partner_values['first_name'],
'last_name': partner_values['last_name'],
'return': '%s' % urlparse.urljoin(base_url, PaypalController._return_url),
'notify_url': '%s' % urlparse.urljoin(base_url, PaypalController._notify_url),
'cancel_return': '%s' % urlparse.urljoin(base_url, PaypalController._cancel_url),
}
})
if acquirer.fees_active:
tx_values['handling'] = '%.2f' % tx_custom_values.pop('fees', 0.0)
if tx_custom_values and tx_custom_values.get('return_url'):
tx_values['custom'] = json.dumps({'return_url': '%s' % tx_custom_values.pop('return_url')})
if tx_custom_values:
tx_values.update(tx_custom_values)
return tx_values
paypal_tx_values['handling'] = '%.2f' % paypal_tx_values.pop('fees', 0.0)
if paypal_tx_values.get('return_url'):
paypal_tx_values['custom'] = json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')})
return partner_values, paypal_tx_values
def paypal_get_form_action_url(self, cr, uid, id, context=None):
acquirer = self.browse(cr, uid, id, context=context)
return acquirer.paypal_tx_url
return self._get_paypal_urls(cr, uid, acquirer.env, context=context)['paypal_form_url']
def _paypal_s2s_get_access_token(self, cr, uid, ids, context=None):
"""
@ -144,28 +148,6 @@ class TxPaypal(osv.Model):
# FORM RELATED METHODS
# --------------------------------------------------
def paypal_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None):
tx = self.browse(cr, uid, id, context=context)
tx_data = {
'item_name': tx.name,
'first_name': payment_acquirer._partner_split_name(tx.partner_name)[0],
'last_name': payment_acquirer._partner_split_name(tx.partner_name)[0],
'email': tx.partner_email,
'zip': tx.partner_zip,
'address1': tx.partner_address,
'city': tx.partner_city,
'country': tx.partner_country_id and tx.partner_country_id.name or '',
}
if tx_custom_values:
tx_data.update(tx_custom_values)
return self.pool['payment.acquirer'].paypal_form_generate_values(
cr, uid, tx.acquirer_id.id,
tx.reference, tx.amount, tx.currency_id,
tx_custom_values=tx_data,
context=context
)
def _paypal_form_get_tx_from_data(self, cr, uid, data, context=None):
reference, txn_id = data.get('item_number'), data.get('txn_id')
if not reference or not txn_id:
@ -186,9 +168,8 @@ class TxPaypal(osv.Model):
return self.browse(cr, uid, tx_ids[0], context=context)
def _paypal_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
# TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details
invalid_parameters = []
if data.get('notify_version')[0] != '2.6':
if data.get('notify_version')[0] != '3.4':
_logger.warning(
'Received a notification from Paypal with version %s instead of 2.6. This could lead to issues when managing it.' %
data.get('notify_version')
@ -197,60 +178,48 @@ class TxPaypal(osv.Model):
_logger.warning(
'Received a notification from Paypal using sandbox'
),
# TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details
if tx.acquirer_reference and data.get('txn_id') != tx.acquirer_reference:
invalid_parameters.append(('txn_id', data.get('txn_id'), tx.acquirer_reference))
# check what is buyed
if float_compare(float(data.get('mc_gross', '0.0')), tx.amount, 2) != 0:
invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % tx.amount))
if float_compare(float(data.get('mc_gross', '0.0')), (tx.amount + tx.fees), 2) != 0:
invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % tx.amount)) # mc_gross is amount + fees
if data.get('mc_currency') != tx.currency_id.name:
invalid_parameters.append(('mc_currency', data.get('mc_currency'), tx.currency_id.name))
# if parameters.get('payment_fee') != tx.payment_fee:
# invalid_parameters.append(('payment_fee', tx.payment_fee))
# if parameters.get('quantity') != tx.quantity:
# invalid_parameters.append(('mc_currency', tx.quantity))
# if parameters.get('shipping') != tx.shipping:
# invalid_parameters.append(('shipping', tx.shipping))
invalid_parameters.append(('mc_currency', data.get('mc_currency'), tx.currency_id.name))
if 'handling_amount' in data and float_compare(float(data.get('handling_amount')), tx.fees, 2) != 0:
invalid_parameters.append(('handling_amount', data.get('handling_amount'), tx.fees))
# check buyer
# if parameters.get('payer_id') != tx.payer_id:
# invalid_parameters.append(('mc_gross', tx.payer_id))
# if parameters.get('payer_email') != tx.payer_email:
# invalid_parameters.append(('payer_email', tx.payer_email))
if tx.partner_reference and data.get('payer_id') != tx.partner_reference:
invalid_parameters.append(('payer_id', data.get('payer_id'), tx.partner_reference))
# check seller
# if parameters.get('receiver_email') != tx.receiver_email:
# invalid_parameters.append(('receiver_email', tx.receiver_email))
# if parameters.get('receiver_id') != tx.receiver_id:
# invalid_parameters.append(('receiver_id', tx.receiver_id))
if data.get('receiver_email') != tx.acquirer_id.paypal_email_id:
invalid_parameters.append(('receiver_email', data.get('receiver_email'), tx.acquirer_id.paypal_email_id))
if data.get('receiver_id') != tx.acquirer_id.paypal_seller_id:
invalid_parameters.append(('receiver_id', data.get('receiver_id'), tx.acquirer_id.paypal_seller_id))
return invalid_parameters
def _paypal_form_validate(self, cr, uid, tx, data, context=None):
status = data.get('payment_status')
data = {
'acquirer_reference': data.get('txn_id'),
'paypal_txn_type': data.get('payment_type'),
'partner_reference': data.get('payer_id')
}
if status in ['Completed', 'Processed']:
_logger.info('Validated Paypal payment for tx %s: set as done' % (tx.reference))
tx.write({
'state': 'done',
'date_validate': data.get('payment_date', fields.datetime.now()),
'paypal_txn_id': data['txn_id'],
'paypal_txn_type': data.get('express_checkout'),
})
return True
data.update(state='done', date_validate=data.get('payment_date', fields.datetime.now()))
return tx.write(data)
elif status in ['Pending', 'Expired']:
_logger.info('Received notification for Paypal payment %s: set as pending' % (tx.reference))
tx.write({
'state': 'pending',
'state_message': data.get('pending_reason', ''),
'paypal_txn_id': data['txn_id'],
'paypal_txn_type': data.get('express_checkout'),
})
return True
data.udpate(state='pending', state_message=data.get('pending_reason', ''))
return tx.write(data)
else:
error = 'Received unrecognized status for Paypal payment %s: %s, set as error' % (tx.reference, status)
_logger.info(error)
tx.write({
'state': 'error',
'state_message': error,
'paypal_txn_id': data['txn_id'],
'paypal_txn_type': data.get('express_checkout'),
})
return False
data.update(state='error', state_message=error)
return tx.write(data)
# --------------------------------------------------
# SERVER2SERVER RELATED METHODS

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -15,22 +15,10 @@ class PaypalCommon(PaymentAcquirerCommon):
def setUp(self):
super(PaypalCommon, self).setUp()
cr, uid = self.cr, self.uid
self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url')
model, self.paypal_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_paypal', 'paypal_acquirer_button')
# create a new ogone account
self.paypal_id = self.payment_acquirer.create(
cr, uid, {
'name': 'paypal',
'env': 'test',
'view_template_id': self.paypal_view_id,
'paypal_email_id': 'dummy',
'paypal_username': 'dummy',
'paypal_api_enabled': True,
'paypal_api_username': 'dummy',
'paypal_api_password': 'dummy',
})
# get the paypal account
model, self.paypal_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_paypal', 'payment_acquirer_paypal')
# tde+seller@openerp.com - tde+buyer@openerp.com - tde+buyer-it@openerp.com
# some CC
@ -50,6 +38,9 @@ class PaypalServer2Server(PaypalCommon):
def test_00_tx_management(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid things
paypal = self.payment_acquirer.browse(self.cr, self.uid, self.paypal_id, None)
self.assertEqual(paypal.env, 'test', 'test without test env')
res = self.payment_acquirer._paypal_s2s_get_access_token(cr, uid, [self.paypal_id], context=context)
self.assertTrue(res[self.paypal_id] is not False, 'paypal: did not generate access token')
@ -78,24 +69,10 @@ class PaypalServer2Server(PaypalCommon):
class PaypalForm(PaypalCommon):
def test_00_paypal_acquirer(self):
cr, uid, context = self.cr, self.uid, {}
# forgot some mandatory fields: should crash
with self.assertRaises(except_orm):
self.payment_acquirer.create(
cr, uid, {
'name': 'paypal',
'env': 'test',
'view_template_id': self.paypal_view_id,
'paypal_email_id': 'tde+paypal-facilitator@openerp.com',
}, context=context)
paypal = self.payment_acquirer.browse(self.cr, self.uid, self.paypal_id, None)
self.assertEqual(paypal.env, 'test', 'test without test env')
def test_10_paypal_form_render(self):
cr, uid, context = self.cr, self.uid, {}
# be sure not to do stupid things
self.payment_acquirer.write(cr, uid, self.paypal_id, {'fees_active': False}, context)
paypal = self.payment_acquirer.browse(cr, uid, self.paypal_id, context)
self.assertEqual(paypal.env, 'test', 'test without test env')
@ -103,6 +80,14 @@ class PaypalForm(PaypalCommon):
# Test: button direct rendering
# ----------------------------------------
# render the button
res = self.payment_acquirer.render(
cr, uid, self.paypal_id,
'test_ref0', 0.01, self.currency_euro_id,
partner_id=None,
partner_values=self.buyer_values,
context=context)
form_values = {
'cmd': '_xclick',
'business': 'tde+paypal-facilitator@openerp.com',
@ -122,14 +107,6 @@ class PaypalForm(PaypalCommon):
'cancel_return': '%s' % urlparse.urljoin(self.base_url, PaypalController._cancel_url),
}
# render the button
res = self.payment_acquirer.render(
cr, uid, self.paypal_id,
'test_ref0', 0.01, self.currency_euro,
partner_id=None,
partner_values=self.buyer_values,
context=context)
# check form result
tree = objectify.fromstring(res)
self.assertEqual(tree.get('action'), 'https://www.sandbox.paypal.com/cgi-bin/webscr', 'paypal: wrong form POST url')
@ -139,19 +116,24 @@ class PaypalForm(PaypalCommon):
self.assertEqual(
form_input.get('value'),
form_values[form_input.get('name')],
'paypal: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')])
'paypal: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])
)
def test_11_paypal_form_with_fees(self):
cr, uid, context = self.cr, self.uid, {}
self.payment_acquirer.write(cr, uid, self.paypal_id, {
'fees_active': True,
}, context)
# be sure not to do stupid things
paypal = self.payment_acquirer.browse(self.cr, self.uid, self.paypal_id, None)
self.assertEqual(paypal.env, 'test', 'test without test env')
# update acquirer: compute fees
self.payment_acquirer.write(cr, uid, self.paypal_id, {
'fees_active': True,
'fees_dom_fixed': 1.0,
'fees_dom_var': 0.35,
'fees_int_fixed': 1.5,
'fees_int_var': 0.50,
}, context)
# render the button
res = self.payment_acquirer.render(
cr, uid, self.paypal_id,
@ -167,7 +149,7 @@ class PaypalForm(PaypalCommon):
for form_input in tree.input:
if form_input.get('name') in ['handling']:
handling_found = True
self.assertEqual(form_input.get('value'), '0.84', 'paypal: wrong computed fees')
self.assertEqual(form_input.get('value'), '1.56', 'paypal: wrong computed fees')
self.assertTrue(handling_found, 'paypal: fees_active did not add handling input in rendered form')
@mute_logger('openerp.addons.payment_acquirer_paypal.models.paypal', 'ValidationError')
@ -233,6 +215,7 @@ class PaypalForm(PaypalCommon):
'currency_id': self.currency_euro_id,
'reference': 'test_ref_2',
'partner_name': 'Norbert Buyer',
'partner_country_id': self.country_france_id,
}, context=context
)
# validate it

View File

@ -10,14 +10,16 @@
<xpath expr='//group[@name="acquirer_display"]' position='after'>
<group attrs="{'invisible': [('name', '!=', 'paypal')]}">
<group>
<field name="paypal_email_id"/>
<field name="paypal_username"/>
<field name="paypal_use_ipn"/>
<field name="paypal_api_enabled"/>
<field name="paypal_api_username"
attrs="{'invisible': [('paypal_api_enabled', '=', False)]}"/>
<field name="paypal_api_password"
attrs="{'invisible': [('paypal_api_enabled', '=', False)]}"/>
<group>
<field name="paypal_email_id"/>
<field name="paypal_seller_id"/>
<field name="paypal_use_ipn"/>
<field name="paypal_api_enabled"/>
<field name="paypal_api_username"
attrs="{'invisible': [('paypal_api_enabled', '=', False)]}"/>
<field name="paypal_api_password"
attrs="{'invisible': [('paypal_api_enabled', '=', False)]}"/>
</group>
</group>
</group>
</xpath>

View File

@ -3,16 +3,14 @@
<data noupdate="0">
<template id="paypal_acquirer_button">
<form t-if="acquirer.paypal_email_id" t-att-action="acquirer.paypal_tx_url" method="post" target="_self">
<form t-if="acquirer.paypal_email_id" t-att-action="tx_url" method="post" target="_self">
<input type="hidden" name="cmd" t-att-value="tx_values['cmd']"/>
<input type="hidden" name="business" t-att-value="tx_values['business']"/>
<input type="hidden" name="item_name" t-att-value="tx_values['item_name']"/>
<input type="hidden" name="item_number" t-att-value="tx_values['item_number']"/>
<input type="hidden" name="amount" t-att-value="tx_values['amount']"/>
<t t-if="'handling' in tx_values">
<input type="hidden" name="handling" t-att-value="tx_values['handling']"/>
</t>
<input type="hidden" name="amount" t-att-value="tx_values['amount']"/>
<input t-if="'handling' in tx_values" type="hidden" name="handling"
t-att-value="tx_values.get('handling')"/>
<input type="hidden" name="currency_code" t-att-value="tx_values['currency_code']"/>
<!-- partner / address data -->
<input type="hidden" name="address1" t-att-value="tx_values['address1']"/>
@ -23,20 +21,21 @@
<input type="hidden" name="last_name" t-att-value="tx_values['last_name']"/>
<input type="hidden" name="zip" t-att-value="tx_values['zip']"/>
<!-- after payment parameters -->
<t t-if='tx_values.get("custom")'>
<input type='hidden' name="custom" t-att-value='tx_values["custom"]'/>
</t>
<input t-if='tx_values.get("custom")' type='hidden' name="custom"
t-att-value='tx_values.get("custom")'/>
<!-- URLs -->
<input t-if="tx_values.get('return')" type='hidden' name='return'
t-att-value="tx_values['return']"/>
t-att-value="tx_values.get('return')"/>
<input t-if="acquirer.paypal_use_ipn" type='hidden' name='notify_url'
t-att-value="tx_values['notify_url']"/>
t-att-value="tx_values.get('notify_url')"/>
<input t-if="tx_values.get('cancel_return')" type="hidden" name="cancel_return"
t-att-value="tx_values['cancel_return']"/>
<!-- button -->
<input type="image" name="submit" id="payment_submit"
width="100px"
src="/payment_acquirer_paypal/static/src/img/paypal_logo.png"/>
t-att-value="tx_values.get('cancel_return')"/>
<!-- submit -->
<button type="image" name="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_acquirer_paypal/static/src/img/paypal_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/></span>
</button>
</form>
</template>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<data noupdate="1">
<record id="payment_acquirer_transfer" model="payment.acquirer">
<field name="name">transfer</field>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -3,16 +3,19 @@
<data noupdate="0">
<template id="transfer_acquirer_button">
<div t-field="acquirer.message"/>
<form t-if="acquirer" action="/payment/transfer/feedback" method="post" target="_self">
<form t-if="acquirer" t-att-action="tx_url" method="post" target="_self">
<t t-if="tx_values.get('return_url')">
<input type='hidden' name='return_url' t-att-value='tx_values["return_url"]'/>
</t>
<input type='hidden' name='reference' t-att-value='reference'/>
<input type='hidden' name='amount' t-att-value='amount'/>
<input type='hidden' name='currency' t-att-value='currency.name'/>
<input type="submit" name="submit" id="payment_submit"
class="btn btn-primary" value="Confirmer"/>
<!-- submit -->
<button type="image" name="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_acquirer_transfer/static/src/img/transfer_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/></span>
</button>
</form>
</template>

View File

@ -96,7 +96,7 @@ class Website(openerp.addons.web.controllers.main.Home):
if request.context['editable']:
page = 'website.page_404'
else:
return request.registry['ir.http']._handle_404(e)
return request.registry['ir.http']._handle_exception(e, 404)
return request.website.render(page, values)
@website.route('/website/customize_template_toggle', type='json', auth='user')

View File

@ -61,7 +61,7 @@ class ir_http(orm.AbstractModel):
request.lang = request.context['lang'] = path.pop(1)
path = '/'.join(path) or '/'
return self.reroute(path)
return self._handle_404()
return self._handle_exception(code=404)
return super(ir_http, self)._dispatch()
def reroute(self, path):
@ -79,37 +79,44 @@ class ir_http(orm.AbstractModel):
return self._dispatch()
def _handle_403(self, exception):
def _handle_exception(self, exception=None, code=500):
if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.response:
return exception.response
if getattr(request, 'cms', False) and request.website:
logger.warn("403 Forbidden:\n\n%s", traceback.format_exc(exception))
self._auth_method_public()
return self._render_error(403, {
'error': exception.message
})
raise exception
values = dict(
exception=exception,
traceback=traceback.format_exc(exception),
)
if exception:
if isinstance(exception, openerp.exceptions.AccessError):
code = 403
else:
code = getattr(exception, 'code', code)
values.update(
qweb_template=getattr(exception, 'qweb_template', None),
qweb_node=getattr(exception, 'qweb_node', None),
qweb_eval=getattr(exception, 'qweb_eval', None),
)
if code == 500:
logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
elif code == 403:
logger.warn("403 Forbidden:\n\n%s", values['traceback'])
def _handle_404(self, exception=None):
if getattr(request, 'cms', False) and request.website:
return self._render_error(404)
raise request.not_found()
values.update(
status_message=werkzeug.http.HTTP_STATUS_CODES[code],
status_code=code,
)
def _handle_500(self, exception):
if getattr(request, 'cms', False) and request.website:
logger.error("500 Internal Server Error:\n\n%s", traceback.format_exc(exception))
return self._render_error(500, {
'exception': exception,
'traceback': traceback.format_exc(),
'qweb_template': getattr(exception, 'qweb_template', None),
'qweb_node': getattr(exception, 'qweb_node', None),
'qweb_eval': getattr(exception, 'qweb_eval', None),
})
raise exception
if not request.uid:
self._auth_method_public()
def _render_error(self, code, values=None):
return werkzeug.wrappers.Response(
request.website._render('website.%s' % code, values),
status=code,
content_type='text/html;charset=utf-8')
try:
html = request.website._render('website.%s' % code, values)
except:
html = request.website._render('website.http_error', values)
return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
return super(ir_http, self)._handle_exception(exception)
class ModelConverter(ir.ir_http.ModelConverter):
def __init__(self, url_map, model=False):

View File

@ -121,33 +121,49 @@ class Date(orm.AbstractModel):
_name = 'website.qweb.field.date'
_inherit = ['website.qweb.field', 'ir.qweb.field.date']
def attributes(self, cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context,
context=None):
attrs = super(Date, self).attributes(
cr, uid, field_name, record, options, source_element, g_att, t_att,
qweb_context, context=None)
return itertools.chain(attrs, [('data-oe-original', record[field_name])])
def from_html(self, cr, uid, model, column, element, context=None):
lang = self.user_lang(cr, uid, context=context)
in_format = lang.date_format.encode('utf-8')
value = element.text_content().strip()
try:
dt = datetime.datetime.strptime(in_format, value)
except ValueError:
dt = parse_fuzzy(in_format, value)
if not value: return False
return dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
datetime.datetime.strptime(value, DEFAULT_SERVER_DATE_FORMAT)
return value
class DateTime(orm.AbstractModel):
_name = 'website.qweb.field.datetime'
_inherit = ['website.qweb.field', 'ir.qweb.field.datetime']
def attributes(self, cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context,
context=None):
column = record._model._all_columns[field_name].column
value = record[field_name]
if isinstance(value, basestring):
value = datetime.datetime.strptime(
value, DEFAULT_SERVER_DATETIME_FORMAT)
value = column.context_timestamp(
cr, uid, timestamp=value, context=context)
attrs = super(DateTime, self).attributes(
cr, uid, field_name, record, options, source_element, g_att, t_att,
qweb_context, context=None)
return itertools.chain(attrs, [
('data-oe-original', value.strftime(openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT))
])
def from_html(self, cr, uid, model, column, element, context=None):
lang = self.user_lang(cr, uid, context=context)
in_format = (u"%s %s" % (lang.date_format, lang.time_format)).encode('utf-8')
value = element.text_content().strip()
try:
dt = datetime.datetime.strptime(in_format, value)
except ValueError:
dt = parse_fuzzy(in_format, value)
if not value: return False
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
datetime.datetime.strptime(value, DEFAULT_SERVER_DATETIME_FORMAT)
return value
class Text(orm.AbstractModel):
_name = 'website.qweb.field.text'
@ -302,3 +318,34 @@ class Monetary(orm.AbstractModel):
return float(value.replace(lang.thousands_sep, '')
.replace(lang.decimal_point, '.'))
class Duration(orm.AbstractModel):
_name = 'website.qweb.field.duration'
_inherit = [
'ir.qweb.field.duration',
'website.qweb.field.float',
]
def attributes(self, cr, uid, field_name, record, options,
source_element, g_att, t_att, qweb_context,
context=None):
attrs = super(Duration, self).attributes(
cr, uid, field_name, record, options, source_element, g_att, t_att,
qweb_context, context=None)
return itertools.chain(attrs, [('data-oe-original', record[field_name])])
def from_html(self, cr, uid, model, column, element, context=None):
value = element.text_content().strip()
# non-localized value
return float(value)
class RelativeDatetime(orm.AbstractModel):
_name = 'website.qweb.field.relative'
_inherit = [
'ir.qweb.field.relative',
'website.qweb.field.datetime',
]
# get formatting from ir.qweb.field.relative but edition/save from datetime

View File

@ -80,6 +80,23 @@ def urlplus(url, params):
def quote_plus(value):
return urllib.quote_plus(value.encode('utf-8') if isinstance(value, unicode) else str(value))
def preload_records(*args, **kwargs):
""" This helper allows to check the existence and prefetch one or many browse_records at once.
If the browse record(s) does not exists in the db it will raise a LazyResponse
"""
field = kwargs.pop('field', 'name')
on_error = kwargs.pop('on_error', 'website.404')
error_code = kwargs.pop('error_code', 404)
try:
for arg in args:
if isinstance(arg, orm.browse_record):
arg[field]
elif isinstance(arg, orm.browse_record_list):
[record[field] for record in arg]
except:
lazy_error = request.website.render(on_error, status_code=error_code)
raise werkzeug.exceptions.HTTPException(response=lazy_error)
class website(osv.osv):
def _get_menu_website(self, cr, uid, ids, context=None):
# IF a menu is changed, update all websites

View File

@ -206,10 +206,6 @@ ul.oe_menu_editor .disclose {
position: static !important;
}
.cke_widget_drag_handler_container {
display: none !important;
}
.cke_widget_editable:empty:after {
content: " ";
white-space: pre-wrap;

View File

@ -168,9 +168,6 @@ ul.oe_menu_editor
// Breaks completely horribly crazy products listing page, so take it out.
.cke_widget_wrapper
position: static !important
// "remove" drag & drop of CKE widgets
.cke_widget_drag_handler_container
display: none !important
// prevent inline widgets from entirely disappearing when their (textual)
// content is removed
.cke_widget_editable:empty:after

View File

@ -323,14 +323,26 @@
init: function (editor) {
editor.widgets.add('oeref', {
editables: { text: '*' },
draggable: false,
upcast: function (el) {
return el.attributes['data-oe-type']
&& el.attributes['data-oe-type'] !== 'monetary';
var matches = el.attributes['data-oe-type'] && el.attributes['data-oe-type'] !== 'monetary';
if (!matches) { return false; }
if (el.attributes['data-oe-original']) {
while (el.children.length) {
el.children[0].remove();
}
el.add(new CKEDITOR.htmlParser.text(
el.attributes['data-oe-original']
));
}
return true;
},
});
editor.widgets.add('monetary', {
editables: { text: 'span.oe_currency_value' },
draggable: false,
upcast: function (el) {
return el.attributes['data-oe-type'] === 'monetary';

View File

@ -404,6 +404,74 @@
</t>
</template>
<template id="http_error">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure">
<h1 class="container mt32"><t t-esc="status_code"/>: <t t-esc="status_message"/></h1>
</div>
<t t-if="editable or request.debug">
<t t-call="website.http_error_debug"/>
</t>
</div>
</t>
</template>
<template id="http_error_debug">
<div class="container panel-group mb32 mt32" id="debug_infos">
<div class="panel panel-default" t-if="exception">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#debug_infos" href="#error_main">
Error
</a>
</h4>
</div>
<div id="error_main" class="panel-collapse collapse in">
<div class="panel-body">
<p t-if="website_controller">The following error was raised in the website controller <code t-esc="website_controller"/></p>
<p><strong>Error message:</strong> <pre t-esc="exception.message"/></p>
</div>
</div>
</div>
<div class="panel panel-default" t-if="qweb_template">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#adebug_infos" href="#error_qweb">
QWeb
</a>
</h4>
</div>
<div id="error_qweb" class="panel-collapse collapse">
<div class="panel-body">
<p>
The error occured while rendering the template <code t-esc="qweb_template"/>
<t t-if="qweb_eval">and evaluating the following expression: <code t-esc="qweb_eval"/></t>
</p>
<t t-if="qweb_node">
<pre id="exception_node" t-esc="qweb_node.toxml()"/>
</t>
</div>
</div>
</div>
<div class="panel panel-default" t-if="traceback">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#adebug_infos" href="#error_traceback">
Traceback
</a>
</h4>
</div>
<div id="error_traceback" class="panel-collapse collapse">
<div class="panel-body">
<pre id="exception_traceback" t-esc="traceback"/>
</div>
</div>
</div>
</div>
</template>
<template id="404">
<t t-call="website.layout">
<div id="wrap">
@ -423,70 +491,16 @@
</ul>
</div>
</div>
<t t-if="request.debug">
<t t-call="website.http_error_debug"/>
</t>
</div>
</t>
</template>
<template id="500">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure">
<h1 class="container mt32">500: Internal Server Error!</h1>
</div>
<div class="container panel-group" id="debug_infos" t-if="editable">
<div class="panel panel-default" t-if="exception">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#debug_infos" href="#error_main">
Error
</a>
</h4>
</div>
<div id="error_main" class="panel-collapse collapse in">
<div class="panel-body">
<p t-if="website_controller">The following error was raised in the website controller <code t-esc="website_controller"/></p>
<p><strong>Error message:</strong> <t t-esc="exception.message"/></p>
</div>
</div>
</div>
<div class="panel panel-default" t-if="qweb_template">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#adebug_infos" href="#error_qweb">
QWeb
</a>
</h4>
</div>
<div id="error_qweb" class="panel-collapse collapse">
<div class="panel-body">
<p>
The error occured while rendering the template <code t-esc="qweb_template"/>
<t t-if="qweb_eval">and evaluating the following expression: <code t-esc="qweb_eval"/></t>
</p>
<t t-if="qweb_node">
<pre id="exception_node" t-esc="qweb_node.toxml()"/>
</t>
</div>
</div>
</div>
<div class="panel panel-default" t-if="traceback">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#adebug_infos" href="#error_traceback">
Traceback
</a>
</h4>
</div>
<div id="error_traceback" class="panel-collapse collapse">
<div class="panel-body">
<pre id="exception_traceback" t-esc="traceback"/>
</div>
</div>
</div>
</div>
</div>
</t>
<t t-call="website.http_error"/>
</template>
<template id="403">
@ -498,9 +512,6 @@
The page you were looking for could not be
authorized.
</p>
<t t-if="error and editable">
<pre t-esc="error"/>
</t>
<p>
Maybe you were looking for one of these
popular pages ?
@ -510,6 +521,9 @@
<li><a href="/page/website.contactus/">Contact Us</a></li>
</ul>
</div>
<t t-if="editable or request.debug">
<t t-call="website.http_error_debug"/>
</t>
</div>
</t>
</template>

View File

@ -97,6 +97,7 @@ class WebsiteBlog(http.Controller):
"""
BYPAGE = 10
website.preload_records(category, tag)
cr, uid, context = request.cr, request.uid, request.context
blog_post_obj = request.registry['blog.post']
@ -178,6 +179,8 @@ class WebsiteBlog(http.Controller):
- 'nav_list': a dict [year][month] for archives navigation
"""
website.preload_records(blog_post)
pager_url = "/blogpost/%s" % blog_post.id
pager = request.website.pager(

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="website.secure_layout">
<template id="secure_layout" inherit_id="website.secure_layout">
<xpath expr="//t[@id='editable_scripts_hook']" position="inside">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.editor.js" t-ignore="true"></script>
<script type="text/javascript" src="/website_blog/static/src/js/website.tour.blog.js" t-ignore="true"></script>

View File

@ -14,5 +14,5 @@ OpenERP Contact Form
'data/website_crm_data.xml',
'views/website_crm.xml',
],
'installable': False,
'installable': True,
}

View File

@ -7,14 +7,17 @@ from openerp.addons.web import http
from openerp.tools.translate import _
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp.addons.website_partner.controllers import main as website_partner
class WebsiteCrmPartnerAssign(http.Controller):
_references_per_page = 20
@website.route([
'/partners/', '/partners/page/<int:page>/',
'/partners/country/<int:country_id>', '/partners/country/page/<int:country_id>/',
'/partners/',
'/partners/page/<int:page>/',
'/partners/country/<int:country_id>',
'/partners/country/page/<int:country_id>/',
], type='http', auth="public", multilang=True)
def partners(self, country_id=0, page=0, **post):
country_obj = request.registry['res.country']
@ -24,7 +27,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
country = None
# format displayed membership lines domain
base_partner_domain = [('is_company', '=', True)]
base_partner_domain = [('is_company', '=', True), ('grade_id', '!=', False), ('website_published', '=', True)]
partner_domain = list(base_partner_domain)
if grade_id and grade_id != "all":
partner_domain += [('grade_id', '=', int(grade_id))] # try/catch int
@ -36,26 +39,26 @@ class WebsiteCrmPartnerAssign(http.Controller):
# format pager
partner_ids = partner_obj.search(
request.cr, request.uid, partner_domain,
request.cr, openerp.SUPERUSER_ID, partner_domain,
context=request.context)
pager = request.website.pager(url="/partners/", total=len(partner_ids), page=page, step=self._references_per_page, scope=7, url_args=post)
# search for partners to display
partner_ids = partner_obj.search(
request.cr, request.uid, partner_domain,
request.cr, openerp.SUPERUSER_ID, partner_domain,
context=request.context,
limit=self._references_per_page, offset=pager['offset'],
order="grade_id ASC,partner_weight DESC")
google_map_partner_ids = ",".join([str(p) for p in partner_ids])
partners = partner_obj.browse(
request.cr, request.uid, partner_ids, request.context)
partners_data = partner_obj.read(
request.cr, openerp.SUPERUSER_ID, partner_ids, website_partner.white_list, context=request.context)
# group by country
countries = partner_obj.read_group(
request.cr, request.uid, base_partner_domain, ["id", "country_id"],
request.cr, openerp.SUPERUSER_ID, base_partner_domain, ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
countries_partners = partner_obj.search(
request.cr, request.uid, base_partner_domain,
request.cr, openerp.SUPERUSER_ID, base_partner_domain,
context=request.context, count=True)
countries.insert(0, {
'country_id_count': countries_partners,
@ -64,10 +67,10 @@ class WebsiteCrmPartnerAssign(http.Controller):
# group by grade
grades = partner_obj.read_group(
request.cr, request.uid, base_partner_domain, ["id", "grade_id"],
request.cr, openerp.SUPERUSER_ID, base_partner_domain, ["id", "grade_id"],
groupby="grade_id", orderby="grade_id", context=request.context)
grades_partners = partner_obj.search(
request.cr, request.uid, base_partner_domain,
request.cr, openerp.SUPERUSER_ID, base_partner_domain,
context=request.context, count=True)
grades.insert(0, {
'grade_id_count': grades_partners,
@ -80,7 +83,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
'current_country': country,
'grades': grades,
'grade_id': grade_id,
'partners': partners,
'partners_data': partners_data,
'google_map_partner_ids': google_map_partner_ids,
'pager': pager,
'searches': post,
@ -88,16 +91,9 @@ class WebsiteCrmPartnerAssign(http.Controller):
}
return request.website.render("website_crm_partner_assign.index", values)
@website.route(['/partners/<int:partner_id>/'], type='http', auth="public", multilang=True)
def partners_ref(self, partner_id=0, **post):
partner_obj = request.registry['res.partner']
partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
if not partner_ids:
return self.members(post)
values = {
'partner_id': partner_obj.browse(
request.cr, request.uid, partner_ids[0],
context=dict(request.context, show_address=True)),
}
@website.route(['/partners/<model("res.partner"):partner>/'], type='http', auth="public", multilang=True)
def partners_ref(self, partner, **post):
values = website_partner.get_partner_template_value(partner)
if not values:
return self.partners(**post)
return request.website.render("website_crm_partner_assign.partner", values)

View File

@ -75,22 +75,20 @@
</div>
</div>
<div>
<t t-foreach="partners" t-as="partner_id">
<t t-if="internal_gid != partner_id.grade_id.id">
<t t-set="internal_gid" t-value="partner_id.grade_id.id"/>
<t t-foreach="partners_data" t-as="partner_data">
<t t-if="internal_gid != partner_data['grade_id'][1]">
<h3 class="text-center">
<span t-field="partner_id.grade_id"/> Partners
<span t-esc="partner_data['grade_id'][1]"/> Partners
<t t-if="current_country"> in <t t-esc="current_country.name"/></t>
</h3>
</t>
<div class="media thumbnail">
<t t-call="website.publish_management"><t t-set="object" t-value="partner_id"/></t>
<a class="pull-left" t-href="/partners/#{ partner_id.id }/">
<img class="media-object" t-att-src="partner_id.img('image_small')"/>
<a class="pull-left" t-href="/partners/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
<img class="media-object" t-attf-src="data:image/png;base64,#{partner_data['image_small']}"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-href="/partners/#{ partner_id.id }/"><span t-field="partner_id.parent_id"/> <span t-field="partner_id.name"/></a> - <span t-field="partner_id.grade_id"/>
<div t-field="partner_id.website_short_description"/>
<a class="media-heading" t-href="/partners/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/"><t t-if="partner_data['parent_id']"><span t-esc="partner_data['parent_id'][1]"/></t> <span t-esc="partner_data['name']"/></a> - <span t-esc="partner_data['grade_id'][1]"/>
<div t-esc="partner_data['website_short_description']"/>
</div>
</div>
</t>

View File

@ -40,5 +40,5 @@ OpenERP Customer References
'views/website_customer.xml',
],
'qweb': [],
'installable': False,
'installable': True,
}

View File

@ -5,6 +5,7 @@ from openerp.addons.web import http
from openerp.tools.translate import _
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp.addons.website_partner.controllers import main as website_partner
import urllib
@ -12,10 +13,12 @@ class WebsiteCustomer(http.Controller):
_references_per_page = 20
@website.route([
'/customers/', '/customers/page/<int:page>/',
'/customers/country/<int:country_id>', '/customers/country/<int:country_id>/page/<int:page>/'
'/customers/',
'/customers/page/<int:page>/',
'/customers/country/<model("res.country"):country>',
'/customers/country/<model("res.country"):country>/page/<int:page>/'
], type='http', auth="public", multilang=True)
def customers(self, country_id=None, page=0, **post):
def customers(self, country=None, page=0, **post):
cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry['res.partner']
partner_name = post.get('search', '')
@ -28,22 +31,24 @@ class WebsiteCustomer(http.Controller):
('name', 'ilike', "%%%s%%" % post.get("search")),
('website_description', 'ilike', "%%%s%%" % post.get("search"))
]
if country_id:
domain += [('country_id', '=', country_id)]
country_id = None
if country:
domain += [('country_id', '=', country.id)]
country_id = country.id
# group by country, based on all customers (base domain)
countries = partner_obj.read_group(
cr, uid, base_domain, ["id", "country_id"],
cr, openerp.SUPERUSER_ID, base_domain, ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
country_count = partner_obj.search(
cr, uid, base_domain, count=True, context=request.context)
cr, openerp.SUPERUSER_ID, base_domain, count=True, context=request.context)
countries.insert(0, {
'country_id_count': country_count,
'country_id': (0, _("All Countries"))
})
# search customers to display
partner_ids = partner_obj.search(cr, uid, domain, context=request.context)
partner_ids = partner_obj.search(cr, openerp.SUPERUSER_ID, domain, context=request.context)
google_map_partner_ids = ",".join([str(p) for p in partner_ids])
# pager
@ -54,15 +59,14 @@ class WebsiteCustomer(http.Controller):
# browse page of customers to display
partner_ids = partner_obj.search(
cr, uid, domain,
cr, openerp.SUPERUSER_ID, domain,
limit=self._references_per_page, offset=pager['offset'], context=context)
partners = partner_obj.browse(request.cr, request.uid,
partner_ids, request.context)
partners_data = partner_obj.read(
request.cr, openerp.SUPERUSER_ID, partner_ids, website_partner.white_list, context=request.context)
values = {
'countries': countries,
'current_country_id': country_id or 0,
'partner_ids': partners,
'partners_data': partners_data,
'google_map_partner_ids': google_map_partner_ids,
'pager': pager,
'post': post,
@ -71,9 +75,23 @@ class WebsiteCustomer(http.Controller):
return request.website.render("website_customer.index", values)
@website.route(['/customers/<model("res.partner"):partner>/'], type='http', auth="public", multilang=True)
def customer(self, partner=None, **post):
""" Route for displaying a single partner / customer. """
values = {
'partner': partner
}
def customer(self, partner, **post):
values = website_partner.get_partner_template_value(partner, ["commercial_partner_id", "assigned_partner_id", "implemented_partner_ids"])
if not values:
return self.customers(**post)
partner_obj = request.registry['res.partner']
if values['partner_data']['assigned_partner_id']:
values['assigned_partner_data'] = partner_obj.read(
request.cr, openerp.SUPERUSER_ID, [values['partner_data']['assigned_partner_id'][0]],
website_partner.white_list, context=request.context)[0]
if values['partner_data']['implemented_partner_ids']:
implemented_partners_data = partner_obj.read(
request.cr, openerp.SUPERUSER_ID, values['partner_data']['implemented_partner_ids'],
website_partner.white_list, context=request.context)
values['implemented_partners_data'] = []
for data in implemented_partners_data:
if data.get('website_published'):
values['implemented_partners_data'].append(data)
return request.website.render("website_customer.details", values)

View File

@ -39,16 +39,16 @@
</div>
<div class="row">
<t t-foreach="partner_ids" t-as="partner" class="media">
<t t-foreach="partners_data" t-as="partner_data" class="media">
<div class="col-md-2">
<a t-href="/customers/#{ slug(partner) }/">
<img class="img img-thumbnail" t-att-src="partner.img('image_medium')"/>
<a t-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
<img class="img img-thumbnail" t-attf-src="data:image/png;base64,#{partner_data.get('image')}"/>
</a>
</div><div class="col-md-10">
<h4>
<a t-href="/customers/#{ slug(partner) }/" t-field="partner.name"/>
<a t-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/" t-esc="partner_data.get('name')"/>
</h4>
<div t-field="partner.website_short_description"/>
<div t-raw="partner_data.get('website_short_description')"/>
</div>
<div class="clearfix mb8"/>
</t>
@ -66,7 +66,7 @@
<template id="opt_country" inherit_option_id="website_customer.index" name="Show Map">
<xpath expr="//div[@id='ref_left_column']" position="inside">
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/&amp;output=embed"
<iframe t-attf-src="/google_map/?partner_ids=#{ google_map_partner_ids }&amp;partner_url=/customers/&amp;output=embed/"
style="width:100%; border:0; padding:0; margin:0;"></iframe>
</xpath>
</template>
@ -78,7 +78,7 @@
<t t-foreach="countries" t-as="country_dict">
<t t-if="country_dict['country_id']">
<li t-att-class="country_dict['country_id'][0] == current_country_id and 'active' or ''">
<a t-href="/customers/country/#{ country_dict['country_id'][0] }">
<a t-href="/customers/country/#{ slug(country_dict['country_id']) }/">
<span class="badge pull-right" t-esc="country_dict['country_id_count']"/>
<t t-esc="country_dict['country_id'][1]"/>
</a>
@ -99,20 +99,17 @@
<div class="col-md-5">
<ol class="breadcrumb">
<li><a href="/customers">Our References</a></li>
<li class="active"><span t-field="partner.name"/></li>
<li class="active"><span t-esc="partner_data.get('name')"/></li>
</ol>
</div>
<div class="col-md-7">
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
</div>
<div class="col-md-12">
<h1 class="text-center" t-field="partner.name"/>
</div>
<div class="col-md-9">
<div t-field="partner.website_description"/>
</div>
<div class="col-md-3" id="ref_right_column">
</div>
<t t-call="website_partner.partner_detail">
<t t-set="left_column">
<div id="left_column"></div>
</t>
<t t-set="right_column">
<div id="right_column"></div>
</t>
</t>
</div>
</div>
<div class="oe_structure"/>
@ -121,25 +118,27 @@
</template>
<template id="customer_contact" inherit_id="website_customer.details" inherit_option_id="website_customer.details" name="Customer Contacts">
<xpath expr="//div[@id='ref_right_column']" position="inside">
<xpath expr="//div[@id='left_column']" position="inside">
<div class="panel panel-default">
<div class="panel-heading">
<h4>Customer Reference</h4>
</div>
<div class="panel-body">
<div class="text-center">
<img t-att-src="partner.img('image')" class="img img-shadow"/>
<img class="img img-shadow" t-attf-src="data:image/png;base64,#{partner_data.get('image_medium')}"/>
</div>
<address class="mt16 mb8">
<strong t-field="partner.name"/>
<p t-field="partner.commercial_partner_id"/>
<div t-if="partner.phone">
<span class="fa fa-phone"/> <span t-field="partner.phone"/>
<strong t-esc="partner_data.get('name')"/>
<t t-if="partner_data.get('commercial_partner_id')">
<p t-raw="'&lt;br/&gt;'.join(partner_data.get('commercial_partner_id')[1].split('\n')[1:])"/>
</t>
<div t-if="partner_data.get('phone')">
<span class="fa fa-phone"/> <span t-esc="partner_data.get('phone')"/>
</div>
<div t-if="partner.email">
<div t-if="partner_data.get('email')">
<span class="fa fa-envelope"/>
<a t-att-href="'mailto:'+partner.email">
<span t-field="partner.email"/>
<a t-att-href="'mailto:'+partner_data.get('email')">
<span t-esc="partner_data.get('email')"/>
</a>
</div>
</address>
@ -148,5 +147,58 @@
</xpath>
</template>
<template id="partner_assign" inherit_option_id="website_customer.details" inherit_id="website_customer.details" name="Implemented By">
<xpath expr="//div[@id='left_column']" position="inside">
<t t-if="assigned_partner_data">
<div class="panel panel-default">
<div class="panel-heading">
<h4>Implemented By</h4>
</div>
<div class="panel-body">
<div class="text-center">
<img class="img img-shadow" t-attf-src="data:image/png;base64,#{assigned_partner_data.get('image_medium')}"/>
</div>
<address class="mt16 mb8">
<strong t-esc="assigned_partner_data.get('name')"/>
<div t-if="assigned_partner_data.get('phone')">
<span class="fa fa-phone"/> <span t-esc="assigned_partner_data.get('phone')"/>
</div>
<div t-if="assigned_partner_data.get('email')">
<span class="fa fa-envelope"/>
<a t-att-href="'mailto:'+assigned_partner_data.get('email')">
<span t-esc="assigned_partner_data.get('email')"/>
</a>
</div>
</address>
<div>
<a t-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/#references/" t-if="implemented_partner_ids">
<t t-esc="len(implemented_partner_ids)"/> references
</a>
</div>
</div>
</div>
</t>
</xpath>
</template>
<template id="references" inherit_id="website_customer.details" name="Partner References">
<xpath expr="//div[@id='right_column']" position="inside">
<t t-if="implemented_partners_data">
<h3 id="references">References</h3>
<div t-foreach="implemented_partners_data" t-as="partner_data" class="media thumbnail">
<a class="pull-left" t-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
<img class="media-object" t-attf-src="data:image/png;base64,#{partner_data.get('image_small')}"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-href="/customers/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
<t t-if="partner_data.get('parent_id')"><span t-esc="partner_data.get('parent_id')[1]"/></t> <span t-esc="partner_data.get('name')"/>
</a>
<div t-if="partner_data.get('website_short_description')" t-raw="partner_data.get('website_short_description')"/>
</div>
</div>
</t>
</xpath>
</template>
</data>
</openerp>

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP S.A. (<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/>.
#
##############################################################################

View File

@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2013-Today OpenERP S.A. (<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/>.
#
##############################################################################
{
'name': 'Public Customer References + Partner Assign',
'category': 'Website',
'summary': 'Add Partner Assignment Info to your Customer References',
'version': '1.0',
'description': """
OpenERP Customer References + Partner Assign
============================================
""",
'author': 'OpenERP SA',
'depends': [
'crm_partner_assign',
'website_customer'
],
'data': [
'views/website_customer.xml',
],
'qweb': [],
'installable': False,
'auto_install': False,
}

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="partner_assign" inherit_option_id="website_customer.details" inherit_id="website_customer.details" name="Implemented By">
<xpath expr="//div[@id='ref_right_column']" position="inside">
<t t-if="partner.assigned_partner_id">
<div class="panel panel-default">
<div class="panel-heading">
<h4>Implemented By</h4>
</div>
<div class="panel-body">
<div class="text-center">
<img t-att-src="partner.assigned_partner_id.img('image')" class="img img-shadow"/>
</div>
<address class="mt16 mb8">
<strong t-field="partner.assigned_partner_id.name"/>
<p t-field="partner.assigned_partner_id"/>
<div t-if="partner.assigned_partner_id.phone">
<span class="fa fa-phone"/> <span t-field="partner.assigned_partner_id.phone"/>
</div>
<div t-if="partner.assigned_partner_id.email">
<span class="fa fa-envelope"/>
<a t-att-href="'mailto:'+partner.assigned_partner_id.email">
<span t-field="partner.assigned_partner_id.email"/>
</a>
</div>
</address>
<div>
<a t-href="/customers/#{ slug(partner) }#references" t-if="partner.implemented_partner_ids">
<t t-esc="len(partner.implemented_partner_ids)"/> references
</a>
</div>
</div>
</div>
</t>
</xpath>
</template>
<template id="references" inherit_id="website_customer.details" name="Partner References">
<xpath expr="//div[@t-field='partner.website_description']" position="after">
<h3 id="references">References</h3>
<div>
<div t-foreach="partner.implemented_partner_ids" t-as="partner" class="media thumbnail">
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
<a class="pull-left" t-href="/customers/#{ slug(partner) }/">
<img class="media-object" t-att-src="partner.img('image_small')"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-href="/customers/#{ slug(partner) }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<div t-field="partner.website_short_description"/>
</div>
</div>
</div>
</xpath>
</template>
</data>
</openerp>

View File

@ -163,13 +163,15 @@ class website_event(http.Controller):
@website.route(['/event/<model("event.event"):event>/page/<page:page>'], type='http', auth="public", multilang=True)
def event_page(self, event, page, **post):
website.preload_records(event, on_error="website_event.404")
values = {
'event': event,
}
return request.website.render(page, values)
@website.route(['/event/<model("event.event"):event>'], type='http', auth="public", multilang=True)
def event(self, event=None, **post):
def event(self, event, **post):
website.preload_records(event, on_error="website_event.404")
if event.menu_id and event.menu_id.child_id:
target_url = event.menu_id.child_id[0].url
else:
@ -179,7 +181,8 @@ class website_event(http.Controller):
return request.redirect(target_url);
@website.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", multilang=True)
def event_register(self, event=None, **post):
def event_register(self, event, **post):
website.preload_records(event, on_error="website_event.404")
values = {
'event': event,
'range': range,

View File

@ -2,7 +2,7 @@
<openerp>
<data>
<template id="website.secure_layout">
<template id="secure_layout" inherit_id="website.secure_layout">
<xpath expr="//t[@id='editable_scripts_hook']" position="inside">
<script type="text/javascript" src="/website_event/static/src/js/website_event.editor.js" t-ignore="true"></script>
<script type="text/javascript" src="/website_event/static/src/js/website.tour.event.js" t-ignore="true"></script>
@ -243,6 +243,20 @@
</t>
</template>
<template id="404">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure oe_empty">
<div class="container">
<h1 class="mt32">Event not found!</h1>
<p>Sorry, the requested event is not available anymore.</p>
<p><a t-href="/event/">Return to the event list.</a></p>
</div>
</div>
</div>
</t>
</template>
<template id="event_description_full">
<t t-call="website_event.event_details">
<div class="col-md-8">

View File

@ -7,7 +7,7 @@
<field name="description">Job Posts on your website</field>
</record>
<template id="website.secure_layout">
<template id="secure_layout" inherit_id="website.secure_layout">
<xpath expr="//t[@id='editable_scripts_hook']" position="inside">
<script type="text/javascript" src="/website_hr_recruitment/static/src/js/website_hr_recruitment.editor.js" t-ignore="true"></script>
</xpath>

View File

@ -21,7 +21,7 @@
</div>
</template>
<template id="website.secure_layout" name="Mail customization">
<template id="secure_layout" inherit_id="website.secure_layout" name="Mail customization">
<xpath expr="//head" position="inside">
<script type="text/javascript" src="/website_mail/static/src/js/website_mail.js"></script>
</xpath>

View File

@ -4,6 +4,7 @@ import openerp
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp.addons.website_partner.controllers import main as website_partner
from openerp.tools.translate import _
import urllib
@ -39,7 +40,7 @@ class WebsiteMembership(http.Controller):
# group by country, based on all customers (base domain)
membership_line_ids = membership_line_obj.search(cr, uid, base_line_domain, context=context)
countries = partner_obj.read_group(
cr, uid, [('member_lines', 'in', membership_line_ids)], ["id", "country_id"],
cr, uid, [('member_lines', 'in', membership_line_ids), ("website_published", "=", True)], ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
countries_total = sum(country_dict['country_id_count'] for country_dict in countries)
countries.insert(0, {
@ -57,6 +58,10 @@ class WebsiteMembership(http.Controller):
partner_ids = [m.partner and m.partner.id for m in membership_lines]
google_map_partner_ids = ",".join(map(str, partner_ids))
partners_data = {}
for partner in partner_obj.read(cr, openerp.SUPERUSER_ID, partner_ids, website_partner.white_list, context=context):
partners_data[partner.get("id")] = partner
# format domain for group_by and memberships
membership_ids = product_obj.search(cr, uid, [('membership', '=', True)], context=context)
memberships = product_obj.browse(cr, uid, membership_ids, context=context)
@ -65,6 +70,7 @@ class WebsiteMembership(http.Controller):
pager = request.website.pager(url="/members/", total=len(membership_line_ids), page=page, step=self._references_per_page, scope=7, url_args=post)
values = {
'partners_data': partners_data,
'membership_lines': membership_lines,
'memberships': memberships,
'membership': membership,
@ -78,12 +84,7 @@ class WebsiteMembership(http.Controller):
@website.route(['/members/<model("res.partner"):partner>/'], type='http', auth="public", multilang=True)
def partners_ref(self, partner, **post):
if not partner.exists():
values = website_partner.get_partner_template_value(partner)
if not values:
return self.members(**post)
values = {
'partner_id': request.registry['res.partner'].browse(
request.cr, request.uid, partner.id,
context=dict(request.context, show_address=True)),
}
return request.website.render("website_membership.partner", values)

View File

@ -31,7 +31,7 @@
<div class="col-md-4" id="left_column">
<ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>Associations</h3></li>
<li t-att-class="membership and '' or 'active'"><a href="/members/">All</a></li>
<li t-att-class="'' if membership else 'active'"><a href="/members/">All</a></li>
<t t-foreach="memberships" t-as="membership_id">
<li t-att-class="membership and membership_id.id == membership.id and 'active' or ''">
<a t-href="/members/association/#{ membership_id.id }"><t t-esc="membership_id.name"/></a>
@ -59,17 +59,14 @@
<t t-set="current_membership_id" t-value="membership_line_id.membership_id.id"/>
<h3 class="text-center"><span t-field="membership_line_id.membership_id"/></h3>
</t>
<t t-set="partner" t-value="membership_line_id.partner"/>
<t t-set="partner_data" t-value="partners_data[membership_line_id.partner.id]"/>
<div class="media">
<t t-call="website.publish_management">
<t t-set="object" t-value="partner"/>
</t>
<a class="pull-left" t-href="/members/#{ slug(partner) }/">
<img class="media-object" t-att-src="partner.img('image_small')"/>
<a class="pull-left" t-href="/members/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/">
<img class="media-object" t-attf-src="data:image/png;base64,#{partner_data.get('image_small')}"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-href="/members/#{ slug(partner) }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<div t-field="partner.website_short_description"/>
<a class="media-heading" t-href="/members/#{ slug([partner_data.get('id'), partner_data.get('name')]) }/"><t t-if="partner_data.get('parent_id')"><span t-esc="partner_data.get('parent_id')[1]"/></t> <span t-esc="partner_data.get('name')"/></a>
<div t-raw="partner_data.get('website_short_description')"/>
</div>
</div>
</t>

View File

@ -30,7 +30,6 @@
'data': [
'views/res_partner_view.xml',
'views/website_partner_view.xml',
'security/website_partner_security.xml',
'data/website_data.xml',
],
'demo': ['website_partner_demo.xml'],

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
import openerp
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
import werkzeug
white_list = ["grade_id", "name", "parent_id", 'website_short_description', "website_published",
"website_description", "tel", "fax", "image", "image_small", "image_medium"]
def get_partner_template_value(partner, add_white_list=None):
ctx = dict(request.context, show_address=True)
partner_obj = request.registry['res.partner']
partner_id = partner.id
partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
if not partner.exists() or not partner_ids:
partner = None
partner_data = partner_obj.read(
request.cr, openerp.SUPERUSER_ID, [partner_id], white_list + (add_white_list or []), context=ctx)[0]
if not partner_data["website_published"]:
return None
partner_data['name_get'] = partner_obj.name_get(request.cr, openerp.SUPERUSER_ID, [partner_id],context=request.context)[0]
partner_data['address'] = '<br/>'.join(partner_obj.name_get(
request.cr, openerp.SUPERUSER_ID, [partner_id],context=ctx)[0][1].split('\n')[1:])
values = {
'partner': partner,
'partner_data': partner_data,
}
return values
class WebsitePartner(http.Controller):
@website.route(['/partners/<model("res.partner"):partner>/'], type='http', auth="public", multilang=True)
def partner(self, partner, **post):
""" Route for displaying a single partner / customer. """
values = get_partner_template_value(partner)
if not values:
raise werkzeug.exceptions.NotFound
return request.website.render("website_partner.partner_detail", values)

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<!-- <data noupdate="1"> -->
<data>
<!-- res_partner related security rules -->
<record id="res_partner_public_website_rule" model="ir.rule">
<field name="name">res_partner: public: child of commercial_partner + website_published partners</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="domain_force">['|', ('id', 'child_of', user.commercial_partner_id.id), ('website_published', '=', True)]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
<field name="perm_write" eval="False"/>
</record>
</data>
</openerp>

View File

@ -2,12 +2,17 @@
<openerp>
<data>
<template id="partner_detail" name="Partner">
<t t-call="website.publish_management"><t t-set="object" t-value="partner_id"/></t>
<h1 class="col-md-12 text-center" t-field="partner_id.name"/>
<template id="partner_detail" name="Partner Details (Complex Template for Access Right)">
<t t-if="partner" t-call="website.publish_management">
<t t-set="object" t-value="partner"/>
<t t-set="publish_edit" t-value="True"/>
</t>
<t t-if="partner"><h1 class="col-md-12 text-center" t-field="partner.name"/></t>
<t t-if="not partner"><h1 class="col-md-12 text-center" t-esc="partner_data.get('name_get')[1]"/></t>
<div class="col-md-4">
<div class="text-center">
<img t-att-src="partner_id.img('image_medium')"/>
<t t-if="partner"><img t-att-src="partner.img('image')"/></t>
<t t-if="not partner"><img t-attf-src="data:image/png;base64,#{partner_data.get('image')}"/></t>
</div>
<address>
<table style="margin: auto;" class="well">
@ -16,24 +21,49 @@
<col/>
</colgroup>
<tbody>
<t t-set="address" t-value="'&lt;br/&gt;'.join(partner_id.name_get()[0][1].split('\n')[1:])"/>
<tr t-if="address or editable"><th class="texttop">Address</th><td class="span2" t-raw="address"/></tr>
<tr t-if="partner_id.website or editable"><th>Website</th><td class="span2">
<t t-if="partner_id.website"><span t-field="partner_id.website"/></t></td></tr>
<tr t-if="partner_id.phone or editable"><th>Phone</th><td class="span2">
<t t-if="partner_id.phone"><span t-field="partner_id.phone"/></t></td></tr>
<tr t-if="partner_id.mobile or editable"><th>Tel</th><td class="span2">
<t t-if="partner_id.mobile"><span t-field="partner_id.mobile"/></t></td></tr>
<tr t-if="partner_id.fax or editable"><th>Fax</th><td class="span2">
<t t-if="partner_id.fax"><span t-field="partner_id.fax"/></t></td></tr>
<tr t-if="partner_id.email or editable"><th>Email</th><td class="span2">
<t t-if="partner_id.email"><span t-field="partner_id.email"/></t></td></tr>
<t t-if="partner">
<t t-set="address" t-value="'&lt;br/&gt;'.join(partner.name_get()[0][1].split('\n')[1:])"/>
<tr t-if="address or editable"><th class="texttop">Address</th><td class="span2" t-raw="address"/></tr>
</t>
<tr t-if="not partner and partner_data.get('address')"><th class="texttop">Address</th><td class="span2" t-raw="partner_data.get('address')"/></tr>
<tr t-if="partner and (partner.website or editable)"><th>Website</th><td class="span2">
<t t-if="partner.website"><span t-field="partner.website"/></t></td></tr>
<tr t-if="partner_data.get('website')"><th>Website</th><td class="span2"><span t-esc="partner_data.get('website')"/></td></tr>
<tr t-if="partner and (partner.phone or editable)"><th>Phone</th><td class="span2">
<t t-if="partner.phone"><span t-field="partner.phone"/></t></td></tr>
<tr t-if="partner_data.get('phone')"><th>Phone</th><td class="span2"><span t-esc="partner_data.get('phone')"/></td></tr>
<tr t-if="partner and (partner.mobile or editable)"><th>Tel</th><td class="span2">
<t t-if="partner.mobile"><span t-field="partner.mobile"/></t></td></tr>
<tr t-if="partner_data.get('mobile')"><th>Tel</th><td class="span2"><span t-esc="partner_data.get('mobile')"/></td></tr>
<tr t-if="partner and (partner.fax or editable)"><th>Fax</th><td class="span2">
<t t-if="partner.fax"><span t-field="partner.fax"/></t></td></tr>
<tr t-if="partner_data.get('fax')"><th>Fax</th><td class="span2"><span t-esc="partner_data.get('fax')"/></td></tr>
<tr t-if="partner and (partner.email or editable)"><th>Email</th><td class="span2">
<t t-if="partner.email"><span t-field="partner.email"/></t></td></tr>
<tr t-if="partner_data.get('email')"><th>Email</th><td class="span2"><span t-esc="partner_data.get('email')"/></td></tr>
</tbody>
</table>
</address>
<t t-raw="left_column or ''"/>
</div>
<div class="col-md-8 mt32">
<t t-if="partner">
<div t-field="partner.website_description"/>
<t t-if="editable">
<h2 class="css_non_editable_mode_hidden">Short Description for List View</h2>
<div class="css_non_editable_mode_hidden" t-field="partner.website_short_description"/>
</t>
</t>
<t t-if="not partner">
<div class="col-md-8 mt32" t-raw="partner_data.get('website_description')"/>
</t>
<t t-raw="right_column or ''"/>
</div>
<div class="col-md-8 mt32" t-field="partner_id.website_description"/>
</template>
</data>
</openerp>

View File

@ -18,5 +18,3 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import controllers

View File

@ -1,42 +1,18 @@
# -*- 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/>.
#
##############################################################################
{
'name': 'Payment: Website Integration (Test Module)',
'name': 'Payment: Website Integration',
'category': 'Website',
'summary': 'Payment: Website Integration (Test Module)',
'summary': 'Payment: Website Integration',
'version': '1.0',
'description': """Module installing all sub-payment modules and adding some
controllers and menu entries in order to test them.""",
'description': """Bridge module for acquirers and website.""",
'author': 'OpenERP SA',
'depends': [
'website',
'payment_acquirer',
'payment_acquirer_ogone',
'payment_acquirer_paypal',
'payment_acquirer_transfer',
],
'data': [
'views/website_payment_templates.xml',
],
'installable': False,
'active': False,
'auto_install': True,
}

View File

@ -1,95 +0,0 @@
# -*- 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.website.models import website
class WebsitePayment(http.Controller):
@website.route([
'/payment/paypal/test',
], type='http', auth="public")
def paypal_test(self, **post):
""" TODO
"""
cr, uid, context = request.cr, request.uid, request.context
acquirer_obj = request.registry['payment.acquirer']
payment_obj = request.registry['payment.transaction']
currency_obj = request.registry['res.currency']
paypal_id = acquirer_obj.search(cr, uid, [('name', '=', 'paypal')], limit=1, context=context)[0]
currency_id = currency_obj.search(cr, uid, [('name', '=', 'EUR')], limit=1, context=context)[0]
nbr_tx = payment_obj.search(cr, uid, [], count=True, context=context)
tx_id = payment_obj.create(cr, uid, {
'reference': 'test_ref_%s' % (nbr_tx),
'amount': 1.95,
'currency_id': currency_id,
'acquirer_id': paypal_id,
'partner_name': 'Norbert Buyer',
'partner_email': 'norbert.buyer@example.com',
'partner_lang': 'fr_FR',
}, context=context)
paypal_form = acquirer_obj.render(cr, uid, paypal_id, None, None, None, tx_id=tx_id, context=context)
paypal = acquirer_obj.browse(cr, uid, paypal_id, context=context)
values = {
'acquirer': paypal,
'acquirer_form': paypal_form,
}
return request.website.render("website_payment.index_paypal", values)
@website.route([
'/payment/ogone/test',
], type='http', auth="public")
def ogone_test(self, **post):
""" TODO
"""
cr, uid, context = request.cr, request.uid, request.context
acquirer_obj = request.registry['payment.acquirer']
payment_obj = request.registry['payment.transaction']
currency_obj = request.registry['res.currency']
ogone_id = acquirer_obj.search(cr, uid, [('name', '=', 'ogone')], limit=1, context=context)[0]
currency_id = currency_obj.search(cr, uid, [('name', '=', 'EUR')], limit=1, context=context)[0]
nbr_tx = payment_obj.search(cr, uid, [], count=True, context=context)
tx_id = payment_obj.create(cr, uid, {
'reference': 'test_ref_%s' % (nbr_tx),
'amount': 1.95,
'currency_id': currency_id,
'acquirer_id': ogone_id,
'partner_name': 'Norbert Buyer',
'partner_email': 'norbert.buyer@example.com',
'partner_lang': 'fr_FR',
}, context=context)
ogone_form = acquirer_obj.render(cr, uid, ogone_id, None, None, None, tx_id=tx_id, context=context)
ogone = acquirer_obj.browse(cr, uid, ogone_id, context=context)
values = {
'acquirer': ogone,
'acquirer_form': ogone_form,
}
return request.website.render("website_payment.index_ogone", values)

View File

@ -1,100 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<!-- Add menus for testing -->
<data noupdate="0">
<record id="menu_paypal_test" model="website.menu">
<field name="name">Paypal (Test)</field>
<field name="url">/payment/paypal/test</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">90</field>
</record>
<record id="menu_paypal_ogone" model="website.menu">
<field name="name">Ogone (Test)</field>
<field name="url">/payment/ogone/test</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">91</field>
</record>
<record id="menu_transfer_test" model="website.menu">
<field name="name">Transfer (Test)</field>
<field name="url">/payment/transfer/test</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">92</field>
</record>
</data>
<!-- Page -->
<data>
<template id="index_paypal" name="Paypal (Test)" page="True">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_payment/static/src/js/payment_acquirer.js"></script>
<script type="text/javascript" src="/website_payment/static/lib/jquery.payment/jquery.payment.js"></script>
<link rel='stylesheet' href='/website_payment/static/src/css/website_payment.css'/>
</t>
<div id="wrap">
<div class="container mt16 js_website_blog">
<!-- <div class="row">
<h3>Paypal payment: server 2 server</h3>
<form class="form-horizontal col-sm-4 oe_cc" role="form">
<div class="form-group col-sm-8">
<label class="control-label" for="cc_number">Card number</label>
<input type="tel" id="cc_number" class="form-control"/>
<div class="card_placeholder"></div>
<div class="visa"></div>
</div>
<div class="form-group col-sm-4">
<label class="control-label" for="cc_cvc">Card code</label>
<input type="text" id="cc_cvc" class="form-control" maxlength="4" palceholder="CVC"/>
</div>
<div class="form-group col-sm-7">
<label class="control-label" for="cc_holder_name">Holder Name</label>
<input type="text" id="cc_hoder_name" class="form-control"/>
</div>
<div class="form-group col-sm-5">
<label class="control-label" for="cc_expires_mm">Expires</label>
<input type="text" id="cc_expiry" class="form-control" maxlength="7" placeholder="MM / YY"/>
</div>
</form>
</div> -->
<div>
<h3>Paypal payment: form based</h3>
<t t-raw="acquirer_form"/>
</div>
<template id="cc_form" name="Paypal (Test)" page="True">
<div class="row">
<h3>Paypal payment: server 2 server</h3>
<form class="form-horizontal col-sm-4 oe_cc" role="form">
<div class="form-group col-sm-8">
<label class="control-label" for="cc_number">Card number</label>
<input type="tel" id="cc_number" class="form-control"/>
<div class="card_placeholder"></div>
<div class="visa"></div>
</div>
</div>
</t>
</template>
<template id="index_ogone" name="Ogone (Test)" page="True">
<t t-call="website.layout">
<div id="wrap">
<div class="container mt16 js_website_blog">
<div class="row">
Ogone payment
<t t-raw="acquirer_form"/>
</div>
<div class="form-group col-sm-4">
<label class="control-label" for="cc_cvc">Card code</label>
<input type="text" id="cc_cvc" class="form-control" maxlength="4" palceholder="CVC"/>
</div>
</div>
</t>
</template>
<template id="index_transfer" name="Ogone (Test)" page="True">
<t t-call="website.layout">
<div id="wrap">
<div class="container mt16 js_website_blog">
<div class="row">
Transfer payment
<t t-raw="acquirer_form"/>
</div>
<div class="form-group col-sm-7">
<label class="control-label" for="cc_holder_name">Holder Name</label>
<input type="text" id="cc_hoder_name" class="form-control"/>
</div>
</div>
</t>
<div class="form-group col-sm-5">
<label class="control-label" for="cc_expires_mm">Expires</label>
<input type="text" id="cc_expiry" class="form-control" maxlength="7" placeholder="MM / YY"/>
</div>
</form>
</div>
</template>
</data>
</openerp>

View File

@ -121,19 +121,19 @@ class Ecommerce(http.Controller):
return product_obj.browse(request.cr, request.uid, product_ids, context=request.context)
def has_search_filter(self, attribute_id, value_id=None):
if request.httprequest.args.get('filter'):
filter = simplejson.loads(request.httprequest.args['filter'])
if request.httprequest.args.get('filters'):
filters = simplejson.loads(request.httprequest.args['filters'])
else:
filter = []
for key_val in filter:
filters = []
for key_val in filters:
if key_val[0] == attribute_id and (not value_id or value_id in key_val[1:]):
return key_val
return False
@website.route(['/shop/filter/'], type='http', auth="public", multilang=True)
def filter(self, **post):
@website.route(['/shop/filters/'], type='http', auth="public", multilang=True)
def filters(self, **post):
index = []
filter = []
filters = []
for key, val in post.items():
cat = key.split("-")
if len(cat) < 3 or cat[2] in ('max','minmem','maxmem'):
@ -145,19 +145,19 @@ class Ecommerce(http.Controller):
_max = int(post.pop("att-%s-max" % cat[1]))
_min = int(val)
if (minmem != _min or maxmem != _max) and cat_id not in index:
filter.append([cat_id , [_min, _max] ])
filters.append([cat_id , [_min, _max] ])
index.append(cat_id)
elif cat_id not in index:
filter.append([ cat_id, int(cat[2]) ])
filters.append([ cat_id, int(cat[2]) ])
index.append(cat_id)
else:
cat[2] = int(cat[2])
if cat[2] not in filter[index.index(cat_id)][1:]:
filter[index.index(cat_id)].append( cat[2] )
if cat[2] not in filters[index.index(cat_id)][1:]:
filters[index.index(cat_id)].append( cat[2] )
post.pop(key)
return request.redirect("/shop/?filter=%s%s%s" % (
simplejson.dumps(filter),
return request.redirect("/shop/?filters=%s%s%s" % (
simplejson.dumps(filters),
post.get("search") and ("&search=%s" % post.get("search")) or "",
post.get("category") and ("&category=%s" % post.get("category")) or ""
))
@ -188,7 +188,7 @@ class Ecommerce(http.Controller):
'/shop/category/<int:category>/',
'/shop/category/<int:category>/page/<int:page>/'
], type='http', auth="public", multilang=True)
def shop(self, category=0, page=0, filter_domain='', search='', **post):
def shop(self, category=0, page=0, filters='', search='', **post):
cr, uid, context = request.cr, request.uid, request.context
product_obj = request.registry.get('product.template')
domain = request.registry.get('website').get_website_sale_domain()
@ -198,10 +198,10 @@ class Ecommerce(http.Controller):
('description', 'ilike', "%%%s%%" % search)]
if category:
domain.append(('product_variant_ids.public_categ_id', 'child_of', category))
if filter_domain:
filter_domain = simplejson.loads(filter_domain)
if filter_domain:
ids = self.attributes_to_ids(filter_domain)
if filters:
filters = simplejson.loads(filters)
if filters:
ids = self.attributes_to_ids(filters)
domain.append(('id', 'in', ids or [0]))
product_count = product_obj.search_count(cr, uid, domain, context=context)
@ -228,10 +228,12 @@ class Ecommerce(http.Controller):
values = {
'products': products,
'bins': table_compute().process(products),
'rows': PPR,
'range': range,
'search': {
'search': search,
'category': category,
'filter_domain': filter_domain,
'filters': filters,
},
'pager': pager,
'styles': styles,
@ -242,7 +244,9 @@ class Ecommerce(http.Controller):
return request.website.render("website_sale.products", values)
@website.route(['/shop/product/<model("product.template"):product>/'], type='http', auth="public", multilang=True)
def product(self, product, search='', category='', filter_domain='', **kwargs):
def product(self, product, search='', category='', filters='', **kwargs):
website.preload_records(product, on_error="website_sale.404")
category_obj = request.registry.get('product.public.category')
category_ids = category_obj.search(request.cr, request.uid, [], context=request.context)
@ -263,7 +267,7 @@ class Ecommerce(http.Controller):
'search': {
'search': search,
'category': category and str(category.id),
'filter': filter_domain,
'filters': filters,
}
}
return request.website.render("website_sale.product", values)
@ -600,21 +604,23 @@ class Ecommerce(http.Controller):
# fetch all registered payment means
if tx:
payment_ids = [tx.acquirer_id.id]
acquirer_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,
acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('portal_published', '=', True)], context=context)
values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context)
render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now')
for acquirer in values['acquirers']:
render_ctx['tx_url'] = '/shop/payment/transaction/%s' % acquirer.id
acquirer.button = payment_obj.render(
cr, uid, acquirer.id,
order.name,
order.amount_total,
order.pricelist_id.currency_id,
order.pricelist_id.currency_id.id,
partner_id=shipping_partner_id,
tx_custom_values={
tx_values={
'return_url': '/shop/payment/validate',
},
context=context)
context=render_ctx)
return request.website.render("website_sale.payment", values)

View File

@ -7,13 +7,6 @@ $(document).ready(function () {
$(".oe_website_sale .js_shipping").toggle();
});
var $payment = $(".oe_website_sale .js_payment");
$payment.find("input[name='payment_type']").click(function (ev) {
var payment_id = $(ev.currentTarget).val();
$("div[data-id]", $payment).addClass("hidden");
$("a.btn:last, div[data-id='"+payment_id+"']", $payment).removeClass("hidden");
});
// change for css
$(document).on('mouseup', '.js_publish', function (ev) {
$(ev.currentTarget).parents(".thumbnail").toggleClass("disabled");

View File

@ -1,14 +1,11 @@
$(document).ready(function () {
/* Hitting the payment button: payment transaction process begins
* We redirect the user to a custom shop page in oder to create the
* transaction. The form POST data will be used to perform the post
* query.
*/
$('input#payment_submit').on('click', function (ev) { // TDEFIXME: change input#ID to input inside payment form, less strict
var acquirer_id = $(this).closest('form').closest('div.oe_payment_acquirer').data().id || 0;
var form_action = $(this).closest("form").attr('action');
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);
// When choosing an acquirer, display its Pay Now button
var $payment = $("#payment_method");
$payment.find("input[name='acquirer']").click(function (ev) {
var payment_id = $(ev.currentTarget).val();
$("div.oe_sale_acquirer_button[data-id]", $payment).addClass("hidden");
$("div.oe_sale_acquirer_button[data-id='"+payment_id+"']", $payment).removeClass("hidden");
});
});

View File

@ -4,7 +4,7 @@
<!-- Layout add nav and footer -->
<template id="website.secure_layout">
<template id="secure_layout" inherit_id="website.secure_layout">
<xpath expr="//t[@id='editable_scripts_hook']" position="inside">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.editor.js" t-ignore="true"></script>
<script type="text/javascript" src="/website_sale/static/src/js/website.tour.shop.js" t-ignore="true"></script>
@ -40,7 +40,7 @@
<template id="search" name="Search hidden fields">
<input type="hidden" name="category" t-att-value="search.get('category') or ''"/>
<input type="hidden" name="filter_domain" t-att-value="search.get('filter_domain') or ''"/>
<input type="hidden" name="filters" t-att-value="search.get('filters') or ''"/>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
</template>
@ -49,7 +49,7 @@
<div class="ribbon">Promo</div>
</div>
<div class="oe_product_description">
<a t-href="/shop/product/#{ slug(product) }/" t-keep-query="category,search,filter_domain">
<a t-href="/shop/product/#{ slug(product) }/" t-keep-query="category,search,filters">
<b t-field="product.name"/>
</a>
</div>
@ -69,7 +69,7 @@
</b>
</div>
<div class="oe_product_image text-center">
<a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,filter_domain">
<a t-href="/shop/product/#{ product.id }/" t-keep-query="category,search,filters">
<span t-field="product.image" t-field-options='{"widget": "image"}'/>
</a>
</div>
@ -105,12 +105,14 @@
<div class="col-md-12" id="products_grid">
<table width="100%">
<tbody>
<tr>
<td t-foreach="range(0,rows)" t-as="row" t-attf-width="#{100/rows}%"></td>
</tr>
<tr t-foreach="bins" t-as="tr_product">
<t t-foreach="tr_product" t-as="td_product">
<t t-if="td_product">
<t t-set="product" t-value="td_product['product']"/>
<td t-att-colspan="td_product['x']"
t-attf-width="#{td_product['x']*25}%"
t-att-rowspan="td_product['y']"
t-attf-class="oe_product oe-height-#{td_product['y']*2} #{ td_product['class'] }">
@ -227,6 +229,19 @@
<!-- product -->
<template id="404">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure oe_empty">
<div class="container">
<h1 class="mt32">Product not found!</h1>
<p>Sorry, this product is not available anymore.</p>
<p><a t-href="/shop/">Return to the product list.</a></p>
</div>
</div>
</div>
</t>
</template>
<template id="product" name="Product">
<t t-call="website.layout">
@ -241,7 +256,7 @@
<div class="col-sm-5">
<ol class="breadcrumb">
<li><a href="/shop">Products</a></li>
<li t-if="search.get('category')"><a t-href="/shop/" t-keep-query="category,search,filter_domain"><span t-field="category.name"/></a></li>
<li t-if="search.get('category')"><a t-href="/shop/" t-keep-query="category,search,filters"><span t-field="category.name"/></a></li>
<li class="active"><span t-field="product.name"/></li>
</ol>
</div><div class="col-sm-3">
@ -512,7 +527,7 @@
<template id="products_attributes" inherit_option_id="website_sale.products" name="Product Filters and Attributes">
<xpath expr="//div[@id='products_grid_before']" position="inside">
<form t-action="/shop/filter/" method="post" t-keep-query="category,search">
<form t-action="/shop/filters/" method="post" t-keep-query="category,search">
<ul class="nav nav-pills nav-stacked mt16">
<t t-set="attribute_ids" t-value="Ecommerce.get_attribute_ids()"/>
<t t-foreach="attribute_ids" t-as="attribute_id">
@ -545,7 +560,7 @@
</t>
</ul>
<button class="btn btn-xs btn-primary mt16">Apply filter</button>
<a t-href="/shop/" t-keep-query="category,search,add_filter" class="btn btn-xs btn-default mt16">Cancel filter</a>
<a t-href="/shop/" t-keep-query="category,search" class="btn btn-xs btn-default mt16">Cancel filter</a>
</form>
</xpath>
<xpath expr="//div[@id='products_grid_before']" position="attributes">
@ -882,20 +897,22 @@
</div>
</div>
<div class="js_payment mb64" t-if="not payment_acquirer_id and payments" id="payment_method">
<div class="js_payment mb64" t-if="acquirers" id="payment_method">
<h3>Choose your payment method</h3>
<div class="col-lg-5 col-sm-6">
<t t-foreach="payments or []" t-as="payment">
<label t-if="payment._content">
<input t-att-value="payment.id" type="radio" name="payment_type"/> <span t-field="payment.name"/>
<t t-foreach="acquirers or []" t-as="acquirer">
<label t-if="acquirer.button" class="oe_sale_acquirer_logo" style="display: block;">
<input t-att-value="acquirer.id" type="radio" name="acquirer"/>
<img class="media-object" style="width: 60px; display: inline-block;"
t-att-title="acquirer.name"
t-att-src="'/payment_acquirer_%s/static/src/img/%s_icon.png' % (acquirer.name, acquirer.name)"/>
</label>
</t>
<t t-foreach="payments" t-as="payment">
<div t-att-data-id="payment.id" t-raw="payment._content" class="oe_payment_acquirer hidden"/>
</t>
</div>
<div class="col-lg-3 col-sm-3">
<button class="btn btn-primary pull-right">Pay Now !!!</button>
<t t-foreach="acquirers or []" t-as="acquirer">
<div t-att-data-id="acquirer.id" t-raw="acquirer.button" class="oe_sale_acquirer_button hidden"/>
</t>
</div>
</div>