Skip to content

fix(payment_djomy): auto-cancel zombie draft/pending transactions on retry#1

Open
founet wants to merge 1 commit into
mainfrom
fix/payment-djomy-auto-cancel-zombie-tx
Open

fix(payment_djomy): auto-cancel zombie draft/pending transactions on retry#1
founet wants to merge 1 commit into
mainfrom
fix/payment-djomy-auto-cancel-zombie-tx

Conversation

@founet

@founet founet commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

When a customer abandons a payment (browser closed, network timeout, IPN lost on Djomy side), the previous transaction stays attached to the sale order / invoice in draft or pending state. This blocks the portal UX: the customer cannot retry from the portal because the orphan draft transaction remains visible and active.

Without manual cleanup of the draft payment.transaction and the draft account.payment linked to it, the portal keeps showing the abandoned attempt and a new Pay click can fail or chain on top of the dead one.

Fix

Override payment.transaction.create() to auto-cancel previous Djomy draft/pending transactions attached to the same sale order / invoice as the new one. Strict guards:

  • Final states (done, cancel, error) are preserved.
  • Other providers' transactions are untouched (strict provider_code='djomy' scope).
  • Stale draft account.payment records linked to a cancelled tx are also cancelled (so the invoice doesn't keep a polluting orphan draft).
  • An audit note is posted on the SO / invoice chatter — not in the transaction state_message, which is rendered on the customer portal (payment/views/payment_templates.xml) and would surface internal cleanup details to the end user.

Tests

8 TransactionCase tests in payment_djomy/tests/test_zombie_cleanup.py:

  • test_new_tx_cancels_previous_draft_on_same_so — nominal: draft superseded
  • test_new_tx_cancels_previous_pending_on_same_so — nominal: pending superseded
  • test_done_tx_is_never_cancelled — guard: final states preserved
  • test_already_cancelled_tx_is_left_alone — guard
  • test_error_tx_is_left_alone — guard
  • test_tx_on_other_so_is_not_touched — isolation by SO
  • test_non_djomy_tx_is_not_touched — isolation by provider (uses payment.payment_provider_transfer)
  • test_stale_account_payment_is_cancelledaccount.payment draft is also cancelled (search mocked to avoid full accounting setup)

Run:

odoo -d <db> -i payment_djomy --test-enable \
     --test-tags=/payment_djomy:TestDjomyZombieCleanup \
     --workers=0 --stop-after-init

Result: 8/8 green on Odoo 19.0.

Backwards compatibility

  • No new module dependency added.
  • sale_order_ids / invoice_ids are checked via _fields before use — works whether payment_sale / account_payment are installed or not.
  • Behavior triggered only at create() time; existing transactions are not affected by the module upgrade itself.

Files changed

  • payment_djomy/models/payment_transaction.pycreate() override + _djomy_cancel_stale_siblings() helper.
  • payment_djomy/tests/__init__.py — new (declares the test module).
  • payment_djomy/tests/test_zombie_cleanup.py — new (8 test cases).

…retry

When a customer abandons a payment (browser closed, network timeout, IPN
lost on Djomy side), the previous transaction stays attached to the sale
order / invoice in `draft` or `pending` state. This blocks the portal UX:
the customer cannot retry from the portal because the orphan draft
transaction remains visible and active.

This change overrides `payment.transaction.create()` to auto-cancel
previous Djomy `draft`/`pending` transactions attached to the same SO /
invoice when a new one is created. Strict guards:

  - Final states (done, cancel, error) are preserved.
  - Other providers' transactions are untouched (provider_code='djomy'
    scope is enforced).
  - Stale draft `account.payment` records linked to the cancelled tx are
    also cancelled (so the invoice doesn't keep a polluting orphan draft).

An audit note is posted on the sale order / invoice chatter — not in the
transaction `state_message`, which would be rendered on the customer
portal and surface internal cleanup details to the end user.

8 TransactionCase tests cover the nominal cases, the guards (done /
cancel / error / other provider / other SO untouched), and the
account.payment cleanup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant