stripe-ruby-mock 2.3.1 → 2.5.8

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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.env +2 -0
  3. data/.travis.yml +8 -4
  4. data/README.md +12 -5
  5. data/lib/stripe_mock/api/account_balance.rb +14 -0
  6. data/lib/stripe_mock/api/client.rb +4 -4
  7. data/lib/stripe_mock/api/conversion_rate.rb +14 -0
  8. data/lib/stripe_mock/api/errors.rb +25 -13
  9. data/lib/stripe_mock/api/instance.rb +16 -6
  10. data/lib/stripe_mock/api/webhooks.rb +8 -1
  11. data/lib/stripe_mock/client.rb +18 -2
  12. data/lib/stripe_mock/data/list.rb +14 -2
  13. data/lib/stripe_mock/data.rb +398 -58
  14. data/lib/stripe_mock/instance.rb +105 -9
  15. data/lib/stripe_mock/request_handlers/accounts.rb +41 -2
  16. data/lib/stripe_mock/request_handlers/balance.rb +17 -0
  17. data/lib/stripe_mock/request_handlers/balance_transactions.rb +18 -2
  18. data/lib/stripe_mock/request_handlers/charges.rb +44 -33
  19. data/lib/stripe_mock/request_handlers/country_spec.rb +22 -0
  20. data/lib/stripe_mock/request_handlers/coupons.rb +5 -4
  21. data/lib/stripe_mock/request_handlers/customers.rb +29 -11
  22. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +13 -0
  23. data/lib/stripe_mock/request_handlers/external_accounts.rb +55 -0
  24. data/lib/stripe_mock/request_handlers/helpers/bank_account_helpers.rb +1 -1
  25. data/lib/stripe_mock/request_handlers/helpers/card_helpers.rb +9 -7
  26. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +10 -6
  27. data/lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb +49 -0
  28. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +59 -16
  29. data/lib/stripe_mock/request_handlers/helpers/token_helpers.rb +3 -2
  30. data/lib/stripe_mock/request_handlers/invoices.rb +93 -14
  31. data/lib/stripe_mock/request_handlers/orders.rb +5 -5
  32. data/lib/stripe_mock/request_handlers/payouts.rb +32 -0
  33. data/lib/stripe_mock/request_handlers/plans.rb +1 -0
  34. data/lib/stripe_mock/request_handlers/products.rb +43 -0
  35. data/lib/stripe_mock/request_handlers/recipients.rb +12 -0
  36. data/lib/stripe_mock/request_handlers/refunds.rb +91 -0
  37. data/lib/stripe_mock/request_handlers/subscription_items.rb +36 -0
  38. data/lib/stripe_mock/request_handlers/subscriptions.rb +101 -39
  39. data/lib/stripe_mock/request_handlers/tax_rates.rb +36 -0
  40. data/lib/stripe_mock/request_handlers/tokens.rb +9 -3
  41. data/lib/stripe_mock/request_handlers/transfers.rb +11 -5
  42. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +15 -1
  43. data/lib/stripe_mock/server.rb +14 -1
  44. data/lib/stripe_mock/test_strategies/base.rb +10 -5
  45. data/lib/stripe_mock/test_strategies/live.rb +5 -0
  46. data/lib/stripe_mock/test_strategies/mock.rb +8 -0
  47. data/lib/stripe_mock/util.rb +8 -2
  48. data/lib/stripe_mock/version.rb +1 -1
  49. data/lib/stripe_mock/webhook_fixtures/account.external_account.created.json +27 -0
  50. data/lib/stripe_mock/webhook_fixtures/account.external_account.deleted.json +27 -0
  51. data/lib/stripe_mock/webhook_fixtures/account.external_account.updated.json +27 -0
  52. data/lib/stripe_mock/webhook_fixtures/account.updated.json +1 -1
  53. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_reinstated.json +88 -0
  54. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_withdrawn.json +88 -0
  55. data/lib/stripe_mock/webhook_fixtures/charge.updated.json +58 -0
  56. data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +40 -10
  57. data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +39 -10
  58. data/lib/stripe_mock/webhook_fixtures/customer.subscription.trial_will_end.json +39 -10
  59. data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +40 -11
  60. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +3 -2
  61. data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +1 -1
  62. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +92 -85
  63. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +3 -2
  64. data/lib/stripe_mock/webhook_fixtures/plan.created.json +1 -1
  65. data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +1 -1
  66. data/lib/stripe_mock/webhook_fixtures/plan.updated.json +1 -1
  67. data/lib/stripe_mock.rb +15 -0
  68. data/spec/api/instance_spec.rb +30 -0
  69. data/spec/instance_spec.rb +54 -4
  70. data/spec/integration_examples/prepare_error_examples.rb +6 -6
  71. data/spec/list_spec.rb +27 -10
  72. data/spec/readme_spec.rb +2 -0
  73. data/spec/server_spec.rb +7 -3
  74. data/spec/shared_stripe_examples/account_examples.rb +46 -0
  75. data/spec/shared_stripe_examples/balance_examples.rb +11 -0
  76. data/spec/shared_stripe_examples/balance_transaction_examples.rb +28 -0
  77. data/spec/shared_stripe_examples/bank_examples.rb +28 -1
  78. data/spec/shared_stripe_examples/card_examples.rb +23 -5
  79. data/spec/shared_stripe_examples/card_token_examples.rb +1 -0
  80. data/spec/shared_stripe_examples/charge_examples.rb +131 -26
  81. data/spec/shared_stripe_examples/country_specs_examples.rb +18 -0
  82. data/spec/shared_stripe_examples/coupon_examples.rb +8 -2
  83. data/spec/shared_stripe_examples/customer_examples.rb +90 -0
  84. data/spec/shared_stripe_examples/dispute_examples.rb +19 -8
  85. data/spec/shared_stripe_examples/ephemeral_key_examples.rb +17 -0
  86. data/spec/shared_stripe_examples/error_mock_examples.rb +15 -5
  87. data/spec/shared_stripe_examples/external_account_examples.rb +170 -0
  88. data/spec/shared_stripe_examples/extra_features_examples.rb +2 -0
  89. data/spec/shared_stripe_examples/invoice_examples.rb +314 -51
  90. data/spec/shared_stripe_examples/payout_examples.rb +68 -0
  91. data/spec/shared_stripe_examples/plan_examples.rb +47 -4
  92. data/spec/shared_stripe_examples/product_example.rb +65 -0
  93. data/spec/shared_stripe_examples/recipient_examples.rb +13 -7
  94. data/spec/shared_stripe_examples/refund_examples.rb +453 -84
  95. data/spec/shared_stripe_examples/subscription_examples.rb +477 -32
  96. data/spec/shared_stripe_examples/subscription_items_examples.rb +75 -0
  97. data/spec/shared_stripe_examples/tax_rate_examples.rb +42 -0
  98. data/spec/shared_stripe_examples/transfer_examples.rb +72 -23
  99. data/spec/shared_stripe_examples/webhook_event_examples.rb +74 -5
  100. data/spec/spec_helper.rb +7 -6
  101. data/spec/stripe_mock_spec.rb +16 -3
  102. data/spec/support/stripe_examples.rb +8 -1
  103. data/spec/util_spec.rb +35 -1
  104. data/stripe-ruby-mock.gemspec +1 -1
  105. metadata +44 -8
  106. data/ChangeLog.rdoc +0 -4
