| @@ -663,13 +663,19 @@ | ||
| 663 | 663 | if ( empty( $get_discounts ) ) { |
| 664 | 664 | continue; |
| 665 | 665 | } |
| 666 | 666 | |
| 667 | - // Only stage campaigns whose discount actually applied to the cart. | |
| 668 | - ( new UserLimit )->disco_start_session_on_checkout( $intent->campaign->id ); | |
| 669 | - | |
| 670 | 667 | foreach ( $get_discounts as $item_id => $discount ) { |
| 668 | + /** | |
| 669 | + * Keep the campaign alongside its amount at the same index. | |
| 670 | + * | |
| 671 | + * Staging cannot happen yet: several campaigns can offer an amount | |
| 672 | + * for the same item and only one of them survives the min/max | |
| 673 | + * reduction below. Crediting every campaign here would consume the | |
| 674 | + * usage limit of campaigns whose discount never reached the cart. | |
| 675 | + */ | |
| 671 | 676 | $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] ); |
| 677 | + $discounts[ $item_id ]['campaigns'][] = (int) $intent->campaign->id; | |
| 672 | 678 | } |
| 673 | 679 | } |
| 674 | 680 | |
| 675 | 681 | // Check if the discounts are empty. |
| @@ -676,13 +682,27 @@ | ||
| 676 | 682 | if ( empty( $discounts ) ) { |
| 677 | 683 | return false; |
| 678 | 684 | } |
| 679 | 685 | |
| 686 | + $applied_campaign_ids = array(); | |
| 687 | + | |
| 680 | 688 | // Get the min or max discount amount for each item. |
| 681 | 689 | foreach ( $discounts as $item_id => $discount ) { |
| 682 | - $discounts[ $item_id ] = $this->min_max_average( $discount['discounts'] ); | |
| 690 | + $winning_amount = $this->min_max_average( $discount['discounts'] ); | |
| 691 | + $winning_index = array_search( $winning_amount, $discount['discounts'], true ); | |
| 692 | + | |
| 693 | + if ( false !== $winning_index && isset( $discount['campaigns'][ $winning_index ] ) ) { | |
| 694 | + $applied_campaign_ids[ $discount['campaigns'][ $winning_index ] ] = true; | |
| 695 | + } | |
| 696 | + | |
| 697 | + $discounts[ $item_id ] = $winning_amount; | |
| 683 | 698 | } |
| 684 | 699 | |
| 700 | + // Stage only the campaigns that actually won an item. | |
| 701 | + foreach ( array_keys( $applied_campaign_ids ) as $applied_campaign_id ) { | |
| 702 | + ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id ); | |
| 703 | + } | |
| 704 | + | |
| 685 | 705 | return $discounts; |
| 686 | 706 | } |
| 687 | 707 | |
| 688 | 708 | /** |
| @@ -761,9 +781,10 @@ | ||
| 761 | 781 | if ( empty( $intents ) ) { |
| 762 | 782 | return false; |
| 763 | 783 | } |
| 764 | 784 | |
| 765 | - $discounts = array(); | |
| 785 | + $discounts = array(); | |
| 786 | + $candidate_campaign_ids = array(); | |
| 766 | 787 | |
| 767 | 788 | // Loop through the intents. |
| 768 | 789 | foreach ( $intents as $intent ) { |
| 769 | 790 | /** |
| @@ -781,10 +802,18 @@ | ||
| 781 | 802 | if ( empty( $get_discounts ) ) { |
| 782 | 803 | continue; |
| 783 | 804 | } |
| 784 | 805 | |
| 785 | - // Only stage campaigns whose discount actually applied to the cart. | |
| 786 | - ( new UserLimit )->disco_start_session_on_checkout( $intent->campaign->id ); | |
| 806 | + /** | |
| 807 | + * Collect the campaign now, stage it later. | |
| 808 | + * | |
| 809 | + * What this method finally grants is decided well below: the rewards | |
| 810 | + * are unioned across campaigns and a BuyXGetY (products) campaign can | |
| 811 | + * then override the whole result authoritatively. Crediting a campaign | |
| 812 | + * here would consume its usage limit even when the final result grants | |
| 813 | + * it nothing. | |
| 814 | + */ | |
| 815 | + $candidate_campaign_ids[] = (int) $intent->campaign->id; | |
| 787 | 816 | |
| 788 | 817 | foreach ( $get_discounts as $item_id => $discount ) { |
| 789 | 818 | $discounts[ $item_id ]['discounts'][] = max( $discount['discounts'] ); // phpcs:ignore |
| 790 | 819 | $discounts[ $item_id ]['free'] = $discount['free']; // phpcs:ignore |
| @@ -871,8 +900,24 @@ | ||
| 871 | 900 | $discounts['get_qty'] = empty( $reward_map ) ? 0 : max( $reward_map ); // phpcs:ignore |
| 872 | 901 | $discounts['free'] = ! empty( $reward_map ); // phpcs:ignore |
| 873 | 902 | $discounts['bogo_type'] = $bogo_type; |
| 874 | 903 | $discounts['free_item_selection'] = $intent->campaign->get_free_item_selection(); |
| 904 | + | |
| 905 | + /** | |
| 906 | + * This branch is authoritative for the whole result, so the campaign | |
| 907 | + * that owns it is the only one that can be credited. | |
| 908 | + */ | |
| 909 | + $candidate_campaign_ids = array( (int) $intent->campaign->id ); | |
| 910 | + } | |
| 911 | + | |
| 912 | + /** | |
| 913 | + * Stage the campaigns only once the result is final, and only when it | |
| 914 | + * actually grants free items. | |
| 915 | + */ | |
| 916 | + if ( ! empty( $discounts['free'] ) ) { | |
| 917 | + foreach ( array_unique( $candidate_campaign_ids ) as $applied_campaign_id ) { | |
| 918 | + ( new UserLimit )->disco_start_session_on_checkout( $applied_campaign_id ); | |
| 919 | + } | |
| 875 | 920 | } |
| 876 | 921 | |
| 877 | 922 | return $discounts; |
| 878 | 923 | } |