[CLN] payment: update docstrings for Sphinx's autodoc

task-2804999

closes odoo/odoo#102716

X-original-commit: 5800c773184dce836a0fc3b67189ec6a14124178
Related: odoo/documentation#2797
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
Antoine Vandevenne (anv)
2022-10-09 09:11:43 +02:00
parent f1120ca254
commit 4aebe16d56
5 changed files with 351 additions and 304 deletions
+109 -81
View File
@@ -92,7 +92,7 @@ class PaymentProvider(models.Model):
ondelete='restrict',
)
# Availability fields.
# Availability fields
available_country_ids = fields.Many2many(
string="Countries",
comodel_name='res.country',
@@ -170,7 +170,7 @@ class PaymentProvider(models.Model):
# Module-related fields
module_id = fields.Many2one(string="Corresponding Module", comodel_name='ir.module.module')
module_state = fields.Selection(
string="Installation State", related='module_id.state', store=True) # Stored for sorting
string="Installation State", related='module_id.state', store=True) # Stored for sorting.
module_to_buy = fields.Boolean(string="Odoo Enterprise Module", related='module_id.to_buy')
# View configuration fields
@@ -204,11 +204,25 @@ class PaymentProvider(models.Model):
@api.depends('code')
def _compute_view_configuration_fields(self):
""" Compute view configuration fields based on the provider.
""" Compute the view configuration fields based on the provider.
By default, all fields are set to `True`.
For a provider to hide generic elements (pages, fields) in a view, it must override this
method and set their corresponding view configuration field to `False`.
View configuration fields are used to hide specific elements (notebook pages, fields, etc.)
from the form view of payment providers. These fields are set to `True` by default and are
as follows:
- `show_credentials_page`: Whether the "Credentials" notebook page should be shown.
- `show_allow_tokenization`: Whether the `allow_tokenization` field should be shown.
- `show_allow_express_checkout`: Whether the `allow_express_checkout` field should be shown.
- `show_payment_icon_ids`: Whether the `payment_icon_ids` field should be shown.
- `show_pre_msg`: Whether the `pre_msg` field should be shown.
- `show_pending_msg`: Whether the `pending_msg` field should be shown.
- `show_auth_msg`: Whether the `auth_msg` field should be shown.
- `show_done_msg`: Whether the `done_msg` field should be shown.
- `show_cancel_msg`: Whether the `cancel_msg` field should be shown.
For a provider to hide specific elements of the form view, it must override this method and
set the related view configuration fields to `False` on the appropriate `payment.provider`
records.
:return: None
"""
@@ -225,9 +239,24 @@ class PaymentProvider(models.Model):
})
def _compute_feature_support_fields(self):
""" Compute the feature support fields.
""" Compute the feature support fields based on the provider.
For an provider to support one or more additional feature, it must override this method.
Feature support fields are used to specify which additional features are supported by a
given provider. These fields are as follows:
- `support_express_checkout`: Whether the "express checkout" feature is supported. `False`
by default.
- `support_fees`: Whether the "extra fees" feature is supported. `False` by default.
- `support_manual_capture`: Whether the "manual capture" feature is supported. `False` by
default.
- `support_refund`: Which type of the "refunds" feature is supported: `None`,
`'full_only'`, or `'partial'`. `None` by default.
- `support_tokenization`: Whether the "tokenization feature" is supported. `False` by
default.
For a provider to specify that it supports additional features, it must override this method
and set the related feature support fields to the desired value on the appropriate
`payment.provider` records.
:return: None
"""
@@ -251,16 +280,14 @@ class PaymentProvider(models.Model):
@api.onchange('state')
def _onchange_state_warn_before_disabling_tokens(self):
""" Display a warning about the consequences of disabling an provider.
""" Display a warning about the consequences of disabling a provider.
Let the user know that tokens related to an provider get archived if it is disabled or if
its state is changed from 'test' to 'enabled' and vice versa.
Let the user know that tokens related to a provider get archived if it is disabled or if its
state is changed from 'test' to 'enabled', and vice versa.
:return: The warning message in a client action.
:return: A client action with the warning message, if any.
:rtype: dict
"""
self.ensure_one()
if self._origin.state in ('test', 'enabled') and self._origin.state != self.state:
related_tokens = self.env['payment.token'].search(
[('provider_id', '=', self._origin.id)]
@@ -282,8 +309,8 @@ class PaymentProvider(models.Model):
def _check_fee_var_within_boundaries(self):
""" Check that variable fees are within realistic boundaries.
Variable fees values should always be positive and below 100% to respectively avoid negative
and infinite (division by zero) fees amount.
Variable fee values should always be positive and below 100% to respectively avoid negative
and infinite (division by zero) fee amounts.
:return None
"""
@@ -315,9 +342,10 @@ class PaymentProvider(models.Model):
def _check_required_if_provider(self):
""" Check that provider-specific required fields have been filled.
The fields that have the `required_if_provider='<provider>'` attribute are made required
for all `payment.provider` records with the `code` field equal to <provider> and with the
`state` field equal to 'enabled' or 'test'.
The fields that have the `required_if_provider='<provider_code>'` attribute are made
required for all `payment.provider` records with the `code` field equal to `<provider_code>`
and with the `state` field equal to `'enabled'` or `'test'`.
Provider-specific views should make the form fields required under the same conditions.
:return: None
@@ -339,7 +367,7 @@ class PaymentProvider(models.Model):
)
def _handle_state_change(self):
""" Archive all the payment tokens linked to these providers.
""" Archive all the payment tokens linked to the providers.
:return: None
"""
@@ -348,11 +376,11 @@ class PaymentProvider(models.Model):
#=== ACTION METHODS ===#
def button_immediate_install(self):
""" Install the provider's module and reload the page.
""" Install the module and reload the page.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: The action to reload the page
:return: The action to reload the page.
:rtype: dict
"""
if self.module_id and self.module_state != 'installed':
@@ -363,9 +391,9 @@ class PaymentProvider(models.Model):
}
def action_toggle_is_published(self):
""" Toggle the provider's is_published state.
""" Toggle the field `is_published`.
:return: none
:return: None
:raise UserError: If the provider is disabled.
"""
if self.state != 'disabled':
@@ -382,30 +410,31 @@ class PaymentProvider(models.Model):
):
""" Select and return the providers matching the criteria.
The base criteria are that providers must not be disabled, be in the company that is
provided, and support the country of the partner if it exists.
The criteria are that providers must not be disabled, be in the company that is provided,
and support the country of the partner if it exists. The criteria can be further refined
by providing the keyword arguments.
:param int company_id: The company to which providers must belong, as a `res.company` id
:param int partner_id: The partner making the payment, as a `res.partner` id
:param float amount: The amount to pay, `0` for validation transactions.
:param int currency_id: The payment currency if known beforehand, as a `res.currency` id
:param bool force_tokenization: Whether only providers allowing tokenization can be matched
:param int company_id: The company to which providers must belong, as a `res.company` id.
:param int partner_id: The partner making the payment, as a `res.partner` id.
:param float amount: The amount to pay. `0` for validation transactions.
:param int currency_id: The payment currency, if known beforehand, as a `res.currency` id.
:param bool force_tokenization: Whether only providers allowing tokenization can be matched.
:param bool is_express_checkout: Whether the payment is made through express checkout.
:param bool is_validation: Whether the operation is a validation
:param dict kwargs: Optional data. This parameter is not used here
:return: The compatible providers
:param bool is_validation: Whether the operation is a validation.
:param dict kwargs: Optional data. This parameter is not used here.
:return: The compatible providers.
:rtype: recordset of `payment.provider`
"""
# Compute the base domain for compatible providers
# Compute the base domain for compatible providers.
domain = ['&', ('state', 'in', ['enabled', 'test']), ('company_id', '=', company_id)]
# Handle the is_published state.
if not self.env.user._is_internal():
domain = expression.AND([domain, [('is_published', '=', True)]])
# Handle partner country
# Handle partner country.
partner = self.env['res.partner'].browse(partner_id)
if partner.country_id: # The partner country must either not be set or be supported
if partner.country_id: # The partner country must either not be set or be supported.
domain = expression.AND([
domain, [
'|',
@@ -429,7 +458,7 @@ class PaymentProvider(models.Model):
]
])
# Handle tokenization support requirements
# Handle tokenization support requirements.
if force_tokenization or self._is_tokenization_required(**kwargs):
domain = expression.AND([domain, [('allow_tokenization', '=', True)]])
@@ -446,21 +475,21 @@ class PaymentProvider(models.Model):
For a module to make the tokenization required based on the transaction context, it must
override this method and return whether it is required.
:param dict kwargs: The transaction context. This parameter is not used here
:return: Whether tokenizing the transaction is required
:param dict kwargs: The transaction context. This parameter is not used here.
:return: Whether tokenizing the transaction is required.
:rtype: bool
"""
return False
def _should_build_inline_form(self, is_validation=False):
""" Return whether the inline form should be instantiated if it exists.
""" Return whether the inline payment form should be instantiated.
For a provider to handle both direct payments and payment with redirection, it should
override this method and return whether the inline form should be instantiated (i.e. if the
payment should be direct) based on the operation (online payment or validation).
For a provider to handle both direct payments and payments with redirection, it must
override this method and return whether the inline payment form should be instantiated (i.e.
if the payment should be direct) based on the operation (online payment or validation).
:param bool is_validation: Whether the operation is a validation
:return: Whether the inline form should be instantiated
:param bool is_validation: Whether the operation is a validation.
:return: Whether the inline form should be instantiated.
:rtype: bool
"""
return True
@@ -468,20 +497,19 @@ class PaymentProvider(models.Model):
def _compute_fees(self, amount, currency, country):
""" Compute the transaction fees.
The computation is based on the generic fields `fees_dom_fixed`, `fees_dom_var`,
`fees_int_fixed` and `fees_int_var` and is done according to the following formula:
`fees = (amount * variable / 100.0 + fixed) / (1 - variable / 100.0)` where the value
of `fixed` and `variable` is taken either from the domestic (dom) or international (int)
field depending on whether the country matches the company's country.
The computation is based on the fields `fees_dom_fixed`, `fees_dom_var`, `fees_int_fixed`
and `fees_int_var`, and is performed with the formula
:code:`fees = (amount * variable / 100.0 + fixed) / (1 - variable / 100.0)` where the values
of `fixed` and `variable` are taken from either the domestic (`dom`) or international
(`int`) fields, depending on whether the country matches the company's country.
For a provider to base the computation on different variables, or to use a different
formula, it must override this method and return the resulting fees as a float.
formula, it must override this method and return the resulting fees.
:param float amount: The amount to pay for the transaction
:param recordset currency: The currency of the transaction, as a `res.currency` record
:param recordset country: The customer country, as a `res.country` record
:return: The computed fees
:param float amount: The amount to pay for the transaction.
:param recordset currency: The currency of the transaction, as a `res.currency` record.
:param recordset country: The customer country, as a `res.country` record.
:return: The computed fees.
:rtype: float
"""
self.ensure_one()
@@ -498,30 +526,29 @@ class PaymentProvider(models.Model):
return fees
def _get_validation_amount(self):
""" Get the amount to transfer in a payment method validation operation.
""" Return the amount to use for validation operations.
For a provider to support tokenization, it must override this method and return the amount
to be transferred in a payment method validation operation *if the validation amount is not
null*.
For a provider to support tokenization, it must override this method and return the
validation amount. If it is `0`, it is not necessary to create the override.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: The validation amount
:return: The validation amount.
:rtype: float
"""
self.ensure_one()
return 0.0
def _get_validation_currency(self):
""" Get the currency of the transfer in a payment method validation operation.
""" Return the currency to use for validation operations.
For a provider to support tokenization, it must override this method and return the
currency to be used in a payment method validation operation *if the validation amount is
not null*.
validation currency. If the validation amount is `0`, it is not necessary to create the
override.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: The validation currency
:return: The validation currency.
:rtype: recordset of `res.currency`
"""
self.ensure_one()
@@ -533,10 +560,10 @@ class PaymentProvider(models.Model):
For a provider to return a different view depending on whether the operation is a
validation, it must override this method and return the appropriate view.
Note: self.ensure_one()
Note: `self.ensure_one()`
:param bool is_validation: Whether the operation is a validation
:return: The redirect form template
:param bool is_validation: Whether the operation is a validation.
:return: The view of the redirect form template.
:rtype: record of `ir.ui.view`
"""
self.ensure_one()
@@ -544,20 +571,21 @@ class PaymentProvider(models.Model):
@api.model
def _setup_provider(self, provider_code):
""" Prepare module-specific data for a given provider.
""" Perform module-specific setup steps for the provider.
This method is called after a new provider module is installed and also for all existing
providers when `account_payment` is installed.
This method is called after the module of a provider is installed, with its code passed as
`provider_code`.
:param str provider_code: The code of the provider to setup.
:return: None
"""
return
@api.model
def _remove_provider(self, provider_code):
""" Clean module-specific data for a given provider.
""" Remove the module-specific data of the given provider.
:param str provider_code: The code of the provider to setup.
:param str provider_code: The code of the provider whose data to remove.
:return: None
"""
providers = self.search([('code', '=', provider_code)])
@@ -576,10 +604,10 @@ class PaymentProvider(models.Model):
""")
def _neutralize_fields(self, provider_code, field_names):
""" Helper to neutralize API keys for a specific provider.
""" Helper to neutralize API keys for the given provider.
:param str provider_code: The code of the provider.
:param list field_names: The names of the fields to nullify.
:param str provider_code: The code of the provider whose fields to neutralize.
:param list field_names: The names of the fields to neutralize.
:return: None
"""
self.flush_model()
@@ -592,4 +620,4 @@ class PaymentProvider(models.Model):
fields=sql.SQL(','.join(field_names)),
vals=sql.SQL(', '.join(['NULL'] * len(field_names))),
)
self.env.cr.execute(query, (provider_code, ))
self.env.cr.execute(query, (provider_code,))
+22 -19
View File
@@ -22,7 +22,7 @@ class PaymentToken(models.Model):
related='provider_id.company_id', store=True, index=True)
provider_ref = fields.Char(
string="Provider Reference", help="The provider reference of the token of the transaction",
required=True) # This is not the same thing as the provider reference of the transaction
required=True) # This is not the same thing as the provider reference of the transaction.
transaction_ids = fields.One2many(
string="Payment Transactions", comodel_name='payment.transaction', inverse_name='token_id')
verified = fields.Boolean(string="Verified")
@@ -39,7 +39,7 @@ class PaymentToken(models.Model):
# Include provider-specific create values
values.update(self._get_specific_create_values(provider.code, values))
else:
pass # Let psycopg warn about the missing required field
pass # Let psycopg warn about the missing required field.
return super().create(values_list)
@@ -51,9 +51,9 @@ class PaymentToken(models.Model):
dict of values. Provider-specific values take precedence over those of the dict of generic
create values.
:param str provider_code: The code of the provider managing the token
:param dict values: The original create values
:return: The dict of provider-specific create values
:param str provider_code: The code of the provider managing the token.
:param dict values: The original create values.
:return: The dict of provider-specific create values.
:rtype: dict
"""
return dict()
@@ -76,14 +76,14 @@ class PaymentToken(models.Model):
return super().write(values)
def _handle_archiving(self):
""" Handle the archiving of the current tokens.
""" Handle the archiving of tokens.
For a module to perform additional operations when a token is archived, it must override
this method.
:return: None
"""
return None
return
def name_get(self):
return [(token.id, token._build_display_name()) for token in self]
@@ -91,20 +91,22 @@ class PaymentToken(models.Model):
#=== BUSINESS METHODS ===#
def _build_display_name(self, *args, max_length=34, should_pad=True, **kwargs):
""" Build a token name of the desired maximum length with the format •••• 1234.
""" Build a token name of the desired maximum length with the format `•••• 1234`.
The payment details are padded on the left with up to four padding characters. The padding
is only added if there is enough room for it. If not, it is either reduced or not added at
all. If there is not enough room for the payment details either, they are trimmed from the
left.
For a module to customize the display name of a token, it must override this method.
For a module to customize the display name of a token, it must override this method and
return the customized display name.
Note: self.ensure_one()
Note: `self.ensure_one()`
:param list args: The arguments passed by QWeb when calling this method.
:param int max_length: The desired maximum length of the token name.
:param bool should_pad: Whether the token should be padded or not.
:param int max_length: The desired maximum length of the token name. The default is `34` to
fit the largest IBANs.
:param bool should_pad: Whether the token should be padded.
:param dict kwargs: Optional data used in overrides of this method.
:return: The padded token name.
:rtype: str
@@ -128,17 +130,18 @@ class PaymentToken(models.Model):
""" Return a list of information about records linked to the current token.
For a module to implement payments and link documents to a token, it must override this
method and add information about linked records to the returned list.
method and add information about linked document records to the returned list.
The information must be structured as a dict with the following keys:
- description: The description of the record's model (e.g. "Subscription")
- id: The id of the record
- name: The name of the record
- url: The url to access the record.
Note: self.ensure_one()
- `description`: The description of the record's model (e.g. "Subscription").
- `id`: The id of the record.
- `name`: The name of the record.
- `url`: The url to access the record.
:return: The list of information about linked documents
Note: `self.ensure_one()`
:return: The list of information about the linked document records.
:rtype: list
"""
self.ensure_one()
+215 -199
View File
@@ -36,10 +36,10 @@ class PaymentTransaction(models.Model):
related='provider_id.company_id', store=True, index=True)
reference = fields.Char(
string="Reference", help="The internal reference of the transaction", readonly=True,
required=True) # Already has an index from the UNIQUE SQL constraint
required=True) # Already has an index from the UNIQUE SQL constraint.
provider_reference = fields.Char(
string="Provider Reference", help="The provider reference of the transaction",
readonly=True) # This is not the same thing as the provider reference of the token
readonly=True) # This is not the same thing as the provider reference of the token.
amount = fields.Monetary(
string="Amount", currency_field='currency_id', readonly=True, required=True)
currency_id = fields.Many2one(
@@ -61,8 +61,8 @@ class PaymentTransaction(models.Model):
last_state_change = fields.Datetime(
string="Last State Change Date", readonly=True, default=fields.Datetime.now)
# Fields used for traceability
operation = fields.Selection( # This should not be trusted if the state is 'draft' or 'pending'
# Fields used for traceability.
operation = fields.Selection( # This should not be trusted if the state is draft or pending.
string="Operation",
selection=[
('online_redirect', "Online payment with redirection"),
@@ -103,13 +103,13 @@ class PaymentTransaction(models.Model):
string="Callback Document Model", comodel_name='ir.model', groups='base.group_system')
callback_res_id = fields.Integer(string="Callback Record ID", groups='base.group_system')
callback_method = fields.Char(string="Callback Method", groups='base.group_system')
# Hash for additional security on top of the callback fields' group in case a bug exposes a sudo
# Hash for extra security on top of the callback fields' group in case a bug exposes a sudo.
callback_hash = fields.Char(string="Callback Hash", groups='base.group_system')
callback_is_done = fields.Boolean(
string="Callback Done", help="Whether the callback has already been executed",
groups="base.group_system", readonly=True)
# Duplicated partner values allowing to keep a record of them, should they be later updated
# Duplicated partner values allowing to keep a record of them, should they be later updated.
partner_id = fields.Many2one(
string="Customer", comodel_name='res.partner', readonly=True, required=True,
ondelete='restrict')
@@ -143,7 +143,7 @@ class PaymentTransaction(models.Model):
@api.constrains('state')
def _check_state_authorized_supported(self):
""" Check that authorization is supported for a transaction in the 'authorized' state. """
""" Check that authorization is supported for a transaction in the `authorized` state. """
illegal_authorize_state_txs = self.filtered(
lambda tx: tx.state == 'authorized' and not tx.provider_id.support_manual_capture
)
@@ -169,7 +169,7 @@ class PaymentTransaction(models.Model):
if not values.get('reference'):
values['reference'] = self._compute_reference(provider.code, **values)
# Duplicate partner values
# Duplicate partner values.
partner = self.env['res.partner'].browse(values['partner_id'])
values.update({
'partner_name': partner.name,
@@ -185,7 +185,7 @@ class PaymentTransaction(models.Model):
'partner_phone': partner.phone,
})
# Compute fees, for validation transactions fees are zero
# Compute fees. For validation transactions, fees are zero.
if values.get('operation') == 'validation':
values['fees'] = 0
else:
@@ -197,7 +197,7 @@ class PaymentTransaction(models.Model):
# Include provider-specific create values
values.update(self._get_specific_create_values(provider.code, values))
# Generate the hash for the callback if one has be configured on the tx
# Generate the hash for the callback if one has be configured on the tx.
values['callback_hash'] = self._generate_callback_hash(
values.get('callback_model_id'),
values.get('callback_res_id'),
@@ -222,13 +222,13 @@ class PaymentTransaction(models.Model):
def _get_specific_create_values(self, provider_code, values):
""" Complete the values of the `create` method with provider-specific values.
For a provider to add its own create values, it must overwrite this method and return a
dict of values. Provider-specific values take precedence over those of the dict of generic
create values.
For a provider to add its own create values, it must overwrite this method and return a dict
of values. Provider-specific values take precedence over those of the dict of generic create
values.
:param str provider_code: The code of the provider that handled the transaction
:param dict values: The original create values
:return: The dict of provider-specific create values
:param str provider_code: The code of the provider that handled the transaction.
:param dict values: The original create values.
:return: The dict of provider-specific create values.
:rtype: dict
"""
return dict()
@@ -236,11 +236,11 @@ class PaymentTransaction(models.Model):
#=== ACTION METHODS ===#
def action_view_refunds(self):
""" Return the action for the views of the refund transactions linked to the transaction.
""" Return the windows action to browse the refund transactions linked to the transaction.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: The action
:return: The window action to browse the refund transactions.
:rtype: dict
"""
self.ensure_one()
@@ -284,7 +284,7 @@ class PaymentTransaction(models.Model):
def action_refund(self, amount_to_refund=None):
""" Check the state of the transactions and request their refund.
:param float amount_to_refund: The amount to be refunded
:param float amount_to_refund: The amount to be refunded.
:return: None
"""
if any(tx.state != 'done' for tx in self):
@@ -299,46 +299,51 @@ class PaymentTransaction(models.Model):
def _compute_reference(self, provider_code, prefix=None, separator='-', **kwargs):
""" Compute a unique reference for the transaction.
The reference either corresponds to the prefix if no other transaction with that prefix
already exists, or follows the pattern `{computed_prefix}{separator}{sequence_number}` where
- {computed_prefix} is:
- The provided custom prefix, if any.
- The computation result of `_compute_reference_prefix` if the custom prefix is not
filled but the kwargs are.
- 'tx-{datetime}', if neither the custom prefix nor the kwargs are filled.
- {separator} is a custom string also used in `_compute_reference_prefix`.
- {sequence_number} is the next integer in the sequence of references sharing the exact
same prefix, '1' if there is only one matching reference (hence without sequence number)
The reference corresponds to the prefix if no other transaction with that prefix already
exists. Otherwise, it follows the pattern `{computed_prefix}{separator}{sequence_number}`
where:
Examples:
- Given the custom prefix 'example' which has no match with an existing reference, the
full reference will be 'example'.
- Given the custom prefix 'example' which matches the existing reference 'example', and
the custom separator '-', the full reference will be 'example-1'.
- Given the kwargs {'invoice_ids': [1, 2]}, the custom separator '-' and no custom prefix,
the full reference will be 'INV1-INV2' (or similar) if no existing reference has the
same prefix, or 'INV1-INV2-n' if n existing references have the same prefix.
- `{computed_prefix}` is:
:param str provider_code: The code of the provider handling the transaction
:param str prefix: The custom prefix used to compute the full reference
:param str separator: The custom separator used to separate the prefix from the suffix, and
passed to `_compute_reference_prefix` if it is called
:param dict kwargs: Optional values passed to `_compute_reference_prefix` if no custom
prefix is provided
:return: The unique reference for the transaction
- The provided custom prefix, if any.
- The computation result of :meth:`_compute_reference_prefix` if the custom prefix is not
filled, but the kwargs are.
- `'tx-{datetime}'` if neither the custom prefix nor the kwargs are filled.
- `{separator}` is the string that separates the prefix from the sequence number.
- `{sequence_number}` is the next integer in the sequence of references sharing the same
prefix. The sequence starts with `1` if there is only one matching reference.
.. example::
- Given the custom prefix `'example'` which has no match with an existing reference, the
full reference will be `'example'`.
- Given the custom prefix `'example'` which matches the existing reference `'example'`,
and the custom separator `'-'`, the full reference will be `'example-1'`.
- Given the kwargs `{'invoice_ids': [1, 2]}`, the custom separator `'-'` and no custom
prefix, the full reference will be `'INV1-INV2'` (or similar) if no existing reference
has the same prefix, or `'INV1-INV2-n'` if `n` existing references have the same
prefix.
:param str provider_code: The code of the provider handling the transaction.
:param str prefix: The custom prefix used to compute the full reference.
:param str separator: The custom separator used to separate the prefix from the suffix.
:param dict kwargs: Optional values passed to :meth:`_compute_reference_prefix` if no custom
prefix is provided.
:return: The unique reference for the transaction.
:rtype: str
"""
# Compute the prefix
# Compute the prefix.
if prefix:
# Replace special characters by their ASCII alternative (é -> e ; ä -> a ; ...)
prefix = unicodedata.normalize('NFKD', prefix).encode('ascii', 'ignore').decode('utf-8')
if not prefix: # Prefix not provided or voided above, compute it based on the kwargs
if not prefix: # Prefix not provided or voided above, compute it based on the kwargs.
prefix = self.sudo()._compute_reference_prefix(provider_code, separator, **kwargs)
if not prefix: # Prefix not computed from the kwargs, fallback on time-based value
prefix = payment_utils.singularize_reference_prefix()
# Compute the sequence number
reference = prefix # The first reference of a sequence has no sequence number
# Compute the sequence number.
reference = prefix # The first reference of a sequence has no sequence number.
if self.sudo().search([('reference', '=', prefix)]): # The reference already has a match
# We now execute a second search on `payment.transaction` to fetch all the references
# starting with the given prefix. The load of these two searches is mitigated by the
@@ -358,16 +363,16 @@ class PaymentTransaction(models.Model):
# 'example', 'example-1' and 'example-ref', in that order. Trusting the order to infer
# the sequence number would lead to a collision with 'example-1'.
search_pattern = re.compile(rf'^{re.escape(prefix)}{separator}(\d+)$')
max_sequence_number = 0 # If no match is found, start the sequence with this reference
max_sequence_number = 0 # If no match is found, start the sequence with this reference.
for existing_reference in same_prefix_references:
search_result = re.search(search_pattern, existing_reference)
if search_result: # The reference has the same prefix and is from the same sequence
# Find the largest sequence number, if any
# Find the largest sequence number, if any.
current_sequence = int(search_result.group(1))
if current_sequence > max_sequence_number:
max_sequence_number = current_sequence
# Compute the full reference
# Compute the full reference.
reference = f'{prefix}{separator}{max_sequence_number + 1}'
return reference
@@ -375,12 +380,14 @@ class PaymentTransaction(models.Model):
def _compute_reference_prefix(self, provider_code, separator, **values):
""" Compute the reference prefix from the transaction values.
Note: This method should be called in sudo mode to give access to documents (INV, SO, ...).
Note: This method should be called in sudo mode to give access to the documents (invoices,
sales orders) referenced in the transaction values.
:param str provider_code: The code of the provider handling the transaction
:param str separator: The custom separator used to separate data references
:param str provider_code: The code of the provider handling the transaction.
:param str separator: The custom separator used to separate parts of the computed
reference prefix.
:param dict values: The transaction values used to compute the reference prefix.
:return: an empty string
:return: The computed reference prefix.
:rtype: str
"""
return ''
@@ -390,11 +397,11 @@ class PaymentTransaction(models.Model):
""" Return the hash for the callback on the transaction.
:param int callback_model_id: The model on which the callback method is defined, as a
`res.model` id
`res.model` id.
:param int callback_res_id: The record on which the callback method must be called, as an id
of the callback model
:param str callback_method: The name of the callback method
:return: The callback hash
of the callback method's model.
:param str callback_method: The name of the callback method.
:return: The callback hash.
:rtype: str
"""
if callback_model_id and callback_res_id and callback_method:
@@ -405,21 +412,22 @@ class PaymentTransaction(models.Model):
return None
def _get_processing_values(self):
""" Return a dict of values used to process the transaction.
""" Return the values used to process the transaction.
The returned dict contains the following entries:
- tx_id: The transaction, as a `payment.transaction` id
- provider_id: The provider handling the transaction, as a `payment.provider` id
- provider_code: The code of the provider
- reference: The reference of the transaction
- amount: The rounded amount of the transaction
- currency_id: The currency of the transaction, as a res.currency id
- partner_id: The partner making the transaction, as a res.partner id
- Additional provider-specific entries
The values are returned as a dict containing entries with the following keys:
Note: self.ensure_one()
- `tx_id`: The transaction, as a `payment.transaction` id.
- `provider_id`: The provider handling the transaction, as a `payment.provider` id.
- `provider_code`: The code of the provider.
- `reference`: The reference of the transaction.
- `amount`: The rounded amount of the transaction.
- `currency_id`: The currency of the transaction, as a `res.currency` id.
- `partner_id`: The partner making the transaction, as a `res.partner` id.
- Additional provider-specific entries.
:return: The dict of processing values
Note: `self.ensure_one()`
:return: The processing values.
:rtype: dict
"""
self.ensure_one()
@@ -434,7 +442,7 @@ class PaymentTransaction(models.Model):
'partner_id': self.partner_id.id,
}
# Complete generic processing values with provider-specific values
# Complete generic processing values with provider-specific values.
processing_values.update(self._get_specific_processing_values(processing_values))
_logger.info(
"generic and provider-specific processing values for transaction with reference "
@@ -442,12 +450,12 @@ class PaymentTransaction(models.Model):
{'ref': self.reference, 'values': pprint.pformat(processing_values)},
)
# Render the html form for the redirect flow if available
# Render the html form for the redirect flow if available.
if self.operation in ('online_redirect', 'validation'):
redirect_form_view = self.provider_id._get_redirect_form_view(
is_validation=self.operation == 'validation'
)
if redirect_form_view: # Some provider don't need a redirect form
if redirect_form_view: # Some provider don't need a redirect form.
rendering_values = self._get_specific_rendering_values(processing_values)
_logger.info(
"provider-specific rendering values for transaction with reference "
@@ -467,8 +475,8 @@ class PaymentTransaction(models.Model):
Provider-specific values take precedence over those of the dict of generic processing
values.
:param dict processing_values: The generic processing values of the transaction
:return: The dict of provider-specific processing values
:param dict processing_values: The generic processing values of the transaction.
:return: The dict of provider-specific processing values.
:rtype: dict
"""
return dict()
@@ -480,19 +488,22 @@ class PaymentTransaction(models.Model):
dict of provider-specific values based on the processing values (provider-specific
processing values included).
:param dict processing_values: The processing values of the transaction
:return: The dict of provider-specific rendering values
:param dict processing_values: The processing values of the transaction.
:return: The dict of provider-specific rendering values.
:rtype: dict
"""
return dict()
def _send_payment_request(self):
""" Request the provider handling the transaction to execute the payment.
""" Request the provider handling the transaction to make the payment.
For a provider to support tokenization, it must override this method and call it to log the
'sent' message, then request a money transfer to its provider.
This method is exclusively used to make payments by token, which correspond to both the
`online_token` and the `offline` transaction's `operation` field.
Note: self.ensure_one()
For a provider to support tokenization, it must override this method and make an API request
to make a payment.
Note: `self.ensure_one()`
:return: None
"""
@@ -503,12 +514,12 @@ class PaymentTransaction(models.Model):
def _send_refund_request(self, amount_to_refund=None):
""" Request the provider handling the transaction to refund it.
For a provider to support refunds, it must override this method and request a refund
to its provider.
For a provider to support refunds, it must override this method and make an API request to
make a refund.
Note: self.ensure_one()
Note: `self.ensure_one()`
:param float amount_to_refund: The amount to be refunded
:param float amount_to_refund: The amount to be refunded.
:return: The refund transaction created to process the refund request.
:rtype: recordset of `payment.transaction`
"""
@@ -520,11 +531,12 @@ class PaymentTransaction(models.Model):
return refund_tx
def _create_refund_transaction(self, amount_to_refund=None, **custom_create_values):
""" Create a new transaction with operation 'refund' and link it to the current transaction.
""" Create a new transaction with the operation `refund` and the current transaction as
source transaction.
:param float amount_to_refund: The strictly positive amount to refund, in the same currency
as the source transaction
:return: The refund transaction
as the source transaction.
:return: The refund transaction.
:rtype: recordset of `payment.transaction`
"""
self.ensure_one()
@@ -542,12 +554,12 @@ class PaymentTransaction(models.Model):
})
def _send_capture_request(self):
""" Request the provider handling the transaction to capture it.
""" Request the provider handling the transaction to capture the payment.
For a provider to support authorization, it must override this method and request a capture
to its provider.
For a provider to support authorization, it must override this method and make an API
request to capture the payment.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: None
"""
@@ -555,12 +567,12 @@ class PaymentTransaction(models.Model):
self._ensure_provider_is_not_disabled()
def _send_void_request(self):
""" Request the provider handling the transaction to void it.
""" Request the provider handling the transaction to void the payment.
For a provider to support authorization, it must override this method and request the
transaction to be voided to its provider.
For a provider to support authorization, it must override this method and make an API
request to void the payment.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: None
"""
@@ -568,11 +580,11 @@ class PaymentTransaction(models.Model):
self._ensure_provider_is_not_disabled()
def _ensure_provider_is_not_disabled(self):
""" Ensure that the provider's state is not 'disabled' before sending a request to its
""" Ensure that the provider's state is not `disabled` before sending a request to its
provider.
:return: None
:raise UserError: If the provider's state is 'disabled'.
:raise UserError: If the provider's state is `disabled`.
"""
if self.provider_id.state == 'disabled':
raise UserError(_(
@@ -582,9 +594,9 @@ class PaymentTransaction(models.Model):
def _handle_notification_data(self, provider_code, notification_data):
""" Match the transaction with the notification data, update its state and return it.
:param str provider_code: The code of the provider that handled the transaction
:param dict notification_data: The notification data sent by the provider
:return: The transaction
:param str provider_code: The code of the provider handling the transaction.
:param dict notification_data: The notification data sent by the provider.
:return: The transaction.
:rtype: recordset of `payment.transaction`
"""
tx = self._get_tx_from_notification_data(provider_code, notification_data)
@@ -598,9 +610,9 @@ class PaymentTransaction(models.Model):
For a provider to handle transaction processing, it must overwrite this method and return
the transaction matching the notification data.
:param str provider_code: The code of the provider that handled the transaction
:param dict notification_data: The notification data sent by the provider
:return: The transaction if found
:param str provider_code: The code of the provider handling the transaction.
:param dict notification_data: The notification data sent by the provider.
:return: The transaction, if found.
:rtype: recordset of `payment.transaction`
"""
return self
@@ -608,25 +620,25 @@ class PaymentTransaction(models.Model):
def _process_notification_data(self, notification_data):
""" Update the transaction state and the provider reference based on the notification data.
This method should normally never be called directly. The correct method to call upon
receiving notification data is `_handle_notification_data`.
This method should usually not be called directly. The correct method to call upon receiving
notification data is :meth:`_handle_notification_data`.
For a provider to handle transaction processing, it must overwrite this method and process
the notification data.
Note: self.ensure_one()
Note: `self.ensure_one()`
:param dict notification_data: The notification data sent by the provider
:param dict notification_data: The notification data sent by the provider.
:return: None
"""
self.ensure_one()
def _set_pending(self, state_message=None):
""" Update the transactions' state to 'pending'.
""" Update the transactions' state to `pending`.
:param str state_message: The reason for which the transaction is set in 'pending' state
:return: updated transactions
:rtype: `payment.transaction` recordset
:param str state_message: The reason for setting the transactions in the state `pending`.
:return: The updated transactions.
:rtype: recordset of `payment.transaction`
"""
allowed_states = ('draft',)
target_state = 'pending'
@@ -635,11 +647,11 @@ class PaymentTransaction(models.Model):
return txs_to_process
def _set_authorized(self, state_message=None):
""" Update the transactions' state to 'authorized'.
""" Update the transactions' state to `authorized`.
:param str state_message: The reason for which the transaction is set in 'authorized' state
:return: updated transactions
:rtype: `payment.transaction` recordset
:param str state_message: The reason for setting the transactions in the state `authorized`.
:return: The updated transactions.
:rtype: recordset of `payment.transaction`
"""
allowed_states = ('draft', 'pending')
target_state = 'authorized'
@@ -648,11 +660,11 @@ class PaymentTransaction(models.Model):
return txs_to_process
def _set_done(self, state_message=None):
""" Update the transactions' state to 'done'.
""" Update the transactions' state to `done`.
:param str state_message: The reason for which the transaction is set in 'done' state
:return: updated transactions
:rtype: `payment.transaction` recordset
:param str state_message: The reason for setting the transactions in the state `done`.
:return: The updated transactions.
:rtype: recordset of `payment.transaction`
"""
allowed_states = ('draft', 'pending', 'authorized', 'error')
target_state = 'done'
@@ -661,25 +673,25 @@ class PaymentTransaction(models.Model):
return txs_to_process
def _set_canceled(self, state_message=None):
""" Update the transactions' state to 'cancel'.
""" Update the transactions' state to `cancel`.
:param str state_message: The reason for which the transaction is set in 'cancel' state
:return: updated transactions
:rtype: `payment.transaction` recordset
:param str state_message: The reason for setting the transactions in the state `cancel`.
:return: The updated transactions.
:rtype: recordset of `payment.transaction`
"""
allowed_states = ('draft', 'pending', 'authorized', 'done') # 'done' for Authorize refunds.
target_state = 'cancel'
txs_to_process = self._update_state(allowed_states, target_state, state_message)
# Cancel the existing payments
# Cancel the existing payments.
txs_to_process._log_received_message()
return txs_to_process
def _set_error(self, state_message):
""" Update the transactions' state to 'error'.
""" Update the transactions' state to `error`.
:param str state_message: The reason for which the transaction is set in 'error' state
:return: updated transactions
:rtype: `payment.transaction` recordset
:param str state_message: The reason for setting the transactions in the state `error`.
:return: The updated transactions.
:rtype: recordset of `payment.transaction`
"""
allowed_states = ('draft', 'pending', 'authorized', 'done') # 'done' for Stripe refunds.
target_state = 'error'
@@ -690,36 +702,37 @@ class PaymentTransaction(models.Model):
def _update_state(self, allowed_states, target_state, state_message):
""" Update the transactions' state to the target state if the current state allows it.
If the current state is the same as the target state, the transaction is skipped.
If the current state is the same as the target state, the transaction is skipped and a log
with level INFO is created.
:param tuple[str] allowed_states: The allowed source states for the target state
:param str target_state: The target state
:param str state_message: The message to set as `state_message`
:return: The recordset of transactions whose state was correctly updated
:param tuple[str] allowed_states: The allowed source states for the target state.
:param str target_state: The target state.
:param str state_message: The message to set as `state_message`.
:return: The recordset of transactions whose state was updated.
:rtype: recordset of `payment.transaction`
"""
def _classify_by_state(_transactions):
"""Classify the transactions according to their current state.
def classify_by_state(transactions_):
""" Classify the transactions according to their current state.
For each transaction of the current recordset, if:
- The state is an allowed state: the transaction is flagged as 'to process'.
- The state is equal to the target state: the transaction is flagged as 'processed'.
- The state matches none of above: the transaction is flagged as 'in wrong state'.
:param recordset _transactions: The transactions to classify, as a `payment.transaction`
recordset
- The state is an allowed state: the transaction is flagged as `to process`.
- The state is equal to the target state: the transaction is flagged as `processed`.
- The state matches none of above: the transaction is flagged as `in wrong state`.
:param recordset transactions_: The transactions to classify, as a `payment.transaction`
recordset.
:return: A 3-items tuple of recordsets of classified transactions, in this order:
transactions 'to process', 'processed', and 'in wrong state'
transactions `to process`, `processed`, and `in wrong state`.
:rtype: tuple(recordset)
"""
_txs_to_process = _transactions.filtered(lambda _tx: _tx.state in allowed_states)
_txs_already_processed = _transactions.filtered(lambda _tx: _tx.state == target_state)
_txs_wrong_state = _transactions - _txs_to_process - _txs_already_processed
txs_to_process_ = transactions_.filtered(lambda _tx: _tx.state in allowed_states)
txs_already_processed_ = transactions_.filtered(lambda _tx: _tx.state == target_state)
txs_wrong_state_ = transactions_ - txs_to_process_ - txs_already_processed_
return _txs_to_process, _txs_already_processed, _txs_wrong_state
return txs_to_process_, txs_already_processed_, txs_wrong_state_
txs_to_process, txs_already_processed, txs_wrong_state = _classify_by_state(self)
txs_to_process, txs_already_processed, txs_wrong_state = classify_by_state(self)
for tx in txs_already_processed:
_logger.info(
"tried to write on transaction with reference %s with the same value for the "
@@ -748,31 +761,30 @@ class PaymentTransaction(models.Model):
def _execute_callback(self):
""" Execute the callbacks defined on the transactions.
Callbacks that have already been executed are silently ignored. This case can happen when a
transaction is first authorized before being confirmed, for instance. In this case, both
status updates try to execute the callback.
Callbacks that have already been executed are silently ignored. For example, the callback is
called twice when a transaction is first authorized then confirmed.
Only successful callbacks are marked as done. This allows callbacks to reschedule themselves
should the conditions not be met in the present call.
Only successful callbacks are marked as done. This allows callbacks to reschedule
themselves, should the conditions be unmet in the present call.
:return: None
"""
for tx in self.filtered(lambda t: not t.sudo().callback_is_done):
# Only use sudo to check, not to execute
# Only use sudo to check, not to execute.
tx_sudo = tx.sudo()
model_sudo = tx_sudo.callback_model_id
res_id = tx_sudo.callback_res_id
method = tx_sudo.callback_method
callback_hash = tx_sudo.callback_hash
if not (model_sudo and res_id and method):
continue # Skip transactions with unset (or not properly defined) callbacks
continue # Skip transactions with unset (or not properly defined) callbacks.
valid_callback_hash = self._generate_callback_hash(model_sudo.id, res_id, method)
if not consteq(ustr(valid_callback_hash), callback_hash):
_logger.warning(
"invalid callback signature for transaction with reference %s", tx.reference
)
continue # Ignore tampered callbacks
continue # Ignore tampered callbacks.
record = self.env[model_sudo.model].browse(res_id).exists()
if not record:
@@ -785,10 +797,10 @@ class PaymentTransaction(models.Model):
'ref': tx.reference,
}
)
continue # Ignore invalidated callbacks
continue # Ignore invalidated callbacks.
success = getattr(record, method)(tx) # Execute the callback
tx_sudo.callback_is_done = success or success is None # Missing returns are successful
success = getattr(record, method)(tx) # Execute the callback.
tx_sudo.callback_is_done = success or success is None # Missing returns are successful.
#=== BUSINESS METHODS - POST-PROCESSING ===#
@@ -800,19 +812,21 @@ class PaymentTransaction(models.Model):
generic post-processing values.
The returned dict contains the following entries:
- provider_code: The code of the provider
- reference: The reference of the transaction
- amount: The rounded amount of the transaction
- currency_id: The currency of the transaction, as a res.currency id
- state: The transaction state: draft, pending, authorized, done, cancel or error
- state_message: The information message about the state
- is_post_processed: Whether the transaction has already been post-processed
- landing_route: The route the user is redirected to after the transaction
- Additional provider-specific entries
Note: self.ensure_one()
- `provider_code`: The code of the provider.
- `reference`: The reference of the transaction.
- `amount`: The rounded amount of the transaction.
- `currency_id`: The currency of the transaction, as a `res.currency` id.
- `state`: The transaction state: `draft`, `pending`, `authorized`, `done`, `cancel`, or
`error`.
- `state_message`: The information message about the state.
- `is_post_processed`: Whether the transaction has already been post-processed.
- `landing_route`: The route the user is redirected to after the transaction.
- Additional provider-specific entries.
:return: The dict of processing values
Note: `self.ensure_one()`
:return: The dict of processing values.
:rtype: dict
"""
self.ensure_one()
@@ -833,14 +847,6 @@ class PaymentTransaction(models.Model):
) # DEBUG level because this can get spammy with transactions in non-final states
return post_processing_values
def _finalize_post_processing(self):
""" Trigger the final post-processing tasks and mark the transactions as post-processed.
:return: None
"""
self._reconcile_after_done()
self.is_post_processed = True
def _cron_finalize_post_processing(self):
""" Finalize the post-processing of recently done transactions not handled by the client.
@@ -865,8 +871,8 @@ class PaymentTransaction(models.Model):
try:
tx._finalize_post_processing()
self.env.cr.commit()
except psycopg2.OperationalError: # A collision of accounting sequences occurred
self.env.cr.rollback() # Rollback and try later
except psycopg2.OperationalError:
self.env.cr.rollback() # Rollback and try later.
except Exception as e:
_logger.exception(
"encountered an error while post-processing transaction with reference %s:\n%s",
@@ -874,12 +880,19 @@ class PaymentTransaction(models.Model):
)
self.env.cr.rollback()
def _reconcile_after_done(self):
""" Post relevant fiscal documents and create missing payments.
def _finalize_post_processing(self):
""" Trigger the final post-processing tasks and mark the transactions as post-processed.
As there is nothing to reconcile for validation transactions, no payment is created for
them. This is also true for validations with a validity check (transfer of a small amount
with immediate refund) because validation amounts are not included in payouts.
:return: None
"""
self._reconcile_after_done()
self.is_post_processed = True
def _reconcile_after_done(self):
""" Perform compute-intensive operations on related documents.
For a provider to handle transaction post-processing, it must overwrite this method and
execute its compute-intensive operations on documents linked to confirmed transactions.
:return: None
"""
@@ -888,7 +901,7 @@ class PaymentTransaction(models.Model):
#=== BUSINESS METHODS - LOGGING ===#
def _log_sent_message(self):
""" Log in the chatter of relevant documents that the transactions have been initiated.
""" Log that the transactions have been initiated in the chatter of relevant documents.
:return: None
"""
@@ -897,10 +910,10 @@ class PaymentTransaction(models.Model):
tx._log_message_on_linked_documents(message)
def _log_received_message(self):
""" Log in the chatter of relevant documents that the transactions have been received.
""" Log that the transactions have been received in the chatter of relevant documents.
A transaction is 'received' when a response is received from the system of the provider
handling the transaction.
A transaction is 'received' when a payment status is received from the provider handling the
transaction.
:return: None
"""
@@ -912,11 +925,11 @@ class PaymentTransaction(models.Model):
""" Log a message on the records linked to the transaction.
For a module to implement payments and link documents to a transaction, it must override
this method and call super, then log the message on documents linked to the transaction.
this method and call it, then log the message on documents linked to the transaction.
Note: self.ensure_one()
Note: `self.ensure_one()`
:param str message: The message to be logged
:param str message: The message to log.
:return: None
"""
self.ensure_one()
@@ -926,14 +939,14 @@ class PaymentTransaction(models.Model):
def _get_sent_message(self):
""" Return the message stating that the transaction has been requested.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: The 'transaction sent' message
:return: The 'transaction sent' message.
:rtype: str
"""
self.ensure_one()
# Choose the message based on the payment flow
# Choose the message based on the payment flow.
if self.operation in ('online_redirect', 'online_direct'):
message = _(
"A transaction with reference %(ref)s has been initiated (%(provider_name)s).",
@@ -966,7 +979,10 @@ class PaymentTransaction(models.Model):
def _get_received_message(self):
""" Return the message stating that the transaction has been received by the provider.
Note: self.ensure_one()
Note: `self.ensure_one()`
:return: The 'transaction received' message.
:rtype: str
"""
self.ensure_one()
@@ -1014,7 +1030,7 @@ class PaymentTransaction(models.Model):
def _get_last(self):
""" Return the last transaction of the recordset.
:return: The last transaction of the recordset, sorted by id
:return: The last transaction of the recordset, sorted by id.
:rtype: recordset of `payment.transaction`
"""
return self.filtered(lambda t: t.state != 'draft').sorted()[:1]
@@ -50,7 +50,7 @@ publicWidget.registry.PaymentExpressCheckoutForm = publicWidget.Widget.extend({
/**
* Prepare the provider-specific express checkout form based on the provided data.
*
* For an provider to manage an express checkout form, it must override this method.
* For a provider to manage an express checkout form, it must override this method.
*
* @private
* @param {Object} providerData - The provider-specific data.
@@ -63,8 +63,8 @@ publicWidget.registry.PaymentExpressCheckoutForm = publicWidget.Widget.extend({
/**
* Prepare the params to send to the transaction route.
*
* For an provider to overwrite generic params or to add provider-specific ones, it must
* override this method and return the extended transaction route params.
* For a provider to overwrite generic params or to add provider-specific ones, it must override
* this method and return the extended transaction route params.
*
* @private
* @param {number} providerId - The id of the provider handling the transaction.
@@ -49,7 +49,7 @@ class TestPaymentprovider(PaymentCommon):
self.assertIn(self.provider, compatible_providers)
def test_provider_compatible_when_payment_below_maximum_amount(self):
""" Test that an provider is compatible when the payment amount is less than the maximum
""" Test that a provider is compatible when the payment amount is less than the maximum
amount. """
self.provider.maximum_amount = self.amount + 10.0
@@ -59,7 +59,7 @@ class TestPaymentprovider(PaymentCommon):
self.assertIn(self.provider, compatible_providers)
def test_provider_not_compatible_when_payment_above_maximum_amount(self):
""" Test that an provider is not compatible when the payment amount is more than the maximum
""" Test that a provider is not compatible when the payment amount is more than the maximum
amount. """
self.provider.maximum_amount = self.amount - 10.0