@@ -3,7 +3,7 @@ module StripeMock
3
3
  module Helpers
4
4
 
5
5
  def verify_bank_account(object, bank_account_id, class_name='Customer')
6
- bank_accounts = object[:bank_accounts] || object[:sources]
6
+ bank_accounts = object[:external_accounts] || object[:bank_accounts] || object[:sources]
7
7
  bank_account = bank_accounts[:data].find{|acc| acc[:id] == bank_account_id }
8
8
  return if bank_account.nil?
9
9
  bank_account['status'] = 'verified'
@@ -3,15 +3,15 @@ module StripeMock
3
3
  module Helpers
4
4
 
5
5
  def get_card(object, card_id, class_name='Customer')
6
- cards = object[:cards] || object[:sources]
6
+ cards = object[:cards] || object[:sources] || object[:external_accounts]
7
7
  card = cards[:data].find{|cc| cc[:id] == card_id }
8
8
  if card.nil?
9
9
  if class_name == 'Recipient'
10
10
  msg = "#{class_name} #{object[:id]} does not have a card with ID #{card_id}"
11
- raise Stripe::InvalidRequestError.new(msg, 'card', 404)
11
+ raise Stripe::InvalidRequestError.new(msg, 'card', http_status: 404)
12
12
  else
13
13
  msg = "There is no source with ID #{card_id}"
14
- raise Stripe::InvalidRequestError.new(msg, 'id', 404)
14
+ raise Stripe::InvalidRequestError.new(msg, 'id', http_status: 404)
15
15
  end
16
16
  end
17
17
  card
@@ -36,7 +36,7 @@ module StripeMock
36
36
 
37
37
  def add_card_to_object(type, card, object, replace_current=false)
38
38
  card[type] = object[:id]
39
- cards_or_sources = object[:cards] || object[:sources]
39
+ cards_or_sources = object[:cards] || object[:sources] || object[:external_accounts]
40
40
 
41
41
  is_customer = object.has_key?(:sources)
42
42
 
@@ -58,7 +58,7 @@ module StripeMock
58
58
 
59
59
  def retrieve_object_cards(type, type_id, objects)
60
60
  resource = assert_existence type, type_id, objects[type_id]
