How to Fill a Cancellation Without Overselling Lesson Credits

Prevent a cancellation waitlist from double-spending a package credit: recheck the exact entitlement and capacity, lock both, commit once, and audit the result.

To fill a cancellation without overselling lesson credits, treat the open time and the student’s unused credit as two separate resources that must both still exist when the student accepts. A waitlist match is only a proposal. At acceptance, recheck the exact service entitlement and the exact deliverable slot together, reserve both under one guarded transaction, consume the credit once, create or move one booking, and record the result. If either resource changed, reject the acceptance and recalculate.

This is the difference between a waitlist that sends messages and a recovery workflow that protects the school’s obligations. A student can be eligible when an offer is created and ineligible seconds later because the credit was used, refunded, adjusted, expired, or reserved elsewhere. The same opening can also disappear while the offer is still in a customer’s inbox.

What “overselling a lesson credit” actually means

A lesson credit is not merely a number displayed beside a customer’s name. It should represent a specific remaining right to book instruction under the purchased package: the service, quantity, duration, paid status, and any conditions or included extras that affect delivery.

Overselling happens when the system creates more valid bookings than those entitlements authorize. The customer may appear to have one unused unit in two screens, receive two offers based on the same stale balance, or use the unit through ordinary booking while a cancellation offer is waiting. If the acceptance path trusts the earlier snapshot, the school can create a second lesson without a second paid unit.

The customer did not necessarily do anything wrong. The defect is a concurrency problem: two otherwise valid actions observed the same resource before either action recorded that it had been consumed.

A cancellation offer can become stale in minutes

Consider this illustrative sequence. The times and people are examples, not customer results:

  1. At 2:00 PM, a cancellation releases a route-feasible 60-minute opening for Friday.
  2. At 2:02 PM, the system finds that Maya owns one fully paid, unused 60-minute unit for the matching service and sends her an offer.
  3. At 2:05 PM, Maya uses ordinary self-booking to reserve a different lesson with that same unit.
  4. At 2:06 PM, she opens the earlier cancellation offer and accepts it.

If the offer itself is treated as proof of entitlement, both bookings can survive while the account still contains only one purchased unit. An equally dangerous race occurs when two recipients accept the same opening at nearly the same time.

The safe rule is simple: eligibility at offer time decides who may be invited; eligibility at acceptance time decides whether anything may change. An expiring link reduces exposure, but expiry alone cannot prove that the credit and slot are still available.

Lock the credit and the opening before changing either one

A reliable acceptance path performs one indivisible business operation:

  1. Authenticate the action. Confirm the offer is genuine, unexpired, intended for this customer, and bound to the proposed service and opening.
  2. Start a transaction and obtain the required locks. Protect the specific entitlement and the specific capacity record from competing writers while the decision is completed.
  3. Read current state again. Do not reuse the balance, availability, route result, or customer-conflict result stored when the offer was sent.
  4. Revalidate both sides. Confirm the credit is paid, unused, unexpired, and valid for the exact service facts; also confirm the instructor, vehicle, pickup, buffers, route, customer schedule, and opening still fit.
  5. Commit one complete result. Consume the entitlement and create the booking—or move the already-authorized booking—within the same transaction.
  6. Write an acceptance receipt and delivery event. Record which offer, entitlement, booking, actor, and decision produced the change, then send confirmations from durable post-commit work.

Database locking is not just a technical preference. PostgreSQL’s current documentation explains that a row locked FOR UPDATE cannot be modified or locked incompatibly by another transaction until the first transaction ends. That is the kind of first-committer boundary needed when two acceptances compete for the same finite record. See the PostgreSQL explicit-locking documentation.

Locks must be short-lived. Do not hold a database transaction open while waiting for a customer to click. The offer may wait; the final acceptance transaction should not. PostgreSQL also warns that long-running locks and inconsistent lock ordering create blocking and deadlock risk.

Idempotency prevents retries from becoming extra lessons

Locking handles concurrent competitors. Idempotency handles the same acceptance being submitted more than once—for example, after a slow connection, a double tap, or an automated retry.

Give each acceptance attempt a stable one-use operation key. If the server already completed that operation, return the recorded result instead of creating another booking. Stripe’s idempotent-request documentation describes the same general safeguard for create and update requests: a retry with the same key returns the first result rather than performing the operation again.

Idempotency is not a replacement for current-state validation. The separate paid-but-unbooked recovery checklist applies that safeguard when the money exists but the appointment does not. Two different offer tokens or two different customers can have different keys and still compete for the same slot. A safe design needs both: deduplicate repeated requests and serialize competing claims on finite resources.

