Mobile app for railway ticket sales: bottlenecks
Selling railway tickets online is a store where each item is unique: a seat in a specific carriage on a specific date. In the first minutes after sales open, dozens of people try to book it at the same time.
If the client decides that "the seat is free," two people pass confirmation and receive the same travel document — and it has to be sorted out manually.
The second bottleneck — freezing during payment. The payment went to the bank, but no response came back: the network dropped or the screen closed. The person sees an endless spinner, retries, and gets a second charge with a single ticket not issued.
Next — mismatch of seat availability. The app shows availability from cache, the ticket office sells from its own database, and between updates there remains a window when a seat is "free" only on the phone. The client reaches payment and hits a refusal.
Losing the cart when the connection drops — the most common cause of an abandoned purchase. Train, carriage, seats, and passenger data live in the screen's memory: subway, incoming call, restart — and everything starts over.
Re-entering passengers on a phone takes exactly the minutes during which the seat goes to someone else.
All four points are measurable: share of double sales, depth of mismatch with the ticket office, time to return to an abandoned order. We break down how this is closed — from server-side seat locking to an order draft that survives a disconnection.
What changes after launch: purchase speed and refunds
Launch changes not the visuals but measurable indicators: how many people reach payment, how much time each spends, whether the system withstands peak load. Below — what exactly we check on data after release.
Buyer journey: from search to boarding pass
Before launch, a person takes extra steps: searches for a trip, enters passport data manually, selects a seat separately. After — search parameters are saved, passenger data is filled in from the profile, payment happens on one screen.
Purchase speed decreases, and the share of completed orders grows without discounts: between selection and payment there are fewer decisions where one can change their mind.
We calculate journey time by segments — individual trip, group, return route. This shows where buyers are still lost: most often it is manual data entry for several passengers and repeated authorization.
Behavior on the day sales open
The day sales open is a separate scenario: load arrives within minutes rather than growing smoothly. We check that the order queue, limits on simultaneous requests, and schedule cache withstand the peak, and that payments do not hang in an intermediate status.
If a booking is confirmed both after a minute and in the first hour, the share of completed orders on that day does not fall — resilience becomes measurable, not promised.
The flip side — support requests. When ticket status is visible in the app, and a payment error is explained in human language, the flow of "ticket did not arrive," "charged twice" noticeably decreases.
This is the acceptance criterion: metrics are taken before release and some time after, on the same segments.
Implementation options: native development, single codebase, web
In a mobile app for railway tickets, the approach is chosen by purchase scenario, not by team preference.
For occasional trips, schedule search and payment are critical; for frequent ones — access to the boarding pass without network, platform change notifications, and fingerprint login. This determines where response time is lost.
| Option | When it fits | Limitations |
|---|---|---|
| Native development | Camera for documents, biometrics, notifications, offline operation; platforms as separate products | Two codebases, parallel releases, more expensive support |
| Single codebase | One purchase scenario on two platforms, release speed matters | Heavy graphics and system modules require native inserts |
| Web version installed to home screen | Occasional purchases, following a link without a store | Some system functions unavailable, offline limited |
| Improving a released app | App already in stores, user base exists | Architecture constrained by past decisions |
Improving an already released app
If the app is already released, changing the approach rarely pays off. First we take measurements of cold start, schedule search, and payment completion, find the bottleneck, and close it pointwise: schedule cache, payment screen rework, request queue.
What cannot be solved by changing the approach
The server side cannot be cured by changing the approach. Slow search, payment failures, and the lack of a retry for refunds remain on the services side, while store requirements for updates and personal data handling apply in any option.
Implementation stages: from sales system audit to handover
We start development not with search screens but with an audit of the sales system: which train and seat directories are available, how a booking is made, where refunds and exchanges live.
Until it is clear where current seat availability comes from and how often it changes, any search mockup is just a drawing.
Preparation: audit and data exchange scheme
- Inventory of sources: we analyze requests to the carrier's system — search, booking, payment, order status — and record the response time of each.
- Data exchange scheme: we describe the flow "search → seat selection → booking → payment → ticket," marking operations that must be idempotent on retry.
- Local device loop: schedule cache and queue of unsent requests so an order is not lost on a rail segment or in the subway.
Validation: load, connection drops, refunds
- Load testing: we reproduce the opening of sales for a popular route — search spike, competition for last seats, payment service failure.
- Disconnection scenarios: we break the connection between payment and confirmation and check that resending does not create a second booking or charge twice.
- Refunds and exchanges: we run the full cycle — request from the app, status update, passenger notification.
- Project handover: we hand over source code, exchange documentation, access credentials, and release regulations; acceptance goes by checklist.
By handover, the customer receives not a build but a predictable sales loop that withstands peak load and does not break on weak connectivity.
What the customer keeps: configurations, schemes, regulations
We hand over not an archive with source code but a package your team can work with without us: where data comes from, who has the right to act, how an update is released, and what to do if ticket sales stop. Below — what exactly is recorded in the documentation and remains with the customer.
Documentation on data exchange and access roles
- Data exchange scheme: which requests go between the app, payment gateway, and carrier systems, in what format responses arrive, and how the app behaves when an external service fails.
- Access roles: which employees see orders and personal data, who processes refunds and transfers, who changes schedules and fares; permission boundaries and the procedure for granting them.
- Operator instructions: handling typical passenger requests — payment failed, ticket not displayed, refund, transfer.
- Data and log map: where orders and trips are stored, how to use an identifier to find the full history of an operation and determine the cause of a failure.
Support and update release regulations
- Update release regulation: branching, mandatory checks before publication, gradual rollout to a share of users, and conditions for rollback to the previous build.
- Criticality matrix: what is fixed out of turn (payment, boarding) and what goes into a scheduled release.
- Behavior when external systems are unavailable: how the app works in limited-sales mode and what the passenger sees.
Documents live in the repository alongside the code and pass the same checks as the release — therefore the regulation does not diverge from the app's actual behavior.
Case: launching sales on 40 routes without downtime
What it was before: manual reconciliations and queues
Sales on 40 routes relied on batch exchange with the carrier's ticket system RZD: seat availability was pulled every few minutes, and mismatches were resolved manually — an operator compared the export with app records and corrected statuses. On peak days, requests queued up until confirmation.
The cost of a failure was not only slow processing. A seat sold twice surfaced at boarding: a passenger with a valid ticket was not allowed into the carriage, and the night support shift manually removed the booking and looked for a replacement. Recovering such an incident took up to 40 minutes.
What it became: metrics after launch
We moved ticket operations into a separate booking service: a seat is held during payment and confirmed upon transaction, and the app no longer waits for batch exports.
The hold is released by timeout, and double sale is closed by seat locking — the "sold twice" failure class disappeared along with manual reconciliations.
In terms of load, 40 routes withstand the peak without a queue: booking is confirmed within 2 seconds, and the service comes up in 3-4 minutes after a failure. The repeat purchase share grew from 19 to 43% — driven by stable booking statuses and predictable processing time. Support freed night shifts for real requests instead of reconciling exports.
How to arrange a refund and exchange if a trip is canceled?
Trip cancellation is a batch event: dozens of documents are voided at once, and the app receives not one order but a list of affected trips. We reconcile it with the carrier's ticket accounting system: which forms are voided and which remain valid.
Without reconciliation, some passengers will receive a refund for a trip that will take place, and some will be left without money.
Next we fix the sequence: the document is marked voided, boarding is blocked, then a refund for trip cancellation without a fee is issued, and only after that — a notification with the order number and the amount to be credited. The passenger sees the reason and status before calling support.
Exchange of a travel document is a separate branch. If a suitable trip exists, we offer to rebook in the app: the seat is selected anew, the fare difference is collected or refunded by the same payment method, and the old form is canceled only after the new one is issued. If a failure occurs midway, the passenger remains with a valid ticket rather than without both.
The refund period is set by the issuing bank, so in the interface we show the expected range and status at each step: request accepted, funds returned, rejected with reason. Pressing again does not create a second refund — the operation is marked with a key by document number.
We handle mass refunds with a queue limiting parallel operations and retries with increasing delay: the payment service does not consider the load suspicious, and the ledger does not grow faster than confirmations.
Disputed operations — refund to another card, cancellation after exchange, request after the deadline — go into a separate journal with status history, where a decision is made by an operator.
Technical doubts: integration, load, security
Integration with the carrier's ticket office system is the first thing checked at the start: exchange goes through a separate synchronization layer, ticket offices continue selling in their own mode, and the mobile app receives and releases seats in a short cycle.
If exchange stops, ticket office sales do not stop — the queue catches up with the state on its own.
Below — five technical doubts most often raised before work begins. We address each substantively, because trust in the system rests precisely on them.
How to integrate without stopping ticket offices?
The seat scheme remains in the ticket office system, and the app works with it through an intermediate service. Rollout goes on one route or segment, ticket offices continue operating as before, and disconnection is done by removing the exchange route.
What does the passenger see when the connection drops?
Behavior during a connection drop is described in advance: search and schedule are served from local cache, processing either reaches confirmation or does not start — no "hanging" seats without a ticket remain. A repeated request is recognized as the same one and does not create a second booking.
Who is responsible for seat mismatches?
Mismatch is closed at the exchange level: a seat is marked occupied only after confirmation from the ticket office system, and disputed cases go into the reconciliation journal.
Access separation is built by roles — the carrier sees its trips and refunds, a partner carrier sees only statistics.
How do refunds initiated by the carrier work?
The system receives a cancellation or train composition change event and itself initiates a refund for all affected tickets; the passenger receives a notification. The status of each ticket is visible in the personal account and in the operations journal.
What is included in post-launch support?
Post-launch support is incident duty, analysis of exchange errors, updates for new versions of carrier systems and platforms. Separately, we track metrics: share of successful processing, response time, number of support requests.
Let's discuss the task: what to send and how we start work
To make the conversation substantive rather than "tell us in general terms," send what already exists: a description of sales scenarios, access to the test environment of booking systems, examples of exports.
Based on these materials, we return with an estimate of the task and the scope of work for the first delivery — without counter-requests to "first invent it for us."
What to prepare for the first discussion:
- passenger scenarios: trip search, seat selection, payment, refund and ticket reissue — indicating which of them already work in the web ticket office;
- access and data: test environment, exchange schemes with sales systems, export formats, and if available — a description of existing methods;
- current state: existing app or web version, support request statistics, and share of incomplete purchases;
- constraints: personal data storage requirements, payment providers, refund rules that cannot be changed;
- priorities: what is mandatory in the first delivery and what is moved to subsequent stages;
- technical contact person who will confirm access and answer integration questions.
Then the order is: substantive discussion of the task, estimate of the task and scope of work, approval of the phased implementation plan, then handover of documentation — integration schemes, environment configurations, and instructions for your support team.
If there is still little material, come with what you have: at the meeting we will determine what is missing for the plan and record who prepares what.







