This commit is contained in:
gsinghpal
2026-03-20 11:46:41 -04:00
parent 595dccc17d
commit 92369be6e0
71 changed files with 6588 additions and 8 deletions

View File

@@ -58,6 +58,37 @@ class PoyntPaymentWizard(models.TransientModel):
default='terminal',
)
# --- Card type & surcharge fields ---
card_type = fields.Selection(
selection=[
('visa', "Visa"),
('mastercard', "Mastercard"),
('amex', "American Express"),
('debit', "Debit"),
('other', "Other"),
],
string="Card Type",
)
surcharge_enabled = fields.Boolean(
compute='_compute_surcharge_enabled',
)
surcharge_rate = fields.Float(
string="Surcharge Rate (%)",
digits=(5, 2),
readonly=True,
)
surcharge_amount = fields.Monetary(
string="Surcharge Amount",
currency_field='currency_id',
readonly=True,
)
surcharge_applied = fields.Boolean(default=False)
original_amount = fields.Monetary(
string="Invoice Amount",
currency_field='currency_id',
readonly=True,
)
# --- Terminal fields ---
terminal_id = fields.Many2one(
'poynt.terminal',
@@ -102,6 +133,58 @@ class PoyntPaymentWizard(models.TransientModel):
readonly=True,
)
@api.depends_context('uid')
def _compute_surcharge_enabled(self):
enabled = self.env['ir.config_parameter'].sudo().get_param(
'fusion_poynt.surcharge_enabled', 'False',
) == 'True'
for rec in self:
rec.surcharge_enabled = enabled
@staticmethod
def _detect_card_brand(card_number):
num = (card_number or '').replace(' ', '')
if len(num) < 2:
return 'other'
if num[:2] in ('34', '37'):
return 'amex'
if num[0] == '4':
return 'visa'
prefix2 = int(num[:2])
if 51 <= prefix2 <= 55:
return 'mastercard'
if len(num) >= 4:
prefix4 = int(num[:4])
if 2221 <= prefix4 <= 2720:
return 'mastercard'
return 'other'
def _get_surcharge_rate(self, card_type):
ICP = self.env['ir.config_parameter'].sudo()
rate_key = {
'visa': 'fusion_poynt.surcharge_visa_rate',
'mastercard': 'fusion_poynt.surcharge_mastercard_rate',
'amex': 'fusion_poynt.surcharge_amex_rate',
'debit': 'fusion_poynt.surcharge_debit_rate',
}.get(card_type, 'fusion_poynt.surcharge_other_rate')
return float(ICP.get_param(rate_key, '0') or 0)
@api.onchange('card_number')
def _onchange_card_number(self):
if self.payment_mode == 'card' and self.card_number:
self.card_type = self._detect_card_brand(self.card_number)
@api.onchange('card_type')
def _onchange_card_type(self):
if not self.card_type or not self.surcharge_enabled:
self.surcharge_rate = 0.0
self.surcharge_amount = 0.0
return
rate = self._get_surcharge_rate(self.card_type)
base_amount = self.original_amount or self.amount
self.surcharge_rate = rate
self.surcharge_amount = round(base_amount * rate / 100.0, 2)
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
@@ -112,6 +195,7 @@ class PoyntPaymentWizard(models.TransientModel):
invoice = self.env['account.move'].browse(invoice_id)
res['invoice_id'] = invoice.id
res['amount'] = invoice.amount_residual
res['original_amount'] = invoice.amount_residual
res['currency_id'] = invoice.currency_id.id
provider = self.env['payment.provider'].sudo().search([
@@ -135,10 +219,103 @@ class PoyntPaymentWizard(models.TransientModel):
if provider.poynt_default_terminal_id:
self.terminal_id = provider.poynt_default_terminal_id
def _apply_surcharge_if_needed(self):
"""Add the surcharge invoice line if surcharge is enabled and not yet applied."""
if self.surcharge_applied or not self.surcharge_enabled:
return
if not self.card_type:
raise UserError(_("Please select the card type to calculate the surcharge."))
rate = self._get_surcharge_rate(self.card_type)
if rate <= 0:
return
base_amount = self.original_amount or self.amount
fee_amount = round(base_amount * rate / 100.0, 2)
if fee_amount <= 0:
return
ICP = self.env['ir.config_parameter'].sudo()
product_id = int(ICP.get_param('fusion_poynt.surcharge_product_id', '0') or 0)
product = self.env['product.product'].sudo().browse(product_id).exists()
if not product:
product = self.env.ref('fusion_poynt.product_cc_processing_fee', raise_if_not_found=False)
if not product:
raise UserError(
_("Surcharge product not configured. "
"Go to Settings > Fusion Poynt to set it up.")
)
invoice = self.invoice_id.sudo()
was_posted = invoice.state == 'posted'
if was_posted:
invoice.button_draft()
description = _("Credit Card Processing Fee (%(rate).2f%% surcharge)", rate=rate)
invoice.write({
'invoice_line_ids': [(0, 0, {
'product_id': product.id,
'name': description,
'quantity': 1,
'price_unit': fee_amount,
'tax_ids': [(5, 0, 0)],
})],
})
if was_posted:
invoice.action_post()
self.write({
'surcharge_applied': True,
'surcharge_rate': rate,
'surcharge_amount': fee_amount,
'amount': invoice.amount_residual,
})
def _remove_surcharge_line(self):
"""Remove the surcharge line from the invoice if it was applied."""
if not self.surcharge_applied:
return
ICP = self.env['ir.config_parameter'].sudo()
product_id = int(ICP.get_param('fusion_poynt.surcharge_product_id', '0') or 0)
product = self.env['product.product'].sudo().browse(product_id).exists()
if not product:
product = self.env.ref('fusion_poynt.product_cc_processing_fee', raise_if_not_found=False)
if not product:
return
invoice = self.invoice_id.sudo()
surcharge_lines = invoice.invoice_line_ids.filtered(
lambda l: l.product_id.id == product.id
)
if not surcharge_lines:
self.surcharge_applied = False
return
was_posted = invoice.state == 'posted'
if was_posted:
invoice.button_draft()
surcharge_lines.unlink()
if was_posted:
invoice.action_post()
self.write({
'surcharge_applied': False,
'surcharge_amount': 0.0,
'surcharge_rate': 0.0,
'amount': invoice.amount_residual,
})
def action_collect_payment(self):
"""Dispatch to the appropriate payment method."""
self.ensure_one()
self._apply_surcharge_if_needed()
if self.amount <= 0:
raise UserError(_("Payment amount must be greater than zero."))
@@ -187,6 +364,7 @@ class PoyntPaymentWizard(models.TransientModel):
except (ValidationError, UserError) as e:
self._cleanup_draft_transaction()
self._remove_surcharge_line()
self.write({
'state': 'error',
'status_message': str(e),
@@ -273,6 +451,31 @@ class PoyntPaymentWizard(models.TransientModel):
'poynt_status': status,
'funding_source': result.get('fundingSource', {}),
}
if status in ('DECLINED', 'FAILED', 'REFUND_FAILED'):
tx._set_error(
_("Payment was %(status)s by the processor.",
status=status.lower())
)
self._cleanup_draft_transaction()
self._remove_surcharge_line()
processor = result.get('processorResponse', {})
decline_msg = (
processor.get('statusMessage')
or processor.get('message')
or status.lower()
)
self.write({
'state': 'error',
'status_message': _(
"Payment %(status)s: %(reason)s",
status=status.lower(),
reason=decline_msg,
),
'poynt_transaction_ref': transaction_id,
})
return self._reopen_wizard()
tx._process('poynt', payment_data)
self.write({
@@ -288,6 +491,7 @@ class PoyntPaymentWizard(models.TransientModel):
except (ValidationError, UserError) as e:
self._cleanup_draft_transaction()
self._remove_surcharge_line()
self.write({
'state': 'error',
'status_message': str(e),
@@ -352,6 +556,7 @@ class PoyntPaymentWizard(models.TransientModel):
if status in ('DECLINED', 'VOIDED', 'REFUNDED'):
self._cleanup_draft_transaction()
self._remove_surcharge_line()
self.write({
'state': 'error',
'status_message': _(
@@ -473,6 +678,7 @@ class PoyntPaymentWizard(models.TransientModel):
"""Cancel the payment and clean up the draft transaction."""
self.ensure_one()
self._cleanup_draft_transaction()
self._remove_surcharge_line()
return {'type': 'ir.actions.act_window_close'}
def _cleanup_draft_transaction(self):

View File

@@ -9,6 +9,9 @@
<field name="state" invisible="1"/>
<field name="poynt_transaction_ref" invisible="1"/>
<field name="provider_id" invisible="1"/>
<field name="surcharge_enabled" invisible="1"/>
<field name="surcharge_applied" invisible="1"/>
<field name="original_amount" invisible="1"/>
<!-- Status banner for waiting / done / error -->
<div class="alert alert-info" role="alert"
@@ -44,6 +47,25 @@
</group>
</group>
<!-- Card Type & Surcharge section -->
<group string="Card Type &amp; Surcharge"
invisible="state == 'done' or not surcharge_enabled">
<group>
<field name="card_type"
widget="radio"
required="surcharge_enabled and state in ('draft', 'error')"
readonly="state not in ('draft', 'error')"/>
</group>
<group invisible="not card_type">
<field name="surcharge_rate" string="Rate (%)"/>
<field name="surcharge_amount"/>
<div class="text-muted" colspan="2"
invisible="surcharge_amount == 0">
A surcharge line will be added to the invoice before payment.
</div>
</group>
</group>
<!-- Terminal section -->
<group string="Terminal"
invisible="payment_mode != 'terminal' or state == 'done'">