PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 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 All 84 releases
← All changes | app/Models/Checkout.php +98 -11 3.0.3 → 3.0.16 View file →
@@ -91,24 +91,55 @@
91 91 {
92 92 if (!$this->isTravelerBased() || empty($this->getPriceTypes())) {
93 93 return [];
94 94 }
95 -
95 +
96 96 $breakdown = [];
97 97 $priceTypes = $this->getPriceTypes();
98 98 $travelerCounts = $this->getTravelerCounts();
99 -
99 +
100 + // CalculationService now ships a per-category map of DP-adjusted unit
101 + // prices. When present, we prefer it so the category row in the sidebar
102 + // shows the price the customer is actually paying (e.g. Adult $157.52)
103 + // instead of the pre-DP catalog price plus a separate "Dynamic
104 + // Pricing −$21.48" subtraction line. The fallback keeps working for
105 + // entry points (e.g. legacy sessions, partial recalcs) that don't
106 + // carry this map.
107 + $postDpPrices = isset($this->pricingCalculation['category_prices_post_dp'])
108 + && is_array($this->pricingCalculation['category_prices_post_dp'])
109 + ? $this->pricingCalculation['category_prices_post_dp']
110 + : [];
111 +
112 + // If the session carries explicit per-category counts, trust them
113 + // verbatim. The previous "first category defaults to 1 when missing"
114 + // fallback fired even alongside a real traveler_counts payload, which
115 + // double-counted: e.g. with `{"53": 1}` (1 Senior) the Adult row at
116 + // index 0 was getting a phantom +1, so the sidebar showed two
117 + // travelers when the customer had picked one.
118 + $hasExplicitCounts = is_array($travelerCounts) && !empty($travelerCounts);
119 +
100 120 foreach ($priceTypes as $index => $pt) {
101 121 $pt = (object) $pt;
102 122 $categoryId = $pt->category_id ?? $index;
103 123 $categoryLabel = $pt->category_label ?? __('Traveler', 'yatra');
104 - $categoryPrice = isset($pt->effective_price)
105 - ? (float) $pt->effective_price
124 + $catalogPrice = isset($pt->effective_price)
125 + ? (float) $pt->effective_price
106 126 : \Yatra\Services\TripPricingService::resolveCategoryEffectivePrice((array) $pt);
107 - $count = isset($travelerCounts[$categoryId])
108 - ? (int) $travelerCounts[$categoryId]
109 - : ($index === 0 ? 1 : 0);
110 -
127 +
128 + // Prefer DP-adjusted price (post-rules) when present.
129 + $categoryPrice = isset($postDpPrices[(string) $categoryId])
130 + ? (float) $postDpPrices[(string) $categoryId]
131 + : $catalogPrice;
132 +
133 + if ($hasExplicitCounts) {
134 + $count = isset($travelerCounts[$categoryId]) ? (int) $travelerCounts[$categoryId] : 0;
135 + } else {
136 + // No per-category data at all (legacy session shape) — keep the
137 + // old "default first category to 1" so empty sessions still
138 + // render a sensible single-traveler row.
139 + $count = $index === 0 ? 1 : 0;
140 + }
141 +
111 142 if ($count > 0) {
112 143 $breakdown[] = [
113 144 'category_id' => $categoryId,
114 145 'label' => $categoryLabel,
@@ -113,13 +144,22 @@
113 144 'category_id' => $categoryId,
114 145 'label' => $categoryLabel,
115 146 'count' => $count,
116 147 'price' => $categoryPrice,
117 - 'subtotal' => $categoryPrice * $count,
148 + // The non-DP price stays available so the template can
149 + // optionally render strike-through original pricing if it
150 + // wants to highlight savings — but the default render is
151 + // the post-DP price baked in here.
152 + 'catalog_price' => $catalogPrice,
153 + // Single source of truth for the line amount: per-person ×
154 + // count, flat per-group, or per-block group pricing. Keeps
155 + // the breakdown row in lock-step with the charged total.
156 + 'subtotal' => \Yatra\Services\TripPricingService::categoryLineSubtotal($pt, $count, $categoryPrice),
157 + 'pricing_mode' => $pt->pricing_mode ?? 'per_person',
118 158 ];
119 159 }
120 160 }
121 -
161 +
122 162 return $breakdown;
123 163 }
124 164
125 165 // ========== Pricing Amounts ==========
@@ -223,9 +263,56 @@
223 263 public function getTotalDiscountAmount(): float
224 264 {
225 265 return $this->getGroupDiscountAmount() + $this->getCouponDiscountAmount();
226 266 }
227 -
267 +
268 + // ========== Dynamic Pricing ==========
269 + //
270 + // These accessors expose the dynamic-pricing breakdown that CalculationService
271 + // collects via the `yatra_price_breakdown` filter. The pricing-summary
272 + // template uses them to render a "Dynamic Pricing" line item with the
273 + // original price, applied rules, and savings, rather than silently folding
274 + // the adjustment into the trip subtotal.
275 +
276 + public function getDynamicPricing(): ?array
277 + {
278 + $dp = $this->pricingCalculation['dynamic_pricing'] ?? null;
279 + if (!is_array($dp) || empty($dp['rules'])) {
280 + return null;
281 + }
282 + return $dp;
283 + }
284 +
285 + public function hasDynamicPricing(): bool
286 + {
287 + return $this->getDynamicPricing() !== null;
288 + }
289 +
290 + /**
291 + * Total dollar impact dynamic pricing has on the booking. Negative means
292 + * a discount, positive means a markup. For traveler-based trips the DP
293 + * filter fires per-category so we don't have a single per-unit delta; in
294 + * that case fall back to the breakdown's savings field.
295 + */
296 + public function getDynamicPricingTotalAdjustment(): float
297 + {
298 + $delta = (float) ($this->pricingCalculation['dp_total_adjustment'] ?? 0);
299 + if ($delta !== 0.0) {
300 + return $delta;
301 + }
302 + $dp = $this->getDynamicPricing();
303 + if ($dp && isset($dp['savings'])) {
304 + return -1.0 * (float) $dp['savings']; // savings positive = discount, so negate for signed delta
305 + }
306 + return 0.0;
307 + }
308 +
309 + public function getDynamicPricingLabel(): string
310 + {
311 + $dp = $this->getDynamicPricing();
312 + return $dp ? (string) ($dp['label'] ?? __('Dynamic Pricing', 'yatra')) : '';
313 + }
314 +
228 315 // ========== Tax Information ==========
229 316
230 317 public function isTaxEnabled(): bool
231 318 {