solidus-returnly 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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