61
- cards = resource[:cards] || resource[:sources]
61
+ cards = resource[:cards] || resource[:sources] || resource[:external_accounts]
62
62
 
63
63
  Data.mock_list_object(cards[:data])
64
64
  end
@@ -69,7 +69,7 @@ module StripeMock
69
69
  assert_existence :card, card_id, get_card(resource, card_id)
70
70
 
71
71
  card = { id: card_id, deleted: true }
72
- cards_or_sources = resource[:cards] || resource[:sources]
72
+ cards_or_sources = resource[:cards] || resource[:sources] || resource[:external_accounts]
73
73
  cards_or_sources[:data].reject!{|cc|
74
74
  cc[:id] == card[:id]
75
75
  }
@@ -77,6 +77,7 @@ module StripeMock
77
77
  is_customer = resource.has_key?(:sources)
78
78
  new_default = cards_or_sources[:data].count > 0 ? cards_or_sources[:data].first[:id] : nil
79
79
  resource[:default_card] = new_default unless is_customer
80
+ resource[:sources][:total_count] = cards_or_sources[:data].count if is_customer
80
81
  resource[:default_source] = new_default if is_customer
81
82
  card
82
83
  end
@@ -96,13 +97,14 @@ module StripeMock
96
97
  get_bank_by_token(params[:source])
97
98
  end
98
99
  end
100
+ source[:metadata].merge!(params[:metadata]) if params[:metadata]
99
101
  add_source_to_object(type, source, resource)
100
102
  end
101
103
 
102
104
  def add_card_to(type, type_id, params, objects)
103
105
  resource = assert_existence type, type_id, objects[type_id]
104
106
 
105
- card = card_from_params(params[:card] || params[:source])
107
+ card = card_from_params(params[:card] || params[:source] || params[:external_accounts])
106
108
  add_card_to_object(type, card, resource)
107
109
  end
108
110
 
@@ -1,13 +1,17 @@
1
1
  module StripeMock
2
2
  module RequestHandlers
3
3
  module Helpers
4
+ def add_coupon_to_object(object, coupon)
5
+ discount_attrs = {}.tap do |attrs|
6
+ attrs[object[:object]] = object[:id]
7
+ attrs[:coupon] = coupon
8
+ attrs[:start] = Time.now.to_i
9
+ attrs[:end] = (DateTime.now >> coupon[:duration_in_months].to_i).to_time.to_i if coupon[:duration] == 'repeating'
10
+ end
4
11
 
5
- def add_coupon_to_customer(customer, coupon)
6
- customer[:discount] = { coupon: coupon }
7
-
8
- customer
12
+ object[:discount] = Stripe::Discount.construct_from(discount_attrs)
13
+ object
9
14
  end
10
-
11
15
  end
12
16
  end
13
- end
17
+ end
@@ -0,0 +1,49 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Helpers
4
+
5
+ def add_external_account_to(type, type_id, params, objects)
6
+ resource = assert_existence type, type_id, objects[type_id]
7
+
8
+ source =
9
+ if params[:card]
10
+ card_from_params(params[:card])
11
+ elsif params[:bank_account]
12
+ bank_from_params(params[:bank_account])
13
+ else
14
+ begin
15
+ get_card_by_token(params[:external_account])
16
+ rescue Stripe::InvalidRequestError
17
+ bank_from_params(params[:external_account])
18
+ end
19
+ end
20
+ add_external_account_to_object(type, source, resource)
21
+ end
22
+
23
+ def add_external_account_to_object(type, source, object, replace_current=false)
24
+ source[type] = object[:id]
25
+ accounts = object[:external_accounts]
26
+
27
+ if replace_current && accounts[:data]
28
+ accounts[:data].delete_if {|source| source[:id] == object[:default_source]}
29
+ object[:default_source] = source[:id]
30
+ accounts[:data] = [source]
31
+ else
32
+ accounts[:total_count] = (accounts[:total_count] || 0) + 1
33
+ (accounts[:data] ||= []) << source
34
+ end
35
+ object[:default_source] = source[:id] if object[:default_source].nil?
36
+
37
+ source
38
+ end
39
+
40
+ def bank_from_params(attrs_or_token)
41
+ if attrs_or_token.is_a? Hash
42
+ attrs_or_token = generate_bank_token(attrs_or_token)
43
+ end
44
+ get_bank_by_token(attrs_or_token)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -6,32 +6,68 @@ module StripeMock
6
6
  customer[:subscriptions][:data].find{|sub| sub[:id] == sub_id }
7
7
  end
8
8
 
