PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Services / BookingTaxService.php

BookingTaxService.php in Yatra – Travel Booking & Tour Operator Software trunk, at app/Services/BookingTaxService.php

301 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking Tax Service
4 *
5 * Integrates tax calculations into the booking process
6 *
7 * @package Yatra\Services
8 * @since 3.0.0
9 */
10
11 declare(strict_types=1);
12
13 namespace Yatra\Services;
14
15 class BookingTaxService
16 {
17 /**
18 * Calculate tax breakdown for a booking
19 *
20 * @param array $bookingData Booking data
21 * @return array Tax calculation details
22 */
23 public static function calculateBookingTax(array $bookingData): array
24 {
25 $baseAmount = (float) ($bookingData['subtotal'] ?? $bookingData['total_amount'] ?? 0);
26 $country = $bookingData['contact_country'] ?? null;
27
28 // Get tax details from TaxService
29 $taxDetails = TaxService::calculateTax($baseAmount, $country);
30
31 // Add booking-specific information
32 $taxDetails['booking_id'] = $bookingData['id'] ?? null;
33 $taxDetails['booking_reference'] = $bookingData['reference'] ?? null;
34 $taxDetails['customer_country'] = $country;
35 $taxDetails['base_amount'] = $baseAmount;
36
37 // Calculate final amounts
38 if ($taxDetails['tax_inclusive']) {
39 $taxDetails['subtotal'] = $baseAmount - $taxDetails['tax_amount'];
40 $taxDetails['total_amount'] = $baseAmount;
41 } else {
42 $taxDetails['subtotal'] = $baseAmount;
43 $taxDetails['total_amount'] = $baseAmount + $taxDetails['tax_amount'];
44 }
45
46 return $taxDetails;
47 }
48
49 /**
50 * Apply tax to booking data before creation
51 *
52 * @param array $bookingData Raw booking data
53 * @return array Modified booking data with tax
54 */
55 public static function applyTaxToBooking(array $bookingData): array
56 {
57 // Skip tax calculation if tax is disabled
58 if (!SettingsService::isEnabled('enable_tax')) {
59 return $bookingData;
60 }
61
62 // Skip recalculation when checkout already sent a tax snapshot from CalculationService.
63 // Important: tax_amount may legitimately be 0 while tax is enabled — still skip to avoid
64 // resetting total_amount / amount_due (breaks deposit & partial "pay now" vs balance).
65 if (array_key_exists('tax_amount', $bookingData)
66 && array_key_exists('tax_details', $bookingData)
67 && array_key_exists('tax_inclusive', $bookingData)) {
68 return $bookingData;
69 }
70
71 // Calculate tax
72 $taxDetails = self::calculateBookingTax($bookingData);
73
74 // Update booking data with tax information
75 $bookingData['tax_amount'] = $taxDetails['tax_amount'];
76 $bookingData['tax_rate'] = $taxDetails['tax_rate'];
77 $bookingData['tax_inclusive'] = $taxDetails['tax_inclusive'];
78 $bookingData['tax_details'] = json_encode($taxDetails['taxes']);
79
80 // Update amounts based on tax configuration
81 if ($taxDetails['tax_inclusive']) {
82 // Tax is included - adjust subtotal
83 $bookingData['subtotal'] = $taxDetails['subtotal'];
84 $bookingData['total_amount'] = $taxDetails['total_amount'];
85 } else {
86 // Tax is added - keep original subtotal, update total
87 $bookingData['subtotal'] = $bookingData['subtotal'] ?? $bookingData['total_amount'] ?? 0;
88 $bookingData['total_amount'] = $taxDetails['total_amount'];
89 }
90
91 // Recalculate amount due
92 $amountPaid = (float) ($bookingData['amount_paid'] ?? 0);
93 $bookingData['amount_due'] = $taxDetails['total_amount'] - $amountPaid;
94
95 return $bookingData;
96 }
97
98 /**
99 * Get tax breakdown for display in booking
100 *
101 * @param array $booking Booking data
102 * @return array Formatted tax breakdown
103 */
104 public static function getBookingTaxBreakdown(array $booking): array
105 {
106 $taxDetails = [];
107
108 // Check if booking has tax details stored
109 if (!empty($booking['tax_details'])) {
110 $taxes = json_decode($booking['tax_details'], true) ?: [];
111
112 foreach ($taxes as $tax) {
113 $taxDetails[] = [
114 'name' => $tax['name'],
115 'rate' => $tax['rate'],
116 'amount' => $tax['amount'],
117 'formatted_amount' => yatra_format_price($tax['amount']),
118 'formatted_rate' => number_format($tax['rate'], 2) . '%',
119 'formatted_line' => sprintf('%s (%s%%): %s',
120 $tax['name'],
121 number_format($tax['rate'], 2),
122 yatra_format_price($tax['amount'])
123 ),
124 ];
125 }
126 } elseif (!empty($booking['tax_amount']) && $booking['tax_amount'] > 0) {
127 // Fallback for single tax
128 $taxName = SettingsService::getString('tax_name', __('Tax', 'yatra'));
129 $taxRate = (float) ($booking['tax_rate'] ?? 0);
130 $taxAmount = (float) $booking['tax_amount'];
131
132 $taxDetails[] = [
133 'name' => $taxName,
134 'rate' => $taxRate,
135 'amount' => $taxAmount,
136 'formatted_amount' => yatra_format_price($taxAmount),
137 'formatted_rate' => number_format($taxRate, 2) . '%',
138 'formatted_line' => sprintf('%s (%s%%): %s',
139 $taxName,
140 number_format($taxRate, 2),
141 yatra_format_price($taxAmount)
142 ),
143 ];
144 }
145
146 return $taxDetails;
147 }
148
149 /**
150 * Format tax display for booking summary
151 *
152 * @param array $booking Booking data
153 * @return string Formatted tax display
154 */
155 public static function formatBookingTaxDisplay(array $booking): string
156 {
157 $taxBreakdown = self::getBookingTaxBreakdown($booking);
158
159 if (empty($taxBreakdown)) {
160 return '';
161 }
162
163 $lines = array_map(function($tax) {
164 return $tax['formatted_line'];
165 }, $taxBreakdown);
166
167 return implode("\n", $lines);
168 }
169
170 /**
171 * Calculate tax for booking edit/update
172 *
173 * @param array $currentBooking Current booking data
174 * @param array $newData New booking data
175 * @return array Updated tax calculation
176 */
177 public static function recalculateBookingTax(array $currentBooking, array $newData): array
178 {
179 // Merge current booking with new data
180 $mergedData = array_merge($currentBooking, $newData);
181
182 // Recalculate tax
183 return self::calculateBookingTax($mergedData);
184 }
185
186 /**
187 * Validate tax configuration for booking
188 *
189 * @param array $bookingData Booking data
190 * @return array Validation result
191 */
192 public static function validateBookingTax(array $bookingData): array
193 {
194 $errors = [];
195
196 // Check if tax is enabled
197 if (!SettingsService::isEnabled('enable_tax')) {
198 return ['valid' => true, 'errors' => []];
199 }
200
201 // Validate tax configuration
202 $multipleTaxesEnabled = SettingsService::isEnabled('multiple_taxes_enabled');
203
204 if ($multipleTaxesEnabled) {
205 $multipleTaxes = SettingsService::get('multiple_taxes', []);
206 $validation = TaxValidationService::validateMultipleTaxes($multipleTaxes);
207
208 if (!$validation['valid']) {
209 $errors[] = __('Tax configuration is invalid. Please check your tax settings.', 'yatra');
210 $errors = array_merge($errors, $validation['errors']);
211 }
212 } else {
213 $taxRate = SettingsService::getFloat('tax_rate', 0);
214 $taxName = SettingsService::getString('tax_name', __('Tax', 'yatra'));
215
216 $singleTaxData = [
217 'tax_name' => $taxName,
218 'tax_rate' => $taxRate,
219 ];
220
221 $validation = TaxValidationService::validateSingleTax($singleTaxData);
222
223 if (!$validation['valid']) {
224 $errors[] = __('Tax configuration is invalid. Please check your tax settings.', 'yatra');
225 $errors = array_merge($errors, $validation['errors']);
226 }
227 }
228
229 return [
230 'valid' => empty($errors),
231 'errors' => $errors,
232 ];
233 }
234
235 /**
236 * Get tax summary for booking reports
237 *
238 * @param array $bookings Array of bookings
239 * @return array Tax summary statistics
240 */
241 public static function getTaxSummary(array $bookings): array
242 {
243 $summary = [
244 'total_tax_collected' => 0,
245 'total_bookings_with_tax' => 0,
246 'tax_breakdown' => [],
247 'average_tax_rate' => 0,
248 ];
249
250 $totalTaxRate = 0;
251 $taxRateCount = 0;
252
253 foreach ($bookings as $booking) {
254 $taxAmount = (float) ($booking['tax_amount'] ?? 0);
255
256 if ($taxAmount > 0) {
257 $summary['total_tax_collected'] += $taxAmount;
258 $summary['total_bookings_with_tax']++;
259
260 $taxRate = (float) ($booking['tax_rate'] ?? 0);
261 if ($taxRate > 0) {
262 $totalTaxRate += $taxRate;
263 $taxRateCount++;
264 }
265
266 // Collect tax breakdown
267 $taxBreakdown = self::getBookingTaxBreakdown($booking);
268 foreach ($taxBreakdown as $tax) {
269 $taxName = $tax['name'];
270 if (!isset($summary['tax_breakdown'][$taxName])) {
271 $summary['tax_breakdown'][$taxName] = [
272 'name' => $taxName,
273 'total_amount' => 0,
274 'count' => 0,
275 'average_rate' => 0,
276 ];
277 }
278
279 $summary['tax_breakdown'][$taxName]['total_amount'] += $tax['amount'];
280 $summary['tax_breakdown'][$taxName]['count']++;
281 $summary['tax_breakdown'][$taxName]['average_rate'] += $tax['rate'];
282 }
283 }
284 }
285
286 // Calculate averages
287 if ($taxRateCount > 0) {
288 $summary['average_tax_rate'] = $totalTaxRate / $taxRateCount;
289 }
290
291 // Calculate average rates for tax breakdown
292 foreach ($summary['tax_breakdown'] as $taxName => &$taxData) {
293 if ($taxData['count'] > 0) {
294 $taxData['average_rate'] = $taxData['average_rate'] / $taxData['count'];
295 }
296 }
297
298 return $summary;
299 }
300 }
301