[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,
|
||||
**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)
|
||||
def payment_method(self, **kwargs):
|
||||
@@ -205,16 +208,9 @@ class PaymentPortal(portal.CustomerPortal):
|
||||
tx_sudo = self._create_transaction(
|
||||
amount=amount, currency_id=currency_id, partner_id=partner_id, **kwargs
|
||||
)
|
||||
|
||||
# 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).
|
||||
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}'
|
||||
|
||||
if tx_sudo.operation == 'validation':
|
||||
# The amount and currency have been chosen for the validation, recompute the access token
|
||||
self._update_landing_route(tx_sudo)
|
||||
return tx_sudo._get_processing_values()
|
||||
|
||||
def _create_transaction(
|
||||
@@ -308,6 +304,24 @@ class PaymentPortal(portal.CustomerPortal):
|
||||
|
||||
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)
|
||||
def payment_confirm(self, tx_id, access_token, **kwargs):
|
||||
""" Display the payment confirmation page with the appropriate status message to the user.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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',
|
||||
],
|
||||
'data': [
|
||||
'data/donation_data.xml',
|
||||
'views/payment_acquirer.xml',
|
||||
'views/donation_templates.xml',
|
||||
'views/snippets/snippets.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_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',
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
events: {
|
||||
'click .s_donation_btn': '_onClickPrefilledButton',
|
||||
'click .s_donation_donate_btn': '_onClickDonateNowButton',
|
||||
'input #s_donation_range_slider': '_onInputRangeSlider',
|
||||
},
|
||||
|
||||
@@ -17,8 +18,9 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
||||
async start() {
|
||||
await this._super(...arguments);
|
||||
this.$rangeSlider = this.$('#s_donation_range_slider');
|
||||
this.defaultAmount = this.$target[0].dataset.defaultAmount;
|
||||
if (this.$rangeSlider.length) {
|
||||
this.$rangeSlider.val(this.$target[0].dataset.defaultAmount);
|
||||
this.$rangeSlider.val(this.defaultAmount);
|
||||
this._setBubble(this.$rangeSlider);
|
||||
}
|
||||
await this._displayCurrencies();
|
||||
@@ -29,6 +31,7 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
||||
destroy() {
|
||||
this.$target.find('.s_donation_currency').remove();
|
||||
this._deselectPrefilledButtons();
|
||||
this.$('.alert-danger').remove();
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
@@ -100,6 +103,50 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
<div class="s_donation_prefilled_buttons mb-2">
|
||||
<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"
|
||||
type="button"
|
||||
contenteditable="false"
|
||||
t-att-data-donation-value="prefilled_button_value"
|
||||
t-esc="prefilled_button_value"/>
|
||||
</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"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -18,13 +19,14 @@
|
||||
<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">
|
||||
<button class="s_donation_btn btn btn-outline-primary btn-lg mr-3"
|
||||
type="button"
|
||||
t-att-data-donation-value="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>
|
||||
</div>
|
||||
</t>
|
||||
<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"/>
|
||||
</button>
|
||||
</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-slider-step="5"
|
||||
data-default-amount="25">
|
||||
<span id="s_donation_description_inputs">
|
||||
<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="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 elementary school."/>
|
||||
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions[]" value="One year in high school."/>
|
||||
</span>
|
||||
<a href="#" class="s_donation_donate_btn btn btn-secondary btn-lg mb-2">Donate Now</a>
|
||||
<form class="s_donation_form" action="/donation/pay" method="post" enctype="multipart/form-data">
|
||||
<span id="s_donation_description_inputs">
|
||||
<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="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 elementary school."/>
|
||||
<input type="hidden" class="o_translatable_input_hidden d-block mb-1 w-100" name="donation_descriptions" value="One year in high school."/>
|
||||
</span>
|
||||
<a href="#" type="button" class="s_donation_donate_btn btn btn-secondary btn-lg mb-2">Donate Now</a>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user