19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="payment.express_checkout" name="Payment Express Checkout">
<!-- Parameters description:
- reference_prefix: The custom prefix to compute the full transaction reference.
- amount: The amount to pay.
- minor_amount: The amount to pay in the minor units of its currency.
- currency: The currency of the payment, as a `res.currency` record.
- providers_sudo: The compatible providers, as a sudoed `payment.provider` recordset.
- merchant_name: The merchant name.
- payment_method_unknown_id: The ID of the "Unknown" payment method record, to use as
the generic express checkout method.
- payment_access_token: The access token used to authenticate the partner. Since this
template is loaded in the shopping cart, this parameter is
called `payment_access_token` to prevent mixing up with the
`access_token` used for abandoned carts.
- shipping_info_required: Whether the shipping information is required or not.
- transaction_route: The route used to create a transaction when the user clicks Pay.
- shipping_address_update_route: The route where available carriers are computed based
on the (partial) shipping information available.
Optional.
- express_checkout_route: The route where the billing and shipping information are sent.
- landing_route: The route the user is redirected to after the transaction.
- payment_access_token: The access token used to authenticate the partner. Since this
template is loaded in the shopping cart, this parameter is
called `payment_access_token` to prevent mixing up with the
`access_token` used for abandoned carts.
-->
<form name="o_payment_express_checkout_form" class="container"
t-att-data-reference-prefix="reference_prefix"
t-att-data-amount="amount"
t-att-data-minor-amount="minor_amount"
t-att-data-currency-id="currency and currency.id"
t-att-data-currency-name="currency.name.lower()"
t-att-data-merchant-name="merchant_name"
t-att-data-partner-id="partner_id"
t-att-data-payment-method-unknown-id="payment_method_unknown_id"
t-att-data-access-token="payment_access_token"
t-att-data-shipping-info-required="shipping_info_required"
t-att-data-delivery-amount="delivery_amount"
t-att-data-transaction-route="transaction_route"
t-att-data-shipping-address-update-route="shipping_address_update_route"
t-att-data-express-checkout-route="express_checkout_route"
t-att-data-landing-route="landing_route"
>
<t t-set="provider_sudo" t-value="providers_sudo[:1]"/>
<t t-set="express_checkout_form_xml_id"
t-value="provider_sudo.express_checkout_form_view_id.xml_id"
/>
<t t-if="express_checkout_form_xml_id">
<t t-call="{{express_checkout_form_xml_id}}"/>
</t>
</form>
</template>
</odoo>

View file

