Skip to content

feat(eet): report the cash received, reverse refunds and report from the settling device - #102

Merged
pepakriz merged 9 commits into
finitoapp:mainfrom
i-am-fatik:feat/eet-refunds
Oct 1, 2026
Merged

pepakriz merged 9 commits into
finitoapp:mainfrom
i-am-fatik:feat/eet-refunds

Conversation

@i-am-fatik

@i-am-fatik i-am-fatik commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follows #99 and closes known gaps 1 (for refunds), 3 (for cash) and 5 of docs/eet.md.

  • Cash received. The cash tab asks what the customer handed over, prefilled with the charge rounded to the whole crown, and refuses less. The sale reports what was received (78.90 charged, 79.00 reported, or 80.00 with the change left), because EET wants the money actually taken. The payment, its claim and the cash register keep the charge, so no coverage moves. The payment detail shows the received amount when it differs.
  • Refunds. A paid payment can be refunded from its detail: an amount, or chosen items while it is the only payment claimed for its bill. The money leaves the cash register or is marked as returned outside Payky. A refund is its own record, not a negative payment, so the payment stays paid and its bill stays closed and covered. The payment detail, the bill's payment rows and the history show partly or fully refunded.
  • Storno. EET 2.0 has no storno message, so the device that recorded a refund reports a new sale with the negative amount, the time of the refund and a new sequence number, with no link to the original. The amount is capped at what the sale reported, so a refunded tip that belongs to employees is never reversed. A reversal waits for its sale's POK, then is delivered, retried and flagged overdue like a sale. Unconfirmed reversals are listed in the EET settings. Reversals live in their own table because an older build would deliver a reversal row in the sale table as a positive sale.
  • Settling device. A sale is reported by the device that recorded the payment's settlement, with its cash-register identifier: the device where staff confirmed cash or card, or matched a transfer by hand, and the device that created the payment when Payky matched it on its own. A reversal belongs to the device that recorded the refund. The payment screen now passes its device to every settlement, which it never did. After 10 minutes any other device of the account creates a missing record and delivers an unconfirmed one, still with the recording device's id_pokl, so EET sees a resend of the same sale. Retry on another device waits the same 10 minutes unless the record already has an attempt.
  • First sending. prvni_zaslani is true only for the recording device's first attempt within 5 minutes. An attempt is written before its message leaves, so an app closed mid-request makes the next attempt a repeat and keeps Retry from rewriting the EIČ of a sale EET may hold.
  • Job fixes. An online event that arrived while an attempt was in flight was lost, and the sale waited out its backoff. It now retries at once. The sandbox test in eet-reporting-job.test.ts failed 3 of 16 parallel runs before its fix, because its 10 ms retry could land before the poll saw the first request. It now retries after 250 ms.
  • Docs. docs/eet.md and docs/bill-payment-states.md describe the received amount, refunds and reversals.

Test plan

  • bun run check passes on every commit (915 tests on the last). With both job fixes, eet-reporting-job.test.ts passed 8 of 8 parallel runs, and so did the new takeover tests.
  • e2e/eet.spec.ts against the fake responder: 78.90 CZK in cash confirmed as prefilled reports 79.00, raised to 80 reports 80.00, and refunding one beer of a paid bill and then the rest sends -50.00 and -200.00 while the bill stays closed.
  • The full e2e suite passes 130 of 130 with --workers=2 on the takeover, and the 62 tests through the payment screen pass after the settlement-device fix. A new e2e test seeds another device's sale: 3 minutes old it waits without Retry, 11 minutes old it is sent from here as a repeat.
  • Against the real EET playground with the official test certificate, through a test that forwards the browser's requests: 79.00 for 78.90 in cash, 80.00 with the change left, 250.00 for a bill then -50.00 and -200.00 for its refunds, and a resend after a failed attempt marked as repeated. Every message got a POK and no warning.
  • Two real devices syncing through the relay, with the priority cut to 60 s for the run: a payment created on A and paid in cash on B is sent by B with B's id_pokl as the first sending, and a refund recorded on B is reversed by B. A sale and a reversal A could not send are taken over by B 60.1 s after the sale or refund, marked as repeated, with A's id_pokl, and confirmed.
  • Rendered in Czech on the dev server: the cash tab, the refund dialog with an amount and with items, a confirmed and a pending reversal, and a sale waiting for its recording device.

🤖 Generated with Claude Code

An online event clears every backoff and queues a delivery pass. When it
arrived while an attempt was in flight, that attempt then failed and started
a new backoff, so the queued pass skipped the sale, and the sale waited out
the backoff although the device was online again.
The test waits for exactly one sandbox request before switching to
production, but the retry came 10 ms later. Under the full unit run the
retry could land before the poll saw the first request, so the poll saw two
and failed. A 250 ms retry keeps the retry after the switch, which is the
case the test is about.
EET records what the business actually received, not what it charged. A
78.90 CZK bill paid in cash is 79 CZK after rounding to whole crowns, or
80 CZK when the customer leaves the change, and that is the amount the
sale has to carry. Marking a payment paid in cash now shows the cash
received, prefilled with the charge rounded to the nearest crown, halves
up, and refuses anything lower. Staff can raise it and sees the
difference before confirming.

