by Intelliverse X

Activating a Nayax reader on a migrated Reyeah cabinet

Runbook for the case this repo keeps meeting: a cabinet that shipped on a vendor's Reyeah build (VTM / sapi.vapevendingsoftware.com), whose operator has since moved the machine onto Kiosk-X, and whose Nayax VPOS has never taken a card. Worked example throughout is Kiosk 0036, serial 866902661300036, Nayax Device Number 0434332923153297, Core machine alibi.

Migration steps 1–3 (install, claim, verify) are already done for this cabinet. This document is step 4, the reader — the part of the migration that is not ours to perform, because a VPOS is registered to a Nayax actor, not to a vending cloud.

It assumes the reader stays a peripheral on the VMC's MDB bus, which is the cheapest thing to try first. If it resists, or you want card payments to stop depending on per-cabinet commissioning altogether, the reader can be driven from our cloud or our app instead: Taking cards without MDB.

What is actually broken

Nothing in our software. The reader is online over 4G, has a green LED, and has taken zero transactions ever — including under VTM, whose APK we decompiled and found byte-identical in its card path (same FF00551106 cashless arm, same PayingPOP). There is no working state to return to.

The leading suspect is no longer the MDB level. It is the pair of Ignore flags in Phase 0.5, which disable the handshake outright and were the confirmed root cause on a sister cabinet running the same controller. Check those before arguing about dialects.

The mismatch is in MDB, one layer below anything a cloud can reach:

