`_cashdro_payment_url()` builds the startOperation url with `this.pos.pos_session.name`. 18.0 replaced that accessor by the `session` getter of PosStore, so reading `.name` on the resulting `undefined` raises a TypeError on every single payment, before any request leaves the browser. `cashdro_send_payment_request` catches it with a bare `catch` that neither logs nor rethrows and shows the generic "error connecting to the cashdro", which is why the failure looked like a problem with the machine. `pos_payment_method_cashdro_fix` only covers the data loading, so this goes in a module of its own: it has a longer life than that temporary patch, and it supersedes its `_cashdro_url()` override. Along with the blocking one, the flow gets: * the real error logged and shown in the dialog, so a wrong host, a busy drawer and a dead network stop being the same sentence, * a missing host raised instead of the boolean `false` travelling into `fetch`, where it resolves against the Odoo server, * `finishOperation` always sent on failure, or the drawer keeps the operation open and refuses every later payment of the session, * polling on a 700ms interval with a 5 minute deadline, stopped by a cancel or by leaving the payment screen, instead of a tight unbounded loop, * `send_payment_cancel()` returning whether the drawer took it, so the line no longer sticks in `waitingCard`, * reversals sending the amount of the line negated: `get_due()` only discounts the payments already done, so the base module asked the drawer to collect the money a second time instead of dispensing it, * the `uuid` of the line honoured instead of the selected one, * the CashDro answers validated (HTTP status, JSON body, operation id). `_cashdro_amount()` is split out as the single hook for the amount, which `pos_payment_method_cashdro_rounding` uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
7.7 KiB
ReStructuredText
164 lines
7.7 KiB
ReStructuredText
==============================
|
|
POS CashDro Payment Flow Fixes
|
|
==============================
|
|
|
|
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
|
:target: https://odoo-community.org/page/development-status
|
|
:alt: Beta
|
|
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
|
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
|
:alt: License: AGPL-3
|
|
|
|
|badge1| |badge2|
|
|
|
|
``pos_payment_method_cashdro_fix`` patches the three 17.0 leftovers that the
|
|
18.0 migration of ``pos_payment_method_cashdro`` left in the *data loading*.
|
|
This module takes care of the *payment flow* itself, which stays broken after
|
|
them.
|
|
|
|
**The blocking one.** ``_cashdro_payment_url()`` builds the ``startOperation``
|
|
url with ``this.pos.pos_session.name``. 18.0 replaced that accessor by the
|
|
``session`` getter of ``PosStore``, so ``this.pos.pos_session`` is ``undefined``
|
|
and reading ``.name`` on it raises a ``TypeError`` **on every single payment,
|
|
before any request leaves the browser**.
|
|
``cashdro_send_payment_request`` catches it with a bare ``catch`` that neither
|
|
logs nor rethrows, and shows *"An error occurred while connecting to the
|
|
cashdro"* — the same message a network failure or an unplugged drawer produce.
|
|
That is why the failure looks like a communication problem with the machine,
|
|
and why it happens with any other module of ours enabled or disabled.
|
|
|
|
**The rest.** Once the payment goes through, the flow still has these problems,
|
|
all of them fixed here:
|
|
|
|
#. **Failures are invisible.** The ``catch`` has no error binding, so nothing
|
|
reaches the browser console. Every cause — a wrong host, a busy drawer, a
|
|
bad password, an API mistake — reads as the same sentence. The error is now
|
|
logged and its message shown in the dialog.
|
|
#. **A missing host is not reported.** ``_cashdro_url()`` returns the boolean
|
|
``false``, which the callers concatenate into ``"false&operation=..."``;
|
|
``fetch`` resolves that against the Odoo server, gets a 404 page back and the
|
|
operator sees a connection error. It now raises with the method name.
|
|
#. **A started operation is never closed on failure.** If the acknowledge or the
|
|
polling fails after ``startOperation`` succeeded, the CashDro keeps the
|
|
operation open and refuses the next one: every later payment of the session
|
|
fails until the drawer is power cycled. ``finishOperation`` is now always
|
|
sent.
|
|
#. **The polling has no interval, no bound and no way to stop.** The base
|
|
implementation re-asks the drawer as fast as the network allows for as long
|
|
as the customer is inserting coins, and keeps going after a cancel. It is now
|
|
a 700 ms interval with a 5 minute deadline, stopped by a cancel or by leaving
|
|
the payment screen.
|
|
#. **A cancel is reported as failed.** ``send_payment_cancel()`` resolves to
|
|
``undefined``, which ``payment_screen`` reads as "the cancel failed" and
|
|
leaves the line stuck in ``waitingCard``, re-entering the cancel on every
|
|
click. It now returns whether the drawer took it.
|
|
#. **A reversal asks the drawer to collect the money again.**
|
|
``order.get_due()`` only discounts the payments that are already done, and
|
|
the line being reversed is not one of them, so the amount comes out positive
|
|
and the operation type is 4 (payment) instead of 3 (refund). The reversal
|
|
now uses the amount of the line, negated.
|
|
#. **The uuid of the line is ignored.** The three ``PaymentInterface`` entry
|
|
points receive the uuid of the line to act on and the base module works on
|
|
the selected one instead. The per-line *Send* and *Reverse* buttons do not
|
|
select the line first, so with two payment lines on the order the CashDro
|
|
works on the wrong one.
|
|
#. **The answers are not validated.** An HTTP error page or an envelope without
|
|
``data`` are taken as valid: the module acknowledges an empty operation id
|
|
and polls it forever. Both are now checked.
|
|
|
|
**Table of contents**
|
|
|
|
.. contents::
|
|
:local:
|
|
|
|
Usage
|
|
=====
|
|
|
|
There is nothing to configure: installing the module is enough.
|
|
|
|
Before anything else, check the payment method
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
18.0 added the ``payment_method_type`` field (*Integration*) to
|
|
``pos.payment.method``, and core clears ``use_payment_terminal`` whenever it is
|
|
saved as *None required*. On **Point of Sale ‣ Configuration ‣ Payment
|
|
Methods**, the CashDro method must have:
|
|
|
|
* **Integration** = *Terminal*
|
|
* **Integrate with** = *Cashdro*
|
|
* the terminal host, user and password filled in
|
|
|
|
With *Integration* left at *None required* the terminal is not wired at all:
|
|
clicking the payment method just adds a plain cash line, and nothing is ever
|
|
sent to the drawer — no dialog, no error, no traffic.
|
|
|
|
On site
|
|
~~~~~~~
|
|
|
|
#. Open a PoS session, add a product and go to the payment screen.
|
|
#. Select the CashDro payment method. The payment line must show **0**, not the
|
|
due amount (that one comes from ``pos_payment_method_cashdro_fix``).
|
|
#. The drawer must start the operation and ask for the amount.
|
|
|
|
If it still fails, the dialog now carries the reason instead of the generic
|
|
sentence, and the browser console has the full error under ``[CASHDRO]``. What
|
|
to expect:
|
|
|
|
``The CashDro payment method '...' has no terminal host configured.``
|
|
The credentials did not reach the front end. Either the fields are empty on
|
|
the payment method, or ``pos_payment_method_cashdro_fix`` is not installed.
|
|
|
|
``TypeError: Failed to fetch`` / ``NetworkError``
|
|
The browser cannot reach ``cashdro_host``. Check the host and that the till
|
|
is on the same network as the drawer. A PoS served over HTTPS also blocks a
|
|
plain ``http://`` drawer as mixed content, which shows up here.
|
|
|
|
``The CashDro answered 401/403/500 ...``
|
|
The drawer is reachable and answering: wrong user or password, or the
|
|
CashDro3WS service is failing.
|
|
|
|
``The CashDro did not return an operation id: ...``
|
|
The drawer refused ``startOperation``. Its own envelope is in the message —
|
|
usually a busy drawer with an operation still open from a previous failure.
|
|
|
|
``The CashDro did not finish the operation in time.``
|
|
``startOperation`` was accepted but the drawer never reached the finished
|
|
state within five minutes.
|
|
|
|
There are no automated tests: the flow needs a CashDro terminal answering on the
|
|
network, which is also why none of this is caught by the OCA CI.
|
|
|
|
Known issues / Roadmap
|
|
======================
|
|
|
|
Split from ``pos_payment_method_cashdro_fix`` on purpose: that module is a
|
|
temporary patch for three migration leftovers, meant to disappear as soon as
|
|
its ``upstream/*.patch`` is merged in ``OCA/pos``. This one has a longer life —
|
|
only the ``pos_session`` fix is a migration leftover, the rest are problems the
|
|
module has carried since 17.0 — so it should not go away with it.
|
|
|
|
It supersedes the ``_cashdro_url()`` patch of ``pos_payment_method_cashdro_fix``
|
|
(same method, resolved from the terminal instead of the selected line, raising
|
|
instead of returning ``false``). Once the upstream patch is merged, the
|
|
dependency has to be moved to ``pos_payment_method_cashdro`` and the
|
|
``_load_pos_data_fields`` override checked, as it is the only part of the other
|
|
module this one really needs.
|
|
|
|
Still open:
|
|
|
|
* The success path leaves the operation open on the drawer, as upstream does.
|
|
Whether the CashDro3WS expects a ``finishOperation`` after a finished
|
|
operation could not be checked without a machine, so the behaviour was left
|
|
untouched.
|
|
* ``_cashdro_url()`` does not escape the user and the password, again as
|
|
upstream does: credentials containing ``&``, ``=`` or ``#`` break the query
|
|
string.
|
|
* The whole thing wants a tour with a mocked CashDro3WS endpoint, which would
|
|
also let the OCA CI catch this class of breakage.
|
|
|
|
Contributors
|
|
============
|
|
|
|
* `Criptomart <https://criptomart.net>`_:
|
|
|
|
* Criptomart
|