Preventing Duplicate Dispensing in Connected Vending Machines
Prevent duplicate vending with atomic fulfilment authorization, durable command delivery, device deduplication, replayable results, restart recovery, and fault tests.
Key Insights At A Glance
Deduplicate the business operation as well as the transport message.
Use atomic authorization and durable command scheduling to survive concurrent workers.
Record device acceptance before action and preserve the outcome for replay.
The gap between physical action and durable confirmation requires explicit uncertainty handling.
Retention, maintenance tools, and restored backups must preserve duplicate protection.
The backend retries a command because it never received a result. The machine already dispensed, so it dispenses again. Nothing about that failure requires an unreliable payment provider or a dramatic software crash. It can happen during an ordinary lost acknowledgment. Preventing it means designing how the same physical operation is recognized everywhere, including after a restart.
Payment state machines define the broader lifecycle. This guide concentrates on one invariant: an already accepted operation must not become a fresh vend merely because a message is repeated. It also explains why distributed software cannot casually promise exactly one physical delivery under every possible interruption.
1. Give the physical operation a stable identity
Use separate identifiers for the business order, payment, fulfilment operation, and transport delivery where needed. Several message deliveries may refer to one operation. A new network retry must retain the original operation identity, target, and relevant payload. If the same identity arrives with conflicting contents, reject and investigate it rather than treating the latest message as a revision.
Define identity scope across machines, replacements, environments, and restored data. A counter that restarts at one after every boot is not enough. Bind commands to the intended machine and relevant configuration so an old message cannot accidentally act on a replacement compartment mapping. State how identity is generated, retained, and validated before implementing the protocol.
2. Make fulfilment authorization atomic
Two webhook handlers or retry workers may observe the same paid order concurrently. Checking a flag and then setting it in separate unprotected steps permits both to proceed. Use a database constraint or atomic state transition that authorizes only one fulfilment operation for the eligible order, according to the actual persistence system.
Plan what happens between committing authorization and delivering the command. A durable outbox or equivalent mechanism can record the command intent with the business transition, then deliver it with retries. This avoids losing the operation if the process exits after updating the order but before publishing. Delivery may still repeat; the device must handle that repetition.
3. Persist acceptance at the device boundary
Before energizing an output, validate authorization, target, payload, prerequisites, and whether the operation is already recorded. Retain an acceptance or intent record with a defined crash-consistent storage method. On repetition, return the existing status. A cache held only in RAM disappears at exactly the moment a restarted machine needs it most.
Storage behavior needs engineering: incomplete writes, corruption detection, write endurance, full storage, and recovery after interrupted updates. Choose the mechanism from your platform and requirements. A filename called journal does not make a write atomic. If the required durable record cannot be established, block new physical work and raise a useful fault instead of silently weakening the invariant.
Response to a repeated operation
| Stored state | Expected response | Physical action |
|---|---|---|
| No record and valid prerequisites | Durably accept under the protocol | Only after acceptance is established |
| Accepted or in progress | Return current operation status | Do not start a second independent attempt |
| Completed with known outcome | Replay the stored result | No additional actuation |
| Interrupted with ambiguous outcome | Return uncertain and reconcile | No blind repeat |
| Same ID, conflicting payload | Reject and log a protocol conflict | No actuation |
4. Handle the non-atomic physical step honestly
Software cannot generally commit a database record and move a physical product in one atomic transaction. Recording completion first can claim delivery that never occurred. Acting first can leave a rebooted controller unable to prove what happened. An intent record, physical observations, and startup reconciliation narrow the uncertainty but do not magically remove every ambiguous interval.
Consider a hypothetical unlock pulse followed immediately by power loss. If the door was opened and closed while power was absent, a later closed reading may not establish its history. Define the available evidence and quarantine or service policy. Paid-but-unfulfilled recovery covers customer resolution while this technical question remains open.
Idempotent commands are not universal exactly-once delivery
5. Make results durable and replayable
After observing an outcome, retain the result and its evidence before relying on a successful upload. Reconnect should resend the recorded result until acknowledged under the agreed protocol. Backend deduplication should accept the same result repeatedly without double-changing inventory or creating multiple financial resolutions.
Include operation identity, machine identity, outcome meaning, relevant sequence or boot reference, and protocol version. Do not overwrite a terminal result with an older in-progress event simply because it arrived later. Define permitted transitions and conflict handling. An acknowledgment should say whether the server durably recorded the result, rather than merely whether a socket accepted some bytes.
6. Keep protection for the full replay window
Define how long commands can remain in queues, provider events can be replayed, backups can be restored, and operators can revisit transactions. Duplicate records must cover that window or the protocol must safely reject obsolete operations. An arbitrary small cache may pass a demonstration and fail after a long offline period.
Use bounded command validity and a reviewed sequencing or epoch scheme where appropriate, but account for clock reliability and reconnection. Purging device history requires coordination with upstream replay behavior. Test the retention boundary explicitly: the last retained record, the first purged record, and restored server data should not accidentally revive an old vend.
7. Apply the same rules to support and maintenance
A dashboard re-vend button, a local test mode, or a technician command can bypass an otherwise careful customer path. Separate diagnostic operation from customer fulfilment, restrict its permissions, and record target, reason, authorization, and outcome. Prevent conflicting service work while a live transaction is unresolved.
Replacement controllers need a commissioning procedure for machine identity, compartment mapping, retained transaction state, and pending operations. Do not restore an old device image and immediately consume a backlog of commands. Hardware handover should include the identity and recovery responsibilities, not just the firmware binary.
receive(operation_id, target, payload_digest, authorization)
validate machine, version, validity, target, and prerequisites
if identity exists: return stored status or reject payload conflict
persist accepted intent using the reviewed storage mechanism
perform the permitted attempt and collect physical evidence
persist known outcome, or retain an uncertain state
replay stored result until the backend durably acknowledges
on boot: reconcile interrupted intents before accepting new work8. Test concurrency, reboot, and missing acknowledgments
Send the same command concurrently, repeat it after completion, reorder messages, and reconnect with a backlog. Interrupt power before persistence, after intent recording, during physical action, after observation, and after result transmission. Check actual outputs and stock behavior alongside software records. A test that checks only HTTP responses misses the reason the system exists.
Include storage full or damaged, payload conflict, configuration change, controller replacement, and restored backend backups. For each case, specify whether the expected outcome is completion, rejection, reconciliation, or a service hold. Production test design can establish hardware functionality; the end-to-end duplicate tests remain a separate requirement.
9. Review the invariant across every boundary
Draw the path from verified payment to authorization, command intent, delivery, device acceptance, physical action, durable result, and inventory update. At each boundary ask what survives a crash and what repeats after retry. Assign one owner to the full trace while keeping implementation responsibilities clear.
SolveMpire can discuss firmware and connected-machine architecture around your actual mechanism and evidence. Bring protocol samples, persistence behavior, queue rules, logs, and maintenance tools. The review should leave a testable invariant and a documented response to ambiguity, rather than a reassuring claim that the network normally works.
Seeing repeated or ambiguous vending operations?
Share the order flow, command protocol, device storage, acknowledgment behavior, and recovery tools. We can discuss durable duplicate protection and interruption tests.
