solidus-returnly 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2043001dcfb19498dc6e5ef56b5b39248485632a
4
- data.tar.gz: ec0c2f446acf7b5eac9687c8efa8d81e71f7b020
3
+ metadata.gz: cdcc6a311bd8b506654e3f98002a2e5d50c5149f
4
+ data.tar.gz: 639068f9abf9fdb9aa33b34f7315bd898a217234
5
5
  SHA512:
6
- metadata.gz: 29c6dfe656d053f93198ce4a5987b3070b7a0511d361b44f6cf0a79912f267bb086503e7c445b549a7900c894541965788b5c26d341adb5915fedfb752616964
7
- data.tar.gz: b315483a1960b7640f5004e537a2ecaa21d7300a8a3cc06d4754007a1cf7f1a1a46c190d59f44a8aab59817c7c70ec03aa9af0a0292e81e735f878ac924638d4
6
+ metadata.gz: eee6692d5dfdf2c0c23ea2dafa60c42b4788dae0f6828962f1fc41f1b239cda93da934f448c0ecc290f12aa397015a631e254432e4bb8b98768e4a44bdae8593
7
+ data.tar.gz: 1cd8a79ad38584d16619574af858a3759a0c3f4516ff2c04b1e5dce8e7eaa7e476d17f86e5ae62d5fafc6c4b8d8e3a429af97b506f8e5c99103bc5724cf6dc7c
@@ -0,0 +1,61 @@
1
+ class ReimbursementShipping
2
+ attr_accessor :reimbursement
3
+ attr_reader :order
4
+
5
+ include Spree::ReimbursementType::ReimbursementHelpers
6
+
7
+ def initialize(reimbursement)
8
+ @order = reimbursement.order
9
+ @reimbursement = reimbursement
10
+ end
11
+
12
+ def update!(shipping_amount)
13
+ reimbursement.update!(total: calculated_amount(shipping_amount))
14
+ create_refund_payment!(shipping_amount)
15
+ end
16
+
17
+ private
18
+
19
+ def calculate_shipping_amount(shipping_amount)
20
+ [shipping_amount, available_shipment_amount].min
21
+ end
22
+
23
+ def calculated_amount(shipping_amount)
24
+ calculate_shipping_amount(shipping_amount) + reimbursement.total
25
+ end
26
+
27
+ def available_shipment_amount
28
+ order.shipment_total + order_shipping_tax - refunded_shipping
29
+ end
30
+
31
+ def refunded_shipping
32
+ order.refunds.where(refund_reason_id: refund_reason.id).sum(&:amount)
33
+ end
34
+
35
+ def create_refund_payment!(shipping_amount)
36
+ create_refunds(reimbursement, payments, calculate_shipping_amount(shipping_amount), false)
37
+ end
38
+
39
+ def order_shipping_tax
40
+ order.all_adjustments.where(source_type: 'Spree::TaxRate', adjustable_type: 'Spree::Shipment').sum(&:amount)
41
+ end
42
+
43
+ def payments
44
+ reimbursement.order.payments.completed
45
+ end
46
+
47
+ def create_refund(reimbursement, payment, amount, simulate)
48
+ refund = reimbursement.refunds.build({
49
+ payment: payment,
50
+ amount: amount,
51
+ reason: refund_reason
52
+ })
53
+
54
+ simulate ? refund.readonly! : refund.save!
55
+ refund
56
+ end
57
+
58
+ def refund_reason
59
+ @refund_reason ||= Spree::RefundReason.find_or_create_by!(name: 'Returnly Shipping Refund')
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ module RefundMethods
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ class_attribute :eligible_refund_methods
5
+ self.eligible_refund_methods = whitelisted_refund_methods || defined_refund_methods
6
+ end
7
+
8
+ class_methods do
9
+ def whitelisted_refund_methods
10
+ # Solidus 2.3.0 deprecates "Spree::Gateway::Bogus" and recommends using "Spree::PaymentMethod::BogusCreditCard" instead.
11
+ %w[Spree::Gateway::Bogus Spree::PaymentMethod::SimpleBogusCreditCard Spree::PaymentMethod::BogusCreditCard Spree::PaymentMethod::Check Spree::PaymentMethod::StoreCredit].inject([]) do |methods, payment_method|
12
+ methods << payment_method.constantize if Object.const_defined?(payment_method)
13
+ methods
14
+ end
15
+ end
16
+
17
+ def defined_refund_methods
18
+ Spree::PaymentMethod.order('id DESC').pluck(:type).map(&:constantize)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ class RefundPayments
2
+ attr_accessor :order
3
+
4
+ include RefundMethods
5
+
6
+ def initialize(order)
7
+ @order = order
8
+ end
9
+
10
+ def perform!(payment_amount)
11
+ sorted_payments = sorted_eligible_refund_payments(order.payments.completed)
12
+ sorted_payments.each_with_object([]) do |payment, payments|
13
+ break payments if payment_amount <= 0
14
+ next payments unless payment.can_credit?
15
+
16
+ allowed_amount = [payment_amount, payment.credit_allowed].min
17
+ payment_amount -= allowed_amount
18
+
19
+ payments << {
20
+ code: payment.source_type.constantize.model_name.human,
21
+ amount: allowed_amount.to_f,
22
+ original_transaction_id: ''
23
+ }
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def sorted_eligible_refund_payments(payments)
30
+ payments = payments.select { |p| eligible_refund_methods.include? p.payment_method.class }
31
+ payments.sort_by { |p| eligible_refund_methods.index(p.payment_method.class) }
32
+ end
33
+ end
@@ -9,4 +9,5 @@ require 'returnly/refund_calculator'
9
9
  require 'returnly/version'