@ -0,0 +1,546 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="payment.form" name="Payment Form">
<!-- Form customization parameters:
- mode: The operation mode of the form: `payment` or `validation`; default: `payment`.
- allow_token_selection: Whether tokens can be selected for payment or assignation
(through the `assign_token_route` parameter); default: `True`.
- allow_token_deletion: Whether tokens can be deleted (archived); default: `False`.
- default_token_id: The id of the token that should be pre-selected; default: `None`.
- show_tokenize_input_mapping: For each provider, whether the tokenization checkbox is
shown; used only in `payment` mode.
- display_submit_button: Whether the submit button is displayed; default: `True`.
- submit_button_label: The label of the submit button; default: 'Pay'/'Save'.
-->
<!-- Payment context:
- reference_prefix: The custom prefix to compute the full transaction reference.
- amount: The amount to pay.
- currency: The currency of the payment, as a `res.currency` record.
- partner_id: The id of the partner on behalf of whom the payment should be made.
- providers_sudo: The compatible providers, as a sudoed `payment.provider` recordset.
- payment_methods_sudo: The compatible payment methods, as a sudoed `payment.method`
recordset.
- tokens_sudo: The available payment tokens, as a sudoed `payment.token` recordset.
- transaction_route: The route to call to create the transaction.
- assign_token_route: The route to call to assign a new or existing token to a record.
- landing_route: The route the user is redirected to after payment.
- access_token: The access token used to authenticate the partner.
-->
<t t-set="mode" t-value="mode or 'payment'"/>
<t t-set="allow_token_selection"
t-value="True if allow_token_selection is None else allow_token_selection"
/>
<t t-set="allow_token_deletion" t-value="allow_token_deletion or False"/>
<t t-set="selected_token_id"
t-value="allow_token_selection and (default_token_id or tokens_sudo[:1].id)"
/>
<t t-set="selected_method_id"
t-value="not selected_token_id
and len(payment_methods_sudo) == 1
and payment_methods_sudo[:1].id"
/>
<t t-set="collapse_payment_methods"
t-value="tokens_sudo and allow_token_selection and payment_methods_sudo"
/>
<t t-set="display_submit_button"
t-value="True if display_submit_button is None else display_submit_button"
/>
<t t-set="pay_label">Pay</t> <!-- Allow translating the label. -->
<t t-set="save_label">Save</t> <!-- Allow translating the label. -->
<t t-set="submit_button_label"
t-value="submit_button_label or (pay_label if mode == 'payment' else save_label)"
/>
<form t-if="payment_methods_sudo or tokens_sudo" id="o_payment_form"
class="o_payment_form"
t-att-data-mode="mode"
t-att-data-reference-prefix="reference_prefix"
t-att-data-amount="amount"
t-att-data-currency-id="currency and currency.id"
t-att-data-partner-id="partner_id"
t-att-data-transaction-route="transaction_route"
t-att-data-assign-token-route="assign_token_route"
t-att-data-landing-route="landing_route"
t-att-data-access-token="access_token"
>
<div id="o_payment_form_options" class="d-flex flex-column gap-3">
<!-- === Payment tokens === -->
<div t-if="tokens_sudo">
<!-- === Header === -->
<h4 id="o_payment_tokens_heading">
Your payment methods
</h4>
<!-- === Body === -->
<ul class="list-group">
<t t-foreach="tokens_sudo" t-as="token_sudo">
<li name="o_payment_option"
t-att-class="'list-group-item d-flex flex-column gap-2 py-3'
+ (' o_outline' if allow_token_selection else '')"
>
<t t-call="payment.token_form">
<t t-set="is_selected"
t-value="token_sudo.id == selected_token_id"
/>
</t>
</li>
</t>
</ul>
</div>
<!-- === Payment methods === -->
<div t-if="payment_methods_sudo"
id="o_payment_methods"
t-att-class="'collapse' if collapse_payment_methods else ''"
>
<!-- === Header === -->
<p t-attf-class="mb-3 {{collapse_payment_methods and 'h5' or 'h4'}}">
<t t-if="not collapse_payment_methods">Payment method</t>
<t t-else="">Other payment methods</t>
<t t-call="payment.availability_report_button"/>
</p>
<!-- === Body === -->
<ul class="list-group">
<t t-foreach="payment_methods_sudo" t-as="pm_sudo">
<li name="o_payment_option"
class="list-group-item d-flex flex-column gap-2 py-3 o_outline"
>
<t t-call="payment.method_form">
<t t-set="is_selected"
t-value="pm_sudo.id == selected_method_id"
/>
</t>
</li>
</t>
</ul>
</div>
</div>
<div class="d-flex justify-content-end flex-column flex-md-row gap-2 my-2">
<!-- === Expand payment methods button === -->
<button t-if="collapse_payment_methods"
name="o_payment_expand_button"
type="button"
href="#o_payment_methods"
class="btn btn-link"
data-bs-toggle="collapse"
>
Choose another method <i class="oi oi-arrow-down"/>
</button>
<!-- === Submit button === -->
<t t-if="display_submit_button" t-call="payment.submit_button"/>
</div>
<!-- === Availability report === -->
<t t-call="payment.availability_report"/>
</form>
<t t-else="" t-call="payment.no_pms_available_warning"/>
</template>
<template id="payment.token_form" name="Payment Token Form">
<!-- Parameters description:
- token_sudo: The token to display, as a sudoed `payment.token` recordset.
- allow_token_selection: Whether tokens can be selected for payment or assignation (if
the `assign_route` parameter is provided); default: `True`.
- is_selected: Whether the radio button of the token should be checked.
-->
<t t-set="provider_sudo" t-value="token_sudo.provider_id"/>
<t t-set="is_test" t-value="provider_sudo.state == 'test'"/>
<t t-set="is_unpublished" t-value="not provider_sudo.is_published"/>
<t t-set="inline_form_xml_id" t-value="provider_sudo.token_inline_form_view_id.xml_id"/>
<div class="d-flex gap-3 align-items-start align-items-md-center">
<!-- === Delete button === -->
<button t-if="allow_token_deletion"
name="o_payment_delete_token"
t-att-class="'btn btn-link px-2 py-0 lh-lg z-1'
+ (' d-none' if mode != 'validation' else '')"
t-attf-data-linked-radio="o_payment_token_{{token_sudo.id}}"
>
<i class="fa fa-trash"
title="Delete payment method"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-delay="0"
/>
</button>
<div class="row flex-column flex-md-row flex-grow-1 gap-lg-3 align-items-start
align-items-md-center"
>
<div class="col col-lg-5">
<div t-att-class="'form-check mb-0'
+ (' ps-0' if not allow_token_selection else '')">
<!-- === Radio button === -->
<input t-attf-id="o_payment_token_{{token_sudo.id}}"
name="o_payment_radio"
type="radio"
t-att-checked="is_selected"
t-att-disabled="not allow_token_selection"
t-att-class="'form-check-input'
+ (' d-none' if not allow_token_selection else '')"
data-payment-option-type="token"
t-att-data-payment-option-id="token_sudo.id"
t-att-data-provider-code="token_sudo.provider_id._get_code()"
t-att-data-provider-id="token_sudo.provider_id.id"
/>
<div class="d-flex align-items-center flex-wrap gap-2">
<!-- === Token label === -->
<label t-out="token_sudo.payment_method_id.name"
class="o_payment_option_label text-break"
t-attf-for="o_payment_token_{{token_sudo.id}}"
/>
<div class="d-flex flex-nowrap gap-2">
<!-- === "Unpublished" icon === -->
<t t-if="is_unpublished" t-call="payment.form_icon">
<t t-set="icon_name" t-value="'eye-slash'"/>
<t t-set="color_name" t-value="'danger'"/>
<t t-set="title">Unpublished</t>
</t>
<!-- === "Test mode" icon === -->
<t t-if="is_test" t-call="payment.form_icon">
<t t-set="icon_name" t-value="'exclamation-triangle'"/>
<t t-set="color_name" t-value="'warning'"/>
<t t-set="title">Test mode</t>
</t>
</div>
</div>
</div>
</div>
<!-- === Token name (payment details) === -->
<div class="col">
<p t-out="token_sudo.display_name"
t-att-class="'mb-0 small fw-bold text-break'
+ (' ms-4 ms-md-0' if allow_token_selection else '')"
/>
</div>
<!-- === Provider name (only for desktop and tablet) === -->
<t t-set="hide_secured_by" t-value="False"/>
<div class="col d-none d-md-block">
<p name="o_payment_secured_by_desktop" t-att-class="'mb-0 small text-600'
+ (' ms-4 ms-md-0' if allow_token_selection else '')
+ (' d-none' if hide_secured_by else '')"
>
<span><i class="fa fa-lock"/> Secured by</span>
<span t-out="dict(provider_sudo._fields['code']._description_selection(
provider_sudo.env
))[provider_sudo.code]"
class="text-break"
/>
</p>
</div>
</div>
<!-- === Payment method logo === -->
<div>
<t t-call="payment.form_logo" logo_pm_sudo="token_sudo.payment_method_id"/>
</div>
</div>
<!-- === Inline form === -->
<div t-if="inline_form_xml_id"
name="o_payment_inline_form"
class="position-relative d-none"
>
<t t-call="{{inline_form_xml_id}}"/>
</div>
<!-- === Provider name (only for mobile) === -->
<p name="o_payment_secured_by_mobile"
t-att-class="'align-self-center d-block d-md-none mb-0 small text-600'
+ (' d-none' if hide_secured_by else '')"
>
<span><i class="fa fa-lock"/> Secured by</span>
<span t-out="dict(provider_sudo._fields['code']._description_selection(
provider_sudo.env
))[provider_sudo.code]"
class="text-break"
/>
</p>
</template>
<template id="payment.method_form" name="Payment Method Form">
<!-- Parameters description:
- pm_sudo: The payment method to display, as a sudoed `payment.method` recordset.
- is_selected: Whether the radio button of the payment method should be checked.
-->
<t t-set="provider_sudo"
t-value="pm_sudo.provider_ids.filtered(lambda p: p in providers_sudo)[:1]"
/>
<t t-set="is_test" t-value="provider_sudo.state == 'test'"/>
<t t-set="is_unpublished" t-value="not provider_sudo.is_published"/>
<t t-set="pms_to_display_sudo" t-value="pm_sudo.brand_ids or pm_sudo"/>
<t t-set="inline_form_xml_id" t-value="provider_sudo.inline_form_view_id.xml_id"/>
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-0 p-0"
for="o_payment_radio"
>
<div class="form-check d-flex flex-grow-1 flex-wrap mb-0">
<div class="d-flex justify-content-between align-items-center gap-2 flex-wrap w-100">
<!-- === Radio button === -->
<input t-attf-id="o_payment_method_{{pm_sudo.id}}"
name="o_payment_radio"
type="radio"
t-att-checked="is_selected"
class="form-check-input position-absolute mt-0"
data-payment-option-type="payment_method"
t-att-data-payment-option-id="pm_sudo.id"
t-att-data-payment-method-code="pm_sudo.code"
t-att-data-provider-id="provider_sudo.id"
t-att-data-provider-code="provider_sudo._get_code()"
t-att-data-provider-state="provider_sudo.state"
/>
<div class="d-flex gap-2 flex-grow-1 me-auto">
<!-- === Method label === -->
<label t-out="pm_sudo.name"
class="o_payment_option_label mb-0 text-break"
t-attf-for="o_payment_method_{{pm_sudo.id}}"
/>
<div class="d-flex flex-nowrap gap-2 mt-1">
<!-- === "Unpublished" icon === -->
<t t-if="is_unpublished" t-call="payment.form_icon">
<t t-set="icon_name" t-value="'eye-slash'"/>
<t t-set="color_name" t-value="'danger'"/>
<t t-set="title">Unpublished</t>
</t>
<!-- === "Test mode" icon === -->
<t t-if="is_test" t-call="payment.form_icon">
<t t-set="icon_name" t-value="'exclamation-triangle'"/>
<t t-set="color_name" t-value="'warning'"/>
<t t-set="title">Test mode</t>
</t>
</div>
</div>
<div class="gap-1 flex-wrap d-flex">
<!-- === Payment method logos === -->
<t t-set="pm_index" t-value="0"/>
<t t-foreach="pms_to_display_sudo" t-as="pm_to_display_sudo">
<t t-if="pm_index &lt; 4" t-call="payment.form_logo">
<t t-set="logo_pm_sudo" t-value="pm_to_display_sudo"/>
</t>
<t t-set="pm_index" t-value="pm_index + 1"/>
</t>
</div>
</div>
</div>
<!-- === Help message === -->
<div t-if="not is_html_empty(provider_sudo.pre_msg)"
class="w-100 mb-0 ms-4 small text-600"
>
<t t-out="provider_sudo.pre_msg"/>
</div>
</div>
<!-- === Inline form === -->
<div name="o_payment_inline_form" class="position-relative d-none">
<t t-if="inline_form_xml_id and provider_sudo._should_build_inline_form(
is_validation=mode == 'validation'
)"
t-call="{{inline_form_xml_id}}"
>
<t t-set="provider_id" t-value="provider_sudo.id"/>
</t>
<div class="d-flex flex-column flex-sm-row align-md-items-center justify-content-between
gap-3 mt-2"
>
<!-- === Tokenization checkbox === -->
<div t-if="mode == 'payment'
and pm_sudo.support_tokenization
and show_tokenize_input_mapping[provider_sudo.id]"
name="o_payment_tokenize_container"
class="o-checkbox form-check m-0"
>
<label>
<input name="o_payment_tokenize_checkbox"
type="checkbox"
class="form-check-input"
/>
<small class="text-600">Save my payment details</small>
</label>
</div>
<!-- === Provider name === -->
<t t-set="hide_secured_by" t-value="False"/>
<p name="o_payment_secured_by"
t-att-class="'align-self-end mb-0 mx-auto me-sm-0 small text-600'
+ (' d-none' if hide_secured_by else '')"
>
<span><i class="fa fa-lock"/> Secured by</span>
<span t-out="dict(provider_sudo._fields['code']._description_selection(
provider_sudo.env
))[provider_sudo.code]"
class="text-break"
/>
</p>
</div>
</div>
</template>
<template id="payment.form_icon" name="Form Icon">
<!-- Parameters description:
- icon_name: The name of the FontAwesome icon.
- color_name: The class name of the color (`warning`, `danger`...).
- title: The title to display on hover.
-->
<i t-attf-class="fa fa-{{icon_name}} text-{{color_name}} position-relative z-1"
t-att-title="title"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-delay="0"
/>
</template>
<template id="payment.form_logo" name="Form Logo">
<!-- Parameters description:
- logo_pm_sudo: The payment method whose logo to display, as a sudoed `payment.method`
record.
-->
<span t-field="logo_pm_sudo.image_payment_form"
t-options="{'widget': 'image', 'alt-field': 'name'}"
class="position-relative d-block rounded overflow-hidden z-1 shadow-sm"
t-att-title="logo_pm_sudo.name"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-delay="0"
/>
</template>
<template id="payment.submit_button" name="Submit Button">
<!-- Parameters description:
- label: The label of the submit button.
-->
<button name="o_payment_submit_button"
type="submit"
t-out="submit_button_label"
class="btn btn-primary w-100 w-md-auto ms-auto px-5"
disabled="true"
/>
</template>
<template id="payment.no_pms_available_warning">
<div class="alert alert-warning mt-2">
<div>
<strong>No payment method available</strong>
</div>
<div t-if="request.env.is_system()" class="mt-2">
<p t-if="providers_sudo">
None is configured for:
<t t-out="request.env.company.country_id.name"/>,
<t t-out="request.env.company.currency_id.name"/>.
</p>
<p t-else="">
No payment providers are configured.
</p>
<t
t-set="onboarding_provider"
t-value="request.env['res.config.settings'].sudo().new({}).onboarding_payment_module"
/>
<a
t-if="onboarding_provider and not providers_sudo"
name="start_payment_onboarding"
href="/odoo/action-payment.action_start_payment_onboarding"
role="button"
class="btn btn-primary me-2"
>
ACTIVATE <t t-out="onboarding_provider.upper()"/>
</a>
<a
t-if="availability_report"
role="button"
class="btn-link alert-warning me-2"
data-bs-toggle="collapse"
href="#payment_availability_report"
>
<strong><i class="fa fa-file-text"/> Show availability report</strong>
</a>
<a
t-if="not providers_sudo"
role="button"
type="action"
class="btn-link alert-warning me-2"
href="/odoo/action-payment.action_payment_provider"
>
<strong><i class="oi oi-arrow-right"/> Payment Providers</strong>
</a>
<a
t-else=""
role="button"
type="action"
class="btn-link alert-warning"
href="/odoo/action-payment.action_payment_method"
>
<strong><i class="oi oi-arrow-right"/> Payment Methods</strong>
</a>
</div>
<div t-else="" class="mt-2">
If you believe that it is an error, please contact the website administrator.
</div>
</div>
<!-- === Availability report === -->
<t t-call="payment.availability_report"/>
</template>
<template id="payment.availability_report_button">
<a
t-if="request.env.user._is_system() and availability_report"
role="button"
data-bs-toggle="collapse"
href="#payment_availability_report"
aria-expanded="false"
aria-controls="payment_availability_report"
>
<i class="fa fa-bug"/>
</a>
</template>
<template id="payment.availability_report">
<div
t-if="request.env.user._is_system() and availability_report"
id="payment_availability_report"
class="collapse"
>
<h5> Availability report </h5>
<h6 class="mt-3"> Payment providers </h6>
<t t-call="payment.availability_report_records">
<t t-set="records" t-value="availability_report.get('providers')"/>
</t>
<h6 class="mt-3"> Payment methods </h6>
<t t-call="payment.availability_report_records">
<t t-set="records" t-value="availability_report.get('payment_methods')"/>
</t>
</div>
</template>
<template id="availability_report_records">
<!-- Parameters description:
- records: The records to list in the availability report, as a dict with the structure
{record: {'available': bool, 'reason': str, supported_providers: list}}.
-->
<ul class="list-group">
<t t-foreach="records" t-as="r">
<t t-set="available" t-value="records[r]['available']"/>
<li class="list-group-item ps-0">
<div class="d-flex gap-2">
<div class="ms-2">
<i
t-attf-class="fa fa-fw fa-{{'check-circle' if available else 'times-circle'}}"
t-attf-style="color: {{'green' if available else 'red'}};"
/>
</div>
<div>
<p class="lead mb-0">
<span class="fw-normal"><t t-out="r.name"/></span>
<span class="text-muted">(ID: <t t-out="r.id"/>)</span>
</p>
<t t-if="not available">
<p class="mb-0 fw-light">
Reason: <t t-out="records[r]['reason']"/>
</p>
</t>
<t t-if="r._name == 'payment.method' and 'supported_providers' in records[r]">
<p class="mb-0 fw-light">
Supported providers:
<t t-foreach="records[r]['supported_providers']" t-as="p">
<span t-attf-class="text-{{'success' if p[1] else 'danger'}}">
<t t-out="p[0].name"/>
</span>
<t t-if="records[r]['supported_providers'][-1] != p">,</t>
</t>
</p>
</t>
</div>
</div>
</li>
</t>
</ul>
</template>
</odoo>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_icon_form" model="ir.ui.view">
<field name="name">payment.icon.form</field>
<field name="model">payment.icon</field>
<field name="arch" type="xml">
<form string="Payment Icon">
<sheet>
<field name="image" widget="image" class="oe_avatar"/>
<div class="oe_title">
<h1><field name="name" placeholder="Name"/></h1>
</div>
<notebook>
<page string="Providers list" name="providers">
<field nolabel="1" name="provider_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="payment_icon_tree" model="ir.ui.view">
<field name="name">payment.icon.tree</field>
<field name="model">payment.icon</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="action_payment_icon" model="ir.actions.act_window">
<field name="name">Payment Icons</field>
<field name="res_model">payment.icon</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a payment icon
</p>
</field>
</record>
</odoo>

