stripe-ruby-mock 2.4.0 → 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 (98) 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 -14
  9. data/lib/stripe_mock/api/instance.rb +6 -6
  10. data/lib/stripe_mock/api/webhooks.rb +5 -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 +217 -56
  14. data/lib/stripe_mock/instance.rb +93 -8
  15. data/lib/stripe_mock/request_handlers/accounts.rb +34 -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 +25 -17
  19. data/lib/stripe_mock/request_handlers/coupons.rb +5 -4
  20. data/lib/stripe_mock/request_handlers/customers.rb +26 -8
  21. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +13 -0
  22. data/lib/stripe_mock/request_handlers/external_accounts.rb +55 -0
  23. data/lib/stripe_mock/request_handlers/helpers/bank_account_helpers.rb +1 -1
  24. data/lib/stripe_mock/request_handlers/helpers/card_helpers.rb +9 -7
  25. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +10 -6
  26. data/lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb +49 -0
  27. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +55 -15
  28. data/lib/stripe_mock/request_handlers/helpers/token_helpers.rb +2 -2
  29. data/lib/stripe_mock/request_handlers/invoices.rb +93 -14
  30. data/lib/stripe_mock/request_handlers/orders.rb +5 -5
  31. data/lib/stripe_mock/request_handlers/payouts.rb +32 -0
  32. data/lib/stripe_mock/request_handlers/plans.rb +1 -0
  33. data/lib/stripe_mock/request_handlers/products.rb +43 -0
  34. data/lib/stripe_mock/request_handlers/refunds.rb +10 -7
  35. data/lib/stripe_mock/request_handlers/subscription_items.rb +36 -0
  36. data/lib/stripe_mock/request_handlers/subscriptions.rb +95 -34
  37. data/lib/stripe_mock/request_handlers/tax_rates.rb +36 -0
  38. data/lib/stripe_mock/request_handlers/tokens.rb +9 -3
  39. data/lib/stripe_mock/request_handlers/transfers.rb +11 -5
  40. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +10 -1
  41. data/lib/stripe_mock/server.rb +14 -1
  42. data/lib/stripe_mock/test_strategies/base.rb +7 -5
  43. data/lib/stripe_mock/test_strategies/live.rb +5 -0
  44. data/lib/stripe_mock/test_strategies/mock.rb +8 -0
  45. data/lib/stripe_mock/util.rb +8 -2
  46. data/lib/stripe_mock/version.rb +1 -1
  47. data/lib/stripe_mock/webhook_fixtures/account.updated.json +1 -1
  48. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_reinstated.json +88 -0
  49. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_withdrawn.json +88 -0
  50. data/lib/stripe_mock/webhook_fixtures/charge.updated.json +58 -0
  51. data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +40 -10
  52. data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +39 -10
  53. data/lib/stripe_mock/webhook_fixtures/customer.subscription.trial_will_end.json +39 -10
  54. data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +40 -11
  55. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +3 -2
  56. data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +1 -1
  57. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +92 -85
  58. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +3 -2
  59. data/lib/stripe_mock/webhook_fixtures/plan.created.json +1 -1
  60. data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +1 -1
  61. data/lib/stripe_mock/webhook_fixtures/plan.updated.json +1 -1
  62. data/lib/stripe_mock.rb +13 -0
  63. data/spec/instance_spec.rb +54 -4
  64. data/spec/integration_examples/prepare_error_examples.rb +6 -6
  65. data/spec/list_spec.rb +16 -3
  66. data/spec/readme_spec.rb +2 -0
  67. data/spec/server_spec.rb +6 -2
  68. data/spec/shared_stripe_examples/account_examples.rb +35 -0
  69. data/spec/shared_stripe_examples/balance_examples.rb +11 -0
  70. data/spec/shared_stripe_examples/balance_transaction_examples.rb +28 -0
  71. data/spec/shared_stripe_examples/bank_examples.rb +28 -1
  72. data/spec/shared_stripe_examples/card_examples.rb +10 -3
  73. data/spec/shared_stripe_examples/charge_examples.rb +73 -17
  74. data/spec/shared_stripe_examples/coupon_examples.rb +8 -2
  75. data/spec/shared_stripe_examples/customer_examples.rb +54 -2
  76. data/spec/shared_stripe_examples/dispute_examples.rb +19 -8
  77. data/spec/shared_stripe_examples/ephemeral_key_examples.rb +17 -0
  78. data/spec/shared_stripe_examples/error_mock_examples.rb +3 -3
  79. data/spec/shared_stripe_examples/external_account_examples.rb +170 -0
  80. data/spec/shared_stripe_examples/extra_features_examples.rb +2 -0
  81. data/spec/shared_stripe_examples/invoice_examples.rb +314 -51
  82. data/spec/shared_stripe_examples/payout_examples.rb +68 -0
  83. data/spec/shared_stripe_examples/plan_examples.rb +37 -5
  84. data/spec/shared_stripe_examples/product_example.rb +65 -0
  85. data/spec/shared_stripe_examples/recipient_examples.rb +7 -7
  86. data/spec/shared_stripe_examples/refund_examples.rb +17 -11
  87. data/spec/shared_stripe_examples/subscription_examples.rb +463 -33
  88. data/spec/shared_stripe_examples/subscription_items_examples.rb +75 -0
  89. data/spec/shared_stripe_examples/tax_rate_examples.rb +42 -0
  90. data/spec/shared_stripe_examples/transfer_examples.rb +72 -23
  91. data/spec/shared_stripe_examples/webhook_event_examples.rb +74 -5
  92. data/spec/spec_helper.rb +7 -6
  93. data/spec/stripe_mock_spec.rb +16 -3
  94. data/spec/support/stripe_examples.rb +7 -1
  95. data/spec/util_spec.rb +35 -1
  96. data/stripe-ruby-mock.gemspec +1 -1
  97. metadata +34 -7
  98. data/ChangeLog.rdoc +0 -4