9
- def custom_subscription_params(plan, cus, options = {})
9
+ def resolve_subscription_changes(subscription, plans, customer, options = {})
10
+ subscription.merge!(custom_subscription_params(plans, customer, options))
11
+ items = options[:items]
12
+ items = items.values if items.respond_to?(:values)
13
+ subscription[:items][:data] = plans.map do |plan|
14
+ if items && items.size == plans.size
15
+ quantity = items &&
16
+ items.detect { |item| item[:plan] == plan[:id] }[:quantity] || 1
17
+ Data.mock_subscription_item({ plan: plan, quantity: quantity })
18
+ else
19
+ Data.mock_subscription_item({ plan: plan })
20
+ end
21
+ end
22
+ subscription
23
+ end
24
+
25
+ def custom_subscription_params(plans, cus, options = {})
10
26
  verify_trial_end(options[:trial_end]) if options[:trial_end]
11
27
 
12
- start_time = options[:current_period_start] || Time.now.utc.to_i
13
- params = { plan: plan, customer: cus[:id], current_period_start: start_time }
14
- params.merge! options.select {|k,v| k =~ /application_fee_percent|quantity|metadata|tax_percent/}
28
+ plan = plans.first if plans.size == 1
29
+
30
+ now = Time.now.utc.to_i
31
+ created_time = options[:created] || now
32
+ start_time = options[:current_period_start] || now
33
+ params = { customer: cus[:id], current_period_start: start_time, created: created_time }
34
+ params.merge!({ :plan => (plans.size == 1 ? plans.first : nil) })
35
+ keys_to_merge = /application_fee_percent|quantity|metadata|tax_percent|billing|days_until_due/
36
+ params.merge! options.select {|k,v| k =~ keys_to_merge}
37
+
38
+ if options[:cancel_at_period_end] == true
39
+ params.merge!(cancel_at_period_end: true, canceled_at: now)
40
+ elsif options[:cancel_at_period_end] == false
41
+ params.merge!(cancel_at_period_end: false, canceled_at: nil)
42
+ end
43
+
15
44
  # TODO: Implement coupon logic
16
45
 
17
- if (plan[:trial_period_days].nil? && options[:trial_end].nil?) || options[:trial_end] == "now"
18
- end_time = get_ending_time(start_time, plan)
19
- params.merge!({status: 'active', current_period_end: end_time, trial_start: nil, trial_end: nil})
46
+ if (((plan && plan[:trial_period_days]) || 0) == 0 && options[:trial_end].nil?) || options[:trial_end] == "now"
47
+ end_time = options[:billing_cycle_anchor] || get_ending_time(start_time, plan)
48
+ params.merge!({status: 'active', current_period_end: end_time, trial_start: nil, trial_end: nil, billing_cycle_anchor: options[:billing_cycle_anchor]})
20
49
  else
21
50
  end_time = options[:trial_end] || (Time.now.utc.to_i + plan[:trial_period_days]*86400)
22
- params.merge!({status: 'trialing', current_period_end: end_time, trial_start: start_time, trial_end: end_time})
51
+ params.merge!({status: 'trialing', current_period_end: end_time, trial_start: start_time, trial_end: end_time, billing_cycle_anchor: nil})
23
52
  end
24
53
 
25
54
  params
26
55
  end
27
56
 
28
57
  def add_subscription_to_customer(cus, sub)
29
- id = new_id('ch')
30
- charges[id] = Data.mock_charge(:id => id, :customer => cus[:id], :amount => sub[:plan][:amount])
58
+ if sub[:trial_end].nil? || sub[:trial_end] == "now"
59
+ id = new_id('ch')
60
+ charges[id] = Data.mock_charge(
61
+ :id => id,
62
+ :customer => cus[:id],
63
+ :amount => (sub[:plan] ? sub[:plan][:amount] : total_items_amount(sub[:items][:data]))
64
+ )
65
+ end
66
+
31
67
  if cus[:currency].nil?
32
- cus[:currency] = sub[:plan][:currency]
33
- elsif cus[:currency] != sub[:plan][:currency]
34
- raise Stripe::InvalidRequestError.new( "Can't combine currencies on a single customer. This customer has had a subscription, coupon, or invoice item with currency #{cus[:currency]}", 'currency', 400)
68
+ cus[:currency] = sub[:items][:data][0][:plan][:currency]
69
+ elsif cus[:currency] != sub[:items][:data][0][:plan][:currency]
70
+ raise Stripe::InvalidRequestError.new( "Can't combine currencies on a single customer. This customer has had a subscription, coupon, or invoice item with currency #{cus[:currency]}", 'currency', http_status: 400)
35
71
  end
36
72
  cus[:subscriptions][:total_count] = (cus[:subscriptions][:total_count] || 0) + 1
37
73
  cus[:subscriptions][:data].unshift sub
@@ -47,6 +83,8 @@ module StripeMock
47
83
  # `intervals` is set to 1 when calculating current_period_end from current_period_start & plan
48
84
  # `intervals` is set to 2 when calculating Stripe::Invoice.upcoming end from current_period_start & plan
49
85
  def get_ending_time(start_time, plan, intervals = 1)
