# frozen_string_literal: true # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later require 'numeric' module CalculateSuggestedAmounts MIN = 25 MAX = 100_000_000 BRACKETS = [{ range: MIN...1000, delta: 100 }, { range: 1000...5000, delta: 500 }, { range: 5000...MAX, delta: 2500 }].freeze # Calculates a set of suggested donation amounts based upon our internal special algorithm # This is most useful for suggesting amounts a recurring donor could change to # @return [Array] suggested amounts for your donation # @param [Number] amount the amount in cents to start from def self.calculate(amount) ParamValidation.new({ amount: amount }, amount: { required: true, is_a: Numeric, min: MIN, max: MAX }) result = [] step_down_val = step_down_value(amount) result.push(step_down_val) unless step_down_val.nil? higher_amounts = [] while higher_amounts.empty? || (higher_amounts.length < 3 && !higher_amounts.last.nil?) if higher_amounts.empty? higher_amounts.push(step_up_value(amount)) else higher_amounts.push(step_up_value(higher_amounts.last)) end end result.concat(higher_amounts.reject(&:nil?)) end def self.step_down_value(amount) initial_bracket = get_bracket_by_amount(amount) # check_floor_for_delta delta_floor = amount.floor_for_delta(initial_bracket[:delta]) # not on a delta, just send a floor if delta_floor != amount return delta_floor < MIN ? nil : delta_floor end potential_lower_amount = amount - initial_bracket[:delta] # is potential_lower_amount < our MIN? if so, return nil return nil if potential_lower_amount < MIN new_bracket = get_bracket_by_amount(potential_lower_amount) # if in same bracket, potential_lower_amount is our step_down_value return potential_lower_amount if initial_bracket == new_bracket # we're going to step down by our new bracket value then amount - new_bracket[:delta] end def self.step_up_value(amount) bracket = get_bracket_by_amount(amount) # check_ceil_for_delta delta_ceil = amount.ceil_for_delta(bracket[:delta]) # not on a delta, just send a ceil if delta_ceil != amount return delta_ceil >= MAX ? nil : delta_ceil end potential_higher_amount = amount + bracket[:delta] # is potential_lower_amount < our MIN? if so, return nil return nil if potential_higher_amount >= MAX potential_higher_amount end def self.get_bracket_by_amount(amount) BRACKETS.select { |i| i[:range].cover?(amount) }.first end end