@@ -1,6 +1,7 @@
1
1
  module StripeMock
2
2
  module RequestHandlers
3
3
  module Accounts
4
+ VALID_START_YEAR = 2009
4
5
 
5
6
  def Accounts.included(klass)
6
7
  klass.add_handler 'post /v1/accounts', :new_account
@@ -26,8 +27,14 @@ module StripeMock
26
27
 
27
28
  def update_account(route, method_url, params, headers)
28
29
  route =~ method_url
29
- assert_existence :account, $1, accounts[$1]
30
- accounts[$1].merge!(params)
30
+ account = assert_existence :account, $1, accounts[$1]
31
+ account.merge!(params)
32
+ if blank_value?(params[:tos_acceptance], :date)
33
+ raise Stripe::InvalidRequestError.new("Invalid integer: ", "tos_acceptance[date]", http_status: 400)
34
+ elsif params[:tos_acceptance] && params[:tos_acceptance][:date]
35
+ validate_acceptance_date(params[:tos_acceptance][:date])
36
+ end
37
+ account
31
38
  end
32
39
 
33
40
  def list_accounts(route, method_url, params, headers)
@@ -49,6 +56,31 @@ module StripeMock
49
56
  accounts[acc[:id]] = acc
50
57
  end
51
58
  end
59
+
60
+ # Checks if setting a blank value
61
+ #
62
+ # returns true if the key is included in the hash
63
+ # and its value is empty or nil
64
+ def blank_value?(hash, key)
65
+ if hash.key?(key)
66
+ value = hash[key]
67
+ return true if value.nil? || "" == value
68
+ end
69
+ false
70
+ end
71
+
72
+ def validate_acceptance_date(unix_date)
73
+ unix_now = Time.now.strftime("%s").to_i
74
+ formatted_date = Time.at(unix_date)
75
+
76
+ return if formatted_date.year >= VALID_START_YEAR && unix_now >= unix_date
77
+
78
+ raise Stripe::InvalidRequestError.new(
79
+ "ToS acceptance date is not valid. Dates are expected to be integers, measured in seconds, not in the future, and after 2009",
80
+ "tos_acceptance[date]",
81
+ http_status: 400
82
+ )
83
+ end
52
84
  end
53
85
  end
54
86
  end