View file

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_method_form" model="ir.ui.view">
<field name="name">payment.method.form</field>
<field name="model">payment.method</field>
<field name="arch" type="xml">
<form string="Payment Method">
<sheet>
<field name="is_primary" invisible="True"/>
<field name="image" widget="image" class="oe_avatar"/>
<div class="oe_title">
<h1><field name="name" placeholder="Name"/></h1>
</div>
<group>
<field name="code" readonly="id" groups="base.group_no_one"/>
<field name="primary_payment_method_id" invisible="is_primary"/>
<field name="active"/>
<label for="supported_country_ids"/>
<div>
<field name="supported_country_ids"
class="oe_inline"
widget="many2many_tags"
readonly="1"
/>
<span class="oe_inline text-muted" invisible="supported_country_ids">
All countries are supported.
</span>
</div>
<label for="supported_currency_ids"/>
<div>
<field name="supported_currency_ids"
class="oe_inline"
widget="many2many_tags"
readonly="1"
/>
<span class="oe_inline text-muted" invisible="supported_currency_ids">
All currencies are supported.
</span>
</div>
</group>
<notebook>
<page string="Providers" name="providers">
<field name="provider_ids" readonly="1">
<list decoration-muted="state == 'disabled'" editable="bottom">
<field name="name"/>
<field name="state"/>
</list>
</field>
</page>
<page string="Brands" name="brands" invisible="not is_primary">
<field name="brand_ids"/>
</page>
<page string="Configuration"
name="configuration"
groups="base.group_no_one"
>
<div class="alert alert-warning" role="alert">
<i class="fa fa-exclamation-triangle"/> These properties are set to
match the behavior of providers and that of their integration with
Odoo regarding this payment method. Any change may result in errors
and should be tested on a test database first.
</div>
<group>
<field name="support_tokenization"/>
<field name="support_manual_capture"/>
<field name="support_express_checkout"/>
<field name="support_refund" />
<field name="supported_country_ids"
widget="many2many_tags"
placeholder="Select countries. Leave empty to allow any."
/>
<field name="supported_currency_ids"
widget="many2many_tags"
placeholder="Select currencies. Leave empty to allow any."
/>
<field name="provider_ids"
string="Supported by"
widget="many2many_tags"
/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="payment_method_tree" model="ir.ui.view">
<field name="name">payment.method.list</field>
<field name="model">payment.method</field>
<field name="arch" type="xml">
<list multi_edit="True" decoration-muted="not active">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</record>
<record id="payment_method_kanban" model="ir.ui.view">
<field name="name">payment.method.kanban</field>
<field name="model">payment.method</field>
<field name="priority">1</field>
<field name="arch" type="xml">
<kanban>
<templates>
<t t-name="card" class="flex-row">
<field name="name" class="fw-bolder"/>
<field name="image" widget="image" class="ms-auto"/>
</t>
</templates>
</kanban>
</field>
</record>
<record id="payment_method_search" model="ir.ui.view">
<field name="name">payment.method.search</field>
<field name="model">payment.method</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Name"/>
<filter name="available_pms"
string="Available methods"
domain="[('provider_ids.state', '!=', 'disabled')]"
/>
</search>
</field>
</record>
<record id="action_payment_method" model="ir.actions.act_window">
<field name="name">Payment Methods</field>
<field name="res_model">payment.method</field>
<field name="path">payment-methods</field>
<field name="view_mode">list,kanban,form</field>
<field name="domain">[('is_primary', '=', True)]</field>
<field name="context">{'active_test': False, 'search_default_available_pms': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No payment methods found for your payment providers.
</p>
<p>
<a type="action" class="text-primary" name="%(payment.action_payment_provider)d">
<i class="oi oi-arrow-right me-1"/> Configure a payment provider
</a>
</p>
</field>
</record>
</odoo>

View file

@ -1,199 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Integration of a conditional "Manage payment methods" link in /my -->
<template id="pay_meth_link" inherit_id="portal.portal_layout">
<xpath expr="//div[hasclass('o_portal_my_details')]" position="inside">
<t t-set="partner" t-value="request.env.user.partner_id"/>
<t t-set="providers_allowing_tokenization"
t-value="request.env['payment.provider'].sudo()._get_compatible_providers(request.env.company.id, partner.id, 0., force_tokenization=True, is_validation=True)"/>
<t t-set="existing_tokens" t-value="partner.payment_token_ids + partner.commercial_partner_id.sudo().payment_token_ids"/>
<!-- Only show the link if a token can be created or if one already exists -->
<div t-if="providers_allowing_tokenization or existing_tokens"
class='manage_payment_method mt16'>
<a href="/my/payment_method">Manage payment methods</a>
</div>
</xpath>
</template>
<!-- Display of /payment/pay -->
<template id="pay">
<!-- Variables description:
- 'partner_is_different' - Whether the partner logged in is the one making the payment
-->
<t t-call="portal.frontend_layout">
<t t-set="page_title" t-value="'Payment'"/>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- 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 partner_id" class="alert alert-warning">
<strong>Warning</strong> You must be logged in to pay.
</div>
<div t-elif="not providers 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="">
<div t-if="partner_is_different" class="alert alert-warning">
<strong>Warning</strong> Make sure your are logged in as the right partner before making this payment.
</div>
<t t-if="reference_prefix">
<b>Reference:</b>
<t t-esc="reference_prefix"/><br/>
</t>
<b>Amount:</b>
<t t-esc="amount"
t-options="{'widget': 'monetary', 'display_currency': currency}"/>
<t t-call="payment.checkout"/>
</t>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Display of /my/payment_methods -->
<template id="payment_methods" name="Payment Methods">
<t t-call="portal.frontend_layout">
<t t-set="page_title" t-value="'Payment Methods'"/>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<!-- Manage page -->
<div class="row">
<div class="col-lg-7">
<t t-if="providers or tokens" t-call="payment.manage"/>
<div t-else="" class="alert alert-warning">
<p><strong>No suitable payment provider could be found.</strong></p>
<p>If you believe that it is an error, please contact the website administrator.</p>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Display of /payment/status -->
<template id="payment_status" name="Payment Status">
<t t-call="portal.frontend_layout">
<t t-set="page_title" t-value="'Payment Status'"/>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<!-- Status page -->
<div name="o_payment_status">
<div name="o_payment_status_content"
class="col-sm-6 offset-sm-3">
<!-- The content is generated in JavaScript -->
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Display of /payment/confirmation -->
<template id="confirm">
<!-- Variables description:
- 'tx' - The transaction to display
-->
<t t-call="portal.frontend_layout">
<t t-set="page_title" t-value="'Payment Confirmation'"/>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<!-- Confirmation page -->
<div class="row">
<div class="col-lg-6">
<div>
<t t-call="payment.transaction_status"/>
<div class="mb-3 row">
<label for="form_partner_name" class="col-md-3 col-form-label">
From
</label>
<span name="form_partner_name"
class="col-md-9 col-form-label"
t-esc="tx.partner_name"/>
</div>
<hr/>
<div class="mb-3 row">
<label for="form_reference" class="col-md-3 col-form-label">
Reference
</label>
<span name="form_reference"
class="col-md-9 col-form-label"
t-esc="tx.reference"/>
</div>
<hr/>
<div class="mb-3 row">
<label for="form_amount" class="col-md-3 col-form-label">
Amount
</label>
<span name="form_amount"
class="col-md-9 col-form-label"
t-esc="tx.amount"
t-options="{'widget': 'monetary', 'display_currency': tx.currency_id}"/>
</div>
<hr/>
<div class="row">
<div class="col-md-5 text-muted">
Processed by <t t-esc="tx.provider_id.sudo().name"/>
</div>
<div class="col-md-4 offset-md-3 mt-2 ps-0">
<a role="button"
t-attf-class="btn btn-primary float-end"
href="/my/home">
<i class="fa fa-arrow-circle-right"/> Back to My Account
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Breadcrumb for the portal -->
<template id="portal_breadcrumb">
<!-- Variables description:
- 'page_title' - The title of the breadcrumb item
-->
<div class="row">
<div class="col-md-6">
<ol class="breadcrumb mt8">
<li class="breadcrumb-item">
<a href="/my/home">
<i class="fa fa-home"
role="img"
title="Home"
aria-label="Home"/>
</a>
</li>
<li class="breadcrumb-item"><t t-esc="page_title"/></li>
</ol>
</div>
</div>
</template>
</odoo>

View file

@ -6,112 +6,109 @@
<field name="model">payment.provider</field>
<field name="arch" type="xml">
<form string="Payment provider">
<field name="company_id" invisible="1"/>
<!-- Prevent considering the field readonly and thus allow writing on it. -->
<field name="is_published" invisible="1"/>
<field name="main_currency_id" invisible="1"/>
<field name="support_fees" invisible="1"/>
<field name="support_manual_capture" invisible="1"/>
<field name="support_tokenization" invisible="1"/>
<field name="support_express_checkout" invisible="1"/>
<field name="module_id" invisible="1"/>
<field name="module_state" invisible="1"/>
<field name="module_to_buy" invisible="1"/>
<field name="show_credentials_page" invisible="1"/>
<field name="show_allow_express_checkout" invisible="1"/>
<field name="show_allow_tokenization" invisible="1"/>
<field name="show_payment_icon_ids" invisible="1"/>
<field name="show_pre_msg" invisible="1"/>
<field name="show_pending_msg" invisible="1"/>
<field name="show_auth_msg" invisible="1"/>
<field name="show_done_msg" invisible="1"/>
<field name="show_cancel_msg" invisible="1"/>
<field name="code" invisible="1"/>
<sheet>
<!-- === Stat Buttons === -->
<div class="oe_button_box" name="button_box"
attrs="{'invisible': [('module_state', '!=', 'installed')]}">
invisible="module_state != 'installed'">
<button name="action_toggle_is_published"
attrs="{'invisible': [('is_published', '=', False)]}"
invisible="not is_published"
class="oe_stat_button"
type="object"
icon="fa-globe">
<div class="o_stat_info o_field_widget">
<span class="text-success">Published</span>
<span class="o_stat_text text-success">Published</span>
</div>
</button>
<button name="action_toggle_is_published"
attrs="{'invisible': [('is_published', '=', True)]}"
invisible="is_published"
class="oe_stat_button"
type="object"
icon="fa-eye-slash">
<div class="o_stat_info o_field_widget">
<span class="text-danger">Unpublished</span>
<span class="o_stat_text text-danger">Unpublished</span>
</div>
</button>
</div>
<field name="image_128" widget="image" class="oe_avatar"
attrs="{'readonly': [('module_state', '!=', 'installed')]}"/>
<widget name="web_ribbon" title="Disabled" bg_color="bg-danger" attrs="{'invisible': ['|', ('module_state', '!=', 'installed'), ('state', '!=', 'disabled')]}"/>
<widget name="web_ribbon" title="Test Mode" bg_color="bg-warning" attrs="{'invisible': ['|', ('module_state', '!=', 'installed'), ('state', '!=', 'test')]}"/>
readonly="module_state != 'installed'"/>
<widget name="web_ribbon" title="Disabled" bg_color="text-bg-danger" invisible="module_state != 'installed' or state != 'disabled'"/>
<widget name="web_ribbon" title="Test Mode" bg_color="text-bg-warning" invisible="module_state != 'installed' or state != 'test'"/>
<div class="oe_title">
<h1><field name="name" placeholder="Name"/></h1>
<div attrs="{'invisible': ['|', ('module_state', '=', 'installed'), ('module_id', '=', False)]}">
<a attrs="{'invisible': [('module_to_buy', '=', False)]}" href="https://odoo.com/pricing?utm_source=db&amp;utm_medium=module" target="_blank" class="btn btn-info" role="button">Upgrade</a>
<button attrs="{'invisible': [('module_to_buy', '=', True)]}" type="object" class="btn btn-primary" name="button_immediate_install" string="Install"/>
<div invisible="module_state == 'installed' or not module_id">
<a invisible="not module_to_buy" href="https://odoo.com/pricing?utm_source=db&amp;utm_medium=module" target="_blank" class="btn btn-info" role="button">Upgrade</a>
<button invisible="module_to_buy" type="object" class="btn btn-primary" name="button_immediate_install" string="Install"/>
</div>
</div>
<div id="provider_creation_warning" attrs="{'invisible': [('id', '!=', False)]}" class="alert alert-warning" role="alert">
<div id="provider_creation_warning" invisible="id" class="alert alert-warning" role="alert">
<strong>Warning</strong> Creating a payment provider from the <em>CREATE</em> button is not supported.
Please use the <em>Duplicate</em> action instead.
</div>
<group>
<group name="payment_state" attrs="{'invisible': [('module_state', 'not in', ('installed', False))]}">
<field name="code" groups="base.group_no_one" attrs="{'readonly': [('id', '!=', False)]}"/>
<group name="payment_state" invisible="module_state not in ('installed', False)">
<field name="code" groups="base.group_no_one" readonly="id"/>
<field name="state" widget="radio"/>
<field name="company_id" groups="base.group_multi_company" options='{"no_open":True}'/>
</group>
</group>
<notebook attrs="{'invisible': ['&amp;', ('module_id', '!=', False), ('module_state', '!=', 'installed')]}">
<page string="Credentials" name="credentials" attrs="{'invisible': ['|', ('code', '=', 'none'), ('show_credentials_page', '=', False)]}">
<notebook invisible="module_id and module_state != 'installed'">
<page string="Credentials" name="credentials" invisible="code == 'none'">
<group name="provider_credentials"/>
</page>
<page string="Configuration" name="configuration">
<group name="provider_config">
<group string="Payment Form" name="payment_form">
<field name="display_as" placeholder="If not defined, the provider name will be used."/>
<field name="payment_icon_ids" attrs="{'invisible': [('show_payment_icon_ids', '=', False)]}" widget="many2many_tags"/>
<field name="allow_tokenization" attrs="{'invisible': ['|', ('support_tokenization', '=', False), ('show_allow_tokenization', '=', False)]}"/>
<field name="capture_manually" attrs="{'invisible': [('support_manual_capture', '=', False)]}"/>
<field name="allow_express_checkout" attrs="{'invisible': ['|', ('support_express_checkout', '=', False), ('show_allow_express_checkout', '=', False)]}"/>
<field name="payment_method_ids"
string="Payment Methods"
domain="[('is_primary', '=', True)]"
readonly="True"
invisible="state == 'disabled'"
widget="many2many_tags"
/>
<div colspan="2">
<a type="object"
name="action_view_payment_methods"
class="btn btn-link"
role="button"
invisible="state == 'disabled'"
>
<i class="oi oi-fw o_button_icon oi-arrow-right"/>
Enable Payment Methods
</a>
</div>
<field name="allow_tokenization" invisible="not support_tokenization"/>
<field name="capture_manually" invisible="not support_manual_capture"/>
<field name="allow_express_checkout" invisible="not support_express_checkout"/>
</group>
<group string="Availability" name="availability">
<field name="maximum_amount"/>
<label for="available_currency_ids"/>
<!-- Use `o_row` to allow placing a button next to the field in overrides. -->
<div name="available_currencies" class="o_row">
<field name="available_currency_ids"
widget="many2many_tags"
placeholder="Select currencies. Leave empty not to restrict any."
options="{'no_create': True}"/>
</div>
<field name="available_country_ids"
widget="many2many_tags"
placeholder="Select countries. Leave empty to make available everywhere."
options="{'no_open': True, 'no_create': True}"/>
options="{'no_create': True}"/>
</group>
<group string="Payment Followup" name="payment_followup" invisible="1"/>
</group>
</page>
<page string="Fees" name="fees" attrs="{'invisible': [('support_fees', '=', False)]}">
<group name="payment_fees">
<field name="fees_active"/>
<field name="fees_dom_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_dom_var" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_int_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_int_var" attrs="{'invisible': [('fees_active', '=', False)]}"/>
</group>
</page>
<page string="Messages"
name="messages"
attrs="{'invisible': [('module_id', '=', True), ('module_state', '!=', 'installed')]}">
invisible="module_id and module_state != 'installed'">
<group>
<field name="pre_msg" attrs="{'invisible': [('show_pre_msg', '=', False)]}"/>
<field name="pending_msg" attrs="{'invisible': [('show_pending_msg', '=', False)]}"/>
<field name="auth_msg" attrs="{'invisible': ['|', ('support_manual_capture', '=', False), ('show_auth_msg', '=', False)]}"/>
<field name="done_msg" attrs="{'invisible': [('show_done_msg', '=', False)]}"/>
<field name="cancel_msg" attrs="{'invisible': [('show_cancel_msg', '=', False)]}"/>
<field name="pre_msg"/>
<field name="pending_msg"/>
<field name="auth_msg" invisible="not support_manual_capture"/>
<field name="done_msg"/>
<field name="cancel_msg"/>
</group>
</page>
</notebook>
@ -124,14 +121,14 @@
<field name="name">payment.provider.list</field>
<field name="model">payment.provider</field>
<field name="arch" type="xml">
<tree string="Payment Providers" create="false">
<list string="Payment Providers" create="false">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="code" groups="base.group_no_one"/>
<field name="state"/>
<field name="available_country_ids" widget="many2many_tags" optional="hide"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
</list>
</field>
</record>
@ -139,57 +136,53 @@
<field name="name">payment.provider.kanban</field>
<field name="model">payment.provider</field>
<field name="arch" type="xml">
<kanban create="false" quick_create="false" class="o_kanban_dashboard">
<field name="id"/>
<field name="name"/>
<field name="state"/>
<kanban create="false" quick_create="false">
<field name="is_published"/>
<field name="code"/>
<field name="module_id"/>
<field name="module_state"/>
<field name="module_to_buy"/>
<field name="color"/>
<templates>
<t t-name="kanban-box">
<t t-name="card" class="flex-row">
<t t-set="installed" t-value="!record.module_id.value || (record.module_id.value &amp;&amp; record.module_state.raw_value === 'installed')"/>
<t t-set="to_buy" t-value="record.module_to_buy.raw_value === true"/>
<t t-set="is_disabled" t-value="record.state.raw_value=='disabled'"/>
<t t-set="is_published" t-value="record.is_published.raw_value === true"/>
<t t-set="to_upgrade" t-value="!installed and to_buy"/>
<div t-attf-class="oe_kanban_global_click" class="d-flex p-2">
<div class="o_payment_provider_desc d-flex gap-2">
<img type="open"
t-att-src="kanban_image('payment.provider', 'image_128', record.id.raw_value)"
class="mb-0 o_image_64_max"
alt="provider"/>
<div class="d-flex flex-column justify-content-between w-100">
<div class="o_payment_kanban_info">
<h4 class="mb-0"><t t-esc="record.name.value"/></h4>
<t t-if="installed">
<field name="state"
widget="label_selection"
options="{'classes': {'enabled': 'success', 'test': 'warning', 'disabled' : 'light'}}"/>
<t t-if="!is_disabled">
<span t-if="is_published and installed"
class="badge text-bg-success ms-1">
Published
</span>
<span t-if="!is_published and installed"
class="badge text-bg-info ms-1">
Unpublished
</span>
</t>
</t>
<span t-if="to_upgrade" class="badge text-bg-primary ms-1">Enterprise</span>
</div>
<div class="o_payment_kanban_button text-end">
<button t-if="!installed and !selection_mode and !to_buy" type="object" class="btn btn-sm btn-primary float-end" name="button_immediate_install">Install</button>
<button t-if="installed and is_disabled and !selection_mode" type="edit" class="btn btn-sm btn-secondary float-end">Activate</button>
<button t-if="!installed and to_buy" href="https://odoo.com/pricing?utm_source=db&amp;utm_medium=module" target="_blank" class="btn btn-sm btn-primary float-end">Upgrade</button>
</div>
</div>
<aside>
<field type="open"
name="image_128" widget="image"
class="mb-0 o_image_64_max"
alt="provider"/>
</aside>
<main class="ms-2">
<field name="name" class="mb-0 fw-bold fs-4"/>
<field name="company_id" groups="base.group_multi_company" class="fs-6 text-muted mb-1"/>
<div class="d-flex">
<t t-if="installed">
<field name="state"
widget="label_selection"
options="{'classes': {'enabled': 'success', 'test': 'warning', 'disabled' : 'light'}}"/>
<t t-if="!is_disabled">
<div>
<span t-if="is_published"
class="badge text-bg-success ms-1">
Published
</span>
<span t-else=""
class="badge text-bg-info ms-1">
Unpublished
</span>
</div>
</t>
</t>
<span t-if="to_upgrade" class="badge text-bg-primary">Enterprise</span>
</div>
</div>
<footer>
<button t-if="!installed and !selection_mode and !to_buy" type="object" class="btn btn-sm btn-primary ms-auto" name="button_immediate_install">Install</button>
<button t-if="installed and is_disabled and !selection_mode" type="open" class="btn btn-sm btn-secondary ms-auto">Activate</button>
<button t-if="!installed and to_buy" href="https://odoo.com/pricing?utm_source=db&amp;utm_medium=module" target="_blank" class="btn btn-sm btn-primary ms-auto">Upgrade</button>
</footer>
</main>
</t>
</templates>
</kanban>
@ -202,9 +195,17 @@
<field name="arch" type="xml">
<search>
<field name="name" string="provider" filter_domain="[('name', 'ilike', self)]"/>
<field name="code"/>
<field name="payment_method_ids"
string="payment method"
context="{'active_test': False}"
filter_domain="[
'|',
('payment_method_ids.name', 'ilike', self),
('payment_method_ids.code', 'ilike', self),
]"
/>
<filter name="provider_installed" string="Installed" domain="[('module_state', '=', 'installed')]"/>
<group expand="0" string="Group By">
<group>
<filter string="Provider" name="code" context="{'group_by': 'code'}"/>
<filter string="State" name="state" context="{'group_by': 'state'}"/>
<filter string="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
@ -216,11 +217,17 @@
<record id="action_payment_provider" model="ir.actions.act_window">
<field name="name">Payment Providers</field>
<field name="res_model">payment.provider</field>
<field name="view_mode">kanban,tree,form</field>
<field name="path">payment-providers</field>
<field name="view_mode">kanban,list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new payment provider
No providers available
</p>
<p class="fw-bold">To add a provider to the current company:</p>
<ol class="text-start d-inline-block">
<li>Toggle the main company in the company switcher.</li>
<li>Install the provider you want to add.</li>
</ol>
</field>
</record>

