This commit is contained in:
gsinghpal
2026-03-26 15:16:51 -04:00
parent bd7275881f
commit 2563208f53
13 changed files with 329 additions and 15 deletions

View File

@@ -23,6 +23,10 @@ class AccountMove(models.Model):
string="Has Poynt Receipt",
compute='_compute_has_poynt_receipt',
)
poynt_transaction_count = fields.Integer(
string="Poynt Transactions",
compute='_compute_poynt_transaction_count',
)
@api.depends('reversal_move_ids')
def _compute_poynt_refund_count(self):
@@ -38,6 +42,33 @@ class AccountMove(models.Model):
for move in self:
move.has_poynt_receipt = bool(move._get_poynt_transaction_for_receipt())
def _compute_poynt_transaction_count(self):
for move in self:
move.poynt_transaction_count = self.env['payment.transaction'].sudo().search_count([
('invoice_ids', 'in', move.id),
('provider_code', '=', 'poynt'),
])
def action_view_poynt_transactions(self):
"""Open payment transactions linked to this invoice/credit note."""
self.ensure_one()
transactions = self.env['payment.transaction'].sudo().search([
('invoice_ids', 'in', self.id),
('provider_code', '=', 'poynt'),
])
action = {
'name': _("Poynt Transactions"),
'type': 'ir.actions.act_window',
'res_model': 'payment.transaction',
'domain': [('id', 'in', transactions.ids)],
}
if len(transactions) == 1:
action['view_mode'] = 'form'
action['res_id'] = transactions.id
else:
action['view_mode'] = 'list,form'
return action
def action_view_poynt_refunds(self):
"""Open the credit notes linked to this invoice that were refunded via Poynt."""
self.ensure_one()

View File

@@ -58,6 +58,8 @@ class PaymentTransaction(models.Model):
For direct (online) payments we create a Poynt order upfront and return
identifiers plus the return URL so the frontend JS can complete the flow.
The actual transaction is created later when the frontend sends card
details via the /payment/poynt/process_card route.
"""
if self.provider_code != 'poynt':
return super()._get_specific_processing_values(processing_values)
@@ -65,7 +67,7 @@ class PaymentTransaction(models.Model):
if self.operation == 'online_token':
return {}
poynt_data = self._poynt_create_order_and_authorize()
order_data = self._poynt_create_order()
provider = self._get_provider_sudo()
base_url = provider.get_base_url()
@@ -75,8 +77,7 @@ class PaymentTransaction(models.Model):
)
return {
'poynt_order_id': poynt_data.get('order_id', ''),
'poynt_transaction_id': poynt_data.get('transaction_id', ''),
'poynt_order_id': order_data.get('order_id', ''),
'return_url': return_url,
'business_id': provider.poynt_business_id,
'is_test': provider.state == 'test',
@@ -108,6 +109,33 @@ class PaymentTransaction(models.Model):
return
self._process('poynt', payment_data)
def _poynt_create_order(self):
"""Create a Poynt order without a transaction.
Used by the portal payment flow where card details are collected
on the frontend and the transaction is created separately via
the /payment/poynt/process_card route.
:return: Dict with order_id.
:rtype: dict
"""
try:
provider = self._get_provider_sudo()
order_payload = poynt_utils.build_order_payload(
self.reference, self.amount, self.currency_id,
business_id=provider.poynt_business_id,
store_id=provider.poynt_store_id or '',
)
order_result = provider._poynt_make_request(
'POST', 'orders', payload=order_payload,
)
order_id = order_result.get('id', '')
self.poynt_order_id = order_id
return {'order_id': order_id}
except ValidationError as e:
self._set_error(str(e))
return {}
def _poynt_create_order_and_authorize(self):
"""Create a Poynt order and authorize the transaction.
@@ -383,6 +411,7 @@ class PaymentTransaction(models.Model):
try:
refund_payload = {
'action': 'REFUND',
'fundingSourceType': 'CREDIT_DEBIT',
'parentId': parent_txn_id,
'fundingSource': {
'type': 'CREDIT_DEBIT',
@@ -426,6 +455,7 @@ class PaymentTransaction(models.Model):
try:
capture_payload = {
'action': 'CAPTURE',
'fundingSourceType': 'CREDIT_DEBIT',
'parentId': source_tx.provider_reference,
'amounts': {
'transactionAmount': minor_amount,
@@ -817,6 +847,7 @@ class PaymentTransaction(models.Model):
self._poynt_store_receipt_data(payment_data)
self._poynt_attach_receipt_pdf()
self._poynt_attach_poynt_receipt()
self._poynt_auto_send_invoice_and_receipt()
except Exception:
_logger.exception(
"Receipt generation failed for transaction %s", self.reference,
@@ -925,6 +956,64 @@ class PaymentTransaction(models.Model):
'mimetype': 'text/html',
})
def _poynt_auto_send_invoice_and_receipt(self):
"""Automatically email the invoice and payment receipt to the customer
after a successful payment.
1. Sends the invoice via the standard Odoo invoice email template.
2. Sends the Poynt payment receipt email with the PDF attached.
Best-effort: failures are logged but never block the payment flow.
"""
self.ensure_one()
invoice = self.invoice_ids[:1]
partner = self.partner_id
if not partner.email:
_logger.info(
"Skipping auto-send for %s: partner %s has no email.",
self.reference, partner.display_name,
)
return
# 1. Send the invoice PDF
if invoice and invoice.state == 'posted':
try:
inv_template = self.env.ref(
'account.email_template_edi_invoice',
raise_if_not_found=False,
)
if inv_template:
inv_template.sudo().send_mail(
invoice.id, force_send=True,
)
invoice.sudo().write({'is_move_sent': True})
_logger.info(
"Auto-sent invoice %s to %s",
invoice.name, partner.email,
)
except Exception:
_logger.exception(
"Failed to auto-send invoice %s", invoice.name,
)
# 2. Send the payment receipt
try:
receipt_template = self.env.ref(
'fusion_poynt.mail_template_poynt_receipt',
raise_if_not_found=False,
)
if receipt_template:
receipt_template.sudo().send_mail(self.id, force_send=True)
_logger.info(
"Auto-sent payment receipt for %s to %s",
self.reference, partner.email,
)
except Exception:
_logger.exception(
"Failed to auto-send receipt for %s", self.reference,
)
def _get_poynt_receipt_values(self):
"""Parse the stored receipt JSON for use in QWeb templates.

View File

@@ -2,7 +2,7 @@
import logging
from odoo import _, models
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
@@ -11,6 +11,37 @@ _logger = logging.getLogger(__name__)
class SaleOrder(models.Model):
_inherit = 'sale.order'
poynt_transaction_count = fields.Integer(
string="Poynt Transactions",
compute='_compute_poynt_transaction_count',
)
def _compute_poynt_transaction_count(self):
for order in self:
order.poynt_transaction_count = self.env['payment.transaction'].sudo().search_count([
('sale_order_ids', 'in', order.id),
('provider_code', '=', 'poynt'),
])
def action_view_poynt_transactions(self):
self.ensure_one()
transactions = self.env['payment.transaction'].sudo().search([
('sale_order_ids', 'in', self.id),
('provider_code', '=', 'poynt'),
])
action = {
'name': _("Poynt Transactions"),
'type': 'ir.actions.act_window',
'res_model': 'payment.transaction',
'domain': [('id', 'in', transactions.ids)],
}
if len(transactions) == 1:
action['view_mode'] = 'form'
action['res_id'] = transactions.id
else:
action['view_mode'] = 'list,form'
return action
def action_poynt_collect_payment(self):
"""Create an invoice (if needed) and open the Poynt payment wizard.