Skip to content

feat(billing): payment method correction - #1394

Merged
khaira777 merged 15 commits into
mainfrom
fm/flocafe-1337-payment-method-correction
Oct 29, 2026
Merged

khaira777 merged 15 commits into
mainfrom
fm/flocafe-1337-payment-method-correction

Conversation

@khaira777

@khaira777 khaira777 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1337

Intent

Implement FloCafe issue #1337 (feat(billing): correct the payment method on a paid bill while its shift is open), stacked on top of #1338 (origin/fm/flocafe-1338-pin-quick-switch):

  • Allow correcting the payment method on a recorded payment line of a paid bill while its cash shift is still open, authorized by an active owner or manager with their Staff Approval PIN (same approver selection as refunds and overrides; a cashier's PIN must never authorize this).
  • Once the payment's cash shift is closed (or the business day is closed), refuse the correction with HTTP 409 and a clear reason.
  • Never change the payment amount: only change the payment method (and payment_method_id / card transaction_id) on one payment line (PATCH /api/bills/:id/payments/:index/method).
  • Refuse on refunded bills/lines, split siblings already settled elsewhere (e.g., settled in a closed shift/day or refunded), and unknown or inactive payment methods.
  • Audit every change (old_method, new_method, actor, approver, reason, timestamp), make replays of the same request idempotent, ensure open-shift expected cash and payment-method totals recalculate automatically, ensure receipt reprint shows the corrected method, add UI in OrderDetailPanel.tsx with approver picker and PIN prompt, update docs/reference/roles-and-permissions.md and docs/reference/api.md, and add tests/payment-method-correction.test.ts.

Firstmate spec

  • Note: PATCH /api/bills/:id/payments/:index/method is restricted to owner and manager callers (ROLE_ACCESS.ownerManager) and requires an active or historical cash session on the original payment (sessionless or untagged payments without a verified original cash_session_id are refused with HTTP 409).

What Changed

  • Add PATCH /api/bills/:id/payments/:index/method in main/routes/bills.ts (and share tenantTimezone from main/services/cash-drawer-movements.ts) so active owner and manager callers with payments.take and a verified owner/manager Staff Approval PIN can change a paid bill line's non-wallet payment method and optional transaction_id without altering the amount, while enforcing open-shift, open-business-day, refund, and split-sibling checks, updating payment_transaction_refs, recording bill.payment_method.corrected audit entries, and handling idempotent replays.
  • Add CorrectPaymentMethodModal and wire it into OrderDetailPanel and OrdersPage with translations across all 24 locale message files so eligible owners and managers can select a paid bill and payment line, pick the replacement method and optional reference or reason, and submit with an approver selection and Staff Approval PIN.
  • Add an onRateLimited hook to authRateLimit in main/middleware/security.ts to audit rate-limited pin_login requests in main/routes/auth.ts, update the architecture and API reference docs, and add coverage in tests/payment-method-correction.test.ts.

Risk Assessment

✅ Low: The payment-method correction endpoint, UI modal, shift/day/refund/split-sibling guards, owner/manager role and PIN authorization, idempotency, audit logging, documentation, and behavioral test suite are well-bounded and satisfy all stated acceptance criteria without introducing regressions.

Testing

Built the backend and static desktop frontend, executed the targeted integration suites (test:payment-method-correction, test:pin-quick-switch, and test:script-coverage), ran the Playwright pin-quick-switch.spec.ts browser suite, and drove the live Orders UI and PATCH /api/bills/:id/payments/:index/method endpoint in Chromium across open-shift Cash-to-Card correction, shift/X-report recalculation, receipt reprint, idempotent replay, role/PIN authorization boundaries, closed-shift/closed-day refusals, and immutable-amount/refund/split-sibling guards, capturing five screenshots and a structured JSON evidence trace.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Owner or manager corrects a paid bill's payment method (Cash <-> Card and active custom tender) while its cash shift is open using a Staff Approval PIN, updating OrderDetailPanel, receipt reprint data… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/02-correct-payment-method-modal.png, ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/03-manager-order-deta…
Adversarial: Non-owner/manager callers (cashier, server, chef) have no Correct Payment Method UI action and are rejected with HTTP 403 on PATCH /api/bills/:id/payments/:index/method even with a valid… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/04-cashier-order-detail-no-correct-button.png, ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/payment-met…
Adversarial: Correcting a payment recorded without an open shift (sessionless/untagged), after its cash shift is closed, or after the store business day is closed is refused with HTTP 409 (shift_close… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/05-closed-shift-correction-refused-modal.png, ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/payment-meth…
Adversarial: Attempting to mutate the payment amount (400 amount_immutable), or correcting a refunded bill/line (409 bill_refunded), a split check whose sibling was settled in a closed shift or refund… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/payment-method-correction-live-evidence.json, npm run test:payment-method-correction

Manager Orders detail panel before payment method correction (Cash paid bill on open shift)
CorrectPaymentMethodModal showing immutable amount, Card selection, transaction reference, reason, approver picker, and Staff Approval PIN
Manager Orders detail panel after Cash -> Card correction showing updated Card transaction reference
Cashier Orders detail panel confirming Correct Payment Method button is hidden for non-owner/manager roles
Closed-shift payment method correction refused in live UI with HTTP 409 shift_closed toast

Evidence: Live API, shift expected cash, X-report, audit log, idempotency, and adversarial check trace
{
  "orderId": 2,
  "orderNumber": "ORD-20261029-0002",
  "billId": 2,
  "shiftBefore": {
    "sessionId": 1,
    "expectedCashCents": 11420
  },
  "shiftAfter": {
    "sessionId": 1,
    "expectedCashCents": 5000
  },
  "xReportBefore": {
    "expectedCashCents": 6420,
    "paymentMethods": [
      {
        "method": "cash",
        "count": 1,
        "total": 64.2
      },
      {
        "method": "card",
        "count": 1,
        "total": 64.2
      }
    ]
  },
  "xReportAfter": {
    "expectedCashCents": 0,
    "paymentMethods": [
      {
        "method": "card",
        "count": 2,
        "total": 128.4
      }
    ]
  },
  "correctedPaymentLine": {
    "method": "card",
    "cash_session_id": 1,
    "amount": 64.2,
    "requested_amount": 64.2,
    "amount_omitted": true,
    "timestamp": "2026-10-29 00:11:05",
    "transaction_id": "TX-LIVE-1337-CARD"
  },
  "auditEntry": {
    "id": 1,
    "actor_user_id": "e2e-manager",
    "action": "bill.payment_method.corrected",
    "bill_id": 2,
    "bill_number": "INV-20261029-0002",
    "order_id": 2,
    "payment_index": 0,
    "amount": 64.2,
    "old_method": "cash",
    "old_payment_method_id": null,
    "old_transaction_id": null,
    "new_method": "card",
    "new_payment_method_id": null,
    "new_transaction_id": "TX-LIVE-1337-CARD",
    "actor_id": "e2e-manager",
    "approver_id": "e2e-manager",
    "approved_by": "e2e-manager",
    "reason": "Customer tapped card at terminal",
    "cash_session_id": 1,
    "idempotency_key": "10762055-d20a-402a-8391-ed524b7d84a0",
    "timestamp": "2026-10-29 00:11:08",
    "created_at": "2026-10-29 00:11:08"
  },
  "idempotentReplay": {
    "status": 200,
    "idempotentReplay": true,
    "auditId": 1
  },
  "adversarialChecks": {
    "cashierCallerWithManagerPin": {
      "status": 403,
      "reason": "insufficient_role_permissions"
    },
    "managerCallerWithCashierPin": {
      "status": 403,
      "reason": "invalid_pin"
    },
    "amountMutationAttempt": {
      "status": 400,
      "reason": "amount_immutable"
    },
    "sessionlessPaymentAttempt": {
      "status": 409,
      "reason": "shift_closed"
    },
    "closedShiftAttempt": {
      "status": 409,
      "reason": "shift_closed"
    }
  }
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Owner or manager corrects a paid bill's payment method (Cash <-> Card and active custom tender) while its cash shift is open using a Staff Approval PIN, updating OrderDetailPanel, receipt reprint data… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/02-correct-payment-method-modal.png, ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/03-manager-order-deta…
Adversarial: Non-owner/manager callers (cashier, server, chef) have no Correct Payment Method UI action and are rejected with HTTP 403 on PATCH /api/bills/:id/payments/:index/method even with a valid… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/04-cashier-order-detail-no-correct-button.png, ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/payment-met…
Adversarial: Correcting a payment recorded without an open shift (sessionless/untagged), after its cash shift is closed, or after the store business day is closed is refused with HTTP 409 (shift_close… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/05-closed-shift-correction-refused-modal.png, ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/payment-meth…
Adversarial: Attempting to mutate the payment amount (400 amount_immutable), or correcting a refunded bill/line (409 bill_refunded), a split check whose sibling was settled in a closed shift or refund… ✅ pass live ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48/payment-method-correction-live-evidence.json, npm run test:payment-method-correction
  • npm run test:payment-method-correction
  • npm run test:pin-quick-switch
  • npm run test:script-coverage
  • npm run build && npm run build:frontend
  • cd frontend && npx playwright test --config=playwright.config.ts --project=chromium e2e/pin-quick-switch.spec.ts
  • Live Playwright Chromium session driving OrderDetailPanel and CorrectPaymentMethodModal against tests/e2e-server.cjs with screenshot and JSON evidence capture in ~/.no-mistakes/evidence/01M62F362WHNB6SAXD0S7PPZ48
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Allow 4-6 digit PINs for all staff roles to support tile-based PIN quick user switching on the lock/login screen while keeping all approval paths strictly restricted to active owner/manager PINs.

Fixes #1338
- Use non-blocking per-request rate limit on POST /api/auth/pin-login while preserving per-user lockout
- Serialize per-user PIN verification with withPinVerificationLock and audit missing credential requests
- Re-read bill/order state and compute charge breakdowns inside the write transaction on PATCH /api/bills/:id/charges
- Guard GET /api/settings/auto_lock_minutes with requireAnyPermission across staff roles
- Pass explicit Bearer token to POST /auth/logout before clearing storage on lock/logout
- Exclude autoLockMinutes from localStorage persistence, reset on session changes, and guard stale save rollbacks
- Update kitchenDeliveryOverrideInvalid across all 24 locales to use Staff Approval PIN wording
…c.ts` line 270 to assert `page.getByText('Invalid Staff Approval PIN')` instead of `'Invalid manager PIN'`, matching the updated `pos.kitchenDeliveryOverrideInvalid` translation (`"Invalid Staff Approval PIN. Please try again."`) rendered by `PaymentModal.tsx` when `POST /api/bills/:id/payments` returns 403 `Invalid manager PIN`. The downstream `End-to-End Playwright Tests` and `Required Checks Gate` CI failures were caused by this Shard 1/4 E2E failure. Verification run and passed: - `npm run lint` - `npm run build && npm run build:frontend` - `npm run i18n:check` - `cd frontend && npx playwright test --config=playwright.config.ts --project=chromium e2e/approval-manager-selection.spec.ts e2e/pin-quick-switch.spec.ts` (4 passed) - `cd frontend && npx playwright test --config=playwright.config.ts --project=chromium e2e/kitchen-delivery-payment-retry.spec.ts e2e/kitchen/kitchen-delivery-billing-gate.spec.ts` (5 passed) - `node scripts/ci/check-changed-format.cjs --base 1278f91` - `git diff --check`
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Correct paid-bill payment methods with approval and PIN quick switching

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Correct individual paid-bill payment methods with owner or manager approval while the shift
 remains open.
• Add staff PIN quick switching and terminal locking without carrying privileged cart adjustments
 across sessions.
• Cover authorization, reconciliation, receipts, and session behavior with tests and updated
 references.
Diagram

graph TD
  UI["Order details"] --> API["Correction API"] --> PIN["Staff approval"] --> Gate{"Shift eligible?"} --> Ledger["Bill payments"] --> Outputs["Reports and receipts"]
  Login["PIN sign-in"] --> Session["Terminal lock"]
  Login --> PIN
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Append compensating payment events
  • ➕ Preserves the original tender entry as an immutable accounting record.
  • ➕ Makes successive corrections explicit in the payment ledger.
  • ➖ Requires broader changes to shift totals, reporting, receipts, and existing payment consumers.
  • ➖ Adds reconciliation complexity for a narrowly scoped correction.

Recommendation: Use the PR’s in-place correction with a separate audit entry for the current payment-details model: existing shift reports and receipt rendering can consume the corrected method without a ledger-wide redesign. Consider append-only payment events as part of a broader accounting migration.

Files changed (61) +5361 / -427

Enhancement (18) +2399 / -148
layout.tsxApply tenant idle auto-lock across the dashboard +58/-0

Apply tenant idle auto-lock across the dashboard

• Loads the auto-lock timeout for the active session and locks the terminal after configured inactivity.

frontend/src/app/(dashboard)/layout.tsx

page.tsxConnect order actions to payment correction +23/-0

Connect order actions to payment correction

• Opens the correction modal for authorized staff and refreshes orders after a successful change.

frontend/src/app/(dashboard)/orders/page.tsx

page.tsxClear privileged checkout UI on user changes +13/-0

Clear privileged checkout UI on user changes

• Resets cached override PIN and open checkout, drawer, and shift dialogs when the active user changes.

frontend/src/app/(dashboard)/pos/page.tsx

page.tsxExpose PIN configuration for every staff role +25/-28

Expose PIN configuration for every staff role

• Keeps the PIN field available when creating or editing operational staff, enabling quick sign-in without granting approval authority.

frontend/src/app/(dashboard)/staff/page.tsx

page.tsxAdd staff tile picker and PIN keypad +297/-30

Add staff tile picker and PIN keypad

• Lists PIN-enabled users and supports selected-user PIN sign-in alongside the email/password form, including lockout feedback.

frontend/src/app/auth/login/page.tsx

MenuActionHandler.tsxHandle desktop terminal-lock actions +5/-0

Handle desktop terminal-lock actions

• Routes lock menu commands to the auth store’s terminal-lock action.

frontend/src/components/layout/MenuActionHandler.tsx

Sidebar.tsxAdd sidebar terminal-lock control +14/-1

Add sidebar terminal-lock control

• Exposes a localized lock action in the account menu.

frontend/src/components/layout/Sidebar.tsx

CorrectPaymentMethodModal.tsxAdd approved payment-line correction form +361/-0

Add approved payment-line correction form

• Lets staff choose a bill, payment line, active method, optional transaction reference and reason, then submit an approver PIN with an idempotency key.

frontend/src/components/orders/CorrectPaymentMethodModal.tsx

OrderDetailPanel.tsxExpose correction actions on eligible paid bills +29/-0

Expose correction actions on eligible paid bills

• Adds menu and button triggers when paid bills contain a non-wallet payment line and the caller has the supplied correction action.

frontend/src/components/orders/OrderDetailPanel.tsx

PosTopbar.tsxAdd POS topbar lock button +14/-0

Add POS topbar lock button

• Provides a direct terminal-lock action from the POS topbar.

frontend/src/components/pos/PosTopbar.tsx

auth.tsManage PIN sessions and terminal locking +123/-13

Manage PIN sessions and terminal locking

• Adds PIN login, token-revoking lock, session-boundary notifications, and guards against stale auth refreshes. Resets backend-synced auto-lock state when sessions change.

frontend/src/store/auth.ts

cart.tsRemove privileged cart adjustments at session boundaries +33/-0

Remove privileged cart adjustments at session boundaries

• Preserves unfinished items while clearing price overrides and waived charges when users log in, switch, lock, or log out.

frontend/src/store/cart.ts

session-events.tsIntroduce frontend session-boundary notifications +22/-0

Introduce frontend session-boundary notifications

• Provides a listener mechanism so cart and other transient state can respond to authentication transitions.

frontend/src/store/session-events.ts

db.tsPersist PIN authentication audit events +45/-0

Persist PIN authentication audit events

• Adds schema migration 119 for auth_audit_log and a helper to record PIN login success, failure, and rate-limit outcomes.

main/db.ts

security.tsSupport successful-request skipping in auth rate limiting +2/-1

Support successful-request skipping in auth rate limiting

• Makes successful-request skipping configurable for the authentication endpoint rate limiter.

main/middleware/security.ts

auth.tsAdd selected-user PIN authentication routes +223/-1

Add selected-user PIN authentication routes

• Lists active PIN-enabled users and signs in a selected user with serialized verification, per-user lockout, authentication audit, and a bearer token.

main/routes/auth.ts

bills.tsCorrect audited paid-bill tenders under settlement guards +1105/-57

Correct audited paid-bill tenders under settlement guards

• Adds the approval-protected payment-line correction route with shift, day, refund, sibling, method, transaction-reference, and idempotency checks. Also hardens existing approval paths and recalculates charge updates from state reread inside their write transaction.

main/routes/bills.ts

staff.tsPermit sign-in PINs for operational staff +7/-17

Permit sign-in PINs for operational staff

• Removes the owner/manager-only PIN assignment restriction and preserves a staff member’s PIN when their role changes.

main/routes/staff.ts

Bug fix (3) +49 / -19
CartPanel.tsxScope price-override dialog to its user +15/-5

Scope price-override dialog to its user

• Prevents an override dialog opened by one user from remaining actionable after a user switch.

frontend/src/components/pos/CartPanel.tsx

api.tsIgnore stale unauthorized responses after switching +7/-0

Ignore stale unauthorized responses after switching

• Avoids clearing a new session in response to a 401 from a request carrying a previous user’s token.

frontend/src/lib/api.ts

orders.tsRestrict order override PIN candidates +27/-14

Restrict order override PIN candidates

• Filters order discount, status, and item-void approvers to active owners or managers and tightens item-void approver identifier handling.

main/routes/orders.ts

Tests (8) +2248 / -73
approval-manager-selection.spec.tsAlign approval error expectation +1/-1

Align approval error expectation

• Updates the visible error assertion to use Staff Approval PIN terminology.

frontend/e2e/approval-manager-selection.spec.ts

database-export-import.spec.tsUpdate export schema fixture to version 119 +1/-1

Update export schema fixture to version 119

• Adjusts the database export/import test fixture for the authentication audit migration.

frontend/e2e/database-export-import.spec.ts

pin-quick-switch.spec.tsExercise lock-to-cashier checkout +171/-0

Exercise lock-to-cashier checkout

• Adds a browser flow covering manual POS lock, PIN tile sign-in, retained cart contents, and cashier payment.

frontend/e2e/pin-quick-switch.spec.ts

package.jsonRegister PIN and correction test commands +4/-2

Register PIN and correction test commands

• Adds standalone test scripts and includes them in the authentication and payment integration suites.

package.json

backup-restore-production.test.tsExpect recovery through schema version 119 +2/-2

Expect recovery through schema version 119

• Updates the backup recovery assertion for the authentication audit migration.

tests/backup-restore-production.test.ts

payment-method-correction.test.tsCover payment correction and reconciliation +886/-0

Cover payment correction and reconciliation

• Tests approval restrictions, immutable amounts, replay and audit behavior, corrected reports and receipts, custom methods, refunds, split siblings, and closed shifts and days.

tests/payment-method-correction.test.ts

pin-quick-switch.test.tsCover PIN switching and approval isolation +927/-0

Cover PIN switching and approval isolation

• Exercises selected-user sign-in, concurrent lockout and audit behavior, rejection of cashier approval PINs, auto-lock settings, and frontend lock/session state.

tests/pin-quick-switch.test.ts

staff-authz.test.tsAssert operational staff PIN policy +256/-67

Assert operational staff PIN policy

• Revises authorization expectations to permit operational-role PINs and preserve PINs on demotion while excluding those users from approver listings.

tests/staff-authz.test.ts

Documentation (5) +65 / -16
authentication-and-authorization.mdExplain PIN switching and approval boundaries +38/-6

Explain PIN switching and approval boundaries

• Documents selected-user PIN login, audit and lock behavior, and the distinction between staff sign-in PINs and owner/manager approval PINs.

docs/architecture/authentication-and-authorization.md

frontend.mdDocument session and auto-lock store state +2/-2

Document session and auto-lock store state

• Adds locked-session state and clarifies that the backend-synced auto-lock setting is excluded from local persistence.

docs/architecture/frontend.md

order-lifecycle.mdDescribe paid-payment method correction +3/-1

Describe paid-payment method correction

• Links the correction endpoint to the payment lifecycle and states its open-shift, open-day, and amount-preservation boundaries.

docs/architecture/order-lifecycle.md

api.mdReference correction, PIN login, and auto-lock APIs +9/-6

Reference correction, PIN login, and auto-lock APIs

• Documents the new routes, approval requirements, response and rejection codes, and updated charge-waiver contract.

docs/reference/api.md

roles-and-permissions.mdClarify payment correction and staff PIN permissions +13/-1

Clarify payment correction and staff PIN permissions

• Explains the payments.take plus owner/manager approval requirement and distinguishes quick-sign-in PIN eligibility from approval authority.

docs/reference/roles-and-permissions.md

Other (27) +600 / -171
page.tsxAdd configurable idle auto-lock setting +40/-1

Add configurable idle auto-lock setting

• Adds an administrator-controlled timeout selector with an optimistic save and guarded rollback.

frontend/src/app/(dashboard)/settings/page.tsx

ar.jsonLocalize new workflows in Arabic +20/-7

Localize new workflows in Arabic

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/ar.json

bn.jsonLocalize new workflows in Bengali +20/-7

Localize new workflows in Bengali

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/bn.json

de.jsonLocalize new workflows in German +20/-7

Localize new workflows in German

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/de.json

en.jsonAdd English labels for correction and switching +20/-7

Add English labels for correction and switching

• Introduces payment correction, PIN picker, lock, and auto-lock copy and updates approval PIN language.

frontend/src/lib/i18n/messages/en.json

es.jsonLocalize new workflows in Spanish +20/-7

Localize new workflows in Spanish

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/es.json

fa.jsonLocalize new workflows in Persian +20/-7

Localize new workflows in Persian

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/fa.json

fil.jsonLocalize new workflows in Filipino +20/-7

Localize new workflows in Filipino

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/fil.json

fr.jsonLocalize new workflows in French +20/-7

Localize new workflows in French

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/fr.json

hi.jsonLocalize new workflows in Hindi +20/-7

Localize new workflows in Hindi

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/hi.json

id.jsonLocalize new workflows in Indonesian +20/-7

Localize new workflows in Indonesian

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/id.json

it.jsonLocalize new workflows in Italian +20/-7

Localize new workflows in Italian

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/it.json

ja.jsonLocalize new workflows in Japanese +20/-7

Localize new workflows in Japanese

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/ja.json

ko.jsonLocalize new workflows in Korean +20/-7

Localize new workflows in Korean

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/ko.json

ne.jsonLocalize new workflows in Nepali +20/-7

Localize new workflows in Nepali

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/ne.json

nl.jsonLocalize new workflows in Dutch +20/-7

Localize new workflows in Dutch

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/nl.json

pt.jsonLocalize new workflows in Portuguese +20/-7

Localize new workflows in Portuguese

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/pt.json

ru.jsonLocalize new workflows in Russian +20/-7

Localize new workflows in Russian

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/ru.json

sq.jsonLocalize new workflows in Albanian +20/-7

Localize new workflows in Albanian

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/sq.json

th.jsonLocalize new workflows in Thai +20/-7

Localize new workflows in Thai

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/th.json

tr.jsonLocalize new workflows in Turkish +20/-7

Localize new workflows in Turkish

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/tr.json

ur.jsonLocalize new workflows in Urdu +20/-7

Localize new workflows in Urdu

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/ur.json

vi.jsonLocalize new workflows in Vietnamese +20/-7

Localize new workflows in Vietnamese

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/vi.json

zh-tw.jsonLocalize new workflows in Traditional Chinese +20/-7

Localize new workflows in Traditional Chinese

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/zh-tw.json

zh.jsonLocalize new workflows in Simplified Chinese +20/-7

Localize new workflows in Simplified Chinese

• Adds payment correction, PIN switching, lock, and auto-lock strings and revises approval PIN wording.

frontend/src/lib/i18n/messages/zh.json

pos-settings.tsTrack backend-synced auto-lock timeout +24/-2

Track backend-synced auto-lock timeout

• Adds normalized auto-lock state and hydration while excluding that tenant setting from browser persistence.

frontend/src/store/pos-settings.ts

settings.tsExpose validated terminal auto-lock setting +56/-0

Expose validated terminal auto-lock setting

• Adds an off-by-default idle timeout, an authenticated staff-readable setting endpoint, and validated administrator writes for supported durations.

main/routes/settings.ts

@khaira777 khaira777 changed the title feat: correct paid bill payment method while shift is open feat(billing): correct paid bill payment method Oct 28, 2026
@qodo-code-review

qodo-code-review Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Blocked PIN attempts leave no audit ✓ Resolved
Description
The POST /api/auth/pin-login route applies authRateLimit before its handler, so rejected
requests never call recordAuthAudit. When the endpoint limiter returns 429, the PIN sign-in
attempt is absent from the authentication audit trail even though failed and rate-limited attempts
handled inside the route are recorded.
Code

main/routes/auth.ts[R358-361]

+router.post(
+  '/pin-login',
+  authRateLimit({ max: 600, skipSuccessfulRequests: true }),
+  asyncHandler(async (req: Request, res: Response) => {
Relevance

●●● Strong

Rate-limited authentication attempts should remain auditable, matching the repository’s recent
emphasis on failure observability.

PR-#815
PR-#489

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed route installs authRateLimit before the asynchronous handler, while the handler
contains the audit calls for malformed, failed, internally rate-limited, and successful attempts.
The rate limiter rejects requests before handler execution, so endpoint-level 429 responses cannot
reach any of those audit calls.

Rate-limit and audit PIN sign-ins
main/routes/auth.ts[358-361]
main/routes/auth.ts[403-421]
main/middleware/security.ts[121-131]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Requests rejected by the endpoint-level PIN login rate limiter return before `recordAuthAudit` runs, leaving those sign-in attempts unaudited.

## Fix Focus Areas
- main/routes/auth.ts[358-361]
- main/middleware/security.ts[121-131]

## Recommended Fix
Configure the PIN-login rate limiter with a handler or wrapper that records a `pin_login` audit event with a `rate_limited` outcome before returning `429`, while preserving the existing per-user lockout auditing inside the route handler.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Card corrections submit without required IDs ✓ Resolved
Description
canSubmit does not check whether a card transaction ID is required, and the payload omits an empty
ID. When card_transaction_id_required is enabled, the modal permits Save without that value, but
the correction endpoint rejects the request after submission.
Code

frontend/src/components/orders/CorrectPaymentMethodModal.tsx[R106-109]

+  const canSubmit =
+    Boolean(effectiveBill) &&
+    Boolean(currentLine) &&
+    !submitting &&
Relevance

●●● Strong

Required card transaction IDs are a deterministic submission validation gap in a payment correction
modal.

PR-#733
PR-#1247

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The modal neither loads the setting nor checks the ID before enabling submission. It omits an empty
ID, while the endpoint explicitly rejects that combination when the setting is enabled.

frontend/src/components/orders/CorrectPaymentMethodModal.tsx[71-113]
frontend/src/components/orders/CorrectPaymentMethodModal.tsx[119-139]
main/routes/bills.ts[6133-6143]
frontend/src/components/pos/PaymentModal.tsx[190-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The correction modal enables a card submission that the configured server validation will reject.
## Fix Focus Areas
- frontend/src/components/orders/CorrectPaymentMethodModal.tsx[71-113]
- frontend/src/components/orders/CorrectPaymentMethodModal.tsx[119-139]
## Recommended Fix
Load the card transaction ID requirement as the payment UI does, and require a nonempty transaction ID before enabling Save for built-in card corrections.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Changed reasons replay an earlier audit ✓ Resolved
Description
requestHash omits normalizedReason even though the correction stores it in the audit. Reusing a
key with the same payment fields but a different reason returns the first cached response, so the
changed audit request is neither recorded nor rejected as a conflict.
Code

main/routes/bills.ts[R6158-6161]

+                  method: resolvedMethod,
+                  payment_method_id: resolvedPaymentMethodId ?? null,
+                  transaction_id: newTxId ?? null,
+                }),
Relevance

●●● Strong

Idempotency hashes must include every auditable request field to prevent incorrect cached responses.

PR-#1210
PR-#967

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hash contains the payment fields but not the reason; a matching hash returns the cached response
before the new request can reach the audit write.

main/routes/bills.ts[6151-6184]
main/routes/bills.ts[6354-6379]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An idempotency key accepts a different audit reason as if it were the original request.
## Fix Focus Areas
- main/routes/bills.ts[6151-6164]
- main/routes/bills.ts[6166-6184]
## Recommended Fix
Include the normalized reason in the canonical request hash so a reused key with a different reason receives an idempotency conflict.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Retried corrections fail once the shift closes ✓ Resolved
Description
The correction route checks the idempotency key at L6167 only after the refund, closed-day,
closed-shift and split-sibling checks (L5807-5990), and after it resolves the payment method again.
If a correction succeeds but the client never gets the response, retrying it with the same
Idempotency-Key gets a 409 (shift_closed, day_closed, bill_refunded, or
payment_method_inactive if the tender was later deactivated) instead of the saved result, even
though the correction did go through.
Code

main/routes/bills.ts[R6166-6169]

+        if (idempotencyKey && requestHash) {
+          const existingIdem = db
+            .prepare(
+              'SELECT request_hash, response_json FROM payment_idempotency WHERE user_id = ? AND idempotency_key = ?',
Relevance

●●● Strong

Replay lookup should precede mutable state validation so successful corrections remain safely
retryable.

PR-#1210
PR-#967

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The existing payment route looks up replays before it enforces the shift rule, and says so
explicitly. The new route runs every state check first, so a late retry cannot reach the cached
response.

main/routes/bills.ts[3778-3783]
main/routes/bills.ts[5877-5935]
main/routes/bills.ts[6150-6182]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In PATCH /api/bills/:id/payments/:index/method, the payment_idempotency lookup runs only after the refund, day-closed, shift-closed, split-sibling and method-resolution checks. A retry of a correction that already succeeded therefore gets a 409 once that state changes, instead of the cached response.

## Fix Focus Areas
- main/routes/bills.ts[5801-5810]
- main/routes/bills.ts[6150-6182]

## Recommended Fix
Right after loading the bill (and after verifying the approver), look up `payment_idempotency` by (idempotencyUserId, idempotencyKey). If a row exists for the same bill_id and payment index, return the cached response, or return 409 `idempotency_key_conflict` if the request hash differs. Only then run the refund, day, shift, sibling and method checks. Compute the request hash from the raw request fields (method, payment_method_id, transaction_id, index) so the lookup does not depend on the method still being active.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Wallet line can be preselected and submitted ✓ Resolved
Description
CorrectPaymentMethodModal initializes selectedIndex to 0 even when that line is a wallet tender,
and canSubmit does not reject wallet lines despite their picker options being disabled. When a
multi-tender bill starts with a wallet line and has a correctable line later, the cashier can submit
the wallet line without changing the selection, reaching the backend’s wallet_method_immutable
rejection after the approver enters their PIN.
Code

frontend/src/components/orders/CorrectPaymentMethodModal.tsx[R106-113]

+  const canSubmit =
+    Boolean(effectiveBill) &&
+    Boolean(currentLine) &&
+    !submitting &&
+    !managerApprovers.loading &&
+    !managerApprovers.loadFailed &&
+    Boolean(managerApprovers.selectedApproverId) &&
+    /^\d{4,6}$/.test(overridePin);
Relevance

●●● Strong

The modal must prevent initially selected immutable wallet lines from reaching a backend rejection.

PR-#733
PR-#1247

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The parent offers correction when any payment line is non-wallet, but the modal defaults to index 0
regardless of that line’s method. Disabling wallet options in the picker does not prevent the
initially selected wallet line from passing canSubmit, while the backend rejects wallet target
lines.

frontend/src/components/orders/CorrectPaymentMethodModal.tsx[44-62]
frontend/src/components/orders/CorrectPaymentMethodModal.tsx[106-113]
frontend/src/components/orders/OrderDetailPanel.tsx[216-222]
main/routes/bills.ts[6006-6012]
frontend/src/components/orders/CorrectPaymentMethodModal.tsx[41-62]
frontend/src/components/orders/CorrectPaymentMethodModal.tsx[200-216]
main/routes/bills.ts[6007-6012]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A mixed bill can open the correction modal with an ineligible wallet line selected and allow it to be submitted, even though the backend rejects wallet targets.

## Fix Focus Areas
- frontend/src/components/orders/CorrectPaymentMethodModal.tsx[41-69]
- frontend/src/components/orders/CorrectPaymentMethodModal.tsx[106-113]
- frontend/src/components/orders/CorrectPaymentMethodModal.tsx[171-181]
- frontend/src/components/orders/CorrectPaymentMethodModal.tsx[188-196]

## Recommended Fix
Initialize `selectedIndex` to the first line whose method is not `wallet`, reset it to the first such line when the bill changes, and use that line for `syncLineDefaults`. Add `String(currentLine?.method).toLowerCase() !== 'wallet'` to `canSubmit` so an ineligible line cannot be submitted.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. No-op replay may return another bill's audit ✓ Resolved
Description
When isSameMethod is true, the route looks up the latest bill.payment_method.corrected audit by
order_id alone, without matching bill_id or payment_index. If another bill or payment line on
the same order was corrected more recently, the response attaches that correction’s details to the
requested bill.
Code

main/routes/bills.ts[R6203-6206]

+          const latestAuditRow = db
+            .prepare(
+              `SELECT id, actor_user_id, action, details_json, created_at FROM order_audit_log WHERE order_id = ? AND action = 'bill.payment_method.corrected' ORDER BY id DESC LIMIT 1`,
+            )
Relevance

●●● Strong

Audit replay lookup must scope to the requested bill and payment line, preventing cross-line
response contamination.

PR-#941
PR-#988

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The audit lookup filters only by order_id and action. The response then sets bill_id to the
requested bill’s ID and spreads the retrieved audit details, even though those details may identify
a different bill or payment index.

main/routes/bills.ts[6202-6248]
main/routes/bills.ts[6197-6207]
main/routes/bills.ts[6216-6238]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The same-method replay branch can select a correction audit for another bill or payment line on the order.

## Fix Focus Areas
- main/routes/bills.ts[6202-6238]

## Recommended Fix
Restrict the audit query to details matching both the requested bill and payment line by adding `AND json_extract(details_json, '$.bill_id') = ? AND json_extract(details_json, '$.payment_index') = ?` and binding `bill.id` and `paymentIndex`. Return `audit: null` when no matching audit exists instead of attaching an unrelated order-level correction.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Business-day lookup copies shared helpers ✓ Resolved
Description
resolveBillBusinessDate and isBusinessDayClosed re-implement tenantTimezone()
(cash-closures.ts) and closedDayExists() (cash-drawer-movements.ts), and the copy quietly falls
back to UTC when the regional settings cannot be resolved. The shift and day-close code throws in
that case, so this route could check a different business date than day close uses, and any future
change to the closed-day rule has to be made in two places.
Code

main/routes/bills.ts[R5590-5598]

+  let tz = 'UTC';
+  try {
+    tz = resolveRegionalSnapshot({
+      country: getSettingValue('country') ?? undefined,
+      currency: getSettingValue('currency') ?? undefined,
+      timezone: getSettingValue('timezone') ?? undefined,
+    }).timezone;
+  } catch {
+    tz = 'UTC';
Relevance

●● Moderate

Shared-helper reuse and UTC fallback are plausible maintainability risks, but historical evidence is
not closely matched.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The shared helpers already exist and are used by cash sessions. The new copies are near-identical
but handle errors differently.

main/routes/bills.ts[5586-5617]
main/routes/cash-closures.ts[138-147]
main/services/cash-drawer-movements.ts[24-29]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The bills route duplicates the tenant timezone and closed-day helpers, and falls back to UTC when the shared helpers would throw.

## Fix Focus Areas
- main/routes/bills.ts[5586-5617]

## Recommended Fix
Import `closedDayExists` from services/cash-drawer-movements and a timezone resolver (move `tenantTimezone` into a leaf service if importing cash-closures would create a cycle). Use them in place of the local copies, and do not silently fall back to UTC.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
⚠️ Tickets: not configured — No jira, linear integration is connected for the linked tickets
✅ Compliance rules (platform): 5 rules
✅ Cross-repo context — repo relationships
  Explored: repo: FreeOpenSourcePOS/RevFlo (sha: 37d47cb2) — View relationship
  Explored: repo: FreeOpenSourcePOS/FloAdmin (sha: e5d6830b) — View relationship
Review mode: Auto: 🧠 Deep: Large security-sensitive payment and authentication changes span many independent code paths.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread main/routes/auth.ts
Comment thread main/routes/bills.ts Outdated
Comment thread frontend/src/components/orders/CorrectPaymentMethodModal.tsx
Comment thread main/routes/bills.ts Outdated
Comment thread main/routes/bills.ts
Comment thread frontend/src/components/orders/CorrectPaymentMethodModal.tsx
Comment thread main/routes/bills.ts Outdated
…heck (`test:orders-search`): - **Invariant**: Frontend components imported by `OrdersPage` must resolve `OrderDetailPanel` through the `@/components/orders/OrderDetailPanel` module specifier rather than a relative `./OrderDetailPanel` path, so Node-based `OrdersPage` unit tests that stub `@/components/orders/OrderDetailPanel` and provide a minimal `react` hook stub do not load the real `OrderDetailPanel` module and its `@radix-ui/react-slot` (`React.forwardRef`) dependency at module-load time. - **Root-cause fix**: Updated `frontend/src/components/orders/CorrectPaymentMethodModal.tsx` line 13 to import `normalizeBillPaymentDetails` from `'@/components/orders/OrderDetailPanel'` instead of `'./OrderDetailPanel'`. **Verification**: - `npm run test:orders-search` (passed) - `npm run test:payment-method-correction` (passed) - `npm run test:card-transaction-id` (passed) - `npm run test:ui-regressions-621-623-626` (passed) - `npm run test:password-policy` (passed) - `npm run lint` (passed) - `node scripts/ci/check-changed-format.cjs --base 1278f91` (passed) - `npm run build:frontend` (passed) - `git diff --check` (passed)
@khaira777 khaira777 changed the title feat(billing): correct paid bill payment method feat: correct open-shift bill payment methods and add PIN quick switch Oct 28, 2026
@khaira777 khaira777 changed the title feat: correct open-shift bill payment methods and add PIN quick switch feat(billing): correct paid bill payment method Oct 28, 2026
@khaira777
khaira777 force-pushed the fm/flocafe-1337-payment-method-correction branch 3 times, most recently from e1e8890 to 3cf1afc Compare October 28, 2026 23:51
@khaira777
khaira777 force-pushed the fm/flocafe-1337-payment-method-correction branch from 3cf1afc to e1e8890 Compare October 28, 2026 23:56
@khaira777 khaira777 changed the title feat(billing): correct paid bill payment method feat: correct paid bill payment method while shift is open Oct 29, 2026
@khaira777 khaira777 changed the title feat: correct paid bill payment method while shift is open feat(billing): payment method correction Oct 29, 2026
@khaira777
khaira777 merged commit 43892ae into main Oct 29, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-1337-payment-method-correction branch October 29, 2026 00:24
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.

feat(billing): correct the payment method on a paid bill while its shift is open

1 participant