View file

@ -1,460 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Checkout form -->
<template id="checkout" name="Payment Checkout">
<!-- Variables description:
- 'providers' - The payment providers compatible with the current transaction
- 'tokens' - The payment tokens of the current partner and payment providers
- 'default_token_id' - The id of the token that should be pre-selected. Optional
- 'fees_by_provider' - The dict of transaction fees for each provider. Optional
- 'show_tokenize_input' - Whether the option to save the payment method is shown
- 'reference_prefix' - The custom prefix to compute the full transaction reference
- 'amount' - The amount to pay. Optional (sale_subscription)
- 'currency' - The currency of the transaction, as a `res.currency` record
- 'partner_id' - The id of the partner on behalf of whom the payment should be made
- 'access_token' - The access token used to authenticate the partner.
- 'transaction_route' - The route used to create a transaction when the user clicks Pay
- 'landing_route' - The route the user is redirected to after the transaction
- 'footer_template_id' - The template id for the submit button. Optional
-->
<form name="o_payment_checkout"
class="o_payment_form mt-3 clearfix"
t-att-data-reference-prefix="reference_prefix"
t-att-data-amount="amount"
t-att-data-currency-id="currency and currency.id"
t-att-data-partner-id="partner_id"
t-att-data-access-token="access_token"
t-att-data-transaction-route="transaction_route"
t-att-data-landing-route="landing_route"
t-att-data-allow-token-selection="True">
<t t-set="provider_count" t-value="len(providers) if providers else 0"/>
<t t-set="token_count" t-value="len(tokens) if tokens else 0"/>
<!-- Check the radio button of the default token, if set, or of the first provider if
it is the only payment option -->
<t t-set="default_payment_option_id"
t-value="default_token_id if default_token_id and token_count > 0
else providers[0].id if provider_count == 1 and token_count == 0
else None"/>
<t t-set="fees_by_provider" t-value="fees_by_provider or dict()"/>
<t t-set="footer_template_id"
t-value="footer_template_id or 'payment.footer'"/>
<div class="card">
<!-- === Providers === -->
<t t-foreach="providers" t-as="provider">
<div name="o_payment_option_card" class="card-body o_payment_option_card">
<label>
<!-- === Radio button === -->
<!-- Only shown if linked to the only payment option -->
<input name="o_payment_radio"
type="radio"
t-att-checked="provider.id == default_payment_option_id"
t-att-class="'' if provider_count + token_count > 1 else 'd-none'"
t-att-data-payment-option-id="provider.id"
t-att-data-provider="provider.code"
data-payment-option-type="provider"/>
<!-- === Provider name === -->
<span class="payment_option_name">
<b t-esc="provider.display_as or provider.name"/>
</span>
<!-- === "Test Mode" badge === -->
<span t-if="provider.state == 'test'"
class="badge rounded-pill text-bg-warning ms-1">
Test Mode
</span>
<!-- === "Unpublished" badge === -->
<span t-if="not provider.is_published"
class="badge rounded-pill text-bg-danger ms-1">
Unpublished
</span>
<!-- === Extra fees badge === -->
<t t-if="fees_by_provider.get(provider)">
<span class="badge rounded-pill text-bg-secondary ms-1">
+ <t t-esc="fees_by_provider.get(provider)"
t-options="{'widget': 'monetary', 'display_currency': currency}"/>
Fees
</span>
</t>
</label>
<!-- === Payment icon list === -->
<t t-call="payment.icon_list"/>
<!-- === Help message === -->
<div t-if="not is_html_empty(provider.pre_msg)"
t-out="provider.pre_msg"
class="text-muted ms-3"/>
</div>
<!-- === Provider inline form === -->
<div t-attf-id="o_payment_provider_inline_form_{{provider.id}}"
name="o_payment_inline_form"
class="card-footer px-3 d-none">
<t t-if="provider.sudo()._should_build_inline_form(is_validation=False)">
<t t-set="inline_form_xml_id"
t-value="provider.sudo().inline_form_view_id.xml_id"/>
<!-- === Inline form content (filled by provider) === -->
<div t-if="inline_form_xml_id" class="clearfix">
<t t-call="{{inline_form_xml_id}}">
<t t-set="provider_id" t-value="provider.id"/>
</t>
</div>
</t>
<!-- === "Save my payment details" checkbox === -->
<label t-if="show_tokenize_input[provider.id]">
<input name="o_payment_save_as_token" type="checkbox"/>
Save my payment details
</label>
</div>
</t>
<!-- === Tokens === -->
<t t-foreach="tokens" t-as="token">
<div name="o_payment_option_card" class="card-body o_payment_option_card">
<label>
<!-- === Radio button === -->
<input name="o_payment_radio"
type="radio"
t-att-checked="token.id == default_payment_option_id"
t-att-data-payment-option-id="token.id"
t-att-data-provider="token.provider_code"
data-payment-option-type="token"/>
<!-- === Token name === -->
<span class="payment_option_name" t-esc="token.display_name"/>
<!-- === "V" check mark === -->
<t t-call="payment.verified_token_checkmark"/>
<!-- === "Fees" badge === -->
<span t-if="fees_by_provider.get(token.provider_id)"
class="badge rounded-pill text-bg-secondary ms-1">
+ <t t-esc="fees_by_provider.get(token.provider_id)"
t-options="{'widget': 'monetary', 'display_currency': currency}"/>
Fees
</span>
<!-- === "Unpublished" badge === -->
<span t-if="not token.provider_id.is_published" class="badge rounded-pill text-bg-danger ms-1">
Unpublished
</span>
</label>
</div>
<!-- === Token inline form === -->
<div t-attf-id="o_payment_token_inline_form_{{token.id}}"
name="o_payment_inline_form"
class="card-footer d-none">
<t t-set="token_inline_form_xml_id"
t-value="token.sudo().provider_id.token_inline_form_view_id.xml_id"/>
<!-- === Inline form content (filled by provider) === -->
<div t-if="token_inline_form_xml_id" class="clearfix">
<t t-call="{{token_inline_form_xml_id}}">
<t t-set="token" t-value="token"/>
</t>
</div>
</div>
</t>
</div>
<!-- === "Pay" button === -->
<t t-call="{{footer_template_id}}">
<t t-set="label">Pay</t>
<t t-set="icon_class" t-value="'fa-lock'"/>
</t>
</form>
</template>
<!-- Manage (token create and deletion) form -->
<template id="manage" name="Payment Manage">
<!-- Variables description:
- 'providers' - The payment providers supporting tokenization
- 'tokens' - The set of payment tokens of the current partner
- 'default_token_id' - The id of the token that should be pre-selected. Optional
- 'reference_prefix' - The custom prefix to compute the full transaction reference
- 'partner_id' - The id of the partner managing the tokens
- 'access_token' - The access token used to authenticate the partner.
- 'transaction_route' - The route used to create a validation transaction
- 'assign_token_route' - The route to call to assign a token to a record. If set, it
enables the token assignation mechanisms: creation of a new
token through a refunded transaction and assignation of an
existing token
- 'landing_route' - The route the user is redirected to at then end of the flow
- 'footer_template_id' - The template id for the submit button. Optional
-->
<form name="o_payment_manage"
class="o_payment_form mt-3 clearfix"
t-att-data-reference-prefix="reference_prefix"
t-att-data-partner-id="partner_id"
t-att-data-access-token="access_token"
t-att-data-transaction-route="transaction_route"
t-att-data-assign-token-route="assign_token_route"
t-att-data-landing-route="landing_route"
t-att-data-allow-token-selection="bool(assign_token_route)">
<t t-set="provider_count" t-value="len(providers) if providers else 0"/>
<t t-set="token_count" t-value="len(tokens) if tokens else 0"/>
<t t-set="no_selectable_token" t-value="token_count == 0 or not assign_token_route"/>
<t t-set="default_payment_option_id"
t-value="default_token_id if default_token_id and token_count > 0
else providers[0].id if provider_count == 1 and no_selectable_token
else None"/>
<t t-set="footer_template_id"
t-value="footer_template_id or 'payment.footer'"/>
<div class="card">
<!-- === Providers === -->
<t t-foreach="providers" t-as="provider">
<div name="o_payment_option_card" class="card-body o_payment_option_card">
<label>
<!-- === Radio button === -->
<!-- Only shown if linked to the only payment option -->
<input name="o_payment_radio"
type="radio"
t-att-checked="provider.id == default_payment_option_id"
t-att-class="'' if provider_count + token_count > 1 else 'd-none'"
t-att-data-payment-option-id="provider.id"
t-att-data-provider="provider.code"
data-payment-option-type="provider"/>
<!-- === Provider name === -->
<span class="payment_option_name">
<b t-esc="provider.display_as or provider.name"/>
</span>
<!-- === "Test Mode" badge === -->
<span t-if="provider.state == 'test'"
class="badge rounded-pill text-bg-warning"
style="margin-left:5px">
Test Mode
</span>
<!-- === "Unpublished" badge === -->
<span t-if="not provider.is_published" class="badge rounded-pill text-bg-danger">
Unpublished
</span>
</label>
<!-- === Payment icon list === -->
<t t-call="payment.icon_list"/>
<!-- === Help message === -->
<div t-if="not is_html_empty(provider.pre_msg)"
t-out="provider.pre_msg"
class="text-muted ms-3"/>
</div>
<!-- === Provider inline form === -->
<t t-if="provider.sudo()._should_build_inline_form(is_validation=True)">
<div t-attf-id="o_payment_provider_inline_form_{{provider.id}}"
name="o_payment_inline_form"
class="card-footer d-none">
<t t-set="inline_form_xml_id"
t-value="provider.sudo().inline_form_view_id.xml_id"/>
<!-- === Inline form content (filled by provider) === -->
<div t-if="inline_form_xml_id" class="clearfix">
<t t-call="{{inline_form_xml_id}}">
<t t-set="provider_id" t-value="provider.id"/>
</t>
</div>
</div>
</t>
</t>
<!-- === Tokens === -->
<t t-foreach="tokens" t-as="token">
<div name="o_payment_option_card" class="card-body o_payment_option_card">
<label>
<!-- === Radio button === -->
<!-- Only shown if 'assign_token_route' is set -->
<input name="o_payment_radio"
type="radio"
t-att-checked="token.id == default_payment_option_id"
t-att-class="'' if bool(assign_token_route) else 'd-none'"
t-att-data-payment-option-id="token.id"
t-att-data-provider="token.provider_code"
data-payment-option-type="token"/>
<!-- === Token name === -->
<span class="payment_option_name" t-esc="token.display_name"/>
<!-- === "V" check mark === -->
<t t-call="payment.verified_token_checkmark"/>
<!-- === "Unpublished" badge === -->
<span t-if="not token.provider_id.is_published and token.env.user._is_internal()"
class="badge rounded-pill text-bg-danger ms-1">
Unpublished
</span>
</label>
<!-- === "Delete" token button === -->
<button name="o_payment_delete_token"
class="btn btn-primary btn-sm float-end">
<i class="fa fa-trash"/> Delete
</button>
</div>
<!-- === Token inline form === -->
<div t-attf-id="o_payment_token_inline_form_{{token.id}}"
name="o_payment_inline_form"
class="card-footer d-none">
<t t-set="token_inline_form_xml_id"
t-value="token.sudo().provider_id.token_inline_form_view_id.xml_id"/>
<!-- === Inline form content (filled by provider) === -->
<div t-if="token_inline_form_xml_id" class="clearfix">
<t t-call="{{token_inline_form_xml_id}}">
<t t-set="token" t-value="token"/>
</t>
</div>
</div>
</t>
</div>
<!-- === "Save Payment Method" button === -->
<t t-call="{{footer_template_id}}">
<t t-set="label">Save Payment Method</t>
<t t-set="icon_class" t-value="'fa-plus-circle'"/>
</t>
</form>
</template>
<!-- Express Checkout form -->
<template id="express_checkout" name="Payment Express Checkout">
<!-- Variables description:
- 'providers' - The payment providers compatible with the current transaction.
- 'reference_prefix' - The custom prefix to compute the full transaction reference.
- 'amount' - The amount to pay.
- 'minor_amount' - The amount to pay in the minor units of its currency.
- 'currency' - The currency of the transaction, as a `res.currency` record.
- 'merchant_name' - The merchant name.
- 'access_token' - The access token used to authenticate the partner.
- 'shipping_info_required' - Whether the shipping information is required or not.
- 'transaction_route' - The route used to create a transaction when the user clicks Pay.
- 'shipping_address_update_route' - The route where available carriers are computed
based on the (partial) shipping information
available. Optional
- 'express_checkout_route' - The route where the billing and shipping information are
sent.
- 'landing_route' - The route the user is redirected to after the transaction.
-->
<form name="o_payment_express_checkout_form" class="container"
t-att-data-reference-prefix="reference_prefix"
t-att-data-amount="amount"
t-att-data-minor-amount="minor_amount"
t-att-data-currency-id="currency and currency.id"
t-att-data-currency-name="currency.name.lower()"
t-att-data-merchant-name="merchant_name"
t-att-data-partner-id="partner_id"
t-att-data-access-token="payment_access_token"
t-att-data-shipping-info-required="shipping_info_required"
t-att-data-transaction-route="transaction_route"
t-att-data-shipping-address-update-route="shipping_address_update_route"
t-att-data-express-checkout-route="express_checkout_route"
t-att-data-landing-route="landing_route">
<t t-foreach="providers_sudo" t-as="provider_sudo">
<t t-set="express_checkout_form_xml_id"
t-value="provider_sudo.express_checkout_form_view_id.xml_id"/>
<t t-if="express_checkout_form_xml_id">
<t t-call="{{express_checkout_form_xml_id}}">
<t t-set="provider_sudo" t-value="provider_sudo"/>
</t>
</t>
</t>
</form>
</template>
<!-- Expandable payment icon list -->
<template id="icon_list" name="Payment Icon List">
<ul class="payment_icon_list float-end list-inline" data-max-icons="3">
<t t-set="icon_index" t-value="0"/>
<t t-set="MAX_ICONS" t-value="3"/>
<!-- === Icons === -->
<!-- Only shown if in the first 3 icons -->
<t t-foreach="provider.payment_icon_ids.filtered(lambda r: r.image_payment_form)" t-as="icon">
<li t-attf-class="list-inline-item{{'' if (icon_index &lt; MAX_ICONS) else ' d-none'}}">
<span t-field="icon.image_payment_form"
t-options="{'widget': 'image', 'alt-field': 'name'}"
data-bs-toggle="tooltip"
t-att-title="icon.name"/>
</li>
<t t-set="icon_index" t-value="icon_index + 1"/>
</t>
<t t-if="icon_index >= MAX_ICONS">
<!-- === "show more" button === -->
<!-- Only displayed if too many payment icons -->
<li style="display:block;" class="list-inline-item">
<span class="float-end more_option text-info">
<a name="o_payment_icon_more"
data-bs-toggle="tooltip"
t-att-title="', '.join([icon.name for icon in provider.payment_icon_ids[MAX_ICONS:]])">
show more
</a>
</span>
</li>
<!-- === "show less" button === -->
<!-- Only displayed when "show more" is clicked -->
<li style="display:block;" class="list-inline-item d-none">
<span class="float-end more_option text-info">
<a name="o_payment_icon_less">show less</a>
</span>
</li>
</t>
</ul>
</template>
<!-- Verified token checkmark -->
<template id="verified_token_checkmark" name="Payment Verified Token Checkmark">
<t t-if="0" name="payment_demo_hook"/>
<t t-else="">
<i t-if="token.verified" class="fa fa-check text-success"
title="This payment method has been verified by our system."
role="img"
aria-label="Ok"/>
<i t-else="" class="fa fa-check text-muted"
title="This payment method has not been verified by our system."
role="img"
aria-label="Not verified"/>
</t>
</template>
<!-- Generic footer for payment forms -->
<template id="footer" name="Payment Footer">
<!-- Variables description:
- 'label' - The label for the submit button
- 'icon_class' - The Font Awesome icon class (e.g. 'fa-lock') for the submit button
-->
<div class="float-end mt-2">
<button name="o_payment_submit_button"
type="submit"
class="btn btn-primary btn-lg mb8 mt8"
disabled="true"
t-att-data-icon-class="icon_class">
<i t-attf-class="fa {{icon_class}}"/> <t t-esc="label"/>
</button>
</div>
</template>
<!-- Transaction status in portal -->
<template id="transaction_status">
<!-- Variables description:
- 'tx' - The transaction whose status must be displayed
-->
<t t-if="tx.state == 'draft'">
<t t-set="alert_style" t-value="'info'"/>
<t t-set="status_message">
<p>Your payment has not been processed yet.</p>
</t>
</t>
<t t-elif="tx.state == 'pending'">
<t t-set="alert_style" t-value="'warning'"/>
<t t-set="status_message" t-value="tx.provider_id.sudo().pending_msg"/>
</t>
<t t-elif="tx.state == 'authorized'">
<t t-set="alert_style" t-value="'success'"/>
<t t-set="status_message" t-value="tx.provider_id.sudo().auth_msg"/>
</t>
<t t-elif="tx.state == 'done'">
<t t-set="alert_style" t-value="'success'"/>
<t t-set="status_message" t-value="tx.provider_id.sudo().done_msg"/>
</t>
<t t-elif="tx.state == 'cancel'">
<t t-set="alert_style" t-value="'danger'"/>
<t t-set="status_message" t-value="tx.provider_id.sudo().cancel_msg"/>
</t>
<t t-elif="tx.state == 'error'">
<t t-set="alert_style" t-value="'danger'"/>
<t t-set="status_message">
<p>An error occurred during the processing of your payment.</p>
</t>
</t>
<t t-if="is_html_empty(status_message)" t-set="status_message" t-value="''"/>
<div t-if="status_message or tx.state_message"
id="o_payment_status_alert"
t-attf-class="alert alert-{{alert_style}} alert-dismissible">
<button class="btn-close" data-bs-dismiss="alert" title="Dismiss"/>
<t t-if="status_message" t-out="status_message"/>
<t t-if="tx.state_message" t-out="tx.state_message"/>
</div>
</template>
</odoo>