86
+ return start_time unless plan
87
+
50
88
  case plan[:interval]
51
89
  when "week"
52
90
  start_time + (604800 * (plan[:interval_count] || 1) * intervals)
@@ -62,15 +100,20 @@ module StripeMock
62
100
  def verify_trial_end(trial_end)
63
101
  if trial_end != "now"
64
102
  if !trial_end.is_a? Integer
65
- raise Stripe::InvalidRequestError.new('Invalid timestamp: must be an integer', nil, 400)
103
+ raise Stripe::InvalidRequestError.new('Invalid timestamp: must be an integer', nil, http_status: 400)
66
104
  elsif trial_end < Time.now.utc.to_i
67
- raise Stripe::InvalidRequestError.new('Invalid timestamp: must be an integer Unix timestamp in the future', nil, 400)
105
+ raise Stripe::InvalidRequestError.new('Invalid timestamp: must be an integer Unix timestamp in the future', nil, http_status: 400)
68
106
  elsif trial_end > Time.now.utc.to_i + 31557600*5 # five years
69
- raise Stripe::InvalidRequestError.new('Invalid timestamp: can be no more than five years in the future', nil, 400)
107
+ raise Stripe::InvalidRequestError.new('Invalid timestamp: can be no more than five years in the future', nil, http_status: 400)
70
108
  end
71
109
  end
72
110
  end
73
111
 
112
+ def total_items_amount(items)
113
+ total = 0
114
+ items.each { |i| total += (i[:quantity] || 1) * i[:plan][:amount] }
115
+ total
116
+ end
74
117
  end
75
118
  end
76
119
  end
@@ -28,14 +28,15 @@ module StripeMock
28
28
  if token.nil? || @card_tokens[token].nil?
29
29
  # TODO: Make this strict
30
30
  msg = "Invalid token id: #{token}"
31
- raise Stripe::InvalidRequestError.new(msg, 'tok', 404)
31
+ raise Stripe::InvalidRequestError.new(msg, 'tok', http_status: 404)
32
32
  else
33
33
  @card_tokens.delete(token)
34
34
  end
35
35
  end
36
36
 
37
37
  def get_card_or_bank_by_token(token)
38
- @card_tokens[token] || @bank_tokens[token] || raise(Stripe::InvalidRequestError.new("Invalid token id: #{token}", 'tok', 404))
38
+ token_id = token['id'] || token
39
+ @card_tokens[token_id] || @bank_tokens[token_id] || raise(Stripe::InvalidRequestError.new("Invalid token id: #{token_id}", 'tok', http_status: 404))
39
40
  end
40
41
 
41
42
  end
@@ -52,29 +52,98 @@ module StripeMock
52
52
  def pay_invoice(route, method_url, params, headers)
53
53
  route =~ method_url
54
54
  assert_existence :invoice, $1, invoices[$1]
55
- invoices[$1].merge!(:paid => true, :attempted => true, :charge => 'ch_1fD6uiR9FAA2zc')
55
+ charge = invoice_charge(invoices[$1])
56
+ invoices[$1].merge!(:paid => true, :attempted => true, :charge => charge[:id])
56
57
  end
57
58
 
58
59
  def upcoming_invoice(route, method_url, params, headers)
59
60
  route =~ method_url
60
- raise Stripe::InvalidRequestError.new('Missing required param: customer', nil, 400) if params[:customer].nil?
61
+ raise Stripe::InvalidRequestError.new('Missing required param: customer', nil, http_status: 400) if params[:customer].nil?
62
+ raise Stripe::InvalidRequestError.new('When previewing changes to a subscription, you must specify either `subscription` or `subscription_items`', nil, http_status: 400) if !params[:subscription_proration_date].nil? && params[:subscription].nil? && params[:subscription_plan].nil?
63
+ raise Stripe::InvalidRequestError.new('Cannot specify proration date without specifying a subscription', nil, http_status: 400) if !params[:subscription_proration_date].nil? && params[:subscription].nil?
61
64
 
62
65
  customer = customers[params[:customer]]
63
66
  assert_existence :customer, params[:customer], customer
64
67
 
65
- raise Stripe::InvalidRequestError.new("No upcoming invoices for customer: #{customer[:id]}", nil, 404) if customer[:subscriptions][:data].length == 0
68
+ raise Stripe::InvalidRequestError.new("No upcoming invoices for customer: #{customer[:id]}", nil, http_status: 404) if customer[:subscriptions][:data].length == 0
66
69
 
67
- most_recent = customer[:subscriptions][:data].min_by { |sub| sub[:current_period_end] }
68
- invoice_item = get_mock_subscription_line_item(most_recent)
70
+ subscription =
71
+ if params[:subscription]
72
+ customer[:subscriptions][:data].select{|s|s[:id] == params[:subscription]}.first
73
+ else
74
+ customer[:subscriptions][:data].min_by { |sub| sub[:current_period_end] }
75
+ end
69
76
 
