[IMP] payment_test: support all additional features
After the payment apocalypse, `payment_test` was cleaned and stripped of all useless parts. It was a very basic testing acquirer that allowed to enter fake arbitrary payment details, choose whether they should be tokenized, and then pay. It was intended to test the payment flow of various applications (Sales, Subscriptions, Invoicing, ...) for a single scenario: successful payments with immediate capture. In order to extensively test an app's payment flow (explore other scenarios), we need `payment_test` to have additional features. Now, `payment_test` will allow profound testing of these features: - separate authorization and capture - customer fees computation - tokenization - refunds - on the /pay page, users will be able to select the status of the payment - on the payment transaction form view, users can change the status of a payment 'pending' to either succeed or fail - on the payment token form view, users can change the state of future transactions made with that token task-2512196 closes odoo/odoo#78083 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com> Co-authored-by: xlu-odoo <xlu@odoo.com>
This commit is contained in:
committed by
Antoine Vandevenne (anv)
co-authored by
xlu-odoo
parent
3262adf566
commit
2d81dffd23
@@ -54,6 +54,12 @@ class PaymentAcquirer(models.Model):
|
||||
string="Inline Form Template", comodel_name='ir.ui.view',
|
||||
help="The template rendering the inline payment form when making a direct payment",
|
||||
domain=[('type', '=', 'qweb')])
|
||||
token_inline_form_view_id = fields.Many2one(
|
||||
string="Token Inline Form Template",
|
||||
comodel_name='ir.ui.view',
|
||||
help="The template rendering the inline payment form when making a payment by token.",
|
||||
domain=[('type', '=', 'qweb')],
|
||||
)
|
||||
country_ids = fields.Many2many(
|
||||
string="Countries", comodel_name='res.country', relation='payment_country_rel',
|
||||
column1='payment_id', column2='country_id',
|
||||
|
||||
@@ -85,11 +85,11 @@
|
||||
<!-- === Acquirer inline form === -->
|
||||
<div t-attf-id="o_payment_acquirer_inline_form_{{acquirer.id}}"
|
||||
name="o_payment_inline_form"
|
||||
class="card-footer d-none">
|
||||
<!-- === Inline form content (filled by acquirer) === -->
|
||||
class="card-footer px-3 d-none">
|
||||
<t t-if="acquirer.sudo()._should_build_inline_form(is_validation=False)">
|
||||
<t t-set="inline_form_xml_id"
|
||||
t-value="acquirer.sudo().inline_form_view_id.xml_id"/>
|
||||
<!-- === Inline form content (filled by acquirer) === -->
|
||||
<div t-if="inline_form_xml_id" class="clearfix">
|
||||
<t t-call="{{inline_form_xml_id}}">
|
||||
<t t-set="acquirer_id" t-value="acquirer.id"/>
|
||||
@@ -123,7 +123,16 @@
|
||||
<!-- === Token inline form === -->
|
||||
<div t-attf-id="o_payment_token_inline_form_{{token.id}}"
|
||||
name="o_payment_inline_form"
|
||||
class="card-footer d-none"/>
|
||||
class="card-footer d-none">
|
||||
<t t-set="token_inline_form_xml_id"
|
||||
t-value="token.sudo().acquirer_id.token_inline_form_view_id.xml_id"/>
|
||||
<!-- === Inline form content (filled by acquirer) === -->
|
||||
<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 === -->
|
||||
@@ -207,9 +216,9 @@
|
||||
<div t-attf-id="o_payment_acquirer_inline_form_{{acquirer.id}}"
|
||||
name="o_payment_inline_form"
|
||||
class="card-footer d-none">
|
||||
<!-- === Inline form content (filled by acquirer) === -->
|
||||
<t t-set="inline_form_xml_id"
|
||||
t-value="acquirer.sudo().inline_form_view_id.xml_id"/>
|
||||
<!-- === Inline form content (filled by acquirer) === -->
|
||||
<div t-if="inline_form_xml_id" class="clearfix">
|
||||
<t t-call="{{inline_form_xml_id}}">
|
||||
<t t-set="acquirer_id" t-value="acquirer.id"/>
|
||||
@@ -245,7 +254,16 @@
|
||||
<!-- === Token inline form === -->
|
||||
<div t-attf-id="o_payment_token_inline_form_{{token.id}}"
|
||||
name="o_payment_inline_form"
|
||||
class="card-footer d-none"/>
|
||||
class="card-footer d-none">
|
||||
<t t-set="token_inline_form_xml_id"
|
||||
t-value="token.sudo().acquirer_id.token_inline_form_view_id.xml_id"/>
|
||||
<!-- === Inline form content (filled by acquirer) === -->
|
||||
<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 === -->
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<group>
|
||||
<group name="general_information">
|
||||
<field name="name"/>
|
||||
<field name="partner_id" />
|
||||
</group>
|
||||
<group>
|
||||
<group name="technical_information">
|
||||
<field name="acquirer_id"/>
|
||||
<field name="acquirer_ref"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
|
||||
@@ -12,6 +12,8 @@ It should never be used in production environment. Make sure to disable it befor
|
||||
'data': [
|
||||
'views/payment_templates.xml',
|
||||
'views/payment_test_templates.xml',
|
||||
'views/payment_token_views.xml',
|
||||
'views/payment_transaction_views.xml',
|
||||
'data/payment_acquirer_data.xml',
|
||||
],
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
|
||||
@@ -5,19 +5,13 @@ from odoo.http import request
|
||||
|
||||
|
||||
class PaymentTestController(http.Controller):
|
||||
_simulation_url = '/payment/test/simulate_payment'
|
||||
|
||||
@http.route('/payment/test/simulate_payment', type='json', auth='public')
|
||||
def test_simulate_payment(self, reference, customer_input):
|
||||
@http.route(_simulation_url, type='json', auth='public')
|
||||
def test_simulate_payment(self, **data):
|
||||
""" Simulate the response of a payment request.
|
||||
|
||||
:param str reference: The reference of the transaction
|
||||
:param str customer_input: The payment method details
|
||||
:param dict data: The simulated notification data.
|
||||
:return: None
|
||||
"""
|
||||
fake_api_response = {
|
||||
'reference': reference,
|
||||
'cc_summary': customer_input[-4:],
|
||||
}
|
||||
request.env['payment.transaction'].sudo()._handle_notification_data(
|
||||
'test', fake_api_response
|
||||
)
|
||||
request.env['payment.transaction'].sudo()._handle_notification_data('test', data)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<record id="payment.payment_acquirer_test" model="payment.acquirer">
|
||||
<field name="provider">test</field>
|
||||
<field name="inline_form_view_id" ref="inline_form"/>
|
||||
<field name="token_inline_form_view_id" ref="token_inline_form"/>
|
||||
<field name="allow_tokenization">True</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
|
||||
from . import account_payment_method
|
||||
from . import payment_acquirer
|
||||
from . import payment_token
|
||||
from . import payment_transaction
|
||||
|
||||
@@ -24,6 +24,9 @@ class PaymentAcquirer(models.Model):
|
||||
""" Override of `payment` to enable additional features. """
|
||||
super()._compute_feature_support_fields()
|
||||
self.filtered(lambda acq: acq.provider == 'test').update({
|
||||
'support_fees': True,
|
||||
'support_manual_capture': True,
|
||||
'support_refund': 'partial',
|
||||
'support_tokenization': True,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class PaymentToken(models.Model):
|
||||
_inherit = 'payment.token'
|
||||
|
||||
test_simulated_state = fields.Selection(
|
||||
string="Simulated State",
|
||||
help="The state in which transactions created from this token should be set.",
|
||||
selection=[
|
||||
('pending', "Pending"),
|
||||
('done', "Confirmed"),
|
||||
('cancel', "Canceled"),
|
||||
('error', "Error"),
|
||||
],
|
||||
)
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import _, api, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
|
||||
@@ -13,6 +13,54 @@ _logger = logging.getLogger(__name__)
|
||||
class PaymentTransaction(models.Model):
|
||||
_inherit = 'payment.transaction'
|
||||
|
||||
capture_manually = fields.Boolean(related='acquirer_id.capture_manually')
|
||||
|
||||
#=== ACTION METHODS ===#
|
||||
|
||||
def action_test_set_done(self):
|
||||
""" Set the state of the test transaction to 'done'.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.provider != 'test':
|
||||
return
|
||||
|
||||
notification_data = {'reference': self.reference, 'simulated_state': 'done'}
|
||||
self._handle_notification_data('test', notification_data)
|
||||
|
||||
def action_test_set_canceled(self):
|
||||
""" Set the state of the test transaction to 'cancel'.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.provider != 'test':
|
||||
return
|
||||
|
||||
notification_data = {'reference': self.reference, 'simulated_state': 'cancel'}
|
||||
self._handle_notification_data('test', notification_data)
|
||||
|
||||
def action_test_set_error(self):
|
||||
""" Set the state of the test transaction to 'error'.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.provider != 'test':
|
||||
return
|
||||
|
||||
notification_data = {'reference': self.reference, 'simulated_state': 'error'}
|
||||
self._handle_notification_data('test', notification_data)
|
||||
|
||||
#=== BUSINESS METHODS ===#
|
||||
|
||||
def _send_payment_request(self):
|
||||
""" Override of payment to simulate a payment request.
|
||||
|
||||
@@ -24,7 +72,65 @@ class PaymentTransaction(models.Model):
|
||||
if self.provider != 'test':
|
||||
return
|
||||
|
||||
self._handle_notification_data('test', None)
|
||||
if not self.token_id:
|
||||
raise UserError("Test: " + _("The transaction is not linked to a token."))
|
||||
|
||||
simulated_state = self.token_id.test_simulated_state
|
||||
notification_data = {'reference': self.reference, 'simulated_state': simulated_state}
|
||||
self._handle_notification_data('test', notification_data)
|
||||
|
||||
def _send_refund_request(self, create_refund_transaction=True, **kwargs):
|
||||
""" Override of payment to simulate a refund.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:param bool create_refund_transaction: Whether a refund transaction should be created.
|
||||
:param dict kwargs: The keyword arguments.
|
||||
:return: The refund transaction if any
|
||||
:rtype: recordset of `payment.transaction`
|
||||
"""
|
||||
if self.provider != 'test':
|
||||
return super()._send_refund_request(
|
||||
create_refund_transaction=create_refund_transaction, **kwargs
|
||||
)
|
||||
refund_tx = super()._send_refund_request(create_refund_transaction=True, **kwargs)
|
||||
|
||||
notification_data = {'reference': refund_tx.reference, 'simulated_state': 'done'}
|
||||
refund_tx._handle_notification_data('test', notification_data)
|
||||
|
||||
return refund_tx
|
||||
|
||||
def _send_capture_request(self):
|
||||
""" Override of payment to simulate a capture request.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: None
|
||||
"""
|
||||
super()._send_capture_request()
|
||||
if self.provider != 'test':
|
||||
return
|
||||
|
||||
notification_data = {
|
||||
'reference': self.reference,
|
||||
'simulated_state': 'done',
|
||||
'manual_capture': True, # Distinguish manual captures from regular one-step captures.
|
||||
}
|
||||
self._handle_notification_data('test', notification_data)
|
||||
|
||||
def _send_void_request(self):
|
||||
""" Override of payment to simulate a void request.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: None
|
||||
"""
|
||||
super()._send_void_request()
|
||||
if self.provider != 'test':
|
||||
return
|
||||
|
||||
notification_data = {'reference': self.reference, 'simulated_state': 'cancel'}
|
||||
self._handle_notification_data('test', notification_data)
|
||||
|
||||
def _get_tx_from_notification_data(self, provider, notification_data):
|
||||
""" Override of payment to find the transaction based on dummy data.
|
||||
@@ -60,14 +166,56 @@ class PaymentTransaction(models.Model):
|
||||
if self.provider != "test":
|
||||
return
|
||||
|
||||
self._set_done() # Dummy transactions are always successful
|
||||
if self.tokenize:
|
||||
payment_details_short = notification_data['cc_summary']
|
||||
token = self.env['payment.token'].create({
|
||||
'acquirer_id': self.acquirer_id.id,
|
||||
'name': payment_utils.build_token_name(payment_details_short=payment_details_short),
|
||||
'partner_id': self.partner_id.id,
|
||||
'acquirer_ref': 'fake acquirer reference',
|
||||
'verified': True,
|
||||
})
|
||||
self.token_id = token.id
|
||||
# The reasons why we immediately tokenize the transaction regardless of the state rather
|
||||
# than waiting for the payment method to be validated ('authorized' or 'done') like the
|
||||
# other payment acquirers do are:
|
||||
# - To save the simulated state and payment details on the token while we have them.
|
||||
# - To allow customers to create tokens whose transactions will always end up in the
|
||||
# said simulated state.
|
||||
self._test_tokenize_from_notification_data(notification_data)
|
||||
|
||||
state = notification_data['simulated_state']
|
||||
if state == 'pending':
|
||||
self._set_pending()
|
||||
elif state == 'done':
|
||||
if self.capture_manually and not notification_data.get('manual_capture'):
|
||||
self._set_authorized()
|
||||
else:
|
||||
self._set_done()
|
||||
# Immediately post-process the transaction if it is a refund, as the post-processing
|
||||
# will not be triggered by a customer browsing the transaction from the portal.
|
||||
if self.operation == 'refund':
|
||||
self.env.ref('payment.cron_post_process_payment_tx')._trigger()
|
||||
elif state == 'cancel':
|
||||
self._set_canceled()
|
||||
else: # Simulate an error state.
|
||||
self._set_error(_("You selected the following test payment status: %s", state))
|
||||
|
||||
def _test_tokenize_from_notification_data(self, notification_data):
|
||||
""" Create a new token based on the notification data.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:param dict notification_data: The fake notification data to tokenize from.
|
||||
:return: None
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
payment_details_short = notification_data['cc_summary']
|
||||
state = notification_data['simulated_state']
|
||||
token = self.env['payment.token'].create({
|
||||
'acquirer_id': self.acquirer_id.id,
|
||||
'name': payment_utils.build_token_name(payment_details_short=payment_details_short),
|
||||
'partner_id': self.partner_id.id,
|
||||
'acquirer_ref': 'fake acquirer reference',
|
||||
'verified': True,
|
||||
'test_simulated_state': state,
|
||||
})
|
||||
self.write({
|
||||
'token_id': token,
|
||||
'tokenize': False,
|
||||
})
|
||||
_logger.info(
|
||||
"Created token with id %s for partner with id %s.", token.id, self.partner_id.id
|
||||
)
|
||||
|
||||
@@ -26,11 +26,13 @@ odoo.define('payment_test.payment_form', require => {
|
||||
}
|
||||
|
||||
const customerInput = document.getElementById('customer_input').value;
|
||||
const simulatedPaymentState = document.getElementById('simulated_payment_state').value;
|
||||
return this._rpc({
|
||||
route: '/payment/test/simulate_payment',
|
||||
params: {
|
||||
'reference': processingValues.reference,
|
||||
'customer_input': customerInput,
|
||||
'cc_summary': customerInput.slice(-4),
|
||||
'simulated_state': simulatedPaymentState,
|
||||
},
|
||||
}).then(() => {
|
||||
window.location = '/payment/status';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_payment_transaction
|
||||
from . import test_processing_flows
|
||||
@@ -0,0 +1,18 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.payment.tests.common import PaymentCommon
|
||||
|
||||
|
||||
class PaymentTestCommon(PaymentCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.acquirer = cls._prepare_acquirer(provider='test')
|
||||
|
||||
cls.notification_data = {
|
||||
'reference': cls.reference,
|
||||
'cc_summary': '1234',
|
||||
'simulated_state': 'done',
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from odoo.addons.payment_test.tests.common import PaymentTestCommon
|
||||
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestPaymentTransaction(PaymentTestCommon, PaymentHttpCommon):
|
||||
|
||||
def test_processing_notification_data_sets_transaction_pending(self):
|
||||
""" Test that the transaction state is set to 'pending' when the notification data indicate
|
||||
a pending payment. """
|
||||
tx = self._create_transaction('direct')
|
||||
tx._process_notification_data(dict(self.notification_data, simulated_state='pending'))
|
||||
self.assertEqual(tx.state, 'pending')
|
||||
|
||||
def test_processing_notification_data_authorizes_transaction(self):
|
||||
""" Test that the transaction state is set to 'authorize' when the notification data
|
||||
indicate a successful payment and manual capture is enabled. """
|
||||
self.acquirer.capture_manually = True
|
||||
tx = self._create_transaction('direct')
|
||||
tx._process_notification_data(self.notification_data)
|
||||
self.assertEqual(tx.state, 'authorized')
|
||||
|
||||
def test_processing_notification_data_confirms_transaction(self):
|
||||
""" Test that the transaction state is set to 'done' when the notification data indicate a
|
||||
successful payment. """
|
||||
tx = self._create_transaction('direct')
|
||||
tx._process_notification_data(self.notification_data)
|
||||
self.assertEqual(tx.state, 'done')
|
||||
|
||||
def test_processing_notification_data_cancels_transaction(self):
|
||||
""" Test that the transaction state is set to 'cancel' when the notification data indicate
|
||||
an unsuccessful payment. """
|
||||
tx = self._create_transaction('direct')
|
||||
tx._process_notification_data(dict(self.notification_data, simulated_state='cancel'))
|
||||
self.assertEqual(tx.state, 'cancel')
|
||||
|
||||
def test_processing_notification_data_sets_transaction_in_error(self):
|
||||
""" Test that the transaction state is set to 'error' when the notification data indicate
|
||||
an error during the payment. """
|
||||
tx = self._create_transaction('direct')
|
||||
tx._process_notification_data(dict(self.notification_data, simulated_state='error'))
|
||||
self.assertEqual(tx.state, 'error')
|
||||
|
||||
def test_processing_notification_data_tokenizes_transaction(self):
|
||||
""" Test that the transaction is tokenized when it was requested and the notification data
|
||||
include token data. """
|
||||
tx = self._create_transaction('direct', tokenize=True)
|
||||
with patch(
|
||||
'odoo.addons.payment_test.models.payment_transaction.PaymentTransaction'
|
||||
'._test_tokenize_from_notification_data'
|
||||
) as tokenize_mock:
|
||||
tx._process_notification_data(self.notification_data)
|
||||
self.assertEqual(tokenize_mock.call_count, 1)
|
||||
|
||||
@mute_logger('odoo.addons.payment_test.models.payment_transaction')
|
||||
def test_processing_notification_data_propagates_simulated_state_to_token(self):
|
||||
""" Test that the simulated state of the notification data is set on the token when
|
||||
processing notification data. """
|
||||
for counter, state in enumerate(['pending', 'done', 'cancel', 'error']):
|
||||
tx = self._create_transaction(
|
||||
'direct', reference=f'{self.reference}-{counter}', tokenize=True
|
||||
)
|
||||
tx._process_notification_data(dict(self.notification_data, simulated_state=state))
|
||||
self.assertEqual(tx.token_id.test_simulated_state, state)
|
||||
|
||||
def test_making_a_payment_request_propagates_token_simulated_state_to_transaction(self):
|
||||
""" Test that the simulated state of the token is set on the transaction when making a
|
||||
payment request. """
|
||||
for counter, state in enumerate(['pending', 'done', 'cancel', 'error']):
|
||||
tx = self._create_transaction(
|
||||
'direct', reference=f'{self.reference}-{counter}'
|
||||
)
|
||||
tx.token_id = self._create_token(test_simulated_state=state)
|
||||
tx._send_payment_request()
|
||||
self.assertEqual(tx.state, tx.token_id.test_simulated_state)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.payment_test.controllers.main import PaymentTestController
|
||||
from odoo.addons.payment_test.tests.common import PaymentTestCommon
|
||||
from odoo.addons.payment.tests.http_common import PaymentHttpCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestProcessingFlows(PaymentTestCommon, PaymentHttpCommon):
|
||||
|
||||
def test_portal_payment_triggers_processing(self):
|
||||
""" Test that paying from the frontend triggers the processing of the notification data. """
|
||||
self._create_transaction(flow='direct')
|
||||
url = self._build_url(PaymentTestController._simulation_url)
|
||||
with patch(
|
||||
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
|
||||
'._handle_notification_data'
|
||||
) as handle_notification_data_mock:
|
||||
self._make_json_rpc_request(url, data=self.notification_data)
|
||||
self.assertEqual(handle_notification_data_mock.call_count, 1)
|
||||
@@ -3,17 +3,61 @@
|
||||
|
||||
<template id="inline_form">
|
||||
<div t-attf-id="test-container-{{acquirer_id}}">
|
||||
<div class="row mt-8">
|
||||
<div class="form-group col-lg-12">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<input name="acquirer_id" type="hidden" id="acquirer_id" t-att-value="id"/>
|
||||
<input name="partner_id" type="hidden" t-att-value="partner_id"/>
|
||||
</div>
|
||||
<div class="form-group col-lg-12">
|
||||
<input type="text" name="customer_input" id="customer_input" class="form-control"
|
||||
placeholder="Test Card Information"/>
|
||||
<div class="form-group col mt-0 mb-0">
|
||||
<label for="customer_input" class="mt-0">
|
||||
<small><b>Payment Details (test data)</b></small>
|
||||
</label>
|
||||
<input type="text"
|
||||
name="customer_input"
|
||||
id="customer_input"
|
||||
class="form-control"
|
||||
placeholder="XXXX XXXX XXXX XXXX"/>
|
||||
</div>
|
||||
<div class="form-group col mb-0">
|
||||
<label for="simulated_payment_state" class="mt-0">
|
||||
<small><b>Payment Status</b></small>
|
||||
</label>
|
||||
<select id="simulated_payment_state" class="form-control">
|
||||
<option value="done" title="Successful payment">
|
||||
Successful
|
||||
</option>
|
||||
<option value="pending" title="Payment processing">
|
||||
Pending
|
||||
</option>
|
||||
<option value="cancel" title="Payment canceled by customer">
|
||||
Canceled
|
||||
</option>
|
||||
<option value="error" title="Processing error">
|
||||
Error
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="token_inline_form">
|
||||
<div t-attf-id="test-token-container-{{token.id}}">
|
||||
<div class="alert alert-warning m-2">
|
||||
<span t-if="token.test_simulated_state=='pending'">
|
||||
Payments made with this payment method will remain <b>pending</b>.
|
||||
</span>
|
||||
<span t-elif="token.test_simulated_state=='done'">
|
||||
Payments made with this payment method will be <b>successful</b>.
|
||||
</span>
|
||||
<span t-elif="token.test_simulated_state=='cancel'">
|
||||
Payments made with this payment method will be automatically <b>canceled</b>.
|
||||
</span>
|
||||
<span t-else="">
|
||||
Payments made with this payment method will simulate a processing <b>error</b>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="payment_token_form" model="ir.ui.view">
|
||||
<field name="name">Test Token Form</field>
|
||||
<field name="model">payment.token</field>
|
||||
<field name="inherit_id" ref="payment.payment_token_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="general_information" position="inside">
|
||||
<field name="provider" invisible="1"/>
|
||||
<field name="test_simulated_state"
|
||||
attrs="{'invisible': [('provider', '!=', 'test')], 'required': [('provider', '=', 'test')]}"/>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="payment_transaction_form_inherit_payment_test" model="ir.ui.view">
|
||||
<field name="name">Test Transaction Form</field>
|
||||
<field name="model">payment.transaction</field>
|
||||
<field name="inherit_id" ref="payment.payment_transaction_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<header position="inside">
|
||||
<field name="capture_manually" invisible="1"/>
|
||||
<button string="Authorize"
|
||||
type="object"
|
||||
name="action_test_set_done"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': ['|', '|', ('provider', '!=', 'test'), ('capture_manually', '=', False), ('state', '!=', 'pending')]}"/>
|
||||
<button string="Confirm"
|
||||
type="object"
|
||||
name="action_test_set_done"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': ['|', '|', ('provider', '!=', 'test'), ('capture_manually', '=', True), ('state', '!=', 'pending')]}"/>
|
||||
<button string="Cancel"
|
||||
type="object"
|
||||
name="action_test_set_canceled"
|
||||
attrs="{'invisible': ['|', ('provider', '!=', 'test'), ('state', '!=', 'pending')]}"/>
|
||||
<button string="Set to Error"
|
||||
type="object"
|
||||
name="action_test_set_error"
|
||||
attrs="{'invisible': ['|', ('provider', '!=', 'test'), ('state', '!=', 'pending')]}"/>
|
||||
</header>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user