@@ -0,0 +1,17 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Balance
4
+
5
+ def Balance.included(klass)
6
+ klass.add_handler 'get /v1/balance', :get_balance
7
+ end
8
+
9
+ def get_balance(route, method_url, params, headers)
10
+ route =~ method_url
11
+
12
+ return_balance = Data.mock_balance(account_balance)
13
+ return_balance
14
+ end
15
+ end
16
+ end
17
+ end
@@ -9,11 +9,27 @@ module StripeMock
9
9
 
10
10
  def get_balance_transaction(route, method_url, params, headers)
11
11
  route =~ method_url
12
- assert_existence :balance_transaction, $1, balance_transactions[$1]
12
+ assert_existence :balance_transaction, $1, hide_additional_attributes(balance_transactions[$1])
13
13
  end
14
14
 
15
15
  def list_balance_transactions(route, method_url, params, headers)
16
- Data.mock_list_object(balance_transactions.values, params)
16
+ values = balance_transactions.values
17
+ if params.has_key?(:transfer)
18
+ # If transfer supplied as params, need to filter the btxns returned to only include those with the specified transfer id
19
+ values = values.select{|btxn| btxn[:transfer] == params[:transfer]}
20
+ end
21
+ Data.mock_list_object(values.map{|btxn| hide_additional_attributes(btxn)}, params)
22
+ end
23
+
24
+ private
25
+
26
+ def hide_additional_attributes(btxn)
27
+ # For automatic Stripe transfers, the transfer attribute on balance_transaction stores the transfer which
28
+ # included this balance_transaction. However, it is not exposed as a field returned on a balance_transaction.
29
+ # Therefore, need to not show this attribute if it exists.
30
+ if !btxn.nil?
31
+ btxn.reject{|k,v| k == :transfer }
32
+ end
17
33
  end
18
34
 
19
35
  end
@@ -13,9 +13,12 @@ module StripeMock
13
13
  end
14
14
 
15
15
  def new_charge(route, method_url, params, headers)
16
- if params[:idempotency_key] && charges.any?
17
- original_charge = charges.values.find { |c| c[:idempotency_key] == params[:idempotency_key]}
18
- return charges[original_charge[:id]] if original_charge
16
+ if headers && headers[:idempotency_key]
17
+ params[:idempotency_key] = headers[:idempotency_key]
18
+ if charges.any?
19
+ original_charge = charges.values.find { |c| c[:idempotency_key] == headers[:idempotency_key]}
20
+ return charges[original_charge[:id]] if original_charge
21
+ end
19
22
  end
20
23
 
21
24
  id = new_id('ch')
@@ -31,7 +34,7 @@ module StripeMock
31
34
  params[:source] = get_card_or_bank_by_token(params[:source])
32
35
  end
33
36
  elsif params[:source][:id]
34
- raise Stripe::InvalidRequestError.new("Invalid token id: #{params[:source]}", 'card', 400)
37
+ raise Stripe::InvalidRequestError.new("Invalid token id: #{params[:source]}", 'card', http_status: 400)
35
38
  end
36
39
  elsif params[:customer]
37
40
  customer = customers[params[:customer]]
@@ -41,7 +44,7 @@ module StripeMock
41
44
  end
42
45
 
43
46
  ensure_required_params(params)
44
- bal_trans_params = { amount: params[:amount], source: params[:source] }
47
+ bal_trans_params = { amount: params[:amount], source: id, application_fee: params[:application_fee] }
45
48
 
46
49
  balance_transaction_id = new_balance_transaction('txn', bal_trans_params)
47
50
 
@@ -49,12 +52,13 @@ module StripeMock
49
52
  params.merge :id => id,
50
53
  :balance_transaction => balance_transaction_id)
51
54
 
55
+ charge = charges[id].clone
52
56
  if params[:expand] == ['balance_transaction']
53
- charges[id][:balance_transaction] =
57
+ charge[:balance_transaction] =
54
58
  balance_transactions[balance_transaction_id]
55
59
  end
56
60
 
57
- charges[id]
61
+ charge
58
62
  end
59
63
 
60
64
  def update_charge(route, method_url, params, headers)
@@ -65,7 +69,7 @@ module StripeMock
65
69
  allowed = allowed_params(params)
66
70
  disallowed = params.keys - allowed
67
71
  if disallowed.count > 0