70
- id = new_id('in')
71
- invoices[id] = Data.mock_invoice([invoice_item],
72
- id: id,
77
+ if params[:subscription_proration_date] && !((subscription[:current_period_start]..subscription[:current_period_end]) === params[:subscription_proration_date])
78
+ raise Stripe::InvalidRequestError.new('Cannot specify proration date outside of current subscription period', nil, http_status: 400)
79
+ end
80
+
81
+ prorating = false
82
+ subscription_proration_date = nil
83
+ subscription_plan_id = params[:subscription_plan] || subscription[:plan][:id]
84
+ subscription_quantity = params[:subscription_quantity] || subscription[:quantity]
85
+ if subscription_plan_id != subscription[:plan][:id] || subscription_quantity != subscription[:quantity]
86
+ prorating = true
87
+ invoice_date = Time.now.to_i
88
+ subscription_plan = assert_existence :plan, subscription_plan_id, plans[subscription_plan_id.to_s]
89
+ preview_subscription = Data.mock_subscription
90
+ preview_subscription = resolve_subscription_changes(preview_subscription, [subscription_plan], customer, { trial_end: params[:subscription_trial_end] })
91
+ preview_subscription[:id] = subscription[:id]
92
+ preview_subscription[:quantity] = subscription_quantity
93
+ subscription_proration_date = params[:subscription_proration_date] || Time.now
94
+ else
95
+ preview_subscription = subscription
96
+ invoice_date = subscription[:current_period_end]
97
+ end
98
+
99
+ invoice_lines = []
100
+
101
+ if prorating
102
+ unused_amount = subscription[:plan][:amount] * subscription[:quantity] * (subscription[:current_period_end] - subscription_proration_date.to_i) / (subscription[:current_period_end] - subscription[:current_period_start])
103
+ invoice_lines << Data.mock_line_item(
104
+ id: new_id('ii'),
105
+ amount: -unused_amount,
106
+ description: 'Unused time',
107
+ plan: subscription[:plan],
108
+ period: {
109
+ start: subscription_proration_date.to_i,
110
+ end: subscription[:current_period_end]
111
+ },
112
+ quantity: subscription[:quantity],
113
+ proration: true
114
+ )
115
+
116
+ preview_plan = assert_existence :plan, params[:subscription_plan], plans[params[:subscription_plan]]
117
+ if preview_plan[:interval] == subscription[:plan][:interval] && preview_plan[:interval_count] == subscription[:plan][:interval_count] && params[:subscription_trial_end].nil?
118
+ remaining_amount = preview_plan[:amount] * subscription_quantity * (subscription[:current_period_end] - subscription_proration_date.to_i) / (subscription[:current_period_end] - subscription[:current_period_start])
119
+ invoice_lines << Data.mock_line_item(
120
+ id: new_id('ii'),
121
+ amount: remaining_amount,
122
+ description: 'Remaining time',
123
+ plan: preview_plan,
124
+ period: {
125
+ start: subscription_proration_date.to_i,
126
+ end: subscription[:current_period_end]
127
+ },
128
+ quantity: subscription_quantity,
129
+ proration: true
130
+ )
131
+ end
132
+ end
133
+
134
+ subscription_line = get_mock_subscription_line_item(preview_subscription)
135
+ invoice_lines << subscription_line
136
+
137
+ Data.mock_invoice(invoice_lines,
138
+ id: new_id('in'),
73
139
  customer: customer[:id],
74
- subscription: most_recent[:id],
75
- period_start: most_recent[:current_period_start],
76
- period_end: most_recent[:current_period_end],
77
- next_payment_attempt: most_recent[:current_period_end] + 3600 )
140
+ discount: customer[:discount],
141
+ created: invoice_date,
142
+ starting_balance: customer[:account_balance],
143
+ subscription: preview_subscription[:id],
144
+ period_start: prorating ? invoice_date : preview_subscription[:current_period_start],
145
+ period_end: prorating ? invoice_date : preview_subscription[:current_period_end],
146
+ next_payment_attempt: preview_subscription[:current_period_end] + 3600 )
78
147
  end
79
148
 
80
149
  private
@@ -84,15 +153,25 @@ module StripeMock
84
153
  id: subscription[:id],
85
154
  type: "subscription",
86
155
  plan: subscription[:plan],
87
- amount: subscription[:plan][:amount],
156
+ amount: subscription[:status] == 'trialing' ? 0 : subscription[:plan][:amount] * subscription[:quantity],
88
157
  discountable: true,
89
- quantity: 1,
158
+ quantity: subscription[:quantity],
90
159
  period: {
91
160
  start: subscription[:current_period_end],
92
161
  end: get_ending_time(subscription[:current_period_start], subscription[:plan], 2)
93
162
  })
