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/Services/CalculationService.php +45 -65 3.0.6 → 3.0.16 View file →
@@ -299,22 +299,37 @@
299 299 $is_selected = $is_required || $is_included || in_array($svc_id, $selected_service_ids, true);
300 300
301 301 $base_price = (float) ($svc['price'] ?? 0);
302 302 $price_per = $svc['price_per'] ?? 'person';
303 - switch ($price_per) {
304 - case 'person':
305 - $calculated_price = $base_price * max(1, $travelers_count);
306 - break;
307 - case 'day':
308 - $calculated_price = $base_price * max(1, $duration_days_for_services);
309 - break;
310 - case 'booking':
311 - default:
312 - $calculated_price = $base_price;
313 - break;
303 + // This MUST stay identical to the Pro charge path
304 + // (AdditionalServicesBookingHooks::calculateServicePrice) so the
305 + // displayed line-item equals the amount folded into the subtotal.
306 + if (($svc['price_type'] ?? 'fixed') === 'percentage') {
307 + // Percentage = % of the WHOLE trip base price. `$base_amount`
308 + // already accounts for travelers/categories, so Price Per is NOT
309 + // applied (it would double-count). Matches the "Percentage of
310 + // Trip Price" label and the free booking-services fallback.
311 + $unit_price = round(($base_price / 100) * $base_amount, 2);
312 + $calculated_price = $unit_price;
313 + } else {
314 + // Fixed: Price Per multiplies the entered flat amount.
315 + $unit_price = $base_price;
316 + switch ($price_per) {
317 + case 'person':
318 + $calculated_price = round($unit_price * max(1, $travelers_count), 2);
319 + break;
320 + case 'day':
321 + $calculated_price = round($unit_price * max(1, $duration_days_for_services), 2);
322 + break;
323 + case 'booking':
324 + default:
325 + $calculated_price = round($unit_price, 2);
326 + break;
327 + }
314 328 }
315 329
316 330 $svc['selected'] = $is_selected;
331 + $svc['unit_price'] = $unit_price;
317 332 $svc['calculated_price'] = $calculated_price;
318 333 $additional_services_resolved[] = $svc;
319 334
320 335 // Only paid (non-included) selected services contribute to the
@@ -365,8 +380,11 @@
365 380 // ── Payment amounts (FlexiblePayments Pro module) ───────────────
366 381 $payment_amounts = $this->calculatePaymentAmounts($final_total, $payment_method, [
367 382 'trip_id' => $trip_id,
368 383 'travelers_count' => $travelers_count,
384 + // Tour start → Pro can force full payment when the tour is within
385 + // the balance-due window (tour-anchored scheduled payments).
386 + 'travel_date' => $travel_date,
369 387 ]);
370 388
371 389 // ── Currency ────────────────────────────────────────────────────
372 390 $currency = SettingsService::getCurrency();
@@ -441,15 +459,9 @@
441 459 if ($category_id) {
442 460 $category_prices_post_dp[(string) $category_id] = $post_dp_price;
443 461 }
444 462
445 - if ($pricing_mode === 'per_group') {
446 - if ($count > 0) {
447 - $pre_dp_base += $pre_dp_price;
448 - }
449 - } else {
450 - $pre_dp_base += $pre_dp_price * $count;
451 - }
463 + $pre_dp_base += TripPricingService::categoryLineSubtotal($pt_arr, $count, $pre_dp_price);
452 464 }
453 465 $dp_total_adjustment = $dp_was_suppressed ? 0.0 : ($base_amount - $pre_dp_base);
454 466 }
455 467 $price_breakdown = (array) apply_filters('yatra_price_breakdown', [], $trip_id, [
@@ -603,37 +615,17 @@
603 615 if (empty($types)) {
604 616 return [];
605 617 }
606 618
607 - // Enrich with pricing_mode from category metadata if missing
608 - $needs_enrichment = false;
609 - foreach ($types as $pt) {
610 - $pt = (array) $pt;
611 - if (empty($pt['pricing_mode'])) {
612 - $needs_enrichment = true;
613 - break;
614 - }
615 - }
616 -
617 - if ($needs_enrichment) {
618 - $category_ids = array_filter(array_map(function($pt) {
619 - $pt = (array) $pt;
620 - return isset($pt['category_id']) ? (int) $pt['category_id'] : null;
621 - }, $types));
622 -
623 - if (!empty($category_ids)) {
624 - $category_meta = $this->getCategoryMetadata($category_ids);
625 - foreach ($types as &$pt) {
626 - if (is_object($pt)) $pt = (array) $pt;
627 - $cat_id = isset($pt['category_id']) ? (int) $pt['category_id'] : null;
628 - if ($cat_id && isset($category_meta[$cat_id]) && empty($pt['pricing_mode'])) {
629 - $pt['pricing_mode'] = $category_meta[$cat_id]['pricing_mode'] ?? 'per_person';
630 - }
631 - }
632 - unset($pt);
633 - }
634 - }
635 -
619 + // Resolve pricing_mode / group-size limits authoritatively from the
620 + // TravelerCategory. This must OVERRIDE (not just fill-when-empty): the
621 + // price_types coming from a stored availability row or session can carry
622 + // a literal 'per_person' placeholder that an empty() check would skip,
623 + // which silently charged a per-group category by headcount. For
624 + // per-person categories this resolves back to 'per_person' (a no-op), so
625 + // the booking total for every existing trip is unchanged.
626 + $types = TripPricingService::applyCategoryPricingMeta($types);
627 +
636 628 return $types;
637 629 }
638 630
639 631 /**
@@ -721,15 +713,9 @@
721 713 $pricing_mode = $pt['pricing_mode'] ?? 'per_person';
722 714 $category_price = (float) TripPricingService::resolveCategoryEffectivePrice($pt);
723 715 $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : 0;
724 716
725 - if ($pricing_mode === 'per_group') {
726 - if ($count > 0) {
727 - $base_amount += $category_price;
728 - }
729 - } else {
730 - $base_amount += $category_price * $count;
731 - }
717 + $base_amount += TripPricingService::categoryLineSubtotal($pt, $count, $category_price);
732 718 }
733 719 return round($base_amount, 2);
734 720 }
735 721
@@ -777,22 +763,16 @@
777 763 ]);
778 764
779 765 $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : 0;
780 766
781 - if ($pricing_mode === 'per_group') {
782 - // Per group: charge flat price once if any travelers in this category
783 - if ($count > 0) {
784 - $base_amount += $category_price;
785 - }
786 - } else {
787 - // Per person: charge per traveler
788 - $base_amount += $category_price * $count;
789 - }
767 + // Single source of truth for the per-category line amount
768 + // (per-person × count, flat per-group, or per-block group pricing).
769 + $base_amount += TripPricingService::categoryLineSubtotal($pt, $count, $category_price);
790 770 }
791 -
771 +
792 772 return round($base_amount, 2);
793 773 }
794 -
774 +
795 775 // Regular pricing
796 776 return round($unit_price * $travelers_count, 2);
797 777 }
798 778