[IMP] payment, website_payment: perform the donation payment
This commit adds /donation/* routes to perform the donation: - /pay: shows the form with the contact details and donation details - /transaction: creates the partner_id if missing and executes the transaction - /confirm: displays the payment success page PR-63133 task-2398403 Part-of: odoo/odoo#63133
This commit is contained in:
committed by
Jeremy Kersten
parent
5ffce116a6
commit
7fccbac004
@@ -142,7 +142,10 @@ class PaymentPortal(portal.CustomerPortal):
|
|||||||
'invoice_id': invoice_id,
|
'invoice_id': invoice_id,
|
||||||
**self._get_custom_rendering_context_values(**kwargs),
|
**self._get_custom_rendering_context_values(**kwargs),
|
||||||
}
|
}
|
||||||
return request.render('payment.pay', rendering_context)
|
return request.render(self._get_payment_page_template_xmlid(**kwargs), rendering_context)
|
||||||
|
|
||||||
|
def _get_payment_page_template_xmlid(self, **kwargs):
|
||||||
|
return 'payment.pay'
|
||||||
|
|
||||||
@http.route('/my/payment_method', type='http', methods=['GET'], auth='user', website=True)
|
@http.route('/my/payment_method', type='http', methods=['GET'], auth='user', website=True)
|
||||||
def payment_method(self, **kwargs):
|
def payment_method(self, **kwargs):
|
||||||
@@ -205,16 +208,9 @@ class PaymentPortal(portal.CustomerPortal):
|
|||||||
tx_sudo = self._create_transaction(
|
tx_sudo = self._create_transaction(
|
||||||
amount=amount, currency_id=currency_id, partner_id=partner_id, **kwargs
|
amount=amount, currency_id=currency_id, partner_id=partner_id, **kwargs
|
||||||
)
|
)
|
||||||
|
if tx_sudo.operation == 'validation':
|
||||||
# The generic landing route require the tx id and access token to be provided, since there
|
# The amount and currency have been chosen for the validation, recompute the access token
|
||||||
# is no document to rely on. The access token is recomputed in case we are dealing with a
|
self._update_landing_route(tx_sudo)
|
||||||
# validation transaction (acquirer-specific amount and currency).
|
|
||||||
access_token = payment_utils.generate_access_token(
|
|
||||||
tx_sudo.partner_id.id, tx_sudo.amount, tx_sudo.currency_id.id
|
|
||||||
)
|
|
||||||
tx_sudo.landing_route = f'{tx_sudo.landing_route}' \
|
|
||||||
f'?tx_id={tx_sudo.id}&access_token={access_token}'
|
|
||||||
|
|
||||||
return tx_sudo._get_processing_values()
|
return tx_sudo._get_processing_values()
|
||||||
|
|
||||||
def _create_transaction(
|
def _create_transaction(
|
||||||
@@ -308,6 +304,24 @@ class PaymentPortal(portal.CustomerPortal):
|
|||||||
|
|
||||||
return tx_sudo
|
return tx_sudo
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_landing_route(tx_sudo):
|
||||||
|
""" Recompute the access token stored in the landing route of validation transactions.
|
||||||
|
|
||||||
|
The generic landing route require the tx id and access token to be provided, since there is
|
||||||
|
no document to rely on. The access token is recomputed in case we are dealing with a
|
||||||
|
validation transaction (acquirer-specific amount and currency).
|
||||||
|
|
||||||
|
:param recordset tx_sudo: The transaction whose landing routes to update, as a
|
||||||
|
`payment.transaction` record.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
access_token = payment_utils.generate_access_token(
|
||||||
|
tx_sudo.partner_id.id, tx_sudo.amount, tx_sudo.currency_id.id
|
||||||
|
)
|
||||||
|
tx_sudo.landing_route = f'{tx_sudo.landing_route}' \
|
||||||
|
f'?tx_id={tx_sudo.id}&access_token={access_token}'
|
||||||
|
|
||||||
@http.route('/payment/confirmation', type='http', methods=['GET'], auth='public', website=True)
|
@http.route('/payment/confirmation', type='http', methods=['GET'], auth='public', website=True)
|
||||||
def payment_confirm(self, tx_id, access_token, **kwargs):
|
def payment_confirm(self, tx_id, access_token, **kwargs):
|
||||||
""" Display the payment confirmation page with the appropriate status message to the user.
|
""" Display the payment confirmation page with the appropriate status message to the user.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from . import models
|
from . import controllers
|
||||||
|
from . import models
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ This is a bridge module that adds multi-website support for payment acquirers.
|
|||||||
'portal',
|
'portal',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
|
'data/donation_data.xml',
|
||||||
'views/payment_acquirer.xml',
|
'views/payment_acquirer.xml',
|
||||||
|
'views/donation_templates.xml',
|
||||||
'views/snippets/snippets.xml',
|
'views/snippets/snippets.xml',
|
||||||
'views/snippets/s_donation.xml',
|
'views/snippets/s_donation.xml',
|
||||||
],
|
],
|
||||||
@@ -23,6 +25,10 @@ This is a bridge module that adds multi-website support for payment acquirers.
|
|||||||
'website.assets_wysiwyg': [
|
'website.assets_wysiwyg': [
|
||||||
'website_payment/static/src/snippets/s_donation/options.js',
|
'website_payment/static/src/snippets/s_donation/options.js',
|
||||||
],
|
],
|
||||||
|
'web.assets_frontend': [
|
||||||
|
'website_payment/static/src/js/website_payment_donation.js',
|
||||||
|
'website_payment/static/src/js/website_payment_form.js',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
'license': 'LGPL-3',
|
'license': 'LGPL-3',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import portal
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import http, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.tools.json import scriptsafe as json_safe
|
||||||
|
|
||||||
|
from odoo.addons.payment import utils as payment_utils
|
||||||
|
from odoo.addons.payment.controllers import portal as payment_portal
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentPortal(payment_portal.PaymentPortal):
|
||||||
|
@http.route('/donation/pay', type='http', methods=['GET', 'POST'], auth='public', website=True, sitemap=False)
|
||||||
|
def donation_pay(self, **kwargs):
|
||||||
|
""" Behaves like PaymentPortal.payment_pay but for donation
|
||||||
|
|
||||||
|
:param dict kwargs: As the parameters of in payment_pay, with the additional:
|
||||||
|
- str donation_options: The options settled in the donation snippet
|
||||||
|
- str donation_descriptions: The descriptions for all prefilled amounts
|
||||||
|
:return: The rendered donation form
|
||||||
|
:rtype: str
|
||||||
|
:raise: werkzeug.exceptions.NotFound if the access token is invalid
|
||||||
|
"""
|
||||||
|
kwargs['is_donation'] = True
|
||||||
|
kwargs['currency_id'] = int(kwargs.get('currency', request.env.company.currency_id.id))
|
||||||
|
kwargs['amount'] = float(kwargs.get('amount', 25))
|
||||||
|
kwargs['donation_options'] = kwargs.get('donation_options', json_safe.dumps(dict(customAmount="freeAmount")))
|
||||||
|
|
||||||
|
if request.env.user._is_public():
|
||||||
|
kwargs['partner_id'] = request.env.user.partner_id.id
|
||||||
|
kwargs['access_token'] = payment_utils.generate_access_token(kwargs['partner_id'], kwargs['amount'], kwargs['currency_id'])
|
||||||
|
|
||||||
|
return self.payment_pay(**kwargs)
|
||||||
|
|
||||||
|
@http.route('/donation/transaction/<minimum_amount>', type='json', auth='public', website=True, sitemap=False)
|
||||||
|
def donation_transaction(self, amount, currency_id, partner_id, access_token, minimum_amount=0, **kwargs):
|
||||||
|
if float(amount) < float(minimum_amount):
|
||||||
|
raise ValidationError(_('Donation amount must be at least %.2f.', float(minimum_amount)))
|
||||||
|
use_public_partner = request.env.user._is_public() or not partner_id
|
||||||
|
if use_public_partner:
|
||||||
|
details = kwargs['partner_details']
|
||||||
|
if not details.get('name'):
|
||||||
|
raise ValidationError(_('Name is required.'))
|
||||||
|
if not details.get('email'):
|
||||||
|
raise ValidationError(_('Email is required.'))
|
||||||
|
if not details.get('country_id'):
|
||||||
|
raise ValidationError(_('Country is required.'))
|
||||||
|
partner_id = request.website.user_id.partner_id.id
|
||||||
|
del kwargs['partner_details']
|
||||||
|
|
||||||
|
kwargs.pop('custom_create_values', None) # Don't allow passing arbitrary create values
|
||||||
|
tx_sudo = self._create_transaction(
|
||||||
|
amount=amount, currency_id=currency_id, partner_id=partner_id, **kwargs
|
||||||
|
)
|
||||||
|
tx_sudo.is_donation = True
|
||||||
|
if use_public_partner:
|
||||||
|
tx_sudo.update({
|
||||||
|
'partner_name': details['name'],
|
||||||
|
'partner_email': details['email'],
|
||||||
|
'partner_country_id': details['country_id'],
|
||||||
|
})
|
||||||
|
elif not tx_sudo.partner_country_id:
|
||||||
|
tx_sudo.partner_country_id = kwargs['partner_details']['country_id']
|
||||||
|
self._update_landing_route(tx_sudo)
|
||||||
|
|
||||||
|
# Send a notification to warn that a donation has been made
|
||||||
|
recipient_email = kwargs['donation_recipient_email']
|
||||||
|
comment = kwargs['donation_comment']
|
||||||
|
tx_sudo._send_donation_email(True, comment, recipient_email)
|
||||||
|
|
||||||
|
return tx_sudo._get_processing_values()
|
||||||
|
|
||||||
|
def _get_custom_rendering_context_values(self, donation_options=None, donation_descriptions=None, is_donation=False, **kwargs):
|
||||||
|
rendering_context = super()._get_custom_rendering_context_values(**kwargs)
|
||||||
|
if is_donation:
|
||||||
|
user_sudo = request.env.user
|
||||||
|
logged_in = not user_sudo._is_public()
|
||||||
|
# If the user is logged in, take their partner rather than the partner set in the params.
|
||||||
|
# This is something that we want, since security rules are based on the partner, and created
|
||||||
|
# tokens should not be assigned to the public user. This should have no impact on the
|
||||||
|
# transaction itself besides making reconciliation possibly more difficult (e.g. The
|
||||||
|
# transaction and invoice partners are different).
|
||||||
|
partner_sudo = user_sudo.partner_id
|
||||||
|
partner_details = {}
|
||||||
|
countries = request.env['res.country']
|
||||||
|
if logged_in:
|
||||||
|
partner_details = {
|
||||||
|
'name': partner_sudo.name,
|
||||||
|
'email': partner_sudo.email,
|
||||||
|
'country_id': partner_sudo.country_id.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
countries = request.env['res.country'].sudo().search([])
|
||||||
|
descriptions = request.httprequest.form.getlist('donation_descriptions')
|
||||||
|
|
||||||
|
donation_options = json_safe.loads(donation_options) if donation_options else {}
|
||||||
|
donation_amounts = json_safe.loads(donation_options.get('donationAmounts', '[]'))
|
||||||
|
|
||||||
|
rendering_context.update({
|
||||||
|
'is_donation': True,
|
||||||
|
'partner': partner_sudo,
|
||||||
|
'transaction_route': '/donation/transaction/%s' % donation_options.get('minimumAmount', 0),
|
||||||
|
'partner_details': partner_details,
|
||||||
|
'error': {},
|
||||||
|
'countries': countries,
|
||||||
|
'donation_options': donation_options,
|
||||||
|
'donation_amounts': donation_amounts,
|
||||||
|
'donation_descriptions': descriptions,
|
||||||
|
})
|
||||||
|
return rendering_context
|
||||||
|
|
||||||
|
def _get_payment_page_template_xmlid(self, **kwargs):
|
||||||
|
if kwargs.get('is_donation'):
|
||||||
|
return 'website_payment.donation_pay'
|
||||||
|
return super()._get_payment_page_template_xmlid(**kwargs)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="mail_template_donation" model="mail.template">
|
||||||
|
<field name="name">Donation</field>
|
||||||
|
<field name="model_id" ref="payment.model_payment_transaction" />
|
||||||
|
<field name="lang">${object.partner_id.lang}</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<template id="website_payment.donation_mail_body" name="Donation mail">
|
||||||
|
<table border="0" cellpadding="0" style="background-color: white; padding: 0px; border-collapse:separate;">
|
||||||
|
<tr style="height: 48px;"><td valign="top"><span style="font-size: 24px; font-weight: bold;">
|
||||||
|
<t t-if="is_internal_notification">Donation notification</t>
|
||||||
|
<t t-else="">Donation</t>
|
||||||
|
</span></td></tr>
|
||||||
|
<t t-if="not is_internal_notification">
|
||||||
|
<tr><td valign="top">
|
||||||
|
Dear <t t-out="tx.partner_name"/>,
|
||||||
|
</td></tr>
|
||||||
|
<tr><td valign="top">
|
||||||
|
<div style="margin: 16px 0px 16px 0px;">
|
||||||
|
Thank you for your donation of <span t-out="tx.amount" t-options="{'widget': 'monetary', 'display_currency': tx.currency_id}"/> made on <t t-out="tx.create_date" t-options="{'widget': 'date'}"/>.
|
||||||
|
<br/>
|
||||||
|
We appreciate your support for our organization as such.
|
||||||
|
<br/>
|
||||||
|
Regards.
|
||||||
|
</div>
|
||||||
|
</td></tr>
|
||||||
|
</t>
|
||||||
|
<tr><td valign="top">
|
||||||
|
<div style="margin: 16px 0px 16px 0px;">
|
||||||
|
<table border="0" cellpadding="0" cellspacing="5" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td><b>Donor Name:</b></td>
|
||||||
|
<td><t t-out="tx.partner_name"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Donor Email:</b></td>
|
||||||
|
<td><t t-out="tx.partner_email"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Donation Date:</b></td>
|
||||||
|
<td><t t-out="tx.create_date.date()"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Amount(<t t-out="tx.currency_id.symbol"/>):</b></td>
|
||||||
|
<td><t t-out="tx.amount"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr t-if="is_internal_notification and comment">
|
||||||
|
<td><b>Comment:</b></td>
|
||||||
|
<td><t t-out="comment"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Payment Method:</b></td>
|
||||||
|
<td><t t-out="tx.provider"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><b>Payment ID:</b></td>
|
||||||
|
<td><t t-out="tx.reference"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -1 +1,6 @@
|
|||||||
from . import payment_acquirer
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import account_payment
|
||||||
|
from . import payment_acquirer
|
||||||
|
from . import payment_transaction
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class AccountPayment(models.Model):
|
||||||
|
_inherit = 'account.payment'
|
||||||
|
|
||||||
|
is_donation = fields.Boolean(string="Is Donation", related="payment_transaction_id.is_donation", help="Is the payment a donation")
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import _, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentTransaction(models.Model):
|
||||||
|
_inherit = "payment.transaction"
|
||||||
|
|
||||||
|
is_donation = fields.Boolean(string="Is donation", help="Is the payment a donation")
|
||||||
|
|
||||||
|
def _finalize_post_processing(self):
|
||||||
|
super()._finalize_post_processing()
|
||||||
|
for tx in self.filtered('is_donation'):
|
||||||
|
tx._send_donation_email()
|
||||||
|
msg = [_('Payment received from donation with following details:')]
|
||||||
|
for field in ['company_id', 'partner_id', 'partner_name', 'partner_country_id', 'partner_email']:
|
||||||
|
field_name = tx._fields[field].string
|
||||||
|
value = tx[field]
|
||||||
|
if value:
|
||||||
|
if 'name' in value:
|
||||||
|
value = value.name
|
||||||
|
msg.append('<br/>- %s: %s' % (field_name, value))
|
||||||
|
tx.payment_id._message_log(body=''.join(msg))
|
||||||
|
|
||||||
|
def _send_donation_email(self, is_internal_notification=False, comment=None, recipient_email=None):
|
||||||
|
self.ensure_one()
|
||||||
|
if is_internal_notification or self.state == 'done':
|
||||||
|
subject = _('A donation has been made on your website') if is_internal_notification else _('Donation confirmation')
|
||||||
|
body = self.env.ref('website_payment.donation_mail_body')._render({
|
||||||
|
'is_internal_notification': is_internal_notification,
|
||||||
|
'tx': self,
|
||||||
|
'comment': comment,
|
||||||
|
}, engine='ir.qweb', minimal_qcontext=True)
|
||||||
|
self.env.ref('website_payment.mail_template_donation').send_mail(
|
||||||
|
self.id, notif_layout="mail.mail_notification_light",
|
||||||
|
force_send=True,
|
||||||
|
email_values={
|
||||||
|
'email_to': recipient_email if is_internal_notification else self.partner_email,
|
||||||
|
'email_from': self.company_id.email_formatted,
|
||||||
|
'author_id': self.partner_id.id,
|
||||||
|
'subject': subject,
|
||||||
|
'body_html': body,
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import publicWidget from 'web.public.widget';
|
||||||
|
|
||||||
|
publicWidget.registry.WebsitePaymentDonation = publicWidget.Widget.extend({
|
||||||
|
selector: '.o_donation_payment_form',
|
||||||
|
events: {
|
||||||
|
'focus .o_amount_input': '_onFocusAmountInput',
|
||||||
|
'change #donation_comment_checkbox': '_onChangeDonationComment'
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Handlers
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
_onFocusAmountInput(ev) {
|
||||||
|
this.$target.find('#other_amount').prop("checked", true);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
_onChangeDonationComment(ev) {
|
||||||
|
const $donationComment = this.$target.find('#donation_comment');
|
||||||
|
const checked = $(ev.currentTarget).is(':checked');
|
||||||
|
$donationComment.toggleClass('d-none', !checked);
|
||||||
|
if (!checked) {
|
||||||
|
$donationComment.val('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import {_t} from 'web.core';
|
||||||
|
import checkoutForm from 'payment.checkout_form';
|
||||||
|
|
||||||
|
checkoutForm.include({
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform some validations for donations before performing payment
|
||||||
|
*
|
||||||
|
* @override method from payment.payment_form_mixin
|
||||||
|
* @private
|
||||||
|
* @param {string} provider - The provider of the payment option's acquirer
|
||||||
|
* @param {number} paymentOptionId - The id of the payment option handling the transaction
|
||||||
|
* @param {string} flow - The online payment flow of the transaction
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
_processPayment: function (provider, paymentOptionId, flow) {
|
||||||
|
if ($('.o_donation_payment_form').length) {
|
||||||
|
const errorFields = {};
|
||||||
|
if (!this.$('input[name="email"]')[0].checkValidity()) {
|
||||||
|
errorFields['email'] = _t("Email is invalid");
|
||||||
|
}
|
||||||
|
const mandatoryFields = {
|
||||||
|
'name': _t('Name'),
|
||||||
|
'email': _t('Email'),
|
||||||
|
'country_id': _t('Country'),
|
||||||
|
};
|
||||||
|
for (const id in mandatoryFields) {
|
||||||
|
const $field = this.$('input[name="' + id + '"],select[name="' + id + '"]');
|
||||||
|
$field.removeClass('is-invalid').popover('dispose');
|
||||||
|
if (!$field.val().trim()) {
|
||||||
|
errorFields[id] = _.str.sprintf(_t("Field '%s' is mandatory"), mandatoryFields[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(errorFields).length) {
|
||||||
|
for (const id in errorFields) {
|
||||||
|
const $field = this.$('input[name="' + id + '"],select[name="' + id + '"]');
|
||||||
|
$field.addClass('is-invalid');
|
||||||
|
$field.popover({content: errorFields[id], trigger: 'hover', container: 'body', placement: 'top'});
|
||||||
|
$field.data("bs.popover").config.content = errorFields[id];
|
||||||
|
}
|
||||||
|
this._displayError(
|
||||||
|
_t("Validation Error"),
|
||||||
|
_t("Some information is missing to process your payment.")
|
||||||
|
);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._super(...arguments);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Add params used by the donation snippet to the transaction route params.
|
||||||
|
*
|
||||||
|
* @override method from payment.payment_form_mixin
|
||||||
|
* @private
|
||||||
|
* @param {string} provider - The provider of the selected payment option's acquirer
|
||||||
|
* @param {number} paymentOptionId - The id of the selected payment option
|
||||||
|
* @param {string} flow - The online payment flow of the selected payment option
|
||||||
|
* @return {object} The extended transaction route params
|
||||||
|
*/
|
||||||
|
_prepareTransactionRouteParams: function (provider, paymentOptionId, flow) {
|
||||||
|
const transactionRouteParams = this._super(...arguments);
|
||||||
|
return $('.o_donation_payment_form').length ? {
|
||||||
|
...transactionRouteParams,
|
||||||
|
'partner_details': {
|
||||||
|
'name': this.$('input[name="name"]').val(),
|
||||||
|
'email': this.$('input[name="email"]').val(),
|
||||||
|
'country_id': this.$('select[name="country_id"]').val(),
|
||||||
|
},
|
||||||
|
'donation_comment': this.$('#donation_comment').val(),
|
||||||
|
'donation_recipient_email': this.$('input[name="donation_recipient_email"]').val(),
|
||||||
|
} : transactionRouteParams;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -8,6 +8,7 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
|||||||
disabledInEditableMode: false,
|
disabledInEditableMode: false,
|
||||||
events: {
|
events: {
|
||||||
'click .s_donation_btn': '_onClickPrefilledButton',
|
'click .s_donation_btn': '_onClickPrefilledButton',
|
||||||
|
'click .s_donation_donate_btn': '_onClickDonateNowButton',
|
||||||
'input #s_donation_range_slider': '_onInputRangeSlider',
|
'input #s_donation_range_slider': '_onInputRangeSlider',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -17,8 +18,9 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
|||||||
async start() {
|
async start() {
|
||||||
await this._super(...arguments);
|
await this._super(...arguments);
|
||||||
this.$rangeSlider = this.$('#s_donation_range_slider');
|
this.$rangeSlider = this.$('#s_donation_range_slider');
|
||||||
|
this.defaultAmount = this.$target[0].dataset.defaultAmount;
|
||||||
if (this.$rangeSlider.length) {
|
if (this.$rangeSlider.length) {
|
||||||
this.$rangeSlider.val(this.$target[0].dataset.defaultAmount);
|
this.$rangeSlider.val(this.defaultAmount);
|
||||||
this._setBubble(this.$rangeSlider);
|
this._setBubble(this.$rangeSlider);
|
||||||
}
|
}
|
||||||
await this._displayCurrencies();
|
await this._displayCurrencies();
|
||||||
@@ -29,6 +31,7 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
|||||||
destroy() {
|
destroy() {
|
||||||
this.$target.find('.s_donation_currency').remove();
|
this.$target.find('.s_donation_currency').remove();
|
||||||
this._deselectPrefilledButtons();
|
this._deselectPrefilledButtons();
|
||||||
|
this.$('.alert-danger').remove();
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -100,6 +103,50 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
|||||||
this._setBubble(this.$rangeSlider);
|
this._setBubble(this.$rangeSlider);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onClickDonateNowButton(ev) {
|
||||||
|
if (this.editableMode) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
this.$('.alert-danger').remove();
|
||||||
|
const $buttons = this.$('.s_donation_btn');
|
||||||
|
const $selectedButton = $buttons.filter('.active');
|
||||||
|
let amount = $selectedButton.length ? $selectedButton[0].dataset.donationValue : 0;
|
||||||
|
if (this.$target[0].dataset.displayOptions && !amount) {
|
||||||
|
if (this.$rangeSlider.length) {
|
||||||
|
amount = this.$rangeSlider.val();
|
||||||
|
} else if ($buttons.length) {
|
||||||
|
amount = parseFloat(this.$('#s_donation_amount_input').val());
|
||||||
|
let errorMessage = '';
|
||||||
|
const minAmount = this.$target[0].dataset.minimumAmount;
|
||||||
|
if (!amount) {
|
||||||
|
errorMessage = _t("Please select or enter an amount");
|
||||||
|
} else if (amount < parseFloat(minAmount)) {
|
||||||
|
const before = this.currency.position === "before" ? this.currency.symbol : "";
|
||||||
|
const after = this.currency.position === "after" ? this.currency.symbol : "";
|
||||||
|
errorMessage = _.str.sprintf(_t("The minimum donation amount is %s%s%s"), before, minAmount, after);
|
||||||
|
}
|
||||||
|
if (errorMessage) {
|
||||||
|
$(ev.currentTarget).before($('<p>', {
|
||||||
|
class: 'alert alert-danger',
|
||||||
|
text: errorMessage,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!amount) {
|
||||||
|
amount = this.defaultAmount;
|
||||||
|
}
|
||||||
|
const $form = this.$('.s_donation_form');
|
||||||
|
$('<input>').attr({type: 'hidden', name: 'amount', value: amount}).appendTo($form);
|
||||||
|
$('<input>').attr({type: 'hidden', name: 'currency_id', value: this.currency.id}).appendTo($form);
|
||||||
|
$('<input>').attr({type: 'hidden', name: 'csrf_token', value: odoo.csrf_token}).appendTo($form);
|
||||||
|
$('<input>').attr({type: 'hidden', name: 'donation_options', value: JSON.stringify(this.el.dataset)}).appendTo($form);
|
||||||
|
$form.submit();
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
<div class="s_donation_prefilled_buttons mb-2">
|
<div class="s_donation_prefilled_buttons mb-2">
|
||||||
<t t-foreach="prefilled_buttons" t-as="prefilled_button_value">
|
<t t-foreach="prefilled_buttons" t-as="prefilled_button_value">
|
||||||
<button class="s_donation_btn btn btn-outline-primary btn-lg mb-2 mr-1 o_not_editable"
|
<button class="s_donation_btn btn btn-outline-primary btn-lg mb-2 mr-1 o_not_editable"
|
||||||
|
type="button"
|
||||||
contenteditable="false"
|
contenteditable="false"
|
||||||
t-att-data-donation-value="prefilled_button_value"
|
t-att-data-donation-value="prefilled_button_value"
|
||||||
t-esc="prefilled_button_value"/>
|
t-esc="prefilled_button_value"/>
|
||||||
</t>
|
</t>
|
||||||
<button t-if="custom_input" class="s_donation_btn s_donation_custom_btn btn btn-outline-primary btn-lg mb-2 mr-1">
|
<button t-if="custom_input" type="button" class="s_donation_btn s_donation_custom_btn btn btn-outline-primary btn-lg mb-2 mr-1">
|
||||||
<input id="s_donation_amount_input" type="number" min="5" class="" placeholder="Custom Amount" aria-label="Amount"/>
|
<input id="s_donation_amount_input" type="number" min="5" class="" placeholder="Custom Amount" aria-label="Amount"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,13 +19,14 @@
|
|||||||
<t t-foreach="prefilled_buttons" t-as="prefilled_button">
|
<t t-foreach="prefilled_buttons" t-as="prefilled_button">
|
||||||
<div class="s_donation_btn_description d-sm-flex align-items-center my-3 o_not_editable o_translate_mode_hidden" contenteditable="false">
|
<div class="s_donation_btn_description d-sm-flex align-items-center my-3 o_not_editable o_translate_mode_hidden" contenteditable="false">
|
||||||
<button class="s_donation_btn btn btn-outline-primary btn-lg mr-3"
|
<button class="s_donation_btn btn btn-outline-primary btn-lg mr-3"
|
||||||
|
type="button"
|
||||||
t-att-data-donation-value="prefilled_button.value"
|
t-att-data-donation-value="prefilled_button.value"
|
||||||
t-esc="prefilled_button.value"/>
|
t-esc="prefilled_button.value"/>
|
||||||
<p class="s_donation_description mt-2 my-sm-auto text-muted font-italic" t-esc="prefilled_button.description"></p>
|
<p class="s_donation_description mt-2 my-sm-auto text-muted font-italic" t-esc="prefilled_button.description"></p>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<div t-if="custom_input" class="d-sm-flex align-items-center my-3">
|
<div t-if="custom_input" class="d-sm-flex align-items-center my-3">
|
||||||
<button class="s_donation_btn s_donation_custom_btn btn btn-outline-primary btn-lg">
|
<button type="button" class="s_donation_btn s_donation_custom_btn btn btn-outline-primary btn-lg">
|
||||||
<input id="s_donation_amount_input" type="number" t-att-min="minimum_amount" placeholder="Custom Amount" aria-label="Amount"/>
|
<input id="s_donation_amount_input" type="number" t-att-min="minimum_amount" placeholder="Custom Amount" aria-label="Amount"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<template id="payment_checkout" inherit_id="payment.checkout">
|
||||||
|
<!-- Make available anchor to inject donation form content -->
|
||||||
|
<xpath expr="//form/t[@t-set='acquirer_count']" position="before">
|
||||||
|
<t t-if="is_donation">
|
||||||
|
<t t-set="donation_confirm_button_icon_class" t-valuef="fa-gift"/>
|
||||||
|
<t t-set="donation_confirm_button_label">Donate Now</t>
|
||||||
|
<h3 class="o_page_header mt16 mb4">Donation</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div t-attf-class="form-group #{error.get('name') and 'o_has_error' or ''} col-lg-12 div_name">
|
||||||
|
<label class="col-form-label font-weight-bold" for="name">Name
|
||||||
|
<span class="s_website_form_mark"> *</span>
|
||||||
|
</label>
|
||||||
|
<input t-att-readonly="'1' if 'name' in partner_details and partner_id else None" type="text" name="name" t-attf-class="form-control #{error.get('name') and 'is-invalid' or ''}" t-att-value="partner_details.get('name')" />
|
||||||
|
</div>
|
||||||
|
<div class="w-100"/>
|
||||||
|
<div t-attf-class="form-group #{error.get('email') and 'o_has_error' or ''} col-lg-6" id="div_email">
|
||||||
|
<label class="col-form-label font-weight-bold" for="email">Email
|
||||||
|
<span class="s_website_form_mark"> *</span>
|
||||||
|
</label>
|
||||||
|
<input t-att-readonly="'1' if 'email' in partner_details and partner_id else None" type="email" name="email" t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}" t-att-value="partner_details.get('email')" />
|
||||||
|
</div>
|
||||||
|
<t t-set="country" t-value="partner_details.get('country')"/>
|
||||||
|
<div t-attf-class="form-group #{error.get('country_id') and 'o_has_error' or ''} col-lg-6 div_country">
|
||||||
|
<label class="col-form-label font-weight-bold" for="country_id">Country
|
||||||
|
<span class="s_website_form_mark"> *</span>
|
||||||
|
</label>
|
||||||
|
<select t-att-disabled="'1' if country and country.id and partner_id else None" id="country_id" name="country_id" t-attf-class="form-control #{error.get('country_id') and 'is-invalid' or ''}">
|
||||||
|
<option value="">Country...</option>
|
||||||
|
<t t-foreach="countries" t-as="c">
|
||||||
|
<option t-att-value="c.id" t-att-selected="c.id == (country and country.id or -1)">
|
||||||
|
<t t-out="c.name" />
|
||||||
|
</option>
|
||||||
|
</t>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="w-100"/>
|
||||||
|
<div class="form-group col-lg-12 o_donation_payment_form">
|
||||||
|
<div class="col-lg-6 px-0">
|
||||||
|
<label class="col-form-label font-weight-bold">Amount (<t t-out="currency.symbol"/>)</label>
|
||||||
|
<t t-set="donation_layout" t-value="donation_options.get('customAmount')"/>
|
||||||
|
<t t-set="prefilled_options" t-value="donation_options.get('prefilledOptions')"/>
|
||||||
|
<t t-if="prefilled_options">
|
||||||
|
<t t-foreach="donation_amounts" t-as="donation_amount">
|
||||||
|
<div class="custom-control custom-radio my-2">
|
||||||
|
<t t-set="is_checked" t-value="float(amount) == float(donation_amount)"/>
|
||||||
|
<t t-set="has_checked" t-value="has_checked or is_checked"/>
|
||||||
|
<input class="custom-control-input" type="radio" name="amount"
|
||||||
|
t-attf-id="amount_#{donation_amount_index}" t-att-value="donation_amount"
|
||||||
|
t-att-checked="is_checked or None"/>
|
||||||
|
<label class="custom-control-label mt-0" t-attf-for="amount_#{donation_amount_index}">
|
||||||
|
<t t-out="donation_amount"/>
|
||||||
|
<span t-if="donation_options.get('descriptions')" class="text-muted font-italic ml-1">
|
||||||
|
- <t t-out="donation_descriptions[donation_amount_index]"/>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
<div t-attf-class="custom-control custom-radio my-2 #{not donation_layout and 'd-none' or ''}">
|
||||||
|
<input class="custom-control-input" type="radio" id="other_amount" name="amount"
|
||||||
|
t-att-value="amount" t-att-checked="not has_checked or None"/>
|
||||||
|
<label class="custom-control-label mt-0 d-block" for="other_amount">
|
||||||
|
<t t-call="website_payment.donation_input">
|
||||||
|
<t t-set="amount" t-value="not has_checked and amount or ''"/>
|
||||||
|
</t>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<t t-call="website_payment.donation_input"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12 px-0">
|
||||||
|
<div class="custom-control custom-checkbox mt-3">
|
||||||
|
<input class="custom-control-input" type="checkbox" value="" id="donation_comment_checkbox"/>
|
||||||
|
<label class="custom-control-label" for="donation_comment_checkbox">Write us a comment</label>
|
||||||
|
</div>
|
||||||
|
<textarea class="form-control d-none mt-2" id="donation_comment" placeholder="Your comment"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="donation_recipient_email" t-att-value="donation_options.get('donationEmail')"/>
|
||||||
|
<div class="w-100"/>
|
||||||
|
</div>
|
||||||
|
<h3 class="o_page_header mt16 mb4">Payment Details</h3>
|
||||||
|
</t>
|
||||||
|
</xpath>
|
||||||
|
<!-- Adapt Pay confirm button -->
|
||||||
|
<xpath expr="//t[@t-set='label']" position="after">
|
||||||
|
<t t-set="label" t-value="donation_confirm_button_label or label"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//t[@t-set='icon_class']" position="after">
|
||||||
|
<t t-set="icon_class" t-value="donation_confirm_button_icon_class or icon_class"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Display of /donation/pay -->
|
||||||
|
<template id="website_payment.donation_pay" name="Donation payment">
|
||||||
|
<t t-call="portal.frontend_layout">
|
||||||
|
<t t-set="page_title" t-value="'Donation'"/>
|
||||||
|
<t t-set="additional_title"><t t-out="page_title"/></t>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="oe_structure" id="oe_structure_website_payment_donation_1"/>
|
||||||
|
<div class="container mb-3">
|
||||||
|
<!-- Portal breadcrumb -->
|
||||||
|
<t t-call="payment.portal_breadcrumb"/>
|
||||||
|
<!-- Payment page -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div t-if="not amount" class="alert alert-info">
|
||||||
|
There is nothing to pay.
|
||||||
|
</div>
|
||||||
|
<div t-elif="not currency" class="alert alert-warning">
|
||||||
|
<strong>Warning</strong> The currency is missing or incorrect.
|
||||||
|
</div>
|
||||||
|
<div t-elif="not acquirers and not tokens" class="alert alert-warning">
|
||||||
|
<strong>No suitable payment option could be found.</strong><br/>
|
||||||
|
If you believe that it is an error, please contact the website administrator.
|
||||||
|
</div>
|
||||||
|
<t t-else="" t-call="payment.checkout"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_structure" id="oe_structure_website_payment_donation_2"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="website_payment.donation_input" name="Donation input">
|
||||||
|
<input type="number" class="form-control o_amount_input" t-att-min="donation_options.get('minimumAmount')"
|
||||||
|
t-att-max="donation_options.get('maximumAmount')" t-att-value="amount" placeholder="Custom Amount"/>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -14,13 +14,15 @@
|
|||||||
data-maximum-amount="100"
|
data-maximum-amount="100"
|
||||||
data-slider-step="5"
|
data-slider-step="5"
|
||||||
data-default-amount="25">
|
data-default-amount="25">
|
||||||
<span id="s_donation_description_inputs">
|
<form class="s_donation_form" action="/donation/pay" method="post" enctype="multipart/form-data">
|
||||||
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions[]" value="A year of cultural awakening."/>
|
<span id="s_donation_description_inputs">
|
||||||
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions[]" value="Caring for a baby for 1 month."/>
|
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions" value="A year of cultural awakening."/>
|
||||||
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions[]" value="One year in elementary school."/>
|
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions" value="Caring for a baby for 1 month."/>
|
||||||
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions[]" value="One year in high school."/>
|
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions" value="One year in elementary school."/>
|
||||||
</span>
|
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions" value="One year in high school."/>
|
||||||
<a href="#" class="s_donation_donate_btn btn btn-secondary btn-lg mb-2">Donate Now</a>
|
</span>
|
||||||
|
<a href="#" type="button" class="s_donation_donate_btn btn btn-secondary btn-lg mb-2">Donate Now</a>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user