The plan flags this as the highest-risk module since the payment provider API is strict and version-sensitive. Before writing any code, read Odoo 19's own payment_custom module (its wire-transfer provider) end to end as a reference, since it's the closest first-party analog to a manual/ offline payment flow - this avoided the trial-and-error that hit the other modules and got the core logic right on the first install attempt. payment.provider gains code='interac' (via selection_add, same pattern payment_custom uses for 'custom') plus interac_recipient_email (required_if_provider='interac' - Odoo only enforces this when the provider's state is enabled/test, so the module ships a disabled, unconfigured provider record and the deployment layer configures + enables it, keeping client specifics out of product code) and a configurable interac_deadline_hours. Flow: selecting Interac at checkout calls _apply_updates, which sets the transaction 'pending' and emails instructions (recipient address, amount, reference, deadline) via a mail.template - no dynamic per-transaction data needs to live in the static provider-level pending_msg field, since the reference/amount are already shown on Odoo's generic payment status page. A "Pending Interac Payments" admin list (Interac Payment Verifier group) has a one-click "Payment Received" button calling action_confirm_interac_ payment (-> _set_done, which triggers Odoo's normal order/invoice reconciliation - no need to reimplement that). An hourly cron cancels unconfirmed pending transactions past the deadline and emails a cancellation notice. Verified against a live Odoo 19 + Postgres 16 container: 4/4 automated tests pass, plus a full manual live run of both cycles the plan's gate asks for - drove a transaction through the actual /payment/interac/process controller to pending (confirmed the instructions email), used the treasurer action to confirm it to 'done', and separately backdated a second transaction's last_state_change and triggered the auto-cancel cron via ir.cron's method_direct_trigger, confirming both the state change to 'cancel' and the cancellation email. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
from datetime import timedelta
|
|
|
|
from odoo import fields
|
|
from odoo.tests.common import TransactionCase, tagged
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestInterac(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.provider = self.env.ref('community_interac.payment_provider_interac')
|
|
self.provider.write({
|
|
'interac_recipient_email': 'payments@example.org',
|
|
'state': 'test',
|
|
'interac_deadline_hours': 48,
|
|
})
|
|
self.method = self.env.ref('community_interac.payment_method_interac')
|
|
self.partner = self.env['res.partner'].create({'name': 'Interac Payer', 'email': 'payer@example.com'})
|
|
|
|
def _create_transaction(self):
|
|
return self.env['payment.transaction'].create({
|
|
'provider_id': self.provider.id,
|
|
'payment_method_id': self.method.id,
|
|
'amount': 100.0,
|
|
'currency_id': self.env.company.currency_id.id,
|
|
'partner_id': self.partner.id,
|
|
'reference': self.env['payment.transaction']._compute_reference('interac'),
|
|
})
|
|
|
|
def test_select_sets_pending_and_sends_instructions(self):
|
|
tx = self._create_transaction()
|
|
mail_count_before = self.env['mail.mail'].search_count([])
|
|
|
|
tx._apply_updates({})
|
|
|
|
self.assertEqual(tx.state, 'pending')
|
|
mail_count_after = self.env['mail.mail'].search_count([])
|
|
self.assertGreater(mail_count_after, mail_count_before)
|
|
|
|
def test_confirm_sets_done(self):
|
|
tx = self._create_transaction()
|
|
tx._apply_updates({})
|
|
self.assertEqual(tx.state, 'pending')
|
|
|
|
tx.action_confirm_interac_payment()
|
|
|
|
self.assertEqual(tx.state, 'done')
|
|
|
|
def test_auto_cancel_after_deadline(self):
|
|
tx = self._create_transaction()
|
|
tx._apply_updates({})
|
|
tx.last_state_change = fields.Datetime.now() - timedelta(hours=49)
|
|
mail_count_before = self.env['mail.mail'].search_count([])
|
|
|
|
self.env['payment.transaction']._cron_auto_cancel_interac()
|
|
|
|
self.assertEqual(tx.state, 'cancel')
|
|
mail_count_after = self.env['mail.mail'].search_count([])
|
|
self.assertGreater(mail_count_after, mail_count_before)
|
|
|
|
def test_not_yet_due_is_not_cancelled(self):
|
|
tx = self._create_transaction()
|
|
tx._apply_updates({})
|
|
tx.last_state_change = fields.Datetime.now() - timedelta(hours=1)
|
|
|
|
self.env['payment.transaction']._cron_auto_cancel_interac()
|
|
|
|
self.assertEqual(tx.state, 'pending')
|