10
10
  require 'returnly/refund/amount_calculator'
11
11
  require 'returnly/refund/return_item_restock_policy'
12
+ require 'returnly/services/create_reimbursement'
12
13
  require 'returnly/refund_presenter'
@@ -8,13 +8,56 @@ module Returnly
8
8
  end
9
9
 
10
10
  def return_item_refund_amount(return_item)
11
- default_amount(return_item)
11
+ (item_price(return_item) * item_price_percentage / 100).round(2, :down)
12
12
  end
13
13
 
14
14
  protected
15
- def default_amount(return_item)
16
- refund.refund_amount_per_item -
17
- Money.from_amount(return_item.total - return_item.pre_tax_amount)
15
+
16
+ def total_single_items_price
17
+ refund.line_items.inject(0.0.to_d) do |amount, line_item_hash|
18
+ line_item = Spree::LineItem.find(line_item_hash['order_line_item_id'])
19
+ amount += (line_item_price(line_item) * line_item_hash['units'].to_d).round(2, :down)
20
+ amount
21
+ end.round(2, :down)
22
+ end
23
+
24
+ def line_item_price(line_item)
25
+ (line_item.price + line_item_tax_price(line_item) + line_item_discount_price(line_item)).round(2, :down)
26
+ end
27
+
28
+ def item_price_percentage
29
+ [(available_amount * 100 / total_single_items_price).round(2, :down), 100.0.to_d].min
30
+ end
31
+
32
+ def item_price(return_item)
33
+ line_item_price(return_item.inventory_unit.line_item)
34
+ end
35
+
36
+ def line_item_tax_price(line_item)
37
+ (line_item.adjustments.tax.sum(&:amount).to_d.round(2, :down) / line_item.quantity).round(2, :down)
38
+ end
39
+
40
+ def line_item_discount_price(line_item)
41
+ discounter_calculator.calculate(
42
+ [
43
+ {
44
+ order_line_item_id: line_item.id,
45
+ units: 1
46
+ }
47
+ ]
48
+ )
49
+ end
50
+
51
+ def line_items_price
52
+ refund.order.line_items.sum(&:price).round(2, :down)
53
+ end
54
+
55
+ def available_amount
56
+ refund.product_available_amount.round(2, :down)
57
+ end
58
+
59
+ def discounter_calculator
60
+ @discounter_calculator ||= Returnly::DiscountCalculator.new(refund.order)
18
61
  end
19
62
  end
20
63
  end
@@ -29,7 +29,11 @@ module Returnly
29
29
  end
30
30
 
31
31
  def shipping_tax
32
- order.all_adjustments.where(source_type: 'Spree::TaxRate', adjustable_type: 'Spree::Shipment').sum(&:amount)
32
+ order.all_adjustments.where(source_type: 'Spree::TaxRate', adjustable_type: 'Spree::Shipment').sum(&:amount).to_d.round(2, :down)
33
+ end
34
+
35
+ def line_item_tax(line_item)
36
+ (line_item.adjustments.tax.sum(&:amount).to_d / line_item.quantity).round(2, :down)
33
37
  end
