changes
This commit is contained in:
@@ -5,4 +5,5 @@ from . import payment_provider
|
||||
from . import payment_token
|
||||
from . import payment_transaction
|
||||
from . import poynt_terminal
|
||||
from . import res_config_settings
|
||||
from . import sale_order
|
||||
|
||||
@@ -347,6 +347,21 @@ class PaymentProvider(models.Model):
|
||||
and payment_method_sudo.support_tokenization
|
||||
),
|
||||
}
|
||||
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
surcharge_enabled = ICP.get_param(
|
||||
'fusion_poynt.surcharge_enabled', 'False',
|
||||
) == 'True'
|
||||
if surcharge_enabled:
|
||||
inline_form_values['surcharge'] = {
|
||||
'enabled': True,
|
||||
'visa': float(ICP.get_param('fusion_poynt.surcharge_visa_rate', '0') or 0),
|
||||
'mastercard': float(ICP.get_param('fusion_poynt.surcharge_mastercard_rate', '0') or 0),
|
||||
'amex': float(ICP.get_param('fusion_poynt.surcharge_amex_rate', '0') or 0),
|
||||
'debit': float(ICP.get_param('fusion_poynt.surcharge_debit_rate', '0') or 0),
|
||||
'other': float(ICP.get_param('fusion_poynt.surcharge_other_rate', '0') or 0),
|
||||
}
|
||||
|
||||
return json.dumps(inline_form_values)
|
||||
|
||||
# === ACTION METHODS === #
|
||||
|
||||
@@ -92,13 +92,20 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
poynt_data = self._poynt_create_order_and_authorize()
|
||||
if poynt_data:
|
||||
status = poynt_data.get('status', 'AUTHORIZED')
|
||||
payment_data = {
|
||||
'reference': self.reference,
|
||||
'poynt_order_id': poynt_data.get('order_id'),
|
||||
'poynt_transaction_id': poynt_data.get('transaction_id'),
|
||||
'poynt_status': poynt_data.get('status', 'AUTHORIZED'),
|
||||
'poynt_status': status,
|
||||
'funding_source': poynt_data.get('funding_source', {}),
|
||||
}
|
||||
if status in ('DECLINED', 'FAILED', 'REFUND_FAILED'):
|
||||
self._set_error(
|
||||
_("Payment was %(status)s by the processor.",
|
||||
status=status.lower())
|
||||
)
|
||||
return
|
||||
self._process('poynt', payment_data)
|
||||
|
||||
def _poynt_create_order_and_authorize(self):
|
||||
@@ -148,6 +155,103 @@ class PaymentTransaction(models.Model):
|
||||
self._set_error(str(e))
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def _detect_card_brand_from_details(payment_details):
|
||||
"""Detect card brand from the payment_details string on a token.
|
||||
|
||||
Tokens store details like "VISA ending in 1234" or
|
||||
"AMERICAN_EXPRESS ending in 5678".
|
||||
"""
|
||||
details = (payment_details or '').upper()
|
||||
if 'AMEX' in details or 'AMERICAN_EXPRESS' in details:
|
||||
return 'amex'
|
||||
if 'VISA' in details:
|
||||
return 'visa'
|
||||
if 'MASTER' in details:
|
||||
return 'mastercard'
|
||||
return 'other'
|
||||
|
||||
def _apply_token_surcharge(self):
|
||||
"""Apply surcharge to the linked invoice for token-based payments.
|
||||
|
||||
Checks if surcharge is enabled, detects card brand from the token,
|
||||
adds a surcharge line to the invoice, and updates the transaction
|
||||
amount. Skips rental orders (recurring charges should not get
|
||||
surcharge), invoices with no linked records, or invoices where
|
||||
surcharge is already applied.
|
||||
"""
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
if ICP.get_param('fusion_poynt.surcharge_enabled', 'False') != 'True':
|
||||
return
|
||||
|
||||
if not self.token_id or not self.invoice_ids:
|
||||
return
|
||||
|
||||
for inv in self.invoice_ids:
|
||||
sale_orders = inv.mapped('line_ids.sale_line_ids.order_id')
|
||||
for so in sale_orders:
|
||||
if getattr(so, 'is_rental_order', False):
|
||||
if not getattr(so, 'rental_apply_cc_fee', True):
|
||||
return
|
||||
|
||||
card_type = self._detect_card_brand_from_details(
|
||||
self.token_id.payment_details,
|
||||
)
|
||||
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')
|
||||
rate = float(ICP.get_param(rate_key, '0') or 0)
|
||||
if rate <= 0:
|
||||
return
|
||||
|
||||
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:
|
||||
_logger.warning("Surcharge product not configured; skipping token surcharge")
|
||||
return
|
||||
|
||||
total_fee = 0.0
|
||||
for invoice in self.invoice_ids.sudo():
|
||||
already_has = invoice.invoice_line_ids.filtered(
|
||||
lambda l: l.product_id.id == product.id
|
||||
)
|
||||
if already_has:
|
||||
continue
|
||||
|
||||
fee_amount = round(invoice.amount_residual * rate / 100.0, 2)
|
||||
if fee_amount <= 0:
|
||||
continue
|
||||
|
||||
was_posted = invoice.state == 'posted'
|
||||
if was_posted:
|
||||
invoice.button_draft()
|
||||
|
||||
description = "Credit Card Processing Fee (%.2f%% surcharge)" % 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()
|
||||
|
||||
total_fee += fee_amount
|
||||
|
||||
if total_fee > 0:
|
||||
self.amount += total_fee
|
||||
|
||||
def _poynt_process_token_payment(self):
|
||||
"""Process a payment using a stored token (card on file).
|
||||
|
||||
@@ -156,6 +260,8 @@ class PaymentTransaction(models.Model):
|
||||
were created before the JWT migration.
|
||||
"""
|
||||
try:
|
||||
self._apply_token_surcharge()
|
||||
|
||||
provider = self._get_provider_sudo()
|
||||
action = 'AUTHORIZE' if provider.capture_manually else 'SALE'
|
||||
payment_jwt = self.token_id.poynt_payment_token
|
||||
@@ -213,13 +319,28 @@ class PaymentTransaction(models.Model):
|
||||
if order_id:
|
||||
self.poynt_order_id = order_id
|
||||
|
||||
status = txn_result.get('status', '')
|
||||
payment_data = {
|
||||
'reference': self.reference,
|
||||
'poynt_order_id': order_id,
|
||||
'poynt_transaction_id': transaction_id,
|
||||
'poynt_status': txn_result.get('status', ''),
|
||||
'poynt_status': status,
|
||||
'funding_source': txn_result.get('fundingSource', {}),
|
||||
}
|
||||
|
||||
if status in ('DECLINED', 'FAILED', 'REFUND_FAILED'):
|
||||
processor = txn_result.get('processorResponse', {})
|
||||
decline_msg = (
|
||||
processor.get('statusMessage')
|
||||
or processor.get('message')
|
||||
or status.lower()
|
||||
)
|
||||
self._set_error(
|
||||
_("Payment %(status)s: %(reason)s",
|
||||
status=status.lower(), reason=decline_msg)
|
||||
)
|
||||
return
|
||||
|
||||
self._process('poynt', payment_data)
|
||||
except ValidationError as e:
|
||||
self._set_error(str(e))
|
||||
|
||||
91
fusion_poynt/models/res_config_settings.py
Normal file
91
fusion_poynt/models/res_config_settings.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
poynt_surcharge_enabled = fields.Boolean(
|
||||
string="Enable Credit Card Surcharge",
|
||||
config_parameter='fusion_poynt.surcharge_enabled',
|
||||
)
|
||||
poynt_surcharge_visa_rate = fields.Float(
|
||||
string="Visa Rate (%)",
|
||||
config_parameter='fusion_poynt.surcharge_visa_rate',
|
||||
default=2.5,
|
||||
)
|
||||
poynt_surcharge_mastercard_rate = fields.Float(
|
||||
string="Mastercard Rate (%)",
|
||||
config_parameter='fusion_poynt.surcharge_mastercard_rate',
|
||||
default=2.5,
|
||||
)
|
||||
poynt_surcharge_amex_rate = fields.Float(
|
||||
string="Amex Rate (%)",
|
||||
config_parameter='fusion_poynt.surcharge_amex_rate',
|
||||
default=3.5,
|
||||
)
|
||||
poynt_surcharge_debit_rate = fields.Float(
|
||||
string="Debit Rate (%)",
|
||||
config_parameter='fusion_poynt.surcharge_debit_rate',
|
||||
default=0.0,
|
||||
)
|
||||
poynt_surcharge_other_rate = fields.Float(
|
||||
string="Other Cards Rate (%)",
|
||||
config_parameter='fusion_poynt.surcharge_other_rate',
|
||||
default=2.5,
|
||||
)
|
||||
poynt_surcharge_product_id = fields.Many2one(
|
||||
'product.product',
|
||||
string="Surcharge Product",
|
||||
config_parameter='fusion_poynt.surcharge_product_id',
|
||||
help="The service product used for the credit card processing fee line.",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
res = super().get_values()
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
product_id = int(ICP.get_param('fusion_poynt.surcharge_product_id', '0') or 0)
|
||||
if product_id and self.env['product.product'].sudo().browse(product_id).exists():
|
||||
res['poynt_surcharge_product_id'] = product_id
|
||||
else:
|
||||
default = self.env.ref('fusion_poynt.product_cc_processing_fee', raise_if_not_found=False)
|
||||
res['poynt_surcharge_product_id'] = default.id if default else False
|
||||
return res
|
||||
|
||||
def set_values(self):
|
||||
super().set_values()
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
ICP.set_param(
|
||||
'fusion_poynt.surcharge_product_id',
|
||||
str(self.poynt_surcharge_product_id.id) if self.poynt_surcharge_product_id else '0',
|
||||
)
|
||||
|
||||
def action_open_poynt_provider(self):
|
||||
provider = self.env['payment.provider'].sudo().search(
|
||||
[('code', '=', 'poynt')], limit=1,
|
||||
)
|
||||
if provider:
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'payment.provider',
|
||||
'res_id': provider.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'payment.provider',
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current',
|
||||
'domain': [('code', '=', 'poynt')],
|
||||
}
|
||||
|
||||
def action_open_poynt_terminals(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'poynt.terminal',
|
||||
'view_mode': 'list,form',
|
||||
'target': 'current',
|
||||
}
|
||||
Reference in New Issue
Block a user