94
163
  end
95
164
 
165
+ ## charge the customer on the invoice, if one does not exist, create
166
+ #anonymous charge
167
+ def invoice_charge(invoice)
168
+ begin
169
+ new_charge(nil, nil, {customer: invoice[:customer]["id"], amount: invoice[:amount_due], currency: StripeMock.default_currency}, nil)
170
+ rescue Stripe::InvalidRequestError
171
+ new_charge(nil, nil, {source: generate_card_token, amount: invoice[:amount_due], currency: StripeMock.default_currency}, nil)
172
+ end
173
+ end
174
+
96
175
  end
97
176
  end
98
177
  end
@@ -15,16 +15,16 @@ module StripeMock
15
15
  order_items = []
16
16
 
17
17
  unless params[:currency].to_s.size == 3
18
- raise Stripe::InvalidRequestError.new('You must supply a currency', nil, 400)
18
+ raise Stripe::InvalidRequestError.new('You must supply a currency', nil, http_status: 400)
19
19
  end
20
20
 
21
21
  if params[:items]
22
22
  unless params[:items].is_a? Array
23
- raise Stripe::InvalidRequestError.new('You must supply a list of items', nil, 400)
23
+ raise Stripe::InvalidRequestError.new('You must supply a list of items', nil, http_status: 400)
24
24
  end
25
25
 
26
26
  unless params[:items].first.is_a? Hash
27
- raise Stripe::InvalidRequestError.new('You must supply an item', nil, 400)
27
+ raise Stripe::InvalidRequestError.new('You must supply an item', nil, http_status: 400)
28
28
  end
29
29
  end
30
30
 
@@ -45,7 +45,7 @@ module StripeMock
45
45
  end
46
46
  end
47
47
 
48
- if [:created, :paid, :canceled, :fulfilled, :returned].includes? params[:status]
48
+ if %w(created paid canceled fulfilled returned).include? params[:status]
49
49
  order[:status] = params[:status]
50
50
  end
51
51
  order
@@ -61,7 +61,7 @@ module StripeMock
61
61
  order = assert_existence :order, $1, orders[$1]
62
62
 
63
63
  if params[:source].blank? && params[:customer].blank?
64
- raise Stripe::InvalidRequestError.new('You must supply a source or customer', nil, 400)
64
+ raise Stripe::InvalidRequestError.new('You must supply a source or customer', nil, http_status: 400)
65
65
  end
66
66
 
67
67
  charge_id = new_id('ch')
@@ -0,0 +1,32 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Payouts
4
+
5
+ def Payouts.included(klass)
6
+ klass.add_handler 'post /v1/payouts', :new_payout
7
+ klass.add_handler 'get /v1/payouts', :list_payouts
8
+ klass.add_handler 'get /v1/payouts/(.*)', :get_payout
9
+ end
10
+
11
+ def new_payout(route, method_url, params, headers)
12
+ id = new_id('po')
13
+
14
+ unless params[:amount].is_a?(Integer) || (params[:amount].is_a?(String) && /^\d+$/.match(params[:amount]))
15
+ raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", 'amount', http_status: 400)
16
+ end
17
+
18
+ payouts[id] = Data.mock_payout(params.merge :id => id)
19
+ end
20
+
21
+ def list_payouts(route, method_url, params, headers)
22
+ Data.mock_list_object(payouts.clone.values, params)
23
+ end
24
+
25
+ def get_payout(route, method_url, params, headers)
26
+ route =~ method_url
27
+ assert_existence :payout, $1, payouts[$1]
28
+ payouts[$1] ||= Data.mock_payout(:id => $1)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -11,6 +11,7 @@ module StripeMock
11
11
  end
12
12
 
13
13
  def new_plan(route, method_url, params, headers)
14
+ params[:id] ||= new_id('plan')
14
15
  validate_create_plan_params(params)
15
16
  plans[ params[:id] ] = Data.mock_plan(params)
16
17
  end
@@ -0,0 +1,43 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Products
4
+ def self.included(base)
5
+ base.add_handler 'post /v1/products', :create_product
6
+ base.add_handler 'get /v1/products/(.*)', :retrieve_product
7
+ base.add_handler 'post /v1/products/(.*)', :update_product
8
+ base.add_handler 'get /v1/products', :list_products
9
+ base.add_handler 'delete /v1/products/(.*)', :destroy_product
10
+ end
11
+
12
+ def create_product(_route, _method_url, params, _headers)
13
+ params[:id] ||= new_id('prod')
14
+ products[params[:id]] = Data.mock_product(params)
15
+ end
16
+
17
+ def retrieve_product(route, method_url, _params, _headers)
18
+ id = method_url.match(route).captures.first
19
+ assert_existence :product, id, products[id]
20
+ end
21
+
22
+ def update_product(route, method_url, params, _headers)
23
+ id = method_url.match(route).captures.first
24
+ product = assert_existence :product, id, products[id]
25
+
26
+ product.merge!(params)
27
+ end
28
+
29
+ def list_products(_route, _method_url, params, _headers)
30
+ limit = params[:limit] || 10
31
+ Data.mock_list_object(products.values.take(limit), params)
32
+ end
33
+
34
+ def destroy_product(route, method_url, _params, _headers)
35
+ id = method_url.match(route).captures.first
36
+ assert_existence :product, id, products[id]
37
+
38
+ products.delete(id)
39
+ { id: id, object: 'product', deleted: true }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -12,6 +12,18 @@ module StripeMock
12
12
  params[:id] ||= new_id('rp')
