FCA (First Choice Anderson), Making Payment a Precondition, Not an Afterthought
Key Features
- Payment-gated appointment confirmation
- Two-way Tebra (Kareo) synchronization with full logging
- Server-side availability engine (hours, breaks, time off)
- Stripe payments tied to every visit
- Patient mobile app, booking, payments, messaging, profile
- Drag-and-drop React scheduling calendar
- Provider schedules & time-off management
- Roles & granular permissions
- Audit-safe payment history (survives account deletion)
- Push notifications & transactional emails
About This Project
A companion system built around a medical practice's existing Tebra (Kareo) EHR, closing the copay gap by making it impossible to confirm an appointment until the payment has actually been made.
Client: Medical practice running on Tebra (Kareo)
Built with: Laravel · React · MySQL
Connected to: Kareo SOAP API · Stripe · Firebase
Delivered: Admin web panel + patient mobile app
01. Why this was built
The practice ran its clinical and scheduling operations on Tebra (formerly Kareo). Tebra handles patient charts and billing well, but in its scheduling model, payment is something that happens after care. The appointment gets booked, the patient shows up, and the copay is collected at the front desk, chased later, or never collected at all.
That ordering was the root of the practice's problem. Front-desk staff were confirming appointments in good faith with no payment attached to them, and copays kept slipping through.
The obvious fix, making Tebra require payment at booking, simply isn't possible; Tebra's booking flow can't be extended that way. And replacing an EHR the practice already trusted was never the right answer. So instead of ripping anything out, we built a companion system that takes over the scheduling and payment workflow, while Tebra stays in charge of the clinical record, with both systems kept in sync automatically, in both directions.
The core design decision: an appointment is confirmed by payment, not by a person. Nobody clicks "confirm." The appointment becomes Confirmed the moment the Stripe payment succeeds, and that update is pushed into Tebra at the same time.
02. The payment gate
Every appointment created in the system comes into existence together with an unpaid payment record. The payment isn't optional and isn't added later, from the very first second, every booking has a financial obligation attached to it.
From there, the flow is simple for the patient:
The appointment is created (by the front desk or through the app). The patient receives a Stripe payment link, by email or inside the app, along with a push notification: "Payment is required to confirm your upcoming appointment." Until that payment is made, the appointment cannot be confirmed, not from the admin panel, not from the app, not from anywhere. Any attempt is blocked with a clear message: "Complete the payment first." The moment payment succeeds, the appointment flips to Confirmed and the update is written into Tebra automatically.
Importantly, this rule lives on the server, it's not just a greyed-out button. It applies equally to staff, patients, and any system connecting to the platform, so there is no side door around it.
Every Stripe payment carries the appointment and patient it belongs to, so money is always traceable to a specific visit, no manual matching, no mystery payments. And if anything goes wrong while updating Tebra, the whole operation rolls back, so the two systems never end up telling different stories.
Payment status is kept deliberately simple, every charge is either Due, Paid, or Cancelled. The patient sees this directly in the app: outstanding charges appear in a "Charges" tab, completed ones in a "Payments" tab. And when an appointment is cancelled, its charge is cancelled with it in the same step, so a cancelled visit never leaves a live bill behind.
Even the notifications are honest about what's happening: if a balance is owed, the message says payment is required; if not, it simply asks the patient to confirm. The patient always knows exactly what's blocking them.
03. Real-world problems we solved along the way
The payment gate was the reason the project started, but a large share of the work went into problems that only showed up once real patients and real practice data hit the system. Each of these was a live operational headache before it was fixed:
- Patients couldn't register with their own email. Patients synced in from Tebra already existed in the database, so when they tried to sign up through the app, the system rejected their email as "already taken." We fixed registration to recognise these invited-but-not-yet-registered patients and let them claim their existing profile, instead of blocking them or creating a duplicate.
- Login could pick the wrong account. When the same email legitimately existed on more than one record, a naive login could authenticate against the wrong one, failing even with correct credentials. Login now resolves the right account deliberately, and every failure gives the patient a clear, specific reason instead of a generic error.
- Providers had no way to take a day off. Weekly hours and lunch breaks existed, but there was no concept of leave, so a provider on holiday still showed open slots, and patients booked (and paid for) appointments nobody would attend. We added full-day and partial-day time off, and the calendar now clearly marks those days as unavailable, blocking any new booking on them.
- Appointments could drift by an hour across time zones. Kareo's API works in UTC; the practice works in US Eastern time. Around daylight-saving changes, that mismatch could silently shift an appointment by an hour. We made the conversion happen in exactly one place, applied consistently on every operation, so a stranded patient from a timezone bug can't happen.
- Nobody could see whether sync was working. Data pulled from Tebra either arrived or silently didn't, and when records went missing, there was no way to know which sync failed or why. Now every sync run is logged: what ran, when, how it was triggered, how many records it processed, how long it took, and any errors, all visible from the admin dashboard.
- Appointments created during an outage could be lost forever. The daily sync only looks at a narrow window, so anything created in Tebra while sync was down could fall through the cracks permanently. A dedicated recovery job now sweeps a much wider range, finds what's missing, and fills only the gaps, with a report of exactly what it found and created.
- Deleting a patient used to destroy their payment history. Patients can request account deletion, but financial records must stay auditable. The system now deactivates accounts instead of erasing them, so a deleted patient's payment history remains intact and correctly named in the ledger.
- Bad data could reach the medical record. Because this system creates records inside the EHR, a typo doesn't stay local, it lands in the clinical record. Validation was tightened at the source: strict formats for names and phone numbers, strong password rules, and date-of-birth pickers that make it impossible to even enter a future birthdate.
- Provider schedules could contradict themselves. Breaks could be saved at any time of day, including on days with no working hours at all. Schedules are now validated on save, and anything contradictory is cleaned out before it's stored.
- Small admin annoyances were fixed too, like two patient lists on one screen interfering with each other's pagination, or the page jumping back to the wrong tab after restoring a user. Small things, but they're what staff touch every day.
04. The availability engine
What's bookable is calculated in one place, on the server, from three sources: each provider's weekly working hours, their recurring breaks, and any one-off time-off. A full day off means zero availability; a partial absence removes just that window.
Because every screen, the admin calendar, the patient app, the booking form, reads from this single calculation, they always agree on what's available. There's no scenario where the app shows a slot the front desk can't see, or vice versa.
Slots are computed at 5-minute precision and served in whichever shape each screen needs: open slots for the booking form, blocked slots for the day view, a week's worth at once for the weekly view, and daily appointment counts for the month view.
05. Staying in sync with Tebra
Six types of data sync in from Tebra: patients, providers, practices, service locations, appointments, and appointment reasons. Each sync runs automatically every morning, and can also be run on demand from the admin panel, with the log showing which was which.
The sync layer is built defensively, because real-world API responses are messy: data arrives in inconsistent shapes, fields come back empty or missing, and one bad record shouldn't take down a run of thousands. Every record is handled individually, so a single malformed entry is skipped and logged rather than breaking everything behind it.
Data flows the other way too: when a patient registers, updates their profile, or an appointment is created, changed, or cancelled, those changes are written back into Tebra. The two systems stay reconciled in both directions, continuously.
06. The patient app
Patients get a full self-service experience over a secure, token-authenticated API, without needing to call the practice:
- Sign up or get invited. Patients can register themselves (which also creates them in Tebra), or staff can invite an existing patient by email. Both paths lead to the same verified account.
- Verified accounts. Email verification is required before login, with clear messaging for accounts that haven't verified yet.
- Appointments. See upcoming and past visits; confirm, cancel, or check in, with every action reflected in Tebra and in the payment record at the same time.
- Charges & payments. Outstanding charges and completed payments in two clean views, each tied to its visit so every amount is identifiable.
- Profile. Patients keep their own contact details up to date, and the changes flow into Tebra so the medical record never drifts out of date.
- Secure messaging. Message threads with the practice, unread counts, seen indicators, and push notifications on new messages.
- Password management. Secure password changes, plus an email reset path for patients who get locked out.
- Account deletion. An in-app deletion request that notifies the practice, confirms by email, and cleans up device tokens, meeting app-store data-deletion requirements.
Push notifications (Firebase) cover appointment creation, reschedules, cancellations, no-shows, and new messages, and are built so that a stale phone token never breaks the action that triggered the notification.
07. The admin experience
- Dashboard. Thirteen live metrics covering users, appointments, providers, payments and messages, plus recent activity, monthly trends, and a status panel showing the latest Tebra sync result at a glance.
- User management. Create, edit, search and filter users across roles, with separate active and deactivated lists and a one-click reactivation path.
- Appointments. Full lifecycle management with filtered search, every change written through to Tebra.
- Payments ledger. Searchable by date, status, amount and patient name, including payments belonging to deactivated patients, so the financial record is always complete.
- Provider schedules & time off. Weekly shifts with breaks per location, edited in a familiar 12-hour format, plus full and partial day absences with reasons.
- Roles & permissions. Custom roles with granular permissions, enforced on the server and reflected in the interface, staff simply don't see controls they can't use.
- Deletion requests. A managed queue of patient deletion requests with an admin approval step and automatic confirmation emails.
08. The scheduling calendar
The calendar is the operational centre of the practice's day, a rich, purpose-built React interface with day, week and month views, side-by-side provider columns, filtering by provider and location, drag-and-drop rescheduling, resizable appointment blocks, and zoom.
When appointments overlap, the calendar lays them out side by side automatically so nothing is hidden behind anything else. Colour communicates status at a glance, a look at the grid tells staff not just what is scheduled, but where things stand.
Unavailability is part of the same picture: breaks and out-of-hours slots appear disabled, and a provider's day off is drawn as a clear hatched overlay across their entire column. Existing appointments on that day stay visible and clickable, but any attempt to book something new is stopped with a clear explanation.
09. How it's built
The system is deliberately a companion, not a replacement. Tebra remains the system of record for all clinical data; this platform owns scheduling, payments and patient engagement, and keeps everything reconciled continuously.
Backend: Laravel (PHP), with safe soft-deletes throughout
Frontend: Admin panel with an embedded React calendar, styled with Tailwind
Security: Token-based authentication for the mobile app, session auth for admins, role-based permissions
EHR connection: Kareo SOAP API, reads six data types in, writes patients and appointments back
Payments: Stripe Checkout, with every payment tagged to its appointment and patient
Notifications: Firebase push notifications and transactional emails
Automation: Daily scheduled syncs, each also runnable on demand
Deployment: Fully Dockerised build for consistent, repeatable releases
10. What the practice got
The practice kept Tebra, no migration, no retraining on a new EHR, no clinical data moved anywhere. What changed is the order of events around it. Stated as guarantees the software now makes:
- An unpaid appointment can never be confirmed, not by staff, not by the patient, not by any connected system. The problem that started this project is now structurally impossible, rather than something staff have to police.
- Every payment is tied to a specific visit, so reconciliation happens automatically instead of by hand.
- This system and Tebra always agree. Updates either succeed in both or happen in neither, never a mismatch.
- Unavailable time simply cannot be booked, hours, breaks and leave all feed one shared availability calculation.
- Sync is fully visible. Every run leaves a record of what it did, so a data issue is a diagnosable event, not a mystery.
- Financial history survives account deletion, keeping the ledger complete and auditable.
These are properties of the software itself, verified by reviewing the implementation. Business outcomes, collection rates, no-show rates, staff time saved, depend on the practice's own production data and are deliberately not claimed here.
The broader lesson: the client's problem looked like a payments problem, but it was really an ordering problem. Tebra allowed confirmation before payment, and no amount of front-desk discipline can reliably close a gap the software leaves open. Fixing it meant taking ownership of just enough of the workflow to change that order, and leaving everything else exactly where it already worked.
Technologies Used
Interested in this case study?
Contact us to learn more about how we can build a similar solution for your business.
Get in Touch