core/app/models/spree/promotion_handler/coupon.rb

Summary

Maintainability
A
1 hr
Test Coverage
C
73%
module Spree
  module PromotionHandler
    class Coupon
      attr_reader :order, :store
      attr_accessor :error, :success, :status_code

      def initialize(order)
        @order = order
        @store = order.store
      end

      def apply
        if order.coupon_code.present?
          if promotion.present? && promotion.actions.exists?
            handle_present_promotion
          elsif store.promotions.with_coupon_code(order.coupon_code).try(:expired?)
            set_error_code :coupon_code_expired
          else
            set_error_code :coupon_code_not_found
          end
        else
          set_error_code :coupon_code_not_found
        end
        self
      end

      def remove(coupon_code)
        promotion = order.promotions.with_coupon_code(coupon_code)
        if promotion.present?
          # Order promotion has to be destroyed before line item removing
          order.order_promotions.where(promotion_id: promotion.id).destroy_all

          remove_promotion_adjustments(promotion)
          remove_promotion_line_items(promotion)
          order.update_with_updater!

          set_success_code :adjustments_deleted
        else
          set_error_code :coupon_code_not_found
        end
        self
      end

      def set_success_code(code)
        @status_code = code
        @success = Spree.t(code)
      end

      def set_error_code(code)
        @status_code = code
        @error = Spree.t(code)
      end

      def promotion
        @promotion ||= store.promotions.active.includes(
          :promotion_rules, :promotion_actions
        ).with_coupon_code(order.coupon_code)
      end

      def successful?
        success.present? && error.blank?
      end

      private

      def remove_promotion_adjustments(promotion)
        promotion_actions_ids = promotion.actions.pluck(:id)
        order.all_adjustments.where(source_id: promotion_actions_ids,
                                    source_type: 'Spree::PromotionAction').destroy_all
      end

      def remove_promotion_line_items(promotion)
        create_line_item_actions_ids = promotion.actions.where(type: 'Spree::Promotion::Actions::CreateLineItems').pluck(:id)

        Spree::PromotionActionLineItem.where(promotion_action: create_line_item_actions_ids).find_each do |item|
          line_item = order.find_line_item_by_variant(item.variant)
          next if line_item.blank?

          Spree::Dependencies.cart_remove_item_service.constantize.call(order: order, variant: item.variant, quantity: item.quantity)
        end
      end

      def handle_present_promotion
        return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded?(order)
        return promotion_applied if promotion_exists_on_order?

        unless promotion.eligible?(order)
          self.error = promotion.eligibility_errors.full_messages.first unless promotion.eligibility_errors.blank?
          return (error || ineligible_for_this_order)
        end

        # If any of the actions for the promotion return `true`,
        # then result here will also be `true`.
        if promotion.activate(order: order)
          determine_promotion_application_result
        else
          set_error_code :coupon_code_unknown_error
        end
      end

      def promotion_usage_limit_exceeded
        set_error_code :coupon_code_max_usage
      end

      def ineligible_for_this_order
        set_error_code :coupon_code_not_eligible
      end

      def promotion_applied
        set_error_code :coupon_code_already_applied
      end

      def promotion_exists_on_order?
        order.promotions.include? promotion
      end

      def determine_promotion_application_result
        # Check for applied adjustments.
        discount = order.all_adjustments.promotion.eligible.detect do |p|
          p.source.promotion.code.try(:downcase) == order.coupon_code.downcase
        end

        # Check for applied line items.
        created_line_items = promotion.actions.detect do |a|
          Object.const_get(a.type).ancestors.include?(
            Spree::Promotion::Actions::CreateLineItems
          )
        end

        if discount || created_line_items
          order.update_totals
          order.persist_totals
          set_success_code :coupon_code_applied
        elsif order.promotions.with_coupon_code(order.coupon_code)
          # if the promotion exists on an order, but wasn't found above,
          # we've already selected a better promotion
          set_error_code :coupon_code_better_exists
        else
          # if the promotion was created after the order
          set_error_code :coupon_code_not_found
        end
      end
    end
  end
end