Layer Speaks Who controls it
Kiosk-X cloud HTTP /apk/* us
Android app 28 Reyeah serial commands us (via the APK)
VMC controller MDB master, declares Level 1 Reyeah firmware
VPOS reader MDB peripheral, commissioned MDB L3 Always Idle the Nayax actor

See "Why no API of ours can replace the profile change" in payments-nayax.md for the full command inventory and why the VMC-firmware route is a last resort.

Phase 0 — Confirm the actor before changing anything

15 minutes, no risk, and it decides the rest of the plan. In MoMa or Core:

  1. Find device 0434332923153297. Note the actor / organisation name that owns it, and whether the payout (bank) details on that actor are the operator's.
  2. Note the device's last configuration update timestamp. This is the only reliable signal later that an Update Queue actually landed — "Online" in MoMa will read the same before and after.
  3. Screenshot both, for the record.

Outcomes:

Phase 0.5 — The two Ignore flags (5 minutes, and do it first)

Before any test or profile argument, open the device in Core and confirm both of these are OFF:

They exist so a reader can be powered up on a bench with no machine attached, and they are routinely left on after commissioning. Left on, the reader skips the MDB handshake: it has no price table, no real vend session, and never contacts the payment network — while presenting a perfectly healthy-looking screen that appears to accept taps. Nothing reaches the bank and nothing looks broken.

On a sister cabinet with the same Reyeah controller, switching both off produced the correct product and price on the reader, a real authorization, and "paid" in MoMa — immediately, with no other change.

Switch them off, Actions → Update Queue, wait for the last-configuration timestamp to move, then continue to Phase 1.

Expect the next failure to be the opposite one: an authorization the machine never confirms, which shows in MoMa as "this machine could not complete its recent sales". That is progress, not regression — see "Then the opposite failure" in payments-nayax.md. Do not raise the Vend Result Timeout to mask it.

Phase 0.75 — Bench the reader off the controller (15 minutes, decides the rest)

Every phase below argues about which side is misconfigured while the reader has never once been observed apart from the Reyeah VMC. If you have an MDB-RS232 adapter — the ZHZN cabinets use one — this ends the argument before you spend an evening on it. Unplug the reader's MDB harness from the controller, plug it into the adapter, power the adapter from 24V, and run:

pip install pyserial
python3 scripts/nayax_mdb_bench.py --port /dev/tty.usbserial-1420

The script becomes the VMC and performs the bring-up MDB actually requires: declare a feature level (11 00), set max/min prices (11 01), read the reader's identity (17 00), enable Always Idle (17 04), enable the reader (14 01), then wait for a tap. It prints a verdict, and the default run requests no vend, so a tap authorises nothing and captures nothing.

It also prints the reader's serial straight off the bus, which is the only independent check that the device you are administering in Core is the device in this cabinet. Run it a second time with --level 1 to reproduce what the Reyeah controller is believed to declare: Always Idle is enabled by 17 04, which exists only at Level 3, so a Level 1 master cannot reach the feature at all.

Phase 1 — Tap first (5 minutes, no configuration)

Do this before changing any Nayax setting, because it may simply work.

The cabinet's native money model is credit-first: coins and bills establish credit on the VMC and the purchase deducts from it (FF0055E101). The idle screen already polls credit (FF0055E104…) and, in RootMainFragment, un-hides a balance row whenever the reply is above zero. MDB L3 Always Idle is the Nayax side of exactly that model — the reader sits enabled and authorises on tap, before any product is chosen. Every failed attempt so far did the opposite: select product, arm with the amount, wait.

  1. At the idle or browse screen, with nothing selected, tap a card.
  2. Watch the VPOS for a reaction, and the top of the kiosk screen for a balance amount appearing.
  3. In device logs, the line is VMC 余额: followed by a non-zero number.

If a balance appears, cards work today: select the item and the app deducts from the established credit and vends. Skip to Phase 5.

Phase 2 — Default Credit (15 minutes + queue)

In Always Idle the reader must authorise some amount before it knows what is being bought. If Default Credit is zero or unset, the tap dies silently — which is consistent with everything observed so far.

  1. On the device in Core, set Default Credit above the dearest SKU ($24.99 on this planogram, so $25–$30).
  2. Actions → Update Queue.
  3. Wait for the device to pull and reboot, and confirm the last-configuration-update timestamp from Phase 0 has moved.
  4. Repeat the Phase 1 tap-first test.

Phase 3 — Re-profile to match the controller (30 minutes + queue)

Only if Phases 1–2 fail. This aligns the reader with the dialect the VMC actually speaks.

Setting Value
Machine type Snack or General Merchandise — off Cigarettes / Electronic Cigarettes
Model / MDB MDB Level 1, Pre-Selection (product-first, sessioned) — not Always Idle
Transaction Start Method Product
MDB Level 1
Default Credit above $24.99 (from Phase 2)

Then Actions → Update Queue, confirm the timestamp moves, and test the normal flow: select the item, then tap. The VPOS must display the amount before the Android countdown expires.

Every value here is reversible by re-applying the old one, so this is a low-risk change to attempt and undo.

Phase 4 — Wiring (site visit, only if 1–3 fail)

Cellular "Online" proves the modem, not the bus.

  1. MDB harness runs from the VMC's cashless device #1 to the VPOS.
  2. The reader is powered from MDB, not only from an aux/USB lead.
  3. Continuity check on the harness rather than another tap — at this point taps have stopped being informative.

Phase 5 — Make the payments visible, in both systems

  1. In MoMa: confirm the transaction appears against the device, with matching amount and timestamp. This is the operator-facing proof the money is theirs.
  2. In Kiosk-X: the order moves to shipped and stock decrements. With no webhook configured there will be no transactionId, which is reported as cardPath.status = external_core_capture, not a ghost sale.
  3. Recommended — point Core's settlement notifications at us. Then Kiosk-X carries the real transactionId and settlementStatus: settled, the operator app shows card revenue next to Scan & Pay revenue, and reconciliation stops depending on someone reading two portals. Bind with nayaxWebhookExpected: true afterwards so the strict ghost-sale alarm re-arms.
  4. Bind the record properly, if it is not already:
POST /api/v1/machines/866902661300036/nayax
{ "terminalId": "0434332923153297",
  "deviceSerial": "434332923153297",
  "coreActorName": "<actor from Phase 0>",
  "settlesToOperator": <per Phase 0>,
  "nayaxWebhookExpected": <true once step 3 is done> }

Use the Device Number with its leading zero, never the shorter Machine ID (493134152) — the Machine ID never appears on a settlement webhook, so binding it fails closed.

Phase 6 — VTM cleanup the migration did not cover

Repointing the APK removed VTM from the vending path, including their ability to push VMC firmware over MQTT (installVMC). Two things it did not touch:

  1. Nayax notification subscriptions on the actor. If VTM configured Core to send this operator's settlement notifications to a VTM endpoint, they are still receiving them. Check the actor's webhook/API subscriptions and remove anything pointing at VTM before adding ours.
  2. The machine's registration in VTM's cloud. Harmless once the board no longer calls them, but worth asking them to deregister so the serial cannot be claimed or commanded there later.

Phase 7 — Make this repeatable

Every second-hand Reyeah cabinet will arrive in this state, so treat the Phase 3 table as the required profile for this hardware and check it before installation:

  1. Confirm the actor and payout details (Phase 0) at purchase, not at first tap.
  2. Require the seller to leave the device on MDB Level 1 Pre-Selection with a sensible Default Credit, or to release it to the buyer's actor.
  3. Record coreActorName on every binding, so a machine whose reader pays someone else is visible in the fleet rather than discovered during a revenue argument.

Acceptance criteria

# Proof Where
1 Reader reacts: amount shown, or balance row appears on the kiosk on site
2 Order reaches shipped, stock decrements operator app
3 Transaction listed against the device MoMa
4 Order carries a real transactionId, settlementStatus: settled Kiosk-X, only with Phase 5.3
5 Funds in the operator's payout report Nayax payout

A Kiosk-X webhook emulation (nyx_emu_*) satisfies none of these.

Field card: the on-site test, for whoever is at the machine

One page, no Nayax or Kiosk-X access needed. Takes about fifteen minutes. Print it or send it as-is.

Bring: any contactless bank card (you will not be charged unless a product vends), and a phone to photograph the screens.

Do not change any other setting on the machine or the reader. If something looks wrong, photograph it and stop — a wrong guess here costs a second visit.

If a product vends during any test, note the price and check MoMa afterwards. A sale that vends but shows no transaction — or shows the machine as unavailable — is a different problem from a reader that does nothing, and matters more.

Test 1 — tap before choosing anything.

  1. Leave the kiosk on its normal idle or browse screen. Do not select a product, and do not press the Credit card button.
  2. Hold the card against the Nayax reader for two seconds.
  3. Write down what the reader does: nothing at all, a light change, a beep, or text on its little screen (photograph any text).
  4. Write down whether a dollar amount appears anywhere on the kiosk screen, usually as a small balance line near the top.

If an amount appears on the kiosk: cards are working. Choose the cheapest item and let it vend, then note the item, the price, and whether it dispensed.

Test 2 — the normal way round.

  1. Select Peak Protein ($2.99).
  2. Press Credit card.
  3. Watch the reader for up to 30 seconds. Does it ever show $2.99, or any prompt to tap?
  4. Tap the card. Note whether it is accepted, declined, or ignored.
  5. If nothing happens, let the screen time out on its own rather than cancelling.

Test 3 — the physical check, only if the reader never reacted in either test.

  1. Open the cabinet. Find the cable from the machine's controller board to the back of the Nayax reader.
  2. Photograph both ends, including which socket on the controller it is plugged into.
  3. Confirm it is seated firmly at both ends. Do not rewire anything.

Report back: the four notes from Test 1, the four from Test 2, and the photos. "The reader did nothing at all, both times" is a complete and useful answer — it distinguishes a configuration problem from a wiring one, which is the whole point of the visit.

While cards are down

Scan & Pay (Stripe QR) works on this cabinet today and settles to the operator, and the kiosk now withdraws the card tile after two consecutive dead sessions so buyers are pushed to it instead of a dark reader. The tile returns automatically on the next successful capture, on a re-bind, or after 24 hours.