Fixes & Updates - 2026-06-01: Integrate Back-End v3 updates, fix call/connection issues across apps

This commit is contained in:
Hamza-Ayed
2026-06-01 23:35:29 +03:00
parent 8f555691b9
commit cbf693c804
56 changed files with 6091 additions and 1217 deletions

View File

@@ -0,0 +1,918 @@
# Intaleq Driver App — Complete Ride Lifecycle Analysis Report
## Table of Contents
1. [System Architecture Overview](#1-system-architecture-overview)
2. [Ride State Machine](#2-ride-state-machine)
3. [Phase 1: Ride Request Ingress (Socket → Order Request)](#3-phase-1-ride-request-ingress)
4. [Phase 2: Accept Order & Navigation to Pickup](#4-phase-2-accept-order--navigation-to-pickup)
5. [Phase 3: Arrival & Begin Ride](#5-phase-3-arrival--begin-ride)
6. [Phase 4: In-Ride Navigation & Polyline System](#6-phase-4-in-ride-navigation--polyline-system)
7. [Phase 5: Finish Ride & Payment](#7-phase-5-finish-ride--payment)
8. [Phase 6: Post-Ride (Rating, Review)](#8-phase-6-post-ride-rating-review)
9. [Pricing Engine](#9-pricing-engine)
10. [Socket.IO Communication](#10-socketio-communication)
11. [HTTP Backend API Endpoints](#11-http-backend-api-endpoints)
12. [Polyline Engine Deep Dive](#12-polyline-engine-deep-dive)
13. [Location Tracking System](#13-location-tracking-system)
14. [Voice Call Signaling](#14-voice-call-signaling)
15. [Key Architectural Patterns & Fixes](#15-key-architectural-patterns--fixes)
16. [Data Flow Diagrams](#16-data-flow-diagrams)
---
## 1. System Architecture Overview
### High-Level Component Map
```mermaid
graph TB
subgraph "Driver App sefer_driver"
A[LocationController] -->|Socket.IO| B[Location Server]
C[OrderRequestController] -->|HTTP| D[Ride Server]
E[MapDriverController] -->|HTTP| D
F[NavigationController] -->|HTTP| G[Map SaaS Server]
H[SignalingService] -->|WebSocket| I[Call Server]
J[WalletController] -->|HTTP| K[Payment Server]
end
subgraph "Passenger App Intaleq"
L[RideLifecycleController] -->|Polling/HTTP| D
M[MapEngineController] -->|HTTP| G
end
B -->|Socket.IO Events| A
B <-->|update_location| A
D <-->|Ride CRUD| E
D <-->|Ride CRUD| L
subgraph "Backend Servers"
B[Location Server: location.intaleq.xyz]
D[Ride Server: rides.intaleq.xyz]
G[Map SaaS: map-saas.intaleqapp.com]
I[Call Server: calls.intaleqapp.com]
K[Payment Server: walletintaleq.intaleq.xyz]
end
style A fill:#4a90d9,color:#fff
style C fill:#4a90d9,color:#fff
style E fill:#4a90d9,color:#fff
style F fill:#4a90d9,color:#fff
style L fill:#e67e22,color:#fff
style M fill:#e67e22,color:#fff
```
### Server Infrastructure
| Server | Base URL | Purpose |
|--------|----------|---------|
| API Server | `https://api.intaleq.xyz/intaleq_v3` | Auth, CRUD operations, ride management |
| Ride Server | `https://rides.intaleq.xyz/intaleq/ride` | Ride-specific CRUD |
| Location Server | `https://location.intaleq.xyz` | Socket.IO real-time location, batch uploads, behavior recording |
| Map SaaS | `https://map-saas.intaleqapp.com/api/maps/route` | Route/polyline generation |
| Payment Server | `https://walletintaleq.intaleq.xyz/v1/main` | Wallet management, payment processing |
| Call Server | `wss://calls.intaleqapp.com/ws` | WebRTC signaling for voice/video calls |
### Key Technologies
- **State Management**: GetX (`GetxController`)
- **Real-time**: Socket.IO (`socket_io_client: 1.0.2`) at `https://location.intaleq.xyz`
- **Map**: Custom `intaleq_maps` package (local path: `../map-saas/packages/flutter-sdk/`)
- **Routing API**: OSRM-compatible response format via SaaS
- **Navigation**: Step-by-step with TTS (`flutter_tts: ^4.0.2`)
- **Background**: `flutter_background_service: ^5.1.0`, `flutter_overlay_window: ^0.5.0`
---
## 2. Ride State Machine
The driver app uses an implicit state machine managed via a master `status` variable in [`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart).
```mermaid
stateDiagram-v2
[*] --> NoRide: App start / Return to home
NoRide --> Searching: Socket new_ride_request received
Searching --> DriverApplied: Driver taps Accept Order
DriverApplied --> Searching: Ride taken by another driver
DriverApplied --> DriverArrived: Driver arrives at pickup point
DriverArrived --> InProgress: Driver taps Start Ride / Begin
InProgress --> Finished: Driver taps Finish Ride
Finished --> PreCheckReview: Driver begins review process
PreCheckReview --> NoRide: Review complete / Rate passenger
Finished --> NoRide: Skip review
note right of NoRide: Location streaming active, listening for orders
note right of Searching: OrderRequestPage shown, route calculation
note right of DriverApplied: Navigation to passenger pickup point
note right of DriverArrived: Waiting timer counting, passenger notified
note right of InProgress: Navigation to destination, pricing timer active
```
**State Management Pattern**: The status is stored as a string variable (`status`) and checked throughout with switch/if blocks. Additionally, a `Box` (GetStorage) is used for persistence across app restarts. Key fields in Box include `box.read('status')`, `box.read('ride_id')`, `box.read('tokenPassenger')`.
---
## 3. Phase 1: Ride Request Ingress
### Flow Diagram
```mermaid
sequenceDiagram
participant S as Location Server Socket.IO
participant LC as LocationController
participant ORC as OrderRequestController
participant UI as OrderRequestPage
S->>LC: new_ride_request event
Note over LC: Data format: List or Map
LC->>LC: handleIncomingOrder()
Note over LC: Validate key '16' exists
LC->>LC: Extract DriverList structure
LC->>LC: Sort by distance
LC->>UI: Get.to OrderRequestPage
UI->>ORC: initState -> _initializeData()
ORC->>ORC: Parse List or Map format
ORC->>ORC: _calculateFullJourney()
ORC->>MapSaaS: getRoute for pickup + trip
MapSaaS-->>ORC: Return polylines + distance + duration
ORC->>UI: Update UI with routes
```
### Socket Event: `new_ride_request`
Received in [`location_controller.dart`](lib/controller/functions/location_controller.dart) (lines ~230-323).
**Data Payload** (supports **two formats**):
**Format A — List** (original):
```dart
[
"lat,lng", // [0] start coordinates
"lat,lng", // [1] end coordinates
"price", // [2]
"duration_sec", // [3] trip duration
"total_sec", // [4] total duration
"distance_m", // [5] trip distance
"unknown", // [6]
"passenger_id", // [7]
"customer_name", // [8]
"customer_token", // [9]
"phone", // [10]
"unknown", // [11]
"dist_to_driver_m", // [12] distance to driver
"unknown", // [13]
"unknown", // [14]
"duration_to_driver_sec", // [15]
"ride_id", // [16] - Validation key
...
"start_address", // [29]
"end_address", // [30]
"ride_type", // [31] Speed/Comfort/Lady/etc
"passenger_rate", // [33]
]
```
**Format B — Map** (newer):
```dart
{
'myListString': { ... },
'DriverList': [
{ lat, lng, price, duration, ... },
...
]
}
```
### Validation Gate ([`location_controller.dart`](lib/controller/functions/location_controller.dart), lines 327-399)
```dart
void handleIncomingOrder(dynamic data) {
// Check if data has key '16' (ride_id) — if so, treat as List format
// Otherwise, extract from myListString/DriverList
// Convert all to sorted DriverList format
// Navigate to OrderRequestPage
}
```
### Ride Taken Prevention ([`order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart), lines 624-649)
A dedicated socket listener prevents double-accept:
```dart
socket.on('ride_taken', (data) {
// Check if ride_id matches current displayed order
// If so, show "ride taken" dialogue and return to home
});
```
---
## 4. Phase 2: Accept Order & Navigation to Pickup
### Accept Flow ([`order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart), lines 672-783)
```mermaid
sequenceDiagram
participant D as Driver
participant ORC as OrderRequestController
participant API as Ride Server
participant Box as GetStorage
participant PDP as PassengerLocationMapPage
participant MDC as MapDriverController
D->>ORC: Tap Accept
ORC->>API: GET acceptRide.php?ride_id=X&driver_id=Y
API-->>ORC: Success response
ORC->>Box: Write rideArgs: ride_id, status=applied, tokenPassenger, carType, kazan, etc.
ORC->>PDP: Get.to(PassengerLocationMapPage, arguments: rideArgs)
PDP->>MDC: argumentLoading() parses rideArgs
MDC->>Box: Persist all ride data
MDC->>MapSaaS: getRoute(for pickup location)
MapSaaS-->>MDC: Polyline + steps for driver->passenger route
MDC->>MDC: Draw polyline on map
MDC->>MDC: Start GPS tracking, step-by-step navigation
```
### Ride Arguments Map ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 2212-2279)
The complete `rideArgs` map written to Box:
| Field | Source | Description |
|-------|--------|-------------|
| `passenger_lat`, `passenger_lng` | From order data | Passenger pickup location |
| `passenger_destination_lat`, `passenger_destination_lng` | From order data | Trip destination |
| `ride_id` | From order data | Unique ride identifier |
| `tokenPassenger` | From order data | Passenger auth token |
| `carType` | Driver profile | Speed/Fixed/Comfort/Lady/Electric/Van/Delivery |
| `kazan` | From order data | Commission percentage |
| `status` | Set to `applied` | Current ride state |
| `price` | From order data | Trip price |
| `customerName` | From order data | Passenger name |
| `tripDistance` | From order data | Total trip distance |
| `tripDurationMin` | Calculated | Trip duration in minutes |
---
## 5. Phase 3: Arrival & Begin Ride
### Arrival Detection
The driver's GPS is continuously monitored. When the driver reaches within ~150m of the passenger, the "Arrived" UI state activates.
### Begin Ride ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 838-952)
```mermaid
sequenceDiagram
participant D as Driver
participant MDC as MapDriverController
participant API as Ride Server
participant Box as GetStorage
participant PSGR as Passenger App
D->>MDC: Tap "Begin Ride" / startRideFromDriver()
MDC->>MDC: Validate distance from passenger < 150m
MDC->>API: GET start_ride.php?ride_id=X&driver_id=Y&passenger_id=Z&timestamp=...
API-->>MDC: Success
MDC->>Box: Update status to 'begin'
MDC->>PSGR: Socket emit update_location with status=begin
MDC->>MapSaaS: getRoute(for destination, from current location)
MapSaaS-->>MDC: New polyline from current pos -> destination
MDC->>MDC: Redraw polyline on map
MDC->>MDC: Start pricing timer (rideIsBeginPassengerTimer)
MDC->>MDC: Start step-by-step navigation
```
**Key Validation**: Distance from passenger must be <150m before ride can begin. This prevents starting the ride prematurely.
---
## 6. Phase 4: In-Ride Navigation & Polyline System
### Full Navigation Stack
```mermaid
graph TB
subgraph "Navigation System"
GPS[Geolocator Stream] --> Filter[Jitter Filter <2m]
Filter --> NavCtrl[NavigationController]
Filter --> MDC[MapDriverController]
NavCtrl --> RouteMatch[RouteMatcherWorker Isolate]
NavCtrl --> PolylineDecode[DecodePolylineIsolate]
RouteMatch --> SmartSnap[Smart Sliding Window Snapping]
SmartSnap --> SplitPoly[Split Traveled vs Upcoming]
PolylineDecode --> RouteCoords[Full Route Coordinates]
RouteCoords --> StepNav[Step-by-Step Instructions]
StepNav --> TTS[Flutter TTS Voice]
SplitPoly --> GreyPoly[Grey Traveled Polyline]
SplitPoly --> ColorPoly[Colored Upcoming Polyline]
end
GPS --> Camera[Adaptive Camera]
Camera --> Zoom[Speed-Based Zoom: 15-19]
Camera --> Tilt[Speed-Based Tilt: 0-55deg]
MDC --> Pricing8[Pricing Timer: 1s interval]
```
### Polyline Rendering (Dual Polyline System)
The driver app renders **two polylines simultaneously**:
1. **Upcoming Route** (`polyline` in `MapDriverController`): Colored polyline from the driver's current snapped position to the destination
2. **Traveled Route** (`polyline2` in `MapDriverController`): Grey polyline showing the path already driven
### Smart Sliding Window Snapping ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 2563-2622)
```dart
void _updateTraveledPolylineSmart(LatLng currentLocation) {
// 1. Define sliding window of 60 points around last known index
// 2. Find closest point on the original route polyline within that window
// 3. Split the original route at the matched index:
// - Points [0..matchedIndex] -> traveled (grey polyline)
// - Points [matchedIndex..end] -> upcoming (colored polyline)
// 4. Update map with both polylines
}
```
### Isolate-Based Route Matching ([`route_matcher_worker.dart`](lib/controller/home/navigation/route_matcher_worker.dart))
The heavy computation of finding the closest point on the route polyline is offloaded to a **dedicated isolate**:
```
Messages: init, match, dispose
Response: matchResult { index, lat, lng, dist }
```
- Uses **Float64List** for zero-copy memory sharing
- Sliding window search (default: 120 points, configurable)
- **Haversine distance** for accurate meter-level distance calculation
- **Projection onto line segments** for sub-point accuracy
### Step-by-Step Navigation ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 211-273)
```dart
void startListeningStepNavigation() {
// 1. Subscribe to Geolocator stream with jitter filter (<2m ignore)
// 2. Smooth animation via AnimationController
// 3. Snap to route (update traveled polyline)
// 4. Check proximity to next step waypoint
// 5. If near next waypoint:
// - Speak next instruction via TTS
// - Update currentStepIndex
// - Show next instruction distance
// 6. Update camera position (adaptive zoom/tilt based on speed)
}
```
**Adaptive Camera**:
| Speed | Zoom | Tilt |
|-------|------|------|
| < 15 km/h | 19 | 0° |
| < 40 km/h | 18 | 40° |
| < 70 km/h | 17 | 55° |
| < 100 km/h | 16 | 55° |
| 100+ km/h | 15 | 55° |
---
## 7. Phase 5: Finish Ride & Payment
### Finish Ride Flow ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 1236-1354)
```mermaid
sequenceDiagram
participant D as Driver
participant MDC as MapDriverController
participant API as Ride Server
participant PayAPI as Payment Server
participant Box as GetStorage
D->>MDC: Tap "Finish Ride"
Note over MDC: Validate trip distance anti-fraud
par Parallel Execution
MDC->>API: finish_ride_updates.php
MDC->>PayAPI: process_ride_payments.php
end
API-->>MDC: Ride status updated to finished
PayAPI-->>MDC: Payment processed
MDC->>MDC: Stop pricing timer
MDC->>MDC: Stop navigation / polyline
MDC->>Box: Update status to 'finished'
MDC->>Box: Save ride price to payment_summary
MDC->>MDC: Clear polyline, markers, camera
MDC->>MDC: Show ride summary / review UI
```
### Anti-Fraud Distance Validation ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines ~1290-1330)
```dart
_validateTripDistance() {
// Actual traveled distance must be >= 1/5 of expected trip distance
// If not, auto-reject as potential fraud
// This prevents drivers from starting and immediately finishing rides
}
```
### Payment Processing ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines ~1330-1354)
- Payment is processed in parallel with ride finish
- Payment server generates secure tokens for wallet transactions
- Supports: Stripe, PayMob, MTN, Syriatel, eCash, ShamCash
---
## 8. Phase 6: Post-Ride (Rating, Review)
After finishing, the driver enters the `preCheckReview` state:
1. **Rating**: Rate the passenger (`addRateToPassenger.php`)
2. **Review Screen**: `ride_calculate_driver.dart` shows:
- Trip price breakdown
- Commission (kazan%)
- Net earnings
- Distance/time summary
3. **Return to Home**: Status reset to `noRide`, ready for next order
---
## 9. Pricing Engine
### Core Pricing Timer ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 1481-1570)
```dart
void rideIsBeginPassengerTimer() {
Timer.periodic(Duration(seconds: 1), (timer) {
// 1. Calculate distance delta since last tick
// 2. Fetch current car type pricing config
// 3. Apply time-of-day multiplier
// 4. Apply distance-based thresholds
// 5. Apply airport surcharge if applicable
// 6. Calculate commission (kazan%)
// 7. Update live price display
});
}
```
### Price Calculation Formula ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 1572-1662)
```dart
double _calculateCurrentPrice() {
// Base price depends on carType:
// Speed, Fixed, Comfort, Lady, Electric, Van, Delivery
//
// Time-of-day bands:
// nature (normal), late (evening/night), heavy (peak)
//
// Distance thresholds:
// 25km, 35km, 40km - different pricing tiers
//
// Commission: kazan% taken by platform
//
// Airport contexts: additional surcharge
//
// Formula (simplified):
// basePrice = carType.baseRate * timeMultiplier
// distancePrice = distance * carType.perKmRate
// if distance > 25km: apply longTripMultiplier
// if airport: add airportSurcharge
// finalPrice = (basePrice + distancePrice) * (1 + kazan/100)
}
```
---
## 10. Socket.IO Communication
### Connection Setup ([`location_controller.dart`](lib/controller/functions/location_controller.dart), lines 183-228)
```dart
void initSocket() {
socket = io(
'https://location.intaleq.xyz',
<String, dynamic>{
'transports': ['websocket'], // WebSocket-only transport
'query': {
'driver_id': driverId,
'token': authToken,
},
},
);
}
```
### Events Summary
| Event | Direction | Frequency | Purpose |
|-------|-----------|-----------|---------|
| `new_ride_request` | Server → Driver | On demand | Incoming ride request |
| `ride_taken` | Server → Driver | On demand | Ride accepted by another driver |
| `cancel_ride` | Server → Driver | On demand | Passenger cancelled the ride |
| `update_location` | Driver → Server | Every 5-10s | Driver location broadcast |
| `connect` | Bidirectional | On connect | Socket established |
| `disconnect` | Bidirectional | On disconnect | Socket lost |
| Heartbeat (ping/pong) | Bidirectional | Every 25s | Keep-alive |
### Cancel Ride Handler ([`map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart), lines 339-410)
```dart
void processRideCancelledByPassenger() {
// Gatekeeper: stop all timers immediately (Fix 2)
// Stop: pricingTimer, waitingTimer, navigation
// Show cancellation dialog
// Clear ride data from Box
// Reset status to noRide
// Return to HomeCaptain
}
```
### Location Upload ([`location_controller.dart`](lib/controller/functions/location_controller.dart), lines 420-453)
```dart
void emitLocationToSocket() {
Map<String, dynamic> data = {
'driver_id': driverId,
'lat': currentLat,
'lng': currentLng,
'heading': heading,
'speed': speed,
'status': currentStatus,
'distance': distance,
};
// If ride active, inject passenger_id and ride_id
if (rideActive) {
data['passenger_id'] = passengerId;
data['ride_id'] = rideId;
}
socket.emit('update_location', data);
}
```
---
## 11. HTTP Backend API Endpoints
### Ride Lifecycle Endpoints
| Endpoint | Method | Phase | Purpose |
|----------|--------|-------|---------|
| [`acceptRide.php`](lib/constant/links.dart) | GET | Accept | Driver accepts ride offer |
| [`start_ride.php`](lib/constant/links.dart) | GET | Begin | Start the ride trip |
| [`finish_ride_updates.php`](lib/constant/links.dart) | GET | Finish | Complete ride (parallel) |
| [`process_ride_payments.php`](lib/constant/links.dart) | GET | Finish | Process payment (parallel) |
| [`cancelRide/add.php`](lib/constant/links.dart) | POST | Any | Log cancellation |
| [`addCancelTripFromDriverAfterApplied.php`](lib/constant/links.dart) | POST | Applied | Driver cancels after accepting |
### Ride Data Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| [`rides/add.php`](lib/constant/links.dart) | POST | Create ride record |
| [`rides/get.php`](lib/constant/links.dart) | GET | Retrieve ride details |
| [`rides/update.php`](lib/constant/links.dart) | POST | Update ride status |
| [`rides/delete.php`](lib/constant/links.dart) | DELETE | Remove ride record |
| [`getRideStatus.php`](lib/constant/links.dart) | GET | Check ride status |
| [`getRideOrderID.php`](lib/constant/links.dart) | GET | Get order ID for ride |
| [`updateRideAndCheckIfApplied.php`](lib/constant/links.dart) | POST | Atomic status check + update |
| [`getRideStatusFromStartApp.php`](lib/constant/links.dart) | GET | Recover ride status on app start |
### Location Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| [`add_batch.php`](lib/constant/links.dart) | POST | Batch location upload |
| [`save_behavior.php`](lib/constant/links.dart) | POST | Record driving behavior |
| [`get.php`](lib/constant/links.dart) | GET | Get car locations |
| [`getRidesDriverByDay.php`](lib/constant/links.dart) | GET | Daily ride history |
| [`getTotalDriverDuration.php`](lib/constant/links.dart) | GET | Total driving time |
### Map SaaS Endpoint
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `https://map-saas.intaleqapp.com/api/maps/route` | POST | Route calculation with polyline |
| `https://map-saas.intaleqapp.com/api/geocoding/places` | POST | Place search/geocoding |
**Route API Response Format** (OSRM-compatible):
```json
{
"routes": [{
"geometry": {
"coordinates": [[lng, lat], ...],
"points": "encoded_polyline_string"
},
"legs": [{
"steps": [
{
"maneuver": { "location": [lng, lat], "modifier": "straight" },
"instruction": "Continue straight on Main St",
"distance": 123.4,
"duration": 45.6
}
],
"distance": 5000.0,
"duration": 600.0
}],
"distance": 5000.0,
"duration": 600.0
}]
}
```
### Payment Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| [`payment/add.php`](lib/constant/links.dart) | POST | Record payment |
| [`payment/get.php`](lib/constant/links.dart) | GET | Get today's payments |
| [`getAllPaymentFromRide.php`](lib/constant/links.dart) | GET | All payments for a ride |
| [`addPaymentTokenDriver.php`](lib/constant/links.dart) | POST | Generate payment token |
| [`payWithPayMobCardDriver.php`](lib/constant/links.dart) | POST | PayMob card payment |
| [`payWithWallet.php`](lib/constant/links.dart) | POST | Wallet payment |
| [`payWithMTNConfirm.php`](lib/constant/links.dart) | POST | MTN payment confirmation |
| [`payWithSyriatelConfirm.php`](lib/constant/links.dart) | POST | Syriatel payment confirmation |
---
## 12. Polyline Engine Deep Dive
### Polyline Decoding ([`decode_polyline_isolate.dart`](lib/controller/home/navigation/decode_polyline_isolate.dart))
Standard Google Encoded Polyline Format v5:
```dart
List<LatLng> decodePolylineIsolate(String encoded) {
// Standard algorithm:
// 1. Read 5-bit chunks from charCode - 63
// 2. Reconstruct signed value using ZigZag decoding
// 3. Accumulate lat/lng and divide by 1E5
// 4. Add to points list
}
```
Runs in **separate isolate** via `compute(PolylineUtils.decode, ...)` to avoid jank.
### Route Fetching ([`order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart), lines 341-405)
```dart
Future<Map<String, dynamic>> _fetchRouteData(LatLng from, LatLng to) async {
// 1. POST to AppLink.mapSaasRoute with coordinates
// 2. Parse OSRM-style response
// 3. Decode polyline via compute(_decodePolyline, ...)
// 4. Return {distance, duration, polyline, steps}
}
```
### Dual Polyline System (Visual)
```
Before traveling:
[Passenger] ============================================> [Destination]
(Full route in blue/colored)
During travel:
[Driver] ~~~~~~~~~~> [Current Position] ===============> [Destination]
(Grey traveled) (Colored upcoming)
```
### Smart Snapping Visualization
```
Original route points:
P0 --- P1 --- P2 --- P3 --- P4 --- P5 --- P6 --- P7 --- P8
Driver at position X (near P2-P3 segment):
Sliding window: [P0---P1---P2---P3---P4---P5] (window=60)
Closest projection: on segment P2-P3 at point C
Result:
Traveled: P0---P1---P2---C (grey)
Upcoming: C---P3---P4---P5---P6---P7---P8 (colored)
```
---
## 13. Location Tracking System
### Dual-Interval Architecture ([`location_controller.dart`](lib/controller/functions/location_controller.dart))
```mermaid
graph LR
subgraph "Normal Mode"
GPS3[GPS every 5s] --> Record3[Record to buffer every 3s]
Record3 --> Upload2[Upload batch every 2min]
Upload2 --> Socket[Socket.IO emit]
Upload2 --> HTTP[HTTP batch upload]
end
subgraph "Power Save Mode"
GPS10[GPS every 10s] --> Record10[Record to buffer every 10s]
Record10 --> Upload5[Upload batch every 5min]
Upload5 --> Socket
Upload5 --> HTTP
end
```
### Behavior Recording
In addition to location, the system records **driving behavior**:
- Acceleration/deceleration events
- Speed threshold violations
- Uploaded to `save_behavior.php`
### Battery Optimization
- **Wakelock**: Maintained during active rides (`wakelock_plus`)
- **Background Service**: `flutter_background_service` keeps location streaming alive
- **Overlay Window**: `flutter_overlay_window` shows driver status even when app is backgrounded
---
## 14. Voice Call Signaling
### WebSocket Signaling ([`signaling_service.dart`](lib/services/signaling_service.dart))
```mermaid
sequenceDiagram
participant D as Driver App
participant WS as WebSocket wss://calls.intaleqapp.com/ws
participant P as Passenger App
D->>WS: authenticate { session_id, user_id }
WS-->>D: authenticated
P->>WS: authenticate { session_id, user_id }
WS-->>P: authenticated
P->>WS: call_request { target_user_id }
WS->>D: participant_joined { user_id }
D->>WS: offer { sdp }
WS->>P: offer { sdp }
P->>WS: answer { sdp }
WS->>D: answer { sdp }
D->>WS: ice_candidate { candidate }
WS->>P: ice_candidate { candidate }
P->>WS: ice_candidate { candidate }
WS->>D: ice_candidate { candidate }
Note over D,P: WebRTC Peer Connection established
D->>WS: call_ended
WS->>P: call_ended
```
---
## 15. Key Architectural Patterns & Fixes
### Documented Fixes
| Fix | Issue | Solution |
|-----|-------|----------|
| Fix 1 | Two competing GPS listeners | Merged into single stream subscription |
| Fix 2 | Timer leak on cancel | Stop ALL timers immediately at gatekeeper |
| Fix 3 | Polyline decode blocking UI | Moved to `compute()` isolate |
| Fix 4 | Wrong distance unit in validation | Fixed `_validateTripDistance()` unit conversion |
| Fix 5 | `Future.delayed` without `await` | Added proper `await` |
| Fix 6 | Redundant heartbeat during stream | Skip heartbeat if location stream active |
### Architecture Patterns
1. **Controller-per-Screen**: Each screen has its own `GetxController`
2. **Box Persistence**: GetStorage used for ride state recovery across app restarts
3. **Socket Decoupling**: Location data flows through Socket.IO, but ride CRUD uses HTTP REST
4. **Isolate Offloading**: Heavy polyline operations run in isolates via `compute()`
5. **Parallel Execution**: Finish ride + payment run concurrently via `Future.wait`
6. **Dual Data Format Support**: Socket data arrives as either List or Map — both handled
7. **Gatekeeper Pattern**: Cancellation handler stops all active processes at a single entry point
---
## 16. Data Flow Diagrams
### Complete Ride Lifecycle Data Flow
```mermaid
graph TB
subgraph "Pre-Ride"
A[Socket: new_ride_request] --> B[Parse List/Map]
B --> C[OrderRequestPage]
C --> D[Driver Accepts]
D --> E[HTTP: acceptRide.php]
E --> F[Write rideArgs to Box]
F --> G[Navigate to Map]
end
subgraph "To-Passenger"
G --> H[Route: Driver -> Passenger]
H --> I[Drew colored polyline]
I --> J[Step Nav + TTS]
J --> K[GPS updates every 5s]
K --> L[Socket: update_location]
L --> M[Smart snap to route]
end
subgraph "At Passenger"
M --> N[Arrive ~150m]
N --> O[HTTP: start_ride.php]
O --> P[Redraw route -> Destination]
end
subgraph "To-Destination"
P --> Q[Pricing Timer 1s]
Q --> R[Live price display]
R --> S[Step Nav + TTS]
S --> T[Dual polyline: grey + colored]
T --> U[Socket: update_location with status]
end
subgraph "Finish"
U --> V[HTTP: finish_ride_updates.php]
V --> W[HTTP: process_ride_payments.php]
W --> X[Stop all timers]
X --> Y[Clear polylines]
Y --> Z[Show summary / rating]
Z --> AA[Reset to noRide]
end
style A fill:#e74c3c,color:#fff
style O fill:#2ecc71,color:#fff
style V fill:#2ecc71,color:#fff
style W fill:#2ecc71,color:#fff
style AA fill:#3498db,color:#fff
```
### Socket Event Flow During Ride
```mermaid
sequenceDiagram
participant Driver
participant LS as Location Server
participant PSGR as Passenger App
Note over Driver: Searching for ride
LS->>Driver: new_ride_request { data }
Driver->>LS: update_location { status: searching }
Note over Driver: Ride accepted
Driver->>LS: update_location { status: applied, ride_id }
Note over Driver: En route to passenger
Driver->>LS: update_location { lat, lng, heading, speed, status: goingToPassenger }
LS->>PSGR: driverLocationUpdate { ... }
Note over Driver: Arrived at passenger
Driver->>LS: update_location { status: arrived }
Note over Driver: Ride started
Driver->>LS: update_location { status: inProgress, ride_id }
Note over Driver: En route to destination
Driver->>LS: update_location { lat, lng, heading, speed, status: inProgress }
LS->>PSGR: driverLocationUpdate { ... }
Note over Driver: Ride finished
Driver->>LS: update_location { status: finished }
Note over Driver: Back to idle
Driver->>LS: update_location { status: online }
```
### Key Backend Endpoints Used Per Phase
| Phase | Endpoint | Purpose |
|-------|----------|---------|
| Accept | `acceptRide.php?ride_id=X&driver_id=Y` | Accept ride |
| Begin | `start_ride.php?ride_id=X&driver_id=Y&passenger_id=Z` | Start trip |
| In-Ride | `update_location` (Socket) | Location streaming |
| In-Ride | `updateRideAndCheckIfApplied.php` | Status sync |
| In-Ride | `getKazanPercent.php` | Commission config |
| Finish | `finish_ride_updates.php` | Complete ride |
| Finish | `process_ride_payments.php` | Payment processing |
| Finish | `addRateToPassenger.php` | Passenger rating |
| Post-Ride | `getAllPaymentFromRide.php` | Payment summary |
| Any | `getRideStatusFromStartApp.php` | State recovery on restart |
---
## Appendix: Key File Reference
| File | Lines | Purpose |
|------|-------|---------|
| [`lib/controller/home/captin/map_driver_controller.dart`](lib/controller/home/captin/map_driver_controller.dart) | 2644 | Core driver ride lifecycle, navigation, polyline, pricing |
| [`lib/controller/home/captin/order_request_controller.dart`](lib/controller/home/captin/order_request_controller.dart) | 828 | Ride request handling, accept logic, route display |
| [`lib/controller/functions/location_controller.dart`](lib/controller/functions/location_controller.dart) | 794 | Socket.IO, location tracking, batch upload, behavior |
| [`lib/controller/home/navigation/navigation_controller.dart`](lib/controller/home/navigation/navigation_controller.dart) | 1383 | Step-by-step navigation, route matching, alternative routes |
| [`lib/controller/home/navigation/route_matcher_worker.dart`](lib/controller/home/navigation/route_matcher_worker.dart) | 146 | Isolate-based route matching with sliding window |
| [`lib/controller/home/navigation/decode_polyline_isolate.dart`](lib/controller/home/navigation/decode_polyline_isolate.dart) | 32 | Polyline decode in isolate |
| [`lib/models/model/order_data.dart`](lib/models/model/order_data.dart) | 188 | Order data model (List + Map constructors) |
| [`lib/constant/links.dart`](lib/constant/links.dart) | 424 | All backend API endpoints |
| [`lib/services/signaling_service.dart`](lib/services/signaling_service.dart) | 112 | WebRTC call signaling |
| [`lib/services/offline_map_service.dart`](lib/services/offline_map_service.dart) | - | Offline map tile service |
| [`pubspec.yaml`](pubspec.yaml) | 144 | Dependencies and package config |

View File

@@ -0,0 +1,290 @@
<?php
require_once __DIR__ . '/../../connect.php';
try {
$con_ride = Database::get('ride');
} catch (Exception $e) {
error_log("[finish_ride_updates] Failed to connect to Ride Database: " . $e->getMessage());
}
// ============================================================
// finish_ride_updates.php — Atomic Server-to-Server
// ============================================================
// Driver App calls this ONCE with raw ride data (NOT the price).
// Server calculates price securely, processes payment via S2S,
// and atomically updates all databases within a transaction.
//
// Flow:
// 1. Receive raw params from driver app
// 2. Calculate price server-side (from DB + actual distance)
// 3. BEGIN TRANSACTION (local DB)
// 4. Update ride on local DB + remote DB (con_ride)
// 5. Update driver_orders
// 6. S2S cURL → Wallet Payment Server (process_ride_payments.php)
// 7. If payment OK → COMMIT, notify passenger (Socket + FCM)
// 8. If payment FAIL → ROLLBACK, ride stays 'Begin', safe retry
// ============================================================
// --- Secure S2S Configuration ---
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY') );
define('WALLET_PAYMENT_URL', 'https://walletintaleq.intaleq.xyz/v1/main/ride/payment/process_ride_payments.php');
// ============================================================
// 1. Receive Raw Parameters (NO price from client)
// ============================================================
$rideId = filterRequest("rideId");
$driver_id = filterRequest("driver_id");
$passengerId = filterRequest("passengerId");
$newStatus = filterRequest("status"); // Expected: "Finished"
$actualDistance = filterRequest("actualDistance");
$actualDuration = filterRequest("actualDuration");
$passengerToken = filterRequest("passengerToken");
$driver_token = filterRequest("driver_token");
$walletChecked = filterRequest("walletChecked");
$passengerWalletBurc = filterRequest("passengerWalletBurc");
if (empty($rideId) || empty($newStatus) || empty($driver_id) || empty($passengerId)) {
jsonError("Missing required parameters: rideId, driver_id, passengerId, status");
exit;
}
if ($newStatus !== 'Finished') {
jsonError("Invalid status. Expected: Finished");
exit;
}
// ============================================================
// 2. Server-Side Price Calculation (Secure — NOT from client)
// ============================================================
try {
// Fetch ride data from remote/local DB for server-side calculation
$stmtRideData = $con->prepare("
SELECT id, price AS quoted_price, car_type,
distance AS planned_distance, passenger_id, driver_id
FROM ride WHERE id = ? AND driver_id = ?
LIMIT 1
");
$stmtRideData->execute([$rideId, $driver_id]);
$rideData = $stmtRideData->fetch(PDO::FETCH_ASSOC);
if (!$rideData) {
jsonError("Ride not found or driver mismatch.");
exit;
}
$quotedPrice = floatval($rideData['quoted_price'] ?? 0);
$kazanPercent = 10;
$carType = $rideData['car_type'] ?? 'Fixed Price';
// Fixed-price types: use quoted price as-is
$fixedPriceTypes = ['Speed', 'Fixed Price', 'Awfar Car'];
if (in_array($carType, $fixedPriceTypes)) {
$finalPrice = $quotedPrice;
} else {
// Variable pricing: calculate from actual distance
$cleanDist = preg_replace('/[^0-9.]/', '', $actualDistance);
$distanceKm = floatval($cleanDist);
if ($distanceKm <= 0) {
$finalPrice = $quotedPrice; // fallback
} else {
$perKmRate = getPerKmRate($carType);
$perMinRate = getPerMinRate();
$durationMin = intval(preg_replace('/[^0-9]/', '', $actualDuration));
$calculated = ($distanceKm * $perKmRate) + ($durationMin * $perMinRate);
$calculated *= (1 + ($kazanPercent / 100));
$finalPrice = max($quotedPrice, round($calculated, 2));
}
}
} catch (PDOException $e) {
jsonError("Error calculating price: " . $e->getMessage());
exit;
}
// ============================================================
// 3. Atomic Transaction: Update DBs + Process Payment
// ============================================================
try {
// --- Update Remote DB (con_ride) FIRST ---
// (Not in transaction — remote DB doesn't support cross-DB rollback,
// but we keep it minimal as a "best-effort" update)
if (isset($con_ride)) {
$stmtRemote = $con_ride->prepare(
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
);
$stmtRemote->execute([$newStatus, $finalPrice, $rideId]);
}
// --- BEGIN Local DB Transaction ---
$con->beginTransaction();
// 3a. Update ride (local DB)
$stmtLocal = $con->prepare(
"UPDATE ride SET status = ?, rideTimeFinish = NOW(), price = ? WHERE id = ? AND status = 'Begin'"
);
$stmtLocal->execute([$newStatus, $finalPrice, $rideId]);
if ($stmtLocal->rowCount() == 0) {
throw new Exception("Ride already finished or not found in local DB.");
}
// 3b. Update driver_orders
$checkStmt = $con->prepare("SELECT order_id FROM driver_orders WHERE order_id = ?");
$checkStmt->execute([$rideId]);
if ($checkStmt->rowCount() > 0) {
$con->prepare("UPDATE driver_orders SET driver_id = ?, status = ?, created_at = NOW() WHERE order_id = ?")
->execute([$driver_id, $newStatus, $rideId]);
} else {
$con->prepare("INSERT INTO driver_orders (driver_id, order_id, created_at, status) VALUES (?, ?, NOW(), ?)")
->execute([$driver_id, $rideId, $newStatus]);
}
// ============================================================
// 3c. Server-to-Server Payment Processing (S2S)
// ============================================================
$paymentPayload = [
'rideId' => $rideId,
'driverId' => $driver_id,
'passengerId' => $passengerId,
'paymentAmount' => $finalPrice,
'paymentMethod' => ($walletChecked === 'true') ? 'wallet' : 'cash',
'walletChecked' => $walletChecked,
'passengerWalletBurc' => $passengerWalletBurc,
'authToken' => $driver_token,
];
$ch = curl_init(WALLET_PAYMENT_URL);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($paymentPayload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'X-S2S-Api-Key: ' . S2S_SHARED_KEY,
],
]);
$paymentResponse = curl_exec($ch);
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
// Validate payment response
$paymentSuccess = false;
$paymentError = '';
if ($curlError) {
$paymentError = "S2S connection error: " . $curlError;
} elseif ($httpStatusCode !== 200) {
$paymentError = "Payment server returned HTTP $httpStatusCode";
} else {
$paymentResult = json_decode($paymentResponse, true);
if ($paymentResult && isset($paymentResult['status']) && $paymentResult['status'] === 'success') {
$paymentSuccess = true;
} else {
$paymentError = $paymentResult['error'] ?? 'Payment server returned failure';
}
}
if (!$paymentSuccess) {
// ❌ Payment failed — ROLLBACK everything
$con->rollBack();
error_log("[finish_ride_updates] Payment FAILED for ride $rideId: $paymentError");
jsonError("Payment processing failed: $paymentError");
exit;
}
// ✅ Payment succeeded — COMMIT
$con->commit();
// ============================================================
// 4. Notifications (After successful commit)
// ============================================================
$passenger_id = $passengerId; // alias for legacy code
if (!empty($passenger_id)) {
// Legacy list for backward compatibility
$legacyList = [
(string)$driver_id,
(string)$rideId,
(string)$driver_token,
(string)$finalPrice
];
// a) Socket notification
$socketPayload = [
'ride_id' => $rideId,
'status' => 'finished',
'price' => $finalPrice,
'DriverList' => $legacyList
];
if (function_exists('notifyPassengerOnRideServer')) {
notifyPassengerOnRideServer($passenger_id, $socketPayload);
}
// b) FCM notification
if (!empty($passengerToken)) {
$fcmData = [
'ride_id' => (string)$rideId,
'price' => (string)$finalPrice,
'DriverList' => $legacyList
];
sendFCM_Internal(
$passengerToken,
"تم إنهاء الرحلة 🏁",
"المبلغ المطلوب: " . $finalPrice . " ل.س",
$fcmData,
'Driver Finish Trip',
false
);
}
}
// ============================================================
// 5. Return Success with server-calculated price
// ============================================================
jsonSuccess([
'price' => $finalPrice,
'rideId' => $rideId
], "Ride finished and payment processed successfully.");
} catch (Exception $e) {
if (isset($con) && $con->inTransaction()) {
$con->rollBack();
}
error_log("[finish_ride_updates] Error for ride $rideId: " . $e->getMessage());
jsonError("Transaction failed: " . $e->getMessage());
}
// ============================================================
// Helper Functions
// ============================================================
function getPerKmRate(string $carType): float {
$rates = [
'Comfort' => 44,
'Lady' => 44,
'Mishwar Vip' => 50,
'Electric' => 45,
'Van' => 63,
'Delivery' => 25,
'Speed' => 36,
'Fixed Price' => 36,
'Awfar Car' => 36,
];
return $rates[$carType] ?? 36;
}
function getPerMinRate(): float {
$hour = (int)date('H');
if ($hour >= 21 || $hour < 1) return 11; // Late
if ($hour >= 14 && $hour <= 17) return 10; // Peak
return 9; // Normal
}
?>

View File

@@ -0,0 +1,40 @@
# تقرير مراجعة كلاس MapDriverController - النسخة النهائية
## الإصلاحات المطبقة بالكامل ✅ (15 إصلاحاً)
### المراجعة الأولى (V1) — 12 إصلاحاً
| الكود | المشكلة | الحل | الحالة |
|-------|---------|------|--------|
| C-1 | `updateLocation()` for loop تسبب تسرب ذاكرة | `Timer.periodic` مع `startUpdateLocationTimer` و `stopUpdateLocationTimer` | ✅ |
| C-2 | `_validateTripDistance()` تُرجع قبل إغلاق الديالوج | `Completer<bool>` مع Deadlock protection | ✅ |
| C-3 | تكرار كود تحليل المسافة بين `finishRideFromDriver` و `_validateTripDistance` | دالة مشتركة `_parseDistanceToMeters()` | ✅ |
| C-4 | `myLocation` لا تتحدث في المستمع الأساسي | إضافة `myLocation = newLoc` في `_handleLocationUpdate` | ✅ |
| M-1 | اسم `jitterMeters` مضلّل (القيمة بالكيلومتر) | تغيير إلى `jitterKm = 0.01` | ✅ |
| M-2 | Variable Shadowing في `markDriverAsArrived` | تغيير المتغير إلى `distToPassenger` | ✅ |
| M-3 | كود ميت `_performanceReadings` و `_hasMadeDecision` | إزالة كاملة | ✅ |
| M-4 | تكرار `checkForNextStep` و `_checkNavigationStep` | دمج الدالتين + إزالة الكود المعلّق القديم | ✅ |
| M-5 | `disposeEverything` تستدعاء `onClose` مباشرة | استخدام `_stopAllServices` بدلاً منها | ✅ |
| M-6 | وحدات غير واضحة في `_calculateWaitingCost` | تعليق توضيحي `distanceBetweenDriverAndPassengerWhenConfirm بالكيلومتر` | ✅ |
| N-1 | رابط Google Maps `&` بدلاً من `?` | تصحيح المعاملات | ✅ |
| N-5 | `getLocationArea` تكتب في `box` بدون `update()` | إضافة `update()` بعد كل كتابة | ✅ |
### المراجعة الثانية (V2) — 3 إصلاحات إضافية
| الكود | المشكلة | الحل | الحالة |
|-------|---------|------|--------|
| C-2 v2 | Completer Deadlock عند إغلاق الديالوج بزر الرجوع | استخدام `Get.dialog` مع `.then()` callback يُكمل بـ `false` | ✅ |
| C-3 v2 | ديالوج مكرر عند إنهاء الرحلة بالزر | تمرير `isFromSlider: true` بعد التأكيد لتخطي الديالوج الثاني | ✅ |
| M-7 | Null checks على `String` غير قابلة للـ null | استخدام `isNotEmpty` بدلاً من `!= null` | ✅ |
## الإصلاحات الإضافية المطبقة
- تنظيف جميع التايمرات في `onClose()` و `_stopAllServices()`
- إزالة `@override` المكرر
- إضافة تعليقات توضيحية `[Fix Code]` لكل إصلاح
## الإصلاحات المتبقية (تحسينات منخفضة الأولوية) ⚠️
| الكود | الوصف | الأولوية |
|-------|-------|---------|
| N-4 | تحويل `step0..step4` إلى `List<String>` (تحسين تجاري) | منخفض |
| N-2 | استبدال `Future.delayed` في `argumentLoading` بـ `Completer` (تحسين أداء) | منخفض |

View File

@@ -0,0 +1,141 @@
<?php
/**
* process_ride_payments.php — Payment Processing Server
*
* Receives S2S (Server-to-Server) requests from finish_ride_updates.php.
* Authenticated via X-S2S-Api-Key header matching a shared secret.
*
* Flow:
* 1. Validate X-S2S-Api-Key header
* 2. BEGIN TRANSACTION
* 3. Insert payment record
* 4. Deduct from passenger wallet (if walletChecked)
* 5. Settle passenger debt (if negative balance)
* 6. Deduct driver points (8%)
* 7. COMMIT / ROLLBACK on failure
*/
// Adjust path as needed for your payment server structure
require_once __DIR__ . '/../../jwtconnect.php';
// === Secure S2S Configuration ===
define('S2S_SHARED_KEY', getenv('S2S_SHARED_KEY'));
// ============================================================
// 1. API Key Authentication (X-S2S-Api-Key header)
// ============================================================
$providedKey = $_SERVER['HTTP_X_S2S_API_KEY'] ?? '';
if (empty($providedKey) || $providedKey !== S2S_SHARED_KEY) {
http_response_code(401);
printFailure("Unauthorized: Invalid or missing X-S2S-Api-Key.");
exit;
}
// ============================================================
// 2. Receive All Required Parameters
// ============================================================
$rideId = filterRequest("rideId");
$driverId = filterRequest("driverId");
$passengerId = filterRequest("passengerId");
$paymentAmount = filterRequest("paymentAmount");
$paymentMethod = filterRequest("paymentMethod");
$walletChecked = filterRequest("walletChecked"); // 'true' or 'false'
$passengerWalletBurc = filterRequest("passengerWalletBurc"); // passenger balance before operation
$authToken = filterRequest("authToken"); // kept for logging/audit, not used for auth
// --- Validate required fields ---
if (empty($rideId) || empty($driverId) || empty($passengerId) ||
!isset($paymentAmount) || empty($paymentMethod) ||
!isset($walletChecked) || !isset($passengerWalletBurc)) {
printFailure("Missing required parameters for payment processing.");
exit;
}
// ============================================================
// 3. Atomic Payment Processing
// ============================================================
try {
// --- Begin Transaction ---
$con->beginTransaction();
// 3a. Insert main payment record
$finalPaymentMethod = ($walletChecked === 'true') ? $paymentMethod . "Ride" : $paymentMethod;
$stmtPayment = $con->prepare(
"INSERT INTO payments (id, amount, payment_method, passengerID, rideId, driverID)
VALUES (UUID_SHORT(), :amount, :payment_method, :passengerID, :rideId, :driverID)"
);
$stmtPayment->execute([
':amount' => $paymentAmount,
':payment_method' => $finalPaymentMethod,
':passengerID' => $passengerId,
':rideId' => $rideId,
':driverID' => $driverId,
]);
if ($stmtPayment->rowCount() <= 0) {
throw new Exception("Failed to create payment record.");
}
// 3b. Deduct from passenger wallet (if wallet payment)
if ($walletChecked === 'true') {
$stmtPassengerWallet = $con->prepare(
"INSERT INTO `passengerWallet` (`passenger_id`, `balance`)
VALUES (:passenger_id, :balance)"
);
$stmtPassengerWallet->execute([
':passenger_id' => $passengerId,
':balance' => (-1) * floatval($paymentAmount),
]);
if ($stmtPassengerWallet->rowCount() <= 0) {
throw new Exception("Failed to deduct from passenger wallet.");
}
}
// 3c. Settle existing passenger debt (if balance was negative)
if (floatval($passengerWalletBurc) < 0) {
$stmtPassengerDebt = $con->prepare(
"INSERT INTO `passengerWallet` (`passenger_id`, `balance`)
VALUES (:passenger_id, :balance)"
);
$stmtPassengerDebt->execute([
':passenger_id' => $passengerId,
':balance' => (-1) * floatval($passengerWalletBurc),
]);
if ($stmtPassengerDebt->rowCount() <= 0) {
throw new Exception("Failed to settle passenger debt.");
}
}
// 3d. Deduct driver points (8% of payment amount)
$pointsSubtraction = floatval($paymentAmount) * (-0.08);
$stmtDriverPoints = $con->prepare(
"INSERT INTO `driverWallet` (`driverID`, `paymentID`, `amount`, `paymentMethod`)
VALUES (:driverID, :paymentID, :amount, :paymentMethod)"
);
$stmtDriverPoints->execute([
':driverID' => $driverId,
':paymentID' => 'rideId' . $rideId,
':amount' => number_format($pointsSubtraction, 0, '', ''),
':paymentMethod' => $paymentMethod,
]);
if ($stmtDriverPoints->rowCount() <= 0) {
throw new Exception("Failed to update driver wallet points.");
}
// --- All operations succeeded → Commit ---
$con->commit();
printSuccess("Payment processed successfully for ride $rideId.");
} catch (Exception $e) {
// --- Any failure → Rollback all changes ---
if (isset($con) && $con->inTransaction()) {
$con->rollBack();
}
error_log("[process_ride_payments] Transaction FAILED for ride $rideId: " . $e->getMessage());
printFailure("Transaction failed: " . $e->getMessage());
}