The received amount lives on the payment's cash detail, not on the cash
register transaction, so rounding never shows the bill as overpaid. It is
written in the settlement's own batch because the EET sale freezes its
amount from the first settlement it sees. A cash payment without one, as
settled by an older version, still reports its charge.
Money returned to a customer for a reported sale has to reach EET as a
new sale with a negative amount, the time of the refund and a new
message identifier, with no link to the original. Returning a reported
sale in cash has to be reported. Payky had no refund at all: a paid
payment could not be canceled and nothing deleted one, so a returned
sale stayed reported in full.

Staff can now refund a paid payment from its detail: the remainder, any
amount, or chosen items while the payment is the only one claimed for
its bill, since only then are the bill's lines its own. Refunding the
rest of a line returns the rest of its amount, so rounding leaves
nothing behind. The money leaves the cash register, as a transaction no
payment claims, or is marked as returned outside Payky. A refund is its
own record, not a negative payment, so the payment stays paid, its bill
stays closed and covered, and the detail, the bill rows and the history
show it as partly or fully refunded.

The device that recorded a refund turns it into an EET reversal. The
amount is capped at what the sale reported and earlier reversals left,
so a refunded tip that belongs to employees is never reversed. A
reversal waits for its sale's POK, then is delivered, retried and
flagged overdue like a sale. It lives in its own table because an older
version would deliver a reversal row in the sale table as a positive
sale.
@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

@i-am-fatik is attempting to deploy a commit to the Josef K's projects Team on Vercel.

A member of the Team first needs to authorize it.

@i-am-fatik
i-am-fatik marked this pull request as draft September 29, 2026 13:49
prvni_zaslani marks the first attempt to send a sale, and the tax
administrator counts any attempt without a POK. Payky recorded an
attempt only once its answer arrived, so an app closed mid-request sent
the next attempt as the first again, and Retry could still rewrite the
EIČ of a sale EET may already hold.

The attempt is now written before the message leaves. An attempt that
finds an earlier one without an answer marks the record as possibly
recorded by EET.
The payment screen never passed its device to a cash, bank or card
settlement, and neither did a card result restored after Android killed
the app, so no settlement staff made carried a device. EET reporting
needs it to know the cash register a sale happened on.
A sale belongs to the cash register where the money was received, but
Payky reported it from the device that created the payment, and only
that device ever sent it on its own. A payment created on a phone and
paid in cash at the counter reached EET with the phone's register, and a
sale whose device broke or stayed off was never delivered unless someone
tapped Retry on another device.

The recording device is now the device on the payment's first
settlement, or the device that created the payment when Payky matched
the settlement on its own. For a reversal it is the device that recorded
the refund. It creates and sends the record first. After 10 minutes any
device of the account creates a missing record and delivers an
unconfirmed one, still with the recording device's register, so EET sees
a resend of the same sale. Retry on another device waits the same 10
minutes unless the record already has an attempt.

Only the recording device's first attempt within 5 minutes is marked as
the first sending, and every takeover is a repeat. The 5 minutes before
anyone may take over absorb device clocks that disagree and a request
still in flight.
@i-am-fatik i-am-fatik changed the title feat(eet): report the cash received and reverse refunds as negative sales feat(eet): report the cash received, reverse refunds and report from the settling device Sep 29, 2026
@i-am-fatik
i-am-fatik marked this pull request as ready for review September 29, 2026 15:35
A payment's sale reported the payment amount at its first settlement,
whatever that settlement brought. Money the business keeps is a sale at
the moment it arrives, and EET records the amount actually received, so
this went wrong three ways. A payment settled twice, or by a transfer
larger than its amount, left the rest unreported, a sale nobody
reported once the business kept it. Returning that duplicate went
through the payment's refund, whose reversal was capped at the sale, so
EET ended at zero for goods that were sold. And a first settlement that
paid only part reported money that had not arrived, for good if the
rest never came.

The sale now reports what the first settlement brought, capped at the
payment amount, and cash still reports the cash received. Everything
later settlements bring beyond that, the rest of a split or money above
the amount, is reported as an extra money sale when it arrives, with
the method, time and recording device of the claim that brought it.
Each increase adds one more extra money sale keyed by the extra money
already reported, so a device that missed a claim reports the rest once
it syncs. Extra money brought before EET was enabled is never reported,
as for sales.

A reversal is capped at everything the payment reported, waits while
extra money is still unreported, and is sent only once every sale of
its payment is confirmed. Returning the duplicate leaves the real sale
reported.

Holding the extra money back until staff decides was rejected: it is
lawful only if a mistaken payment is not a sale, which nothing we hold
confirms, while reporting on arrival and reversing on return is lawful
either way.
A refund was capped at the payment amount, or the cash received, so
money a payment received above it, a second settlement or a transfer
larger than the payment, could not be returned through Payky, and its
extra money sale could never be reversed.

The limit now adds what the payment's claims brought beyond its amount,
and when there is such money the refund dialog offers it first, as it
is what the business did not charge for. The payment detail, the
payment history and the bill's payment rows measure the refund state
against the same limit, so returning only the duplicate shows the
payment partly refunded while its real sale stands, and returning
everything leaves nothing reported in EET.
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
payky Ready Ready Preview Oct 1, 2026 10:14am UTC

@pepakriz
pepakriz merged commit 84cf37b into finitoapp:main Oct 1, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
Preview — bd0227b7 Deployed Oct 1, 2026 by vercel[bot]
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.

2 participants