Recheck the entitlement, not only the displayed balance

At acceptance, verify the fields that determine what the customer actually owns:

  • the order and order-item identity behind the unit;
  • fully paid status where the recovery policy requires it;
  • remaining usable quantity after bookings, refunds, expirations, adjustments, and other reservations;
  • the purchased service, duration, attendee count, and branch or instructor restrictions;
  • required or paid extras that change the lesson or allocation footprint;
  • the customer identity and any transfer rule;
  • whether another booking or acceptance already consumed the unit.

A general account balance is not enough. For the office-side math, use the worked reconciliation of purchased, completed, scheduled, and available lesson credits. Nor is “one credit remaining” if the credit belongs to a different service or if the proposed lesson requires an extra that was never purchased. The RouteOS guide to clear driving-lesson packages explains which package facts should be visible before purchase; the recovery workflow must preserve those same facts when the unit is used.

Recheck the slot as a deliverable lesson

The credit can be valid while the opening is no longer safe. Revalidate the exact instructor, vehicle, service, location, pickup, start, duration, buffers, preceding trip, following trip, and customer conflict. A cancellation releases calendar time, but the usable capacity window may move or shrink as the surrounding day changes.

This matters because mainstream appointment products commonly present waitlists and prepaid packages as adjacent capabilities. Square’s current Appointments overview, for example, describes automatically filling cancellation openings and selling prepaid service packages. The owner still needs to verify what the acceptance transaction protects when those two capabilities meet; a feature list does not establish the entitlement and capacity controls.

How RouteOS handles the safeguard

According to the current code-grounded RouteOS feature review dated September 2, 2026, Cancellation Gap Filler can consider an unused unit only when it comes from a fully paid native bundle and the required service facts are known. Candidate generation uses the customer’s clean availability and checks the complete buffered lesson, pickup serviceability, customer conflicts, resources, and both surrounding travel legs.

The offer remains a temporary, action-specific invitation. Final acceptance revalidates under a first-committer database lock and atomically moves a compatible later booking or allocates one qualifying entitlement. It also restricts cascade depth and requires prepaid authority for relevant required, paid, duration-bearing, native-minimum, and pickup extras rather than inventing them during recovery.

The boundaries are equally important. Gap Filler does not create a new sale, change the order or price, switch the purchased service, substitute an instructor or customer location, or manufacture a paid extra. Configurable bundles preserve their exact purchased quantities and adjustments; the recovery workflow must consume what was sold, not rewrite it to fit the opening.

This behavior is implemented, but that does not mean every RouteOS deployment has it enabled or fully configured. The current release uses staff-controlled offer waves, and automatic origin remains deliberately unavailable. Schools still need accurate bundle, service, pickup, route, communication, and staff-policy configuration. The related guide on who should receive a cancellation offer addresses candidate priority; this article addresses what must be true when the chosen candidate accepts.

Run five acceptance tests before relying on the workflow

  1. Spend the credit elsewhere. Send a test offer, use the same unit through normal booking, then accept the old offer. The old acceptance must fail without creating a second lesson.
  2. Race two recipients. Let two test customers accept the same opening at nearly the same time. Exactly one complete booking should commit.
  3. Repeat the same request. Submit an acceptance twice with the same operation key. The second response should return the first result, not consume another unit.
  4. Change one purchased fact. Use a credit for the wrong service, duration, instructor restriction, or required extra. The candidate or final acceptance should be rejected.
  5. Break the route after the offer. Add a nearby commitment or change the pickup so the lesson no longer fits. Acceptance should fail and preserve both the credit and the prior schedule.

For every failure, verify the negative result as carefully as the successful one: no extra booking, no lost credit, no partial payment or entitlement change, a clear customer message, and an audit event staff can trace.

The owner’s acceptance rule

A cancellation is filled safely only when the school can prove, at the moment of acceptance, that one exact customer still owns one exact lesson and one exact deliverable opening still exists—and can commit both facts once. Candidate lists, balance labels, offer expiry, and “first come, first served” language are useful inputs. None of them replaces the final locked recheck.

Research reviewed September 5, 2026. The live answer landscape was checked against current Square Appointments material, Stripe’s idempotency documentation, PostgreSQL’s explicit-locking documentation, existing RouteOS cancellation and package guides, and the supplied RouteOS code-grounded feature report. No claim is made that every scheduling platform, database, or RouteOS account uses the same implementation.

Put the idea to work in your school.

See how RouteOS connects lesson scheduling with the rules, resources and routes behind your day.

See RouteOS in action