68
- raise Stripe::InvalidRequestError.new("Received unknown parameters: #{disallowed.join(', ')}" , '', 400)
72
+ raise Stripe::InvalidRequestError.new("Received unknown parameters: #{disallowed.join(', ')}" , '', http_status: 400)
69
73
  end
70
74
 
71
75
  charges[id] = Util.rmerge(charge, params)
@@ -87,7 +91,15 @@ module StripeMock
87
91
  def get_charge(route, method_url, params, headers)
88
92
  route =~ method_url
89
93
  charge_id = $1 || params[:charge]
90
- assert_existence :charge, charge_id, charges[charge_id]
94
+ charge = assert_existence :charge, charge_id, charges[charge_id]
95
+
96
+ charge = charge.clone
97
+ if params[:expand] == ['balance_transaction']
98
+ balance_transaction = balance_transactions[charge[:balance_transaction]]
99
+ charge[:balance_transaction] = balance_transaction
100
+ end
101
+
102
+ charge
91
103
  end
92
104
 
93
105
  def capture_charge(route, method_url, params, headers)
@@ -130,11 +142,11 @@ module StripeMock
130
142
  elsif params[:currency].nil?
131
143
  require_param(:currency)
132
144
  elsif non_integer_charge_amount?(params)
133
- raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", 'amount', 400)
145
+ raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", 'amount', http_status: 400)
134
146
  elsif non_positive_charge_amount?(params)
135
- raise Stripe::InvalidRequestError.new('Invalid positive integer', 'amount', 400)
147
+ raise Stripe::InvalidRequestError.new('Invalid positive integer', 'amount', http_status: 400)
136
148
  elsif params[:source].nil? && params[:customer].nil?
137
- raise Stripe::InvalidRequestError.new('Must provide source or customer.', nil)
149
+ raise Stripe::InvalidRequestError.new('Must provide source or customer.', http_status: nil)
138
150
  end
139
151
  end
140
152
 
@@ -146,12 +158,8 @@ module StripeMock
146
158
  params[:amount] && params[:amount] < 1
147
159
  end
148
160
 
149
- def require_param(param)
150
- raise Stripe::InvalidRequestError.new("Missing required param: #{param}", param.to_s, 400)
151
- end
152
-
153
161
  def allowed_params(params)
154
- allowed = [:description, :metadata, :receipt_email, :fraud_details, :shipping]
162
+ allowed = [:description, :metadata, :receipt_email, :fraud_details, :shipping, :destination]
155
163
 
156
164
  # This is a workaround for the way the Stripe API sends params even when they aren't modified.
157
165
  # Stipe will include those params even when they aren't modified.
@@ -11,18 +11,19 @@ module StripeMock
11
11
 
12
12
  def new_coupon(route, method_url, params, headers)
13
13
  params[:id] ||= new_id('coupon')
14
- raise Stripe::InvalidRequestError.new('Missing required param: duration', 'coupon', 400) unless params[:duration]
15
- coupons[ params[:id] ] = Data.mock_coupon(params)
14
+ raise Stripe::InvalidRequestError.new('Missing required param: duration', 'coupon', http_status: 400) unless params[:duration]
15
+ raise Stripe::InvalidRequestError.new('You must pass currency when passing amount_off', 'coupon', http_status: 400) if params[:amount_off] && !params[:currency]
16
+ coupons[ params[:id] ] = Data.mock_coupon({amount_off: nil, percent_off:nil}.merge(params))
16
17
  end
17
18
 
18
19
  def get_coupon(route, method_url, params, headers)
19
20
  route =~ method_url
20
- assert_existence :id, $1, coupons[$1]
21
+ assert_existence :coupon, $1, coupons[$1]
21
22
  end
22
23
 
23
24
  def delete_coupon(route, method_url, params, headers)
24
25
  route =~ method_url
25
- assert_existence :id, $1, coupons.delete($1)
26
+ assert_existence :coupon, $1, coupons.delete($1)
26
27
  end
27
28
 
28
29
  def list_coupons(route, method_url, params, headers)
@@ -8,6 +8,7 @@ module StripeMock
8
8
  klass.add_handler 'get /v1/customers/([^/]*)', :get_customer
9
9
  klass.add_handler 'delete /v1/customers/([^/]*)', :delete_customer
