This commit is contained in:
Ernad Husremovic 2025-08-29 17:40:39 +02:00
parent 12c29a983b
commit 95fcc8bd63
189 changed files with 170858 additions and 0 deletions

View file

@ -0,0 +1,46 @@
<?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,199 @@
<?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

@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_provider_form" model="ir.ui.view">
<field name="name">payment.provider.form</field>
<field name="model">payment.provider</field>
<field name="arch" type="xml">
<form string="Payment provider">
<field name="company_id" invisible="1"/>
<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')]}">
<button name="action_toggle_is_published"
attrs="{'invisible': [('is_published', '=', False)]}"
class="oe_stat_button"
type="object"
icon="fa-globe">
<div class="o_stat_info o_field_widget">
<span class="text-success">Published</span>
</div>
</button>
<button name="action_toggle_is_published"
attrs="{'invisible': [('is_published', '=', True)]}"
class="oe_stat_button"
type="object"
icon="fa-eye-slash">
<div class="o_stat_info o_field_widget">
<span class="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')]}"/>
<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>
</div>
<div id="provider_creation_warning" attrs="{'invisible': [('id', '!=', False)]}" 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)]}"/>
<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)]}">
<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)]}"/>
</group>
<group string="Availability" name="availability">
<field name="maximum_amount"/>
<field name="available_country_ids"
widget="many2many_tags"
placeholder="Select countries. Leave empty to make available everywhere."
options="{'no_open': True, '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')]}">
<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)]}"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="payment_provider_list" model="ir.ui.view">
<field name="name">payment.provider.list</field>
<field name="model">payment.provider</field>
<field name="arch" type="xml">
<tree string="Payment Providers" create="false">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<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>
</field>
</record>
<record id="payment_provider_kanban" model="ir.ui.view">
<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"/>
<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-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>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="payment_provider_search" model="ir.ui.view">
<field name="name">payment.provider.search</field>
<field name="model">payment.provider</field>
<field name="arch" type="xml">
<search>
<field name="name" string="provider" filter_domain="[('name', 'ilike', self)]"/>
<field name="code"/>
<filter name="provider_installed" string="Installed" domain="[('module_state', '=', 'installed')]"/>
<group expand="0" string="Group By">
<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"/>
</group>
</search>
</field>
</record>
<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="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new payment provider
</p>
</field>
</record>
</odoo>

View file

@ -0,0 +1,460 @@
<?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

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_token_form" model="ir.ui.view">
<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">
<sheet>
<field name="active" invisible="1"/>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button"
name="%(action_payment_transaction_linked_to_token)d"
type="action" icon="fa-money" string="Payments">
</button>
</div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<group>
<group name="general_information">
<field name="payment_details"/>
<field name="partner_id" />
</group>
<group name="technical_information">
<field name="provider_id"/>
<field name="provider_ref"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="payment_token_list" model="ir.ui.view">
<field name="name">payment.token.list</field>
<field name="model">payment.token</field>
<field name="arch" type="xml">
<tree string="Payment Tokens">
<field name="payment_details"/>
<field name="partner_id"/>
<field name="provider_id" readonly="1"/>
<field name="provider_ref" readonly="1"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
</field>
</record>
<record id="payment_token_search" model="ir.ui.view">
<field name="name">payment.token.search</field>
<field name="model">payment.token</field>
<field name="arch" type="xml">
<search string="Payment Tokens">
<field name="partner_id"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group expand="1" string="Group By">
<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"/>
</group>
</search>
</field>
</record>
<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="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new payment token
</p>
</field>
</record>
</odoo>

View file

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="payment_transaction_form" model="ir.ui.view">
<field name="name">payment.transaction.form</field>
<field name="model">payment.transaction</field>
<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"
confirm="Are you sure you want to void the authorized transaction? This action can't be undone."/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_refunds"
type="object"
class="oe_stat_button"
icon="fa-money"
attrs="{'invisible': [('refunds_count', '=', 0)]}">
<field name="refunds_count" widget="statinfo" string="Refunds"/>
</button>
</div>
<group>
<group name="transaction_details">
<field name="reference"/>
<field name="source_transaction_id"
attrs="{'invisible': [('source_transaction_id', '=', False)]}"/>
<field name="amount"/>
<field name="fees" attrs="{'invisible': [('fees', '=', 0.0)]}"/>
<field name="currency_id" invisible="1"/>
<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="create_date"/>
<field name="last_state_change"/>
</group>
<group name="transaction_partner">
<field name="partner_id" widget="res_partner_many2one"/>
<label for="partner_address" string="Address"/>
<div class="o_address_format">
<field name="partner_address" placeholder="Address" class="o_address_street"/>
<field name="partner_city" placeholder="City" class="o_address_city"/>
<field name="partner_state_id" placeholder="State" class="o_address_state" options="{'no_open': True}"/>
<field name="partner_zip" placeholder="ZIP" class="o_address_zip"/>
<field name="partner_country_id" placeholder="Country" class="o_address_country" options="{'no_open': True}"/>
</div>
<field name="partner_email" widget="email"/>
<field name="partner_phone" widget="phone"/>
<field name="partner_lang"/>
</group>
</group>
<group string="Message" attrs="{'invisible': [('state_message', '=', False)]}">
<field name="state_message" nolabel="1" colspan="2"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="payment_transaction_list" model="ir.ui.view">
<field name="name">payment.transaction.list</field>
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<tree string="Payment Transactions" create="false">
<field name="reference"/>
<field name="create_date"/>
<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="amount"/>
<field name="fees"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company" optional="show"/>
</tree>
</field>
</record>
<record id="payment_transaction_kanban" model="ir.ui.view">
<field name="name">payment.transaction.kanban</field>
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile" create="false">
<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>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="payment_transaction_search" model="ir.ui.view">
<field name="name">payment.transaction.search</field>
<field name="model">payment.transaction</field>
<field name="arch" type="xml">
<search>
<field name="reference"/>
<field name="provider_id"/>
<field name="partner_id"/>
<field name="partner_name"/>
<group expand="1" string="Group By">
<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="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
</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="help" type="html">
<p class="o_view_nocontent_neutral_face">
There are no transactions to show
</p>
</field>
</record>
<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="domain">[('token_id','=', active_id)]</field>
<field name="context">{'create': False}</field>
</record>
</odoo>

View file

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<odoo>
<!-- Add credit card to res.partner -->
<record id="view_partners_form_payment_defaultcreditcard" model="ir.ui.view">
<field name="name">view.res.partner.form.payment.defaultcreditcard</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="priority" eval="15"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<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)]}">
<div class="o_form_field o_stat_info">
<span class="o_stat_value">
<field name="payment_token_count" widget="statinfo" nolabel="1"/>
</span>
<span class="o_stat_text">Saved Payment Methods</span>
</div>
</button>
</div>
</field>
</record>
</odoo>