View file

@ -5,7 +5,7 @@
<field name="name">payment.token.form</field>
<field name="model">payment.token</field>
<field name="arch" type="xml">
<form string="Payment Tokens" create="false" editable="bottom">
<form string="Payment Tokens" create="false" edit="false">
<sheet>
<field name="active" invisible="1"/>
<div class="oe_button_box" name="button_box">
@ -14,10 +14,11 @@
type="action" icon="fa-money" string="Payments">
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger" invisible="active"/>
<group>
<group name="general_information">
<field name="payment_details"/>
<field name="payment_method_id"/>
<field name="partner_id" />
</group>
<group name="technical_information">
@ -35,13 +36,14 @@
<field name="name">payment.token.list</field>
<field name="model">payment.token</field>
<field name="arch" type="xml">
<tree string="Payment Tokens">
<list string="Payment Tokens" create="false">
<field name="payment_details"/>
<field name="partner_id"/>
<field name="provider_id" readonly="1"/>
<field name="provider_ref" readonly="1"/>
<field name="payment_method_id"/>
<field name="provider_id"/>
<field name="provider_ref"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
</list>
</field>
</record>
@ -53,7 +55,7 @@
<field name="partner_id"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group expand="1" string="Group By">
<group>
<filter string="Provider" name="provider_id" context="{'group_by': 'provider_id'}"/>
<filter string="Partner" name="partner_id" context="{'group_by': 'partner_id'}"/>
<filter string="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
@ -65,10 +67,11 @@
<record id="action_payment_token" model="ir.actions.act_window">
<field name="name">Payment Tokens</field>
<field name="res_model">payment.token</field>
<field name="view_mode">tree,form</field>
<field name="path">payment-tokens</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new payment token
There is no token created yet.
</p>
</field>
</record>

