Should I vibe code
Publish booking pages and collect optional payments through a simple workflow
A booking that issues an invoice is bookkeeping. There is no rollback — only a credit note with your name on it.
?
Their verdict, the Pro price and the build-time estimate come from their entry, MIT-licensed. Checked 2026-08-04.
?
Our verdict, the regret score and everything below it. Editorial and unsponsored — nobody can pay to be moved.
The honest answer
why the verdict is what it is
Zencal's own description of itself is the tell: not a calendar, a system for selling services. The booking page is the front of it, and behind that sit paid meetings, prepaid bundles and subscriptions, six payment rails including the Polish ones nobody outside Poland has integrated, and — the part that changes the verdict — invoicing. A booking on Zencal can end with a numbered document sent to a customer stating what they bought, what tax applied and what they owe, and that document is not a UI element. It is a business record with rules attached: sequential numbering with no gaps, contents fixed at issue, corrections made by credit note rather than by UPDATE, and a retention period measured in years by a tax authority rather than by you. An agent will happily generate that PDF in twenty minutes and will just as happily number it `MAX(id)+1` inside a request handler, which is a duplicate the first time two bookings land together and a gap the first time one rolls back. Payments make it worse in the familiar way — Stripe and your database can disagree, and when they do somebody holds a receipt for a session your calendar has never heard of. The booking half is a decent multi-day project. The moment it issues paper, you are doing somebody's books.
What actually breaks
not "if". the specific failures.
- Invoice numbering, the first time two bookings are confirmed in the same second and `MAX(number)+1` hands out the same number twice
- The sequence itself, which grows a hole every time a transaction rolls back after the number was allocated — and a hole is the thing an auditor asks about
- Invoice contents, when someone edits a row to fix a typo and silently changes a document a customer already has a copy of
- Tax rate selection, which depends on where the customer is and whether they gave you a VAT number, and which your code hardcoded to your own country
- Refunds, because the payment provider reverses a charge and nothing in your ledger says a credit note is now owed
- Prepaid bundles and subscriptions, which turn your bookings table into a balance that customers will dispute in writing
- The reconciliation between Stripe and your database, which drifts quietly until a customer forwards a receipt for a meeting that does not exist
- Records retention, since deleting a cancelled customer's row also deletes the invoice a tax authority expects you to still have
The discovery happens in February, at an accountant's desk, which is the worst possible place for it. She is closing last year's books and the export will not balance: invoice 0147 appears twice, issued nine minutes apart, to two different clients, for two different amounts. You already know what happened before you open the code, because you wrote it — the number comes from a count of existing invoices, read inside the confirmation handler, and two Tuesday-morning bookings finished within the same second. That is the small problem. The larger one surfaces as she keeps reading: 0203 does not exist at all, because a payment webhook retried, the transaction rolled back after allocating the number, and nothing put it back. So the sequence has a duplicate and a gap, both of which are exactly the shapes a tax authority is trained to ask about, and the documents are not drafts you can regenerate — they were emailed as PDFs to clients who filed them in their own accounts months ago. The fix is not a migration. It is a set of correction documents, one conversation per client, and an explanation of why your booking page was also the accounting system.
Is that you?
the verdict is a default, not a law
- Money is out of scope entirely — the page books time and somebody invoices by hand afterwards
- Payment, if any, is a hosted Stripe Checkout link and no card detail reaches a server you own
- Invoices come from real accounting software via its API, and your app stores only the returned document id
- There are no bundles, packs or subscriptions, so no customer has a balance with you
- You self-host Cal.com for the scheduling and keep the commercial half in a tool that already knows about tax
- You are generating the numbered document yourself and the number comes from anything other than a dedicated, gap-free sequence
- Customers are in more than one country and your tax logic is a constant
- You sell packs of five or ten sessions, which makes your database the only record of a balance somebody paid for
- Refunds and cancellations are handled by editing rows rather than by issuing a correcting document
- Your accountant does not know this system exists
If you build it anyway
the checklist, then the prompt that enforces it
- Do not issue invoices. Push the line items to accounting software that already handles numbering, tax rules and retention, and store the returned document id. This single decision removes most of this page.
- If you must issue them, allocate numbers from a dedicated database sequence in its own committed transaction, never from a count or a max, and record allocations that were never used rather than reusing them.
- Make issued documents immutable. Store the rendered PDF as the artefact, and implement corrections as new credit or correction documents that reference the original.
- Never let card data touch your server. Hosted Stripe Checkout keeps you in the smallest PCI scope there is; a custom card form drags the whole standard into a side project.
- Treat the payment provider as the source of truth for money and reconcile against it nightly, with an alert on any booking whose paid state disagrees with its charge.
- Decide the tax question before writing a line of billing code: where your customers are, whether reverse charge applies, and what happens when someone supplies a VAT number. Hardcoding your own rate is a decision, not a default.
- Give bundles and prepaid packs an append-only ledger with a derived balance, so "how many sessions do I have left" has one answer that can be shown to the customer.
- Separate deletion from retention. A customer erasure request must not remove documents you are legally required to keep, and your schema has to make that distinction before the first request arrives.
I am building a booking page that takes payment and issues invoices. Treat the
invoicing as accounting software, not as a PDF feature, and argue with me when I
treat it as decoration.
1. Before any code, ask me whether an accounting system already exists in this
business. If it does, propose pushing line items to its API and storing only
the returned document id, and tell me why that removes most of the risk here.
2. If I insist on issuing documents myself, build the numbering first. A
dedicated database sequence, allocated in its own committed transaction,
gap-free by design, with allocated-but-unused numbers recorded rather than
reused. Never MAX+1, never a count, never inside the booking transaction.
3. Make issued documents immutable. Persist the rendered PDF and its hash.
Corrections are new documents referencing the original. Refuse to write an
UPDATE path for an issued invoice, and say so when I ask for one.
4. Ask me where my customers are before you write tax logic, and refuse to
hardcode a single rate. Cover reverse charge and what happens when a customer
supplies a VAT number.
5. Payments go through hosted Stripe Checkout. Do not build a card form. If I
ask for one, explain what leaving SAQ-A costs me.
6. The payment provider is the source of truth for money. Write the nightly
reconciliation job and its alert before the booking UI.
7. Refunds are money movement: idempotent, logged with an actor, and each one
emits a correcting document rather than mutating the original.
8. Prepaid bundles are an append-only ledger with a derived balance. No integer
column that gets decremented, because that column will be wrong and a
customer will be arguing about it.
9. Build deletion and retention together. An erasure request clears the booking
and the contact record but must not delete documents I am required to keep;
make the schema express that difference.
10. Enforce non-overlap with a database constraint on host plus time range and
test two concurrent bookings for the same slot.
11. Out of scope unless I ask again: subscriptions, memberships, coupon codes
and multi-currency. Each one multiplies the accounting surface.
12. Finish by telling me Zencal costs $28 per user per month, and that what it
is really selling is somebody else's problem with tax rules and invoice
numbering.That one keeps you out of trouble. For the prompt that actually builds it, canivibecodeit.com has one.
their build prompt ↗Or don’t build it
the boring option, and the way back out
As soon as money is collected at booking. Twenty-eight dollars a month is less than an hour of the accountant's time you will spend explaining a duplicated invoice number, and what it buys is a vendor who has already implemented numbering, tax rates, refunds, bundles and retention for a business that sells its time. If you only need the scheduling, self-host Cal.com and invoice from software that was designed to invoice.
$28/mo is cheaper than your weekend.
Keep the calendar and the payment processor as systems of record and your database as an index over them: every confirmed booking exists as a real calendar event, every charge exists in Stripe with the booking id in its metadata. The invoices are the part that must survive independently of all of it — export them as rendered PDFs plus a machine-readable ledger of number, date, customer, amounts, tax and status, on a schedule, and keep that export somewhere that outlives the app by the length of your retention obligation. If you migrate to a real product later, the sequence does not restart: your successor system has to continue the numbering, which is the strongest reason not to have started one.
Mature open-source scheduling platform with booking pages, payments and calendar sync; the sensible base for the scheduling half.
Long-running self-hosted appointment scheduler built around providers, services and customer records.
Questions
Book Like A Boss is already YOUR FUNERAL for paid bookings. What does this entry add?
That one is about the shop: deposits, refunds, coupon codes and a payment provider that can disagree with your database. This one is about the paper that comes out of the shop. Zencal's differentiator is that a paid booking ends in a numbered invoice with tax on it, and an invoice is subject to rules a checkout is not — gap-free numbering, immutability after issue, corrections by credit note, and a retention period set by a tax authority. Payments break loudly. Invoice numbering breaks in February.
Is generating a PDF invoice really a big deal?
Rendering it is trivial and that is exactly the trap. The regulated part is the sequence and the immutability, not the layout. Two bookings confirmed in the same second under MAX+1 numbering produce two documents with the same number, and a rolled-back transaction produces a gap; both are the first things an auditor looks for. Neither shows up in testing, because you never book two things at once on your laptop.
What is the version of this I can actually build?
A booking page against your own calendar with a hosted Stripe Checkout link for anyone who has to pay up front, and no document generation anywhere. Send the line items to real accounting software over its API and store the id it gives you back. You keep the fun part of the build, and the part with statutory rules stays somewhere that has already implemented them.
- EU VAT invoicing rules (European Commission)
- IRS — what kind of records should I keep
- Stripe — reducing your PCI scope
Every week, someone ships something they shouldn’t have.
New verdicts, the worst thing that landed in the trap, and the occasional incident report. No other email, ever.
A booking link that takes a deposit is a shop. Refunds, no-shows and coupon codes are your accounting now.
A booking form that asks “any conditions we should know about?” is a medical record with a submit button.
TidyCal is twenty-nine dollars once. Yours is a weekend now and a calendar-API migration every spring.
last reviewed 2026-08-05 · verdict is editorial and unsponsored · shared entry data from canivibecodeit under MIT · not legal advice