10
10
  klass.add_handler 'get /v1/customers', :list_customers
11
+ klass.add_handler 'delete /v1/customers/([^/]*)/discount', :delete_customer_discount
11
12
  end
12
13
 
13
14
  def new_customer(route, method_url, params, headers)
@@ -18,7 +19,7 @@ module StripeMock
18
19
  new_card =
19
20
  if params[:source].is_a?(Hash)
20
21
  unless params[:source][:object] && params[:source][:number] && params[:source][:exp_month] && params[:source][:exp_year]
21
- raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, 400)
22
+ raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, http_status: 400)
22
23
  end
23
24
  card_from_params(params[:source])
24
25
  else
@@ -35,22 +36,22 @@ module StripeMock
35
36
  plan = assert_existence :plan, plan_id, plans[plan_id]
36
37
 
37
38
  if params[:default_source].nil? && params[:trial_end].nil? && plan[:trial_period_days].nil? && plan[:amount] != 0
38
- raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, 400)
39
+ raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, http_status: 400)
39
40
  end
40
41
 
41
42
  subscription = Data.mock_subscription({ id: new_id('su') })
42
- subscription.merge!(custom_subscription_params(plan, customers[ params[:id] ], params))
43
+ subscription = resolve_subscription_changes(subscription, [plan], customers[ params[:id] ], params)
43
44
  add_subscription_to_customer(customers[ params[:id] ], subscription)
44
45
  subscriptions[subscription[:id]] = subscription
45
46
  elsif params[:trial_end]
46
- raise Stripe::InvalidRequestError.new('Received unknown parameter: trial_end', nil, 400)
47
+ raise Stripe::InvalidRequestError.new('Received unknown parameter: trial_end', nil, http_status: 400)
47
48
  end
48
49
 
49
50
  if params[:coupon]
50
51
  coupon = coupons[ params[:coupon] ]
51
52
  assert_existence :coupon, params[:coupon], coupon
52
53
 
53
- add_coupon_to_customer(customers[params[:id]], coupon)
54
+ add_coupon_to_object(customers[params[:id]], coupon)
54
55
  end
55
56
 
56
57
  customers[ params[:id] ]
@@ -77,7 +78,7 @@ module StripeMock
77
78
  new_card = get_card_or_bank_by_token(params.delete(:source))
78
79
  elsif params[:source].is_a?(Hash)
79
80
  unless params[:source][:object] && params[:source][:number] && params[:source][:exp_month] && params[:source][:exp_year]
80
- raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, 400)
81
+ raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, http_status: 400)
81
82
  end
82
83
  new_card = card_from_params(params.delete(:source))
83
84
  end
@@ -89,7 +90,7 @@ module StripeMock
89
90
  coupon = coupons[ params[:coupon] ]
90
91
  assert_existence :coupon, params[:coupon], coupon
91
92
 
92
- add_coupon_to_customer(cus, coupon)
93
+ add_coupon_to_object(cus, coupon)
93
94
  end
94
95
 
95
96
  cus
@@ -107,13 +108,30 @@ module StripeMock
107
108
 
108
109
  def get_customer(route, method_url, params, headers)
109
110
  route =~ method_url
110
- assert_existence :customer, $1, customers[$1]
111
+ customer = assert_existence :customer, $1, customers[$1]
112
+
113
+ customer = customer.clone
114
+ if params[:expand] == ['default_source']
115
+ customer[:default_source] = customer[:sources][:data].detect do |source|
116
+ source[:id] == customer[:default_source]
117
+ end
118
+ end
119
+
120
+ customer
111
121
  end
112
122
 
113
123
  def list_customers(route, method_url, params, headers)
114
124
  Data.mock_list_object(customers.values, params)
115
125
  end
116
126
 
127
+ def delete_customer_discount(route, method_url, params, headers)
128
+ route =~ method_url
129
+ cus = assert_existence :customer, $1, customers[$1]
130
+
131
+ cus[:discount] = nil
132
+
133
+ cus
134
+ end
117
135
  end
118
136
  end
119
137
  end