View file

@ -7,9 +7,17 @@
<field name="arch" type="xml">
<form string="Payment Transactions" create="false" edit="false">
<header>
<button type="object" name="action_capture" states="authorized" string="Capture Transaction" class="oe_highlight"/>
<button type="object" name="action_void" states="authorized" string="Void Transaction"
<button type="object" name="action_capture" invisible="state != 'authorized'" string="Capture Transaction" class="oe_highlight"/>
<button type="object" name="action_void" invisible="state != 'authorized'" string="Void Transaction"
confirm="Are you sure you want to void the authorized transaction? This action can't be undone."/>
<button
string="Post-process"
help="Run the post-processing step for this transaction."
type="object"
name="action_post_process"
invisible="is_post_processed"
groups="base.group_no_one"
/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
@ -18,7 +26,7 @@
type="object"
class="oe_stat_button"
icon="fa-money"
attrs="{'invisible': [('refunds_count', '=', 0)]}">
invisible="refunds_count == 0">
<field name="refunds_count" widget="statinfo" string="Refunds"/>
</button>
</div>
@ -26,18 +34,20 @@
<group name="transaction_details">
<field name="reference"/>
<field name="source_transaction_id"
attrs="{'invisible': [('source_transaction_id', '=', False)]}"/>
invisible="not source_transaction_id"/>
<field name="amount"/>
<field name="fees" attrs="{'invisible': [('fees', '=', 0.0)]}"/>
<field name="currency_id" invisible="1"/>
<field name="payment_method_id"/>
<field name="provider_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<!-- Used by some provider-specific views -->
<field name="provider_code" invisible="1"/>
<field name="provider_reference"/>
<field name="token_id" attrs="{'invisible': [('token_id', '=', False)]}"/>
<field name="token_id" invisible="not token_id"/>
<field name="create_date"/>
<field name="last_state_change"/>
<field name="is_live"/>
<field name="is_post_processed" groups="base.group_no_one"/>
</group>
<group name="transaction_partner">
<field name="partner_id" widget="res_partner_many2one"/>
@ -54,8 +64,10 @@
<field name="partner_lang"/>
</group>
</group>
<group string="Message" attrs="{'invisible': [('state_message', '=', False)]}">
<field name="state_message" nolabel="1" colspan="2"/>
<separator string="Child transactions" invisible="not child_transaction_ids"/>
<field name="child_transaction_ids" invisible="not child_transaction_ids"/>
<group string="Message" invisible="not state_message">
<field colspan="2" name="state_message" nolabel="1"/>
</group>
</sheet>
</form>
@ -66,19 +78,20 @@
<field name="name">payment.transaction.list</field>
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<tree string="Payment Transactions" create="false">
<list string="Payment Transactions" create="false">
<field name="reference"/>
<field name="create_date"/>
<field name="payment_method_id"/>
<field name="provider_id"/>
<field name="partner_id"/>
<field name="partner_name"/>
<!-- Needed to display the currency of the amounts -->
<field name="currency_id" invisible="1"/>
<field name="currency_id" column_invisible="True"/>
<field name="amount"/>
<field name="fees"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
<field name="is_live" optional="hide"/>
</list>
</field>
</record>
@ -87,24 +100,14 @@
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile" create="false">
<field name="currency_id"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_content oe_kanban_global_click">
<div class="row">
<div class="col-6">
<strong><field name="reference"/></strong>
</div>
<div class="col-6">
<span><field name="partner_name"/></span>
</div>
<div class="col-6">
<span class="float-end">
<field name="amount"/>
<field name="currency_id" invisible="1"/>
</span>
</div>
</div>
<t t-name="card">
<div class="d-flex">
<field name="reference" class="fw-bolder"/>
<field name="amount" class="ms-auto"/>
</div>
<field name="partner_name"/>
</t>
</templates>
</kanban>
@ -120,20 +123,54 @@
<field name="provider_id"/>
<field name="partner_id"/>
<field name="partner_name"/>
<group expand="1" string="Group By">
<filter
string="Production Environment"
name="live_transactions"
domain="[('is_live', '=', True)]"
/>
<group>
<filter string="Provider" name="provider_id" context="{'group_by': 'provider_id'}"/>
<filter string="Partner" name="partner_id" context="{'group_by': 'partner_id'}"/>
<filter string="Status" name="state" context="{'group_by': 'state'}"/>
<filter
string="Production Environment"
name="is_live"
context="{'group_by': 'is_live'}"
/>
<filter string="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
</field>
</record>
<record id="payment_transaction_graph" model="ir.ui.view">
<field name="name">payment.transaction.graph</field>
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<graph string="Payment Transactions">
<field name="create_date" interval="month"/>
<field name="state"/>
</graph>
</field>
</record>
<record id="payment_transaction_pivot" model="ir.ui.view">
<field name="name">payment.transaction.pivot</field>
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<pivot string="Payment Transactions" display_quantity="1">
<field name="create_date" interval="month" type="row"/>
<field name="state" type="col"/>
<field name="amount" type="measure"/>
</pivot>
</field>
</record>
<record id="action_payment_transaction" model="ir.actions.act_window">
<field name="name">Payment Transactions</field>
<field name="res_model">payment.transaction</field>
<field name="view_mode">tree,kanban,form</field>
<field name="path">payment-transactions</field>
<field name="view_mode">list,kanban,form,graph,pivot</field>
<field name="help" type="html">
<p class="o_view_nocontent_neutral_face">
There are no transactions to show
@ -144,7 +181,7 @@
<record id="action_payment_transaction_linked_to_token" model="ir.actions.act_window">
<field name="name">Payment Transactions Linked To Token</field>
<field name="res_model">payment.transaction</field>
<field name="view_mode">tree,form</field>
<field name="view_mode">list,form</field>
<field name="domain">[('token_id','=', active_id)]</field>
<field name="context">{'create': False}</field>
</record>