34
38
 
35
39
  def line_items_return_items
@@ -2,25 +2,25 @@ module Returnly
2
2
  class RefundPresenter
3
3
 
4
4
  def self.present_estimate(calculator)
5
- total = Money.new 0
6
5
  sub_total = Money.new 0
6
+ tax = Money.new 0
7
7
 
8
8
  calculator.line_items_return_items.values.flatten.each do |return_item|
9
- total += Money.from_amount return_item.total
10
9
  sub_total += Money.from_amount return_item.pre_tax_amount
10
+ tax += Money.from_amount calculator.line_item_tax(return_item.inventory_unit.line_item)
11
11
  end
12
12
 
13
13
  discount = Money.from_amount(calculator.discount)
14
- total_refund_amount = total - (discount * -1) # discount is negative, so we multiply it by -1 to make it positive for subtraction
14
+ total_refund_amount = sub_total + tax - (discount * -1) # discount is negative, so we multiply it by -1 to make it positive for subtraction
15
15
  shipping_tax = calculator.shipping_tax
16
16
  {
17
17
  product_amount: sub_total.to_f,
18
- tax_amount: (total - sub_total).to_f,
18
+ tax_amount: tax.to_f,
19
19
  discount_amount: discount.to_f,
20
20
  total_refund_amount: total_refund_amount.to_f,
21
21
  refundable_shipping_amount: calculator.order.shipment_total.to_f,
22
22
  refundable_shipping_tax_amount: shipping_tax.to_f,
23
- max_refundable_amount: (calculator.order.total - calculator.order.refunds.sum(&:amount)).to_f,
23
+ max_refundable_amount: (calculator.order.total - calculator.order.refunds.reload.sum(&:amount)).to_f,
24
24
  refunds_by_payment_method: refunds_by_payment(calculator.order, total_refund_amount.to_d + calculator.order.shipment_total + shipping_tax)
25
25
  }
26
26
  end
@@ -68,17 +68,8 @@ module Returnly
68
68
  }
69
69
  end
70
70
 
71
- private_class_method def self.refunds_by_payment(order, amount)
72
- completed_payments = order.payments.completed
73
- payment_amount = amount / completed_payments.count
74
-
75
- completed_payments.each_with_object([]) do |payment, payments|
76
- payments << {
77
- code: payment.source_type.constantize.model_name.human,
78
- amount: payment_amount.to_f,
79
- original_transaction_id: ''
80
- }
81
- end
71
+ private_class_method def self.refunds_by_payment(order, payment_amount)
72
+ ::RefundPayments.new(order).perform!(payment_amount)
82
73
  end
83
74
  end
84
75
  end
@@ -23,8 +23,9 @@ module Returnly
23
23
 
24
24
  def process_return_items
25
25
  each_return_item do |return_item|
26
- return_item.amount = return_item_amount_calculator.return_item_refund_amount return_item
27
- return_item.resellable = return_item_restock_policy.should_return_item? return_item
26
+ return_item.amount = return_item_amount_calculator.return_item_refund_amount return_item
27
+ return_item.additional_tax_total = 0
28
+ return_item.resellable = return_item_restock_policy.should_return_item? return_item
28
29
  end
29
30
  end
30
31
 
@@ -39,19 +40,28 @@ module Returnly
39
40
  end
40
41
 
41
42
  def refund_available_amount
42
- @available_amount ||= Money.from_amount(product_refund_amount.to_f + shipping_refund_amount.to_f)
43
- end
44
-
45
- def refund_amount_per_item
46
- refund_available_amount / line_items.size
43
+ @available_amount ||= Money.from_amount(product_available_amount)
47
44
  end
48
45
 
49
46
  def reimbursement
50
47
  @_reimbursement ||= Spree::Reimbursement.build_from_customer_return(customer_return)
51
48
  end
52
49
 
50
+ def product_available_amount
51
+ total = order.total - (order.shipment_total + refund_calculator.shipping_tax + refunds)
52
+ [total, product_refund_amount.to_d].min
53
+ end
54
+
53
55
  private
54
56
 
57
+ def add_shipping_amount!
58
+ ::ReimbursementShipping.new(reimbursement).update!(shipping_refund_amount.to_d)
59
+ end
60
+
61
+ def refunds
62
+ order.refunds.sum(&:amount).to_d.round(2, :down)
63
+ end
64
+
55
65
  def configure