13
13
  cards = []
14
14
 
15
+ if params[:name].nil?
16
+ raise StripeMock::StripeMockError.new("Missing required parameter name for recipients.")
17
+ end
18
+
19
+ if params[:type].nil?
20
+ raise StripeMock::StripeMockError.new("Missing required parameter type for recipients.")
21
+ end
22
+
23
+ unless %w(individual corporation).include?(params[:type])
24
+ raise StripeMock::StripeMockError.new("Type must be either individual or corporation..")
25
+ end
26
+
15
27
  if params[:bank_account]
16
28
  params[:active_account] = get_bank_by_token(params.delete(:bank_account))
17
29
  end
@@ -0,0 +1,91 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Refunds
4
+
5
+ def Refunds.included(klass)
6
+ klass.add_handler 'post /v1/refunds', :new_refund
7
+ klass.add_handler 'get /v1/refunds', :get_refunds
8
+ klass.add_handler 'get /v1/refunds/(.*)', :get_refund
9
+ klass.add_handler 'post /v1/refunds/(.*)', :update_refund
10
+ end
11
+
12
+ def new_refund(route, method_url, params, headers)
13
+ if headers && headers[:idempotency_key]
14
+ params[:idempotency_key] = headers[:idempotency_key]
15
+ if refunds.any?
16
+ original_refund = refunds.values.find { |c| c[:idempotency_key] == headers[:idempotency_key]}
17
+ return refunds[original_refund[:id]] if original_refund
18
+ end
19
+ end
20
+
21
+ charge = assert_existence :charge, params[:charge], charges[params[:charge]]
22
+ params[:amount] ||= charge[:amount]
23
+ id = new_id('re')
24
+ bal_trans_params = {
25
+ amount: params[:amount] * -1,
26
+ source: id,
27
+ type: 'refund'
28
+ }
29
+ balance_transaction_id = new_balance_transaction('txn', bal_trans_params)
30
+ refund = Data.mock_refund params.merge(
31
+ :balance_transaction => balance_transaction_id,
32
+ :id => id,
33
+ :charge => charge[:id],
34
+ )
35
+ add_refund_to_charge(refund, charge)
36
+ refunds[id] = refund
37
+
38
+ if params[:expand] == ['balance_transaction']
39
+ refunds[id][:balance_transaction] =
40
+ balance_transactions[balance_transaction_id]
41
+ end
42
+ refund
43
+ end
44
+
45
+ def update_refund(route, method_url, params, headers)
46
+ route =~ method_url
47
+ id = $1
48
+
49
+ refund = assert_existence :refund, id, refunds[id]
50
+ allowed = allowed_refund_params(params)
51
+ disallowed = params.keys - allowed
52
+ if disallowed.count > 0
53
+ raise Stripe::InvalidRequestError.new("Received unknown parameters: #{disallowed.join(', ')}" , '', http_status: 400)
54
+ end
55
+
56
+ refunds[id] = Util.rmerge(refund, params)
57
+ end
58
+
59
+ def get_refunds(route, method_url, params, headers)
60
+ params[:offset] ||= 0
61
+ params[:limit] ||= 10
62
+
63
+ clone = refunds.clone
64
+
65
+ Data.mock_list_object(clone.values, params)
66
+ end
67
+
68
+ def get_refund(route, method_url, params, headers)
69
+ route =~ method_url
70
+ refund_id = $1 || params[:refund]
71
+ assert_existence :refund, refund_id, refunds[refund_id]
72
+ end
73
+
74
+ private
75
+
76
+ def ensure_refund_required_params(params)
77
+ if non_integer_charge_amount?(params)
78
+ raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", 'amount', http_status: 400)
79
+ elsif non_positive_charge_amount?(params)
80
+ raise Stripe::InvalidRequestError.new('Invalid positive integer', 'amount', http_status: 400)
81
+ elsif params[:charge].nil?
82
+ raise Stripe::InvalidRequestError.new('Must provide the identifier of the charge to refund.', nil)
83
+ end
84
+ end
85
+
86
+ def allowed_refund_params(params)
87
+ [:metadata]
88
+ end
89
+ end
90
+ end
91
+ end