Developing a Mobile App for Parking Payment
A parking payment app is not just a "Pay" button. Key complexity here is session model: parking session starts at entry, ends at exit or when paid time expires. User must know remaining time and receive warning before penalties kick in. This demands precise push notifications, background timers, and reliable gateway integration.
How Parking Session Works
Central entity—ParkingSession. Has lifecycle:
IDLE → ACTIVE → EXPIRING (15 min before end) → EXPIRED / EXTENDED
State transitions managed server-side. App displays current state via polling or WebSocket. Device background timer—only for UI, not business logic.
Typical session object:
{
"sessionId": "PSN-20240921-4471",
"zoneCode": "A-12",
"vehiclePlate": "А123ВС77",
"startedAt": "2024-09-21T10:15:00+03:00",
"expiresAt": "2024-09-21T12:15:00+03:00",
"rate": 60,
"currency": "RUB",
"status": "ACTIVE",
"paymentStatus": "PAID"
}
License Plate Recognition via Camera
Manual input is poor UX. Better—recognition via camera. On iOS use Vision + VNRecognizeTextRequest:
import Vision
func recognizePlate(from pixelBuffer: CVPixelBuffer) {
let request = VNRecognizeTextRequest { [weak self] request, error in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
let candidates = observations.compactMap { $0.topCandidates(1).first?.string }
let plateRegex = /[АВЕКМНОРСТУХ]{1}\d{3}[АВЕКМНОРСТУХ]{2}\d{2,3}/
let plate = candidates.compactMap { $0.firstMatch(of: plateRegex)?.0 }.first
DispatchQueue.main.async {
self?.vehiclePlateField.text = plate.map(String.init) ?? ""
}
}
request.recognitionLevel = .accurate
request.recognitionLanguages = ["ru-RU"]
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try? handler.perform([request])
}
On Android similarly via ML Kit Text Recognition:
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
val image = InputImage.fromMediaImage(mediaImage, rotationDegrees)
recognizer.process(image)
.addOnSuccessListener { visionText ->
val plateRegex = Regex("[АВЕКМНОРСТУХ]{1}\\d{3}[АВЕКМНОРСТУХ]{2}\\d{2,3}")
val plate = visionText.textBlocks
.flatMap { it.lines }
.mapNotNull { plateRegex.find(it.text)?.value }
.firstOrNull()
vehiclePlateField.setText(plate)
}
Recognition accuracy for Russian plates on quality photos—about 85–90%. For better accuracy additionally apply OpenALPR via server API for complex cases.
Session Timer and Push Notifications
Countdown timer—most requested UI element. On iOS lives in ProgressView + Timer, on Android in CountDownTimer. Background notifications via APNs/FCM.
Server notification logic:
- 15 minutes before
expiresAt→ push "Parking expires in 15 minutes" - 5 minutes before → push with "Extend 1 hour" / "Stop" buttons
- At
expiresAtmoment → push "Parking session ended"
On iOS buttons in push via UNNotificationCategory:
let extendAction = UNNotificationAction(
identifier: "EXTEND_PARKING",
title: "Extend 1 hour",
options: [.foreground]
)
let stopAction = UNNotificationAction(
identifier: "STOP_PARKING",
title: "Stop",
options: [.destructive]
)
let category = UNNotificationCategory(
identifier: "PARKING_EXPIRING",
actions: [extendAction, stopAction],
intentIdentifiers: []
)
UNUserNotificationCenter.current().setNotificationCategories([category])
Tapping "Extend" from notification—app opens on extension screen and auto-initiates payment with saved card without extra steps.
Integration with Parking Equipment
If parking managed by access control or barrier, integration via operator server API. Common protocols: SOAP/XML (legacy), REST JSON (modern). App doesn't communicate with equipment directly—only via backend.
For barrier opening via QR code at exit use AVCaptureSession:
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else { return }
session.addInput(input)
let output = AVCaptureMetadataOutput()
session.addOutput(output)
output.setMetadataObjectsDelegate(self, queue: .main)
output.metadataObjectTypes = [.qr]
session.startRunning()
Payment Flow
Parking payment typically two scenarios:
Pre-paid—buy time before entry. User selects zone, time, pays. Server issues session code. At entry operator scans QR or reads license plate.
Post-paid—payment at exit. Session starts automatically at entry (by plate), sum calculated at exit, app offers payment.
For both use saved card via provider token (CloudPayments, YooKassa, Stripe). One-off payment without card save—via payment web widget in WKWebView/WebView. Recurring payments (parking subscription)—via recurring with token.
Stack and Architecture
| Component | iOS | Android |
|---|---|---|
| UI | SwiftUI + UIKit (camera) | Jetpack Compose + CameraX |
| Plate Recognition | Vision Framework | ML Kit Text Recognition |
| Maps | MapKit / Google Maps SDK | Google Maps SDK |
| Payments | Stripe iOS SDK / CloudPayments | Stripe Android SDK / CloudPayments |
| Push | APNs via Firebase | FCM |
| Architecture | MVVM + Combine | MVVM + StateFlow |
Timeline Estimates
Basic version (sessions, timer, card payment, push): 4–6 weeks. Add plate recognition, equipment integration, subscriptions—another 2–4 weeks. Pricing is calculated individually after requirements analysis.







