<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[BookTaxiService Engineering Notes]]></title><description><![CDATA[BookTaxiService Engineering Notes]]></description><link>https://booktaxiservice.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 04 Nov 2026 15:55:14 GMT</lastBuildDate><atom:link href="https://booktaxiservice.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Payment status is not trip status: modelling card capture in a taxi booking system]]></title><description><![CDATA[Why a taxi booking system should track payment and trip state separately, and how capture policies, refunds and payouts fit around them.]]></description><link>https://booktaxiservice.hashnode.dev/payment-status-is-not-trip-status-modelling-card-capture-in-a-taxi-booking-system</link><guid isPermaLink="true">https://booktaxiservice.hashnode.dev/payment-status-is-not-trip-status-modelling-card-capture-in-a-taxi-booking-system</guid><category><![CDATA[SaaS]]></category><category><![CDATA[stripe]]></category><category><![CDATA[payments]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[BookTaxiService]]></dc:creator><pubDate>Tue, 03 Nov 2026 00:56:48 GMT</pubDate><content:encoded><![CDATA[<p>A common shortcut in booking systems is a single <code>status</code> column: <code>pending</code>, <code>confirmed</code>, <code>completed</code>, <code>paid</code>. It looks tidy until the first time a trip is completed and the card fails, or a customer is charged and then the trip is cancelled. At that point one column is trying to describe two different things that change at different times for different reasons.</p>
<p>This post walks through why a taxi booking system ends up with two state machines, one for the trip and one for the payment, and the decisions that sit between them.</p>
<h2>Two lifecycles, not one</h2>
<p>The trip lifecycle is operational. In a typical taxi workflow it looks something like:</p>
<pre><code>requested -&gt; confirmed -&gt; driver_assigned -&gt; driver_on_way
          -&gt; driver_arrived -&gt; passenger_on_board -&gt; completed
</code></pre>
<p>with <code>cancelled</code> reachable from most states. Drivers and dispatchers move it forward.</p>
<p>The payment lifecycle is financial and is driven by the card processor:</p>
<pre><code>none -&gt; authorised -&gt; captured -&gt; (partially_)refunded
     \-&gt; failed
authorised -&gt; cancelled (authorisation released)
</code></pre>
<p>These move independently. A trip can be <code>completed</code> while the payment is still <code>authorised</code>, or <code>failed</code>. A payment can be <code>captured</code> at booking time while the trip hasn't started. If you collapse both into one field, you lose the ability to say "the job is done but we haven't collected the money", which is exactly the state your office needs to see.</p>
<p>The rule worth writing down: a completed trip is not proof of payment.</p>
<h2>Capture policy is a business decision</h2>
<p>With card processors like Stripe you can capture immediately, or authorise first and capture later. For taxis, both make sense depending on the job:</p>
<ul>
<li><strong>Immediate capture</strong> is simplest. The customer pays when they book or accept a quote. Good for fixed-price jobs where the fare won't change.</li>
<li><strong>Authorise now, capture later</strong> suits work where the final fare may change, or where you only want to take money once the job is real. You then need a trigger: capture on confirmation, on completion, or at a set time before pickup.</li>
</ul>
<p>Making this a configurable policy, rather than hard-coding it, lets an operator run airport transfers one way and hourly bookings another. It also means your capture job has to handle edge cases. Authorisations don't last forever (for online card payments it's typically around seven days with Stripe, so check the current docs for your card types), so a booking made two weeks ahead can't simply be authorised at booking time and captured on the day.</p>
<h2>Make the transitions idempotent</h2>
<p>Payment events arrive late, twice, or out of order. A few general habits help:</p>
<ul>
<li>Use idempotency keys on every create, capture and refund call, derived from your own booking and attempt IDs.</li>
<li>Store the processor's object IDs on your payment record and treat the processor as the source of truth for financial state.</li>
<li>Only allow legal transitions. A refund against a payment that was never captured should be rejected by your code, not by the processor.</li>
<li>Keep an event log per payment so the office can see what happened and when.</li>
</ul>
<h2>Refunds belong to the payment, not the trip</h2>
<p>Cancelling a trip and refunding a payment are separate actions. An operator may cancel and keep a cancellation fee, or refund part of a fare after a complaint on a completed trip. Supporting partial refunds against the payment record, without touching trip state, keeps both histories honest.</p>
<h2>Payouts are accounting, not money movement</h2>
<p>The other side of a taxi business is paying drivers. It's tempting to model "payout processed" as if money moved. Unless your system actually initiates a bank transfer, be explicit that processing a payout is a record. In Australia, a practical middle ground is generating an ABA file the operator uploads to their bank. The system marks the records as paid; the bank moves the money.</p>
<p>If you want the broader picture of how a booking gets from the website form to a driver, I wrote about that in <a href="https://dev.to/booktaxisvc/from-website-form-to-drivers-phone-how-a-taxi-booking-form-and-a-dispatch-board-fit-together-555d">how a taxi booking form and a dispatch board fit together</a>.</p>
<h2>How BookTaxiService does it</h2>
<p>BookTaxiService is a browser-based booking and dispatch platform for taxi operators in Australia and New Zealand, built by 2203 Pty Ltd in Melbourne. A few of its payment design choices line up with the points above:</p>
<ul>
<li>Operator fares go through the <strong>operator's own Stripe credentials</strong>. It does not use Stripe Connect for operator fare checkout.</li>
<li><strong>Immediate capture is the default.</strong> Authorisation and later capture are available under configured policies, which can trigger capture on confirmation, on completion or before pickup.</li>
<li><strong>Payment status is tracked separately from trip status</strong>, which moves through driver on way, arrived, passenger on board and completed.</li>
<li><strong>Full and partial refunds</strong> are issued from the payment workflow.</li>
<li>Card details go into Stripe's payment fields. The platform <strong>doesn't store full card numbers</strong>; for saved cards it keeps Stripe's reference plus the last four digits, expiry and cardholder name.</li>
<li><strong>Driver payout processing records payments as paid</strong> and, on plans that include it, exports Australian ABA files. It doesn't transfer money itself.</li>
</ul>
<p>The full feature description is on the <a href="https://booktaxiservice.net/product/payments/">BookTaxiService payments and driver payouts page</a>.</p>
<h2>Takeaway</h2>
<p>If you're building anything where a service is delivered and paid for at different moments, give each its own state machine and decide up front which events are allowed to move each one. It costs a second column and a few more tests. It saves you from guessing, months later, whether "completed" meant the driver finished or the money arrived.</p>
]]></content:encoded></item></channel></rss>