View file

@ -0,0 +1,393 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Display of /payment/pay -->
<template id="payment.pay">
<!-- Parameters description:
- reference_prefix: The custom prefix to compute the full transaction reference.
- amount: The amount to pay.
- currency: The currency of the payment, as a `res.currency` record.
- partner_id: The id of the partner on behalf of whom the payment should be made.
- payment_methods_sudo: The compatible payment methods, as a sudoed `payment.method`
recordset.
- tokens_sudo: The available payment tokens, as a sudoed `payment.token` recordset.
- availability_report: The availability report of providers and payment methods.
- res_company: The company in which the payment if made (for the company logo).
- company_mismatch: Whether the user should make the payment in another company.
- expected_company: The record of the company that the user should switch to.
- partner_is_different: Whether the partner logged in is the one making the payment.
-->
<t t-call="portal.frontend_layout">
<t t-set="page_title">Payment</t>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<!-- Payment page -->
<div class="row justify-content-center my-3">
<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 partner_id" class="alert alert-warning">
<strong>Warning</strong> You must be logged in to pay.
</div>
<div t-elif="company_mismatch">
<t t-call="payment.company_mismatch_warning"/>
</div>
<t t-else="">
<div t-if="partner_is_different" class="alert alert-warning">
<strong>Warning</strong> Make sure you are logged in as the
correct partner before making this payment.
</div>
<div class="text-bg-light row row-cols-1 row-cols-md-2 mx-0 py-2
rounded"
>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'amount'"/>
<t t-set="label">Amount</t>
<t t-set="value" t-value="amount"/>
<t t-set="options"
t-value="{'widget': 'monetary', 'display_currency': currency}"
/>
</t>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'reference'"/>
<t t-set="label">Reference</t>
<t t-set="value" t-value="reference_prefix"/>
<t t-set="include_separator" t-value="True"/>
</t>
</div>
<div class="mt-4">
<t t-call="payment.form"/>
</div>
</t>
</div>
</div>
</div>
</div>
</t>
</template>
<template id="payment.company_mismatch_warning" name="Company Mismatch Warning">
<!-- Parameters description:
- expected_company: The record of the company that the user should switch to.
-->
<div class="row mr16">
<div class="alert alert-warning col-lg-12 ms-3 me-3" role="alert">
<p>
Please switch to company <t t-esc="expected_company.name"/> to make this
payment.
</p>
</div>
</div>
</template>
<!-- Display of /my/payment_methods -->
<template id="payment.payment_methods" name="Payment Methods">
<t t-call="portal.frontend_layout">
<t t-set="page_title">Payment Methods</t>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<!-- Payment methods page -->
<div class="row justify-content-center">
<div class="col-lg-7">
<t t-call="payment.form"/>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Display of /payment/status -->
<template id="payment.payment_status" name="Payment Status">
<t t-call="portal.frontend_layout">
<t t-set="page_title">Payment Status</t>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<!-- Payment status page -->
<div class="row justify-content-center my-3">
<div class="col-12 col-lg-8">
<div t-if="payment_not_found" class="text-center">
<p>Your payment is on its way!</p>
<p>
You should receive an email confirming your payment within a few
minutes.
</p>
<p>Don't hesitate to contact us if you don't receive it.</p>
</div>
<div t-else="" name="o_payment_status">
<t t-call="payment.state_header">
<t t-set="is_processing" t-value="True"/>
</t>
<div class="text-bg-light row row-cols-1 row-cols-md-2 mx-0 mb-3
py-2 rounded"
>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'amount'"/>
<t t-set="label">Amount</t>
<t t-set="value" t-value="tx.amount"/>
<t t-set="options"
t-value="{
'widget': 'monetary',
'display_currency': tx.currency_id,
}"
/>
</t>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'reference'"/>
<t t-set="label">Reference</t>
<t t-set="value" t-value="tx.reference"/>
<t t-set="include_separator" t-value="True"/>
</t>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Display of /payment/confirmation -->
<template id="payment.confirm">
<!-- Parameters description:
- tx: The transaction to display.
-->
<t t-call="portal.frontend_layout">
<t t-set="page_title">Payment Confirmation</t>
<t t-set="additional_title"><t t-esc="page_title"/></t>
<t t-set="show_pm" t-value="tx.payment_method_code != 'unknown'"/>
<div class="wrap">
<div class="container">
<!-- Portal breadcrumb -->
<t t-call="payment.portal_breadcrumb"/>
<div class="row justify-content-center my-3">
<div class="col-12 col-lg-7">
<!-- Confirmation page -->
<div class="row">
<div class="col">
<t t-call="payment.state_header"/>
</div>
</div>
<div t-att-class="'text-bg-light row row-cols-1 mx-0 mb-3 py-2 rounded'
+ (' row-cols-md-4' if show_pm else ' row-cols-md-3')"
>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'amount'"/>
<t t-set="label">Amount</t>
<t t-set="value" t-value="tx.amount"/>
<t t-set="options"
t-value="{'widget': 'monetary', 'display_currency': tx.currency_id}"
/>
</t>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'reference'"/>
<t t-set="label">Reference</t>
<t t-set="value" t-value="tx.reference"/>
<t t-set="include_separator" t-value="True"/>
</t>
<t t-if="tx.payment_method_code != 'unknown'">
<t t-call="payment.summary_item">
<t t-set="name" t-value="'method'"/>
<t t-set="label">Payment Method</t>
<t t-set="value" t-value="tx.payment_method_id.name"/>
<t t-set="include_separator" t-value="True"/>
</t>
</t>
<t t-call="payment.summary_item">
<t t-set="name" t-value="'provider'"/>
<t t-set="label">Processed by</t>
<t t-set="value" t-value="tx.provider_id.sudo().name"/>
<t t-set="include_separator" t-value="True"/>
</t>
</div>
<div class="row">
<div class="col offset-md-3 ps-0">
<a role="button" class="btn btn-primary float-end" href="/my/home">
Go to my Account <i class="oi oi-arrow-right ms-2"/>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Breadcrumb for the portal -->
<template id="payment.portal_breadcrumb">
<!-- Parameters description:
- page_title: The title of the breadcrumb item.
-->
<div class="row">
<div class="col-md-6">
<ol class="breadcrumb px-0 mt16">
<li id="o_payment_portal_home" class="breadcrumb-item">
<a href="/my/home">
<i class="fa fa-home"
role="img"
title="Home"
aria-label="Home"/>
</a>
</li>
<li class="breadcrumb-item"><t t-esc="page_title"/></li>
</ol>
</div>
</div>
</template>
<template id="payment.summary_item">
<!-- Parameters description:
- name: The summary item name that is suffixed to `o_payment_summary_` to create the id.
- label: The label that is displayed.
- value: The value of the summary item.
- options: The widget options to set.
- include_separator: Whether the summary item should be preceded by a separator.
-->
<t t-set="options" t-value="options or {'widget': 'string'}"/>
<hr t-if="include_separator" class="d-md-none m-0 text-300 opacity-100"/>
<div t-att-class="'col my-3 text-break'
+ (' o_payment_summary_separator' if include_separator else '')"
>
<label t-attf-for="o_payment_summary_{{name}}"
t-out="label"
class="d-block small opacity-75"
/>
<span t-attf-id="o_payment_summary_{{name}}"
t-out="value"
t-options="options"
class="fs-5 fw-bold"
/>
</div>
</template>
<template id="payment.state_header">
<!-- Parameters description:
- tx: The transaction whose status must be displayed.
- is_processing: Whether the transaction is being processed.
-->
<t t-set="waiting_heading">
<p>Please wait...</p>
</t>
<t t-set="tx_status_message" t-value="tx.provider_id.sudo()._get_status_message(tx.state)"/>
<t t-if="tx.state == 'draft'">
<t t-set="alert_style" t-value="'warning'"/>
<t t-if="is_processing" t-set="status_heading" t-value="waiting_heading"/>
<t t-set="status_message">
<p>Your payment has not been processed yet.</p>
</t>
</t>
<t t-elif="tx.state == 'pending'">
<t t-set="alert_style" t-value="'info'"/>
<t t-if="is_processing" t-set="status_heading" t-value="waiting_heading"/>
<t t-if="tx.operation == 'validation'" t-set="status_message">
<p>Saving your payment method.</p>
</t>
<t t-else="" t-set="status_message" t-value="tx_status_message"/>
</t>
<t t-elif="tx.state == 'authorized'">
<t t-set="alert_style" t-value="'success'"/>
<t t-if="is_processing" t-set="status_heading" t-value="waiting_heading"/>
<t t-set="status_message" t-value="tx_status_message"/>
</t>
<t t-elif="tx.state == 'done'">
<t t-set="alert_style" t-value="'success'"/>
<t t-if="not is_processing" t-set="status_heading">
<p>Thank you!</p>
</t>
<t t-if="tx.operation == 'validation'" t-set="status_message">
<p>Your payment method has been saved.</p>
</t>
<t t-else="" t-set="status_message" t-value="tx_status_message"/>
</t>
<t t-elif="tx.state == 'cancel'">
<t t-set="alert_style" t-value="'danger'"/>
<t t-if="tx.operation == 'validation'" t-set="status_message">
<p>The saving of your payment method has been canceled.</p>
</t>
<t t-else="" t-set="status_message" t-value="tx_status_message"/>
</t>
<t t-elif="tx.state == 'error'">
<t t-set="alert_style" t-value="'danger'"/>
<t t-if="tx.operation == 'validation'" t-set="status_message">
<p class="mb-0">An error occurred while saving your payment method.</p>
</t>
<t t-else="" t-set="status_message">
<p class="mb-0">An error occurred during the processing of your payment.</p>
</t>
</t>
<t t-if="not status_message" t-set="status_message" t-value="''"/>
<t t-set="o_payment_status_alert_class"
t-value="'alert alert-'+ alert_style +' d-flex gap-3'"
/>
<div t-if="status_heading or status_message or tx.state_message"
name="o_payment_status_alert"
t-attf-class="{{o_payment_status_alert_class}}"
>
<t t-set="alert_icon"
t-value="'fa-cog fa-spin' if is_processing and alert_style != 'danger'
else 'fa-check' if alert_style == 'success'
else 'fa-info-circle' if alert_style == 'info'
else 'fa-exclamation-triangle'"
/>
<div id="o_payment_status_icon">
<i t-attf-class="fa {{alert_icon}}"/>
</div>
<div id="o_payment_status_message" class="w-100">
<h5 t-if="status_heading" t-out="status_heading" class="alert-heading mb-0"/>
<t t-if="status_message" t-out="status_message" class="mb-0"/>
<t t-if="tx.state_message" t-out="tx.state_message" class="mb-0"/>
</div>
<a t-if="is_processing"
t-att-href="tx.landing_route"
class="alert-link ms-auto text-nowrap"
>
Skip <i class="oi oi-arrow-right ms-1 small"/>
</a>
</div>
</template>
<!-- "Manage payment methods" card on /my -->
<template id="portal_my_home_payment" name="Payment Methods" customize_show="True" inherit_id="portal.portal_my_home" priority="60">
<xpath expr="//div[hasclass('o_portal_docs')]" position="before">
<t t-set="portal_client_category_enable" t-value="True"/>
</xpath>
<div id="portal_client_category" position="inside">
<t t-set="partner_sudo" t-value="request.env.user.partner_id"/>
<t t-set="providers_sudo"
t-value="request.env['payment.provider'].sudo()._get_compatible_providers(request.env.company.id, partner_sudo.id, 0., force_tokenization=True, is_validation=True)"/>
<t t-set="methods_allowing_tokenization"
t-value="request.env['payment.method'].sudo()._get_compatible_payment_methods(
providers_sudo.ids,
partner_sudo.id,
force_tokenization=True,
)"
/>
<t t-set="existing_tokens" t-value="partner_sudo.payment_token_ids + partner_sudo.commercial_partner_id.payment_token_ids"/>
<t t-if="methods_allowing_tokenization or existing_tokens" t-call="portal.portal_docs_entry">
<t t-set="icon" t-value="'/payment/static/img/payment-methods.svg'"/>
<t t-set="title">Payment methods</t>
<t t-set="text">Manage your payment methods</t>
<t t-set="url" t-value="'/my/payment_method'"/>
<t t-set="config_card" t-value="True"/>
</t>
</div>
</template>
</odoo>

View file

@ -12,8 +12,8 @@
<button type="action" class="oe_stat_button"
icon="fa-credit-card-alt"
name="%(payment.action_payment_token)d"
context="{'search_default_partner_id': active_id, 'create': False, 'edit': False}"
attrs="{'invisible': [('payment_token_count', '=', 0)]}">
context="{'search_default_partner_id': id, 'create': False, 'edit': False}"
invisible="payment_token_count == 0">
<div class="o_form_field o_stat_info">
<span class="o_stat_value">
<field name="payment_token_count" widget="statinfo" nolabel="1"/>