@@ -0,0 +1,13 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module EphemeralKey
4
+ def self.included(klass)
5
+ klass.add_handler 'post /v1/ephemeral_keys', :create_ephemeral_key
6
+ end
7
+
8
+ def create_ephemeral_key(route, method_url, params, headers)
9
+ Data.mock_ephemeral_key(params)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,55 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module ExternalAccounts
4
+
5
+ def ExternalAccounts.included(klass)
6
+ klass.add_handler 'get /v1/accounts/(.*)/external_accounts', :retrieve_external_accounts
7
+ klass.add_handler 'post /v1/accounts/(.*)/external_accounts', :create_external_account
8
+ klass.add_handler 'post /v1/accounts/(.*)/external_accounts/(.*)/verify', :verify_external_account
9
+ klass.add_handler 'get /v1/accounts/(.*)/external_accounts/(.*)', :retrieve_external_account
10
+ klass.add_handler 'delete /v1/accounts/(.*)/external_accounts/(.*)', :delete_external_account
11
+ klass.add_handler 'post /v1/accounts/(.*)/external_accounts/(.*)', :update_external_account
12
+ end
13
+
14
+ def create_external_account(route, method_url, params, headers)
15
+ route =~ method_url
16
+ add_external_account_to(:account, $1, params, accounts)
17
+ end
18
+
19
+ def retrieve_external_accounts(route, method_url, params, headers)
20
+ route =~ method_url
21
+ retrieve_object_cards(:account, $1, accounts)
22
+ end
23
+
24
+ def retrieve_external_account(route, method_url, params, headers)
25
+ route =~ method_url
26
+ account = assert_existence :account, $1, accounts[$1]
27
+
28
+ assert_existence :card, $2, get_card(account, $2)
29
+ end
30
+
31
+ def delete_external_account(route, method_url, params, headers)
32
+ route =~ method_url
33
+ delete_card_from(:account, $1, $2, accounts)
34
+ end
35
+
36
+ def update_external_account(route, method_url, params, headers)
37
+ route =~ method_url
38
+ account = assert_existence :account, $1, accounts[$1]
39
+
40
+ card = assert_existence :card, $2, get_card(account, $2)
41
+ card.merge!(params)
42
+ card
43
+ end
44
+
45
+ def verify_external_account(route, method_url, params, headers)
46
+ route =~ method_url
47
+ account = assert_existence :account, $1, accounts[$1]
48
+
49
+ external_account = assert_existence :bank_account, $2, verify_bank_account(account, $2)
50
+ external_account
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -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,20 +6,49 @@ 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
@@ -28,13 +57,17 @@ module StripeMock
28
57
  def add_subscription_to_customer(cus, sub)
29
58
  if sub[:trial_end].nil? || sub[:trial_end] == "now"
30
59
  id = new_id('ch')
31
- charges[id] = Data.mock_charge(:id => id, :customer => cus[:id], :amount => sub[:plan][:amount])
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
+ )
32
65
  end
33
66
 
34
67
  if cus[:currency].nil?
35
- cus[:currency] = sub[:plan][:currency]
36
- elsif cus[:currency] != sub[:plan][:currency]
37
- 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)
38
71
  end
39
72
  cus[:subscriptions][:total_count] = (cus[:subscriptions][:total_count] || 0) + 1
40
73
  cus[:subscriptions][:data].unshift sub
@@ -50,6 +83,8 @@ module StripeMock
50
83
  # `intervals` is set to 1 when calculating current_period_end from current_period_start & plan
51
84
  # `intervals` is set to 2 when calculating Stripe::Invoice.upcoming end from current_period_start & plan
52
85
  def get_ending_time(start_time, plan, intervals = 1)
86
+ return start_time unless plan
87
+
53
88
  case plan[:interval]
54
89
  when "week"
55
90
  start_time + (604800 * (plan[:interval_count] || 1) * intervals)
@@ -65,15 +100,20 @@ module StripeMock
65
100
  def verify_trial_end(trial_end)
66
101
  if trial_end != "now"
67
102
  if !trial_end.is_a? Integer
68
- 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)
69
104
  elsif trial_end < Time.now.utc.to_i
70
- 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)
71
106
  elsif trial_end > Time.now.utc.to_i + 31557600*5 # five years
72
- 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)
73
108
  end
74
109
  end
75
110
  end
76
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
77
117
  end
78
118
  end
79
119
  end