I’m wondering if I’m misunderstanding the intended accounting model of this module.
Currently, when a Stripe charge is fully refunded (e.g. from the Stripe Dashboard), the original account.payment is modified: its amount becomes 0 and its state changes to failed.
From my point of view, a refund is a separate financial transaction rather than a change to the original payment. By modifying the payment, the fact that money was received and later refunded disappears from the payment history. With account_payment_clearing, the clearing move is also cancelled, so the invoice appears unpaid again.
Wouldn’t it be more appropriate to keep the original payment as succeeded and create an account.payment.stripe.refund (or equivalent) for refunds, including those initiated on Stripe?
Maybe I’m missing something about the intended design, so I’d appreciate some clarification before looking into a patch.
Stripe does not have a equivalent object to the refund. It only store the refunded amount on the payment.
So it is simpler to follow the behavior of Stripe than simulate operation based on amount modification.
Maybe I was looking at this from the wrong angle. My point is not that Tryton has to mirror Stripe’s data model, but that Stripe itself treats refunds as separate objects.
The Stripe API exposes refunds as their own resources (re_...) with their own amount, timestamp and balance transaction. They can be retrieved independently through the Refunds API; amount_refunded on the charge is only a summary value.
The question for me is what the correct accounting behaviour in Tryton should be. Once a payment has reached succeeded, it has already caused accounting effects (clearing move posted, receivable reconciled, invoice paid). If the payment amount is then changed afterwards because of a refund, the original transaction no longer represents what actually happened.
Would it make more sense to keep the successful payment unchanged and create a corresponding reversing entry for the refund? That way the original payment and the later refund remain visible as two separate business events.
Maybe I am missing something in the intended design, so I’d appreciate understanding the reasoning behind the current implementation.
If I remember correctly, it is not always the case. The refund object is to request Stripe to perform a refund. But a refund can be done by Stripe without creating such object.
The payments are not really accounting, they do not create any moves.
It is only when the statement from Stripe is booked that accounting moves are created.
The clearing moves are just temporary entries.
The only point is if the clearing move was not posted, it is deleted. But like any draft move can be.
I do not see the point. It generates just more entries.
If you really want such behavior you can make the system post directly any clearing move. But the payment processing may be a nasty process where payment state vary between succeed and failed.
The detail of a payment transaction is any way stored at the processing business like Stripe.
I do not think that having such detail in the accounting does provide any interest.
Thanks Cédric, got it — the missing piece was on my side.
Our Stripe payment journal had no clearing posting delay set, so the clearing moves stayed in draft forever and were silently deleted when a refund came in. With clearing_posting_delay set and the cron_post_clearing_moves cron active, a refund on a posted clearing move now produces a proper cancel move (plus a new clearing move for the residual amount on partial refunds) - exactly the trail we were looking for.