changes
This commit is contained in:
BIN
fusion_poynt/models/__pycache__/payment_provider.cpython-312.pyc
Normal file
BIN
fusion_poynt/models/__pycache__/payment_provider.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -208,7 +208,7 @@ class PaymentProvider(models.Model):
|
||||
json=payload,
|
||||
params=params,
|
||||
headers=headers,
|
||||
timeout=60,
|
||||
timeout=15,
|
||||
)
|
||||
except requests.exceptions.RequestException as e:
|
||||
_logger.error("Poynt API request failed: %s", e)
|
||||
@@ -521,6 +521,11 @@ class PaymentProvider(models.Model):
|
||||
which returns a TransactionReceipt with a ``data`` field containing
|
||||
the rendered receipt content (HTML or text).
|
||||
|
||||
If Poynt has reported a missing receipt template for this business,
|
||||
we fail fast (no network call) until the admin clears the flag.
|
||||
This protects Odoo workers from a thundering-herd of 400s when the
|
||||
merchant's Poynt account is missing its receipt template config.
|
||||
|
||||
:param str transaction_id: The Poynt transaction UUID.
|
||||
:return: The receipt content string, or None on failure.
|
||||
:rtype: str | None
|
||||
@@ -528,14 +533,46 @@ class PaymentProvider(models.Model):
|
||||
self.ensure_one()
|
||||
if not transaction_id:
|
||||
return None
|
||||
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
cooldown_key = f'fusion_poynt.receipt_endpoint_cooldown_until.{self.id}'
|
||||
try:
|
||||
cooldown_until = int(ICP.get_param(cooldown_key, '0'))
|
||||
except (TypeError, ValueError):
|
||||
cooldown_until = 0
|
||||
now_ts = int(fields.Datetime.now().timestamp())
|
||||
if cooldown_until > now_ts:
|
||||
_logger.debug(
|
||||
"Skipping Poynt receipt fetch for %s — endpoint in cooldown "
|
||||
"for %ss (template missing or endpoint unhealthy)",
|
||||
transaction_id, cooldown_until - now_ts,
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
result = self._poynt_make_request(
|
||||
'GET', f'transactions/{transaction_id}/receipt',
|
||||
)
|
||||
return result.get('data') or None
|
||||
except (ValidationError, Exception):
|
||||
except ValidationError as e:
|
||||
err = str(e).lower()
|
||||
if 'template' in err or '400' in err:
|
||||
# Poynt is consistently rejecting the receipt call for this
|
||||
# business. Put the endpoint in a 1-hour cooldown so subsequent
|
||||
# webhooks do not hammer Poynt with known-bad requests.
|
||||
ICP.set_param(cooldown_key, str(now_ts + 3600))
|
||||
_logger.warning(
|
||||
"Poynt receipt endpoint returned %s — suspending receipt "
|
||||
"fetches for provider %s for 1 hour. "
|
||||
"Admin: check that the Poynt business has a receipt "
|
||||
"template configured in the Poynt dashboard.",
|
||||
e, self.id,
|
||||
)
|
||||
return None
|
||||
except Exception:
|
||||
_logger.debug(
|
||||
"Could not fetch Poynt receipt for transaction %s", transaction_id,
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@@ -904,11 +904,24 @@ class PaymentTransaction(models.Model):
|
||||
self.poynt_receipt_data = json.dumps(receipt)
|
||||
|
||||
def _poynt_attach_receipt_pdf(self):
|
||||
"""Render the QWeb receipt report and attach the PDF to the invoice."""
|
||||
"""Render the QWeb receipt report and attach the PDF to the invoice.
|
||||
|
||||
Idempotent: if a PDF for this transaction is already attached,
|
||||
skip. Poynt fires multiple webhooks per payment; without this
|
||||
guard the same invoice would get N duplicate receipt PDFs."""
|
||||
invoice = self.invoice_ids[:1]
|
||||
if not invoice:
|
||||
return
|
||||
|
||||
filename = f"Payment_Receipt_{self.reference}.pdf"
|
||||
existing = self.env['ir.attachment'].sudo().search([
|
||||
('res_model', '=', 'account.move'),
|
||||
('res_id', '=', invoice.id),
|
||||
('name', '=', filename),
|
||||
], limit=1)
|
||||
if existing:
|
||||
return
|
||||
|
||||
try:
|
||||
report = self.env.ref('fusion_poynt.action_report_poynt_receipt')
|
||||
pdf_content, _report_type = report._render_qweb_pdf(report.report_name, [self.id])
|
||||
@@ -916,7 +929,6 @@ class PaymentTransaction(models.Model):
|
||||
_logger.debug("Could not render Poynt receipt PDF for %s", self.reference)
|
||||
return
|
||||
|
||||
filename = f"Payment_Receipt_{self.reference}.pdf"
|
||||
attachment = self.env['ir.attachment'].sudo().create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
@@ -935,18 +947,33 @@ class PaymentTransaction(models.Model):
|
||||
)
|
||||
|
||||
def _poynt_attach_poynt_receipt(self):
|
||||
"""Try the Poynt renderReceipt endpoint and attach the result."""
|
||||
"""Try the Poynt renderReceipt endpoint and attach the result.
|
||||
|
||||
Idempotent: if a Poynt HTML receipt for this transaction is already
|
||||
attached to the invoice, we skip the remote call. Poynt sends
|
||||
several state-change webhooks per transaction (AUTHORIZED, CAPTURED,
|
||||
UPDATED...) and each one lands here; without this guard every
|
||||
webhook would re-fetch the receipt and hammer Poynt.
|
||||
"""
|
||||
invoice = self.invoice_ids[:1]
|
||||
if not invoice:
|
||||
return
|
||||
|
||||
filename = f"Poynt_Receipt_{self.reference}.html"
|
||||
existing = self.env['ir.attachment'].sudo().search([
|
||||
('res_model', '=', 'account.move'),
|
||||
('res_id', '=', invoice.id),
|
||||
('name', '=', filename),
|
||||
], limit=1)
|
||||
if existing:
|
||||
return
|
||||
|
||||
receipt_content = self._get_provider_sudo()._poynt_fetch_receipt(
|
||||
self.poynt_transaction_id,
|
||||
)
|
||||
if not receipt_content:
|
||||
return
|
||||
|
||||
filename = f"Poynt_Receipt_{self.reference}.html"
|
||||
self.env['ir.attachment'].sudo().create({
|
||||
'name': filename,
|
||||
'type': 'binary',
|
||||
@@ -963,6 +990,11 @@ class PaymentTransaction(models.Model):
|
||||
1. Sends the invoice via the standard Odoo invoice email template.
|
||||
2. Sends the Poynt payment receipt email with the PDF attached.
|
||||
|
||||
Idempotent: Poynt fires several state-change webhooks per payment
|
||||
(AUTHORIZED, CAPTURED, UPDATED...). We use `invoice.is_move_sent`
|
||||
as the "we've already done this" flag so duplicate webhooks don't
|
||||
re-email the customer.
|
||||
|
||||
Best-effort: failures are logged but never block the payment flow.
|
||||
"""
|
||||
self.ensure_one()
|
||||
@@ -976,6 +1008,13 @@ class PaymentTransaction(models.Model):
|
||||
)
|
||||
return
|
||||
|
||||
if invoice and invoice.is_move_sent:
|
||||
_logger.debug(
|
||||
"Skipping auto-send for %s: invoice %s already sent.",
|
||||
self.reference, invoice.name,
|
||||
)
|
||||
return
|
||||
|
||||
# 1. Send the invoice PDF
|
||||
if invoice and invoice.state == 'posted':
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user