56
66
  self.return_item_amount_calculator = return_item_amount_calculator_class.new(self)
57
67
  self.return_item_restock_policy = return_item_restock_policy_class.new(self)
@@ -64,8 +74,8 @@ module Returnly
64
74
  end
65
75
 
66
76
  def perform_reimbursement
67
- reimbursement.save!
68
- reimbursement.perform!
77
+ Returnly::Services::CreateReimbursement.new(reimbursement).perform!
78
+ add_shipping_amount!
69
79
  end
70
80
 
71
81
  def reimburse_without_items!
@@ -0,0 +1,74 @@
1
+ module Returnly
2
+ module Services
3
+ class CreateReimbursement
4
+ attr_accessor :reimbursement
5
+
6
+ delegate :return_items, to: :reimbursement
7
+
8
+ def initialize(reimbursement)
9
+ @reimbursement = reimbursement
10
+ end
11
+
12
+ def perform!
13
+ reimbursement.save!
14
+ reimbursement.update!(total: calculated_total)
15
+ performer.perform(reimbursement)
16
+
17
+ if unpaid_amount_within_tolerance?
18
+ reimbursement.reimbursed!
19
+ Spree::Reimbursement.reimbursement_success_hooks.each { |h| h.call reimbursement }
20
+ send_reimbursement_email
21
+ else
22
+ reimbursement.errored!
23
+ Spree::Reimbursement.reimbursement_failure_hooks.each { |h| h.call reimbursement }
24
+ raise incomplete_error_class, Spree.t('validation.unpaid_amount_not_zero', amount: unpaid_amount)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def incomplete_error_class
31
+ Spree::Reimbursement::IncompleteReimbursementError
32
+ end
33
+
34
+ def reimbursement_models
35
+ Spree::Reimbursement.reimbursement_models
36
+ end
37
+
38
+ def paid_amount
39
+ reimbursement_models.sum do |model|
40
+ model.total_amount_reimbursed_for(reimbursement)
41
+ end
42
+ end
43
+
44
+ def unpaid_amount
45
+ reimbursement.total - paid_amount
46
+ end
47
+
48
+ def calculated_total
49
+ return_items.to_a.sum(&:total).to_d.round(2, :down)
50
+ end
51
+
52
+ def performer
53
+ Spree::Reimbursement.reimbursement_performer
54
+ end
55
+
56
+ def send_reimbursement_email
57
+ Spree::ReimbursementMailer.reimbursement_email(reimbursement.id).deliver_later
58
+ end
59
+
60
+ def unpaid_amount_within_tolerance?
61
+ reimbursement_count = reimbursement_models.count do |model|
62
+ model.total_amount_reimbursed_for(reimbursement) > 0
63
+ end
64
+
65
+ leniency = if reimbursement_count > 0
66
+ (reimbursement_count - 1) * 0.01.to_d
67
+ else
68
+ 0
69
+ end
70
+ unpaid_amount.abs.between?(0, leniency)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  module Returnly
2
- VERSION = '0.6.1'
2
+ VERSION = '0.6.2'
3
3
 
4
4
  def platform_version
5
5
  defined?(Solidus)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus-returnly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Returnly Technologies, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-11 00:00:00.000000000 Z
11
+ date: 2017-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solidus
@@ -330,8 +330,11 @@ files:
330
330
  - app/controllers/spree/api/returnly/api_controller.rb
331
331
  - app/controllers/spree/api/returnly/refunds_controller.rb
332
332
  - app/controllers/spree/api/returnly/version_controller.rb
333
+ - app/models/reimbursement_shipping.rb
333
334
  - app/models/reimbursement_type/original_payment_no_items.rb
334
335
  - app/models/spree/return_item_decorator.rb
336
+ - app/services/refund_methods.rb
337
+ - app/services/refund_payments.rb
335
338
  - config/locales/en.yml
336
339
  - config/routes.rb
337
340
  - lib/returnly.rb
@@ -346,6 +349,7 @@ files:
346
349
  - lib/returnly/refund_presenter.rb
347
350
  - lib/returnly/refunder.rb
348
351
  - lib/returnly/refunds_configuration.rb
352
+ - lib/returnly/services/create_reimbursement.rb
349
353
  - lib/returnly/version.rb
350
354
  - lib/solidus-returnly.rb
351
355
  - lib/spree-returnly.rb