stripe-ruby-mock 2.4.0 → 2.5.8

Sign up to get free protection for your applications and to get access to all the features.
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