stripe-ruby-mock 2.5.8 → 4.0.0

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 (159) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rspec_tests.yml +38 -0
  3. data/.gitignore +1 -1
  4. data/.rspec +2 -1
  5. data/CHANGELOG.md +77 -0
  6. data/Gemfile +1 -5
  7. data/README.md +19 -11
  8. data/lib/stripe_mock/api/client.rb +2 -2
  9. data/lib/stripe_mock/api/errors.rb +34 -28
  10. data/lib/stripe_mock/api/instance.rb +1 -1
  11. data/lib/stripe_mock/api/webhooks.rb +68 -24
  12. data/lib/stripe_mock/client.rb +2 -1
  13. data/lib/stripe_mock/data/list.rb +42 -9
  14. data/lib/stripe_mock/data.rb +359 -21
  15. data/lib/stripe_mock/instance.rb +23 -5
  16. data/lib/stripe_mock/request_handlers/account_links.rb +15 -0
  17. data/lib/stripe_mock/request_handlers/accounts.rb +17 -6
  18. data/lib/stripe_mock/request_handlers/balance_transactions.rb +2 -2
  19. data/lib/stripe_mock/request_handlers/charges.rb +31 -5
  20. data/lib/stripe_mock/request_handlers/checkout_session.rb +179 -0
  21. data/lib/stripe_mock/request_handlers/customers.rb +47 -19
  22. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +1 -1
  23. data/lib/stripe_mock/request_handlers/events.rb +30 -3
  24. data/lib/stripe_mock/request_handlers/express_login_links.rb +15 -0
  25. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +6 -0
  26. data/lib/stripe_mock/request_handlers/helpers/search_helpers.rb +67 -0
  27. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +36 -12
  28. data/lib/stripe_mock/request_handlers/helpers/token_helpers.rb +1 -1
  29. data/lib/stripe_mock/request_handlers/invoices.rb +26 -6
  30. data/lib/stripe_mock/request_handlers/payment_intents.rb +202 -0
  31. data/lib/stripe_mock/request_handlers/payment_methods.rb +124 -0
  32. data/lib/stripe_mock/request_handlers/plans.rb +1 -1
  33. data/lib/stripe_mock/request_handlers/prices.rb +71 -0
  34. data/lib/stripe_mock/request_handlers/products.rb +15 -5
  35. data/lib/stripe_mock/request_handlers/promotion_codes.rb +43 -0
  36. data/lib/stripe_mock/request_handlers/refunds.rb +13 -2
  37. data/lib/stripe_mock/request_handlers/setup_intents.rb +100 -0
  38. data/lib/stripe_mock/request_handlers/sources.rb +12 -6
  39. data/lib/stripe_mock/request_handlers/subscriptions.rb +146 -25
  40. data/lib/stripe_mock/request_handlers/tokens.rb +6 -4
  41. data/lib/stripe_mock/request_handlers/transfers.rb +12 -1
  42. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +124 -9
  43. data/lib/stripe_mock/server.rb +2 -2
  44. data/lib/stripe_mock/test_strategies/base.rb +98 -12
  45. data/lib/stripe_mock/test_strategies/live.rb +23 -12
  46. data/lib/stripe_mock/test_strategies/mock.rb +6 -2
  47. data/lib/stripe_mock/version.rb +1 -1
  48. data/lib/stripe_mock/webhook_fixtures/account.updated.json +1 -1
  49. data/lib/stripe_mock/webhook_fixtures/balance.available.json +27 -15
  50. data/lib/stripe_mock/webhook_fixtures/charge.captured.json +143 -0
  51. data/lib/stripe_mock/webhook_fixtures/charge.dispute.created.json +63 -16
  52. data/lib/stripe_mock/webhook_fixtures/charge.failed.json +101 -44
  53. data/lib/stripe_mock/webhook_fixtures/charge.refund.updated.json +35 -0
  54. data/lib/stripe_mock/webhook_fixtures/charge.refunded.json +145 -50
  55. data/lib/stripe_mock/webhook_fixtures/charge.succeeded.json +114 -43
  56. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.json +79 -0
  57. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.payment_mode.json +53 -0
  58. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.setup_mode.json +45 -0
  59. data/lib/stripe_mock/webhook_fixtures/customer.created.json +37 -45
  60. data/lib/stripe_mock/webhook_fixtures/customer.deleted.json +36 -32
  61. data/lib/stripe_mock/webhook_fixtures/customer.source.created.json +31 -22
  62. data/lib/stripe_mock/webhook_fixtures/customer.source.updated.json +36 -25
  63. data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +135 -47
  64. data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +134 -45
  65. data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +135 -56
  66. data/lib/stripe_mock/webhook_fixtures/customer.updated.json +38 -46
  67. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +176 -49
  68. data/lib/stripe_mock/webhook_fixtures/invoice.finalized.json +171 -0
  69. data/lib/stripe_mock/webhook_fixtures/invoice.paid.json +171 -0
  70. data/lib/stripe_mock/webhook_fixtures/invoice.payment_action_required.json +171 -0
  71. data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +149 -83
  72. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +149 -90
  73. data/lib/stripe_mock/webhook_fixtures/invoice.upcoming.json +70 -0
  74. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +178 -50
  75. data/lib/stripe_mock/webhook_fixtures/invoiceitem.created.json +87 -13
  76. data/lib/stripe_mock/webhook_fixtures/invoiceitem.updated.json +88 -14
  77. data/lib/stripe_mock/webhook_fixtures/mandate.updated.json +34 -0
  78. data/lib/stripe_mock/webhook_fixtures/payment_intent.amount_capturable_updated.json +170 -0
  79. data/lib/stripe_mock/webhook_fixtures/payment_intent.canceled.json +73 -0
  80. data/lib/stripe_mock/webhook_fixtures/payment_intent.created.json +86 -0
  81. data/lib/stripe_mock/webhook_fixtures/payment_intent.payment_failed.json +225 -0
  82. data/lib/stripe_mock/webhook_fixtures/payment_intent.processing.json +162 -0
  83. data/lib/stripe_mock/webhook_fixtures/payment_intent.requires_action.json +191 -0
  84. data/lib/stripe_mock/webhook_fixtures/payment_intent.succeeded.json +196 -0
  85. data/lib/stripe_mock/webhook_fixtures/payment_link.created.json +47 -0
  86. data/lib/stripe_mock/webhook_fixtures/payment_link.updated.json +50 -0
  87. data/lib/stripe_mock/webhook_fixtures/payment_method.attached.json +63 -0
  88. data/lib/stripe_mock/webhook_fixtures/payment_method.detached.json +62 -0
  89. data/lib/stripe_mock/webhook_fixtures/payout.created.json +40 -0
  90. data/lib/stripe_mock/webhook_fixtures/payout.paid.json +40 -0
  91. data/lib/stripe_mock/webhook_fixtures/payout.updated.json +46 -0
  92. data/lib/stripe_mock/webhook_fixtures/plan.created.json +30 -13
  93. data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +30 -13
  94. data/lib/stripe_mock/webhook_fixtures/plan.updated.json +34 -14
  95. data/lib/stripe_mock/webhook_fixtures/price.created.json +42 -0
  96. data/lib/stripe_mock/webhook_fixtures/price.deleted.json +42 -0
  97. data/lib/stripe_mock/webhook_fixtures/price.updated.json +48 -0
  98. data/lib/stripe_mock/webhook_fixtures/product.created.json +40 -0
  99. data/lib/stripe_mock/webhook_fixtures/product.deleted.json +40 -0
  100. data/lib/stripe_mock/webhook_fixtures/product.updated.json +47 -0
  101. data/lib/stripe_mock/webhook_fixtures/quote.accepted.json +92 -0
  102. data/lib/stripe_mock/webhook_fixtures/quote.canceled.json +92 -0
  103. data/lib/stripe_mock/webhook_fixtures/quote.created.json +92 -0
  104. data/lib/stripe_mock/webhook_fixtures/quote.finalized.json +92 -0
  105. data/lib/stripe_mock/webhook_fixtures/setup_intent.canceled.json +46 -0
  106. data/lib/stripe_mock/webhook_fixtures/setup_intent.created.json +51 -0
  107. data/lib/stripe_mock/webhook_fixtures/setup_intent.setup_failed.json +100 -0
  108. data/lib/stripe_mock/webhook_fixtures/setup_intent.succeeded.json +46 -0
  109. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.canceled.json +119 -0
  110. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.created.json +114 -0
  111. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.released.json +111 -0
  112. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.updated.json +125 -0
  113. data/lib/stripe_mock/webhook_fixtures/tax_rate.created.json +32 -0
  114. data/lib/stripe_mock/webhook_fixtures/tax_rate.updated.json +37 -0
  115. data/lib/stripe_mock.rb +11 -0
  116. data/spec/instance_spec.rb +13 -13
  117. data/spec/integration_examples/completing_checkout_sessions_example.rb +37 -0
  118. data/spec/list_spec.rb +38 -0
  119. data/spec/readme_spec.rb +1 -1
  120. data/spec/server_spec.rb +6 -3
  121. data/spec/shared_stripe_examples/account_examples.rb +10 -2
  122. data/spec/shared_stripe_examples/account_link_examples.rb +16 -0
  123. data/spec/shared_stripe_examples/balance_examples.rb +6 -0
  124. data/spec/shared_stripe_examples/balance_transaction_examples.rb +3 -3
  125. data/spec/shared_stripe_examples/bank_examples.rb +3 -3
  126. data/spec/shared_stripe_examples/bank_token_examples.rb +5 -7
  127. data/spec/shared_stripe_examples/card_examples.rb +4 -4
  128. data/spec/shared_stripe_examples/card_token_examples.rb +17 -21
  129. data/spec/shared_stripe_examples/charge_examples.rb +106 -22
  130. data/spec/shared_stripe_examples/checkout_session_examples.rb +99 -0
  131. data/spec/shared_stripe_examples/coupon_examples.rb +1 -1
  132. data/spec/shared_stripe_examples/customer_examples.rb +149 -53
  133. data/spec/shared_stripe_examples/dispute_examples.rb +2 -2
  134. data/spec/shared_stripe_examples/error_mock_examples.rb +8 -7
  135. data/spec/shared_stripe_examples/express_login_link_examples.rb +12 -0
  136. data/spec/shared_stripe_examples/external_account_examples.rb +3 -3
  137. data/spec/shared_stripe_examples/invoice_examples.rb +148 -40
  138. data/spec/shared_stripe_examples/invoice_item_examples.rb +1 -1
  139. data/spec/shared_stripe_examples/payment_intent_examples.rb +283 -0
  140. data/spec/shared_stripe_examples/payment_method_examples.rb +454 -0
  141. data/spec/shared_stripe_examples/payout_examples.rb +2 -2
  142. data/spec/shared_stripe_examples/plan_examples.rb +135 -92
  143. data/spec/shared_stripe_examples/price_examples.rb +292 -0
  144. data/spec/shared_stripe_examples/product_examples.rb +215 -0
  145. data/spec/shared_stripe_examples/promotion_code_examples.rb +68 -0
  146. data/spec/shared_stripe_examples/refund_examples.rb +38 -21
  147. data/spec/shared_stripe_examples/setup_intent_examples.rb +85 -0
  148. data/spec/shared_stripe_examples/subscription_examples.rb +706 -324
  149. data/spec/shared_stripe_examples/subscription_items_examples.rb +3 -2
  150. data/spec/shared_stripe_examples/transfer_examples.rb +16 -7
  151. data/spec/shared_stripe_examples/webhook_event_examples.rb +62 -16
  152. data/spec/spec_helper.rb +8 -5
  153. data/spec/stripe_mock_spec.rb +4 -4
  154. data/spec/support/shared_contexts/stripe_validator_spec.rb +8 -0
  155. data/spec/support/stripe_examples.rb +11 -1
  156. data/stripe-ruby-mock.gemspec +9 -5
  157. metadata +115 -47
  158. data/.travis.yml +0 -28
  159. data/spec/shared_stripe_examples/product_example.rb +0 -65
@@ -12,30 +12,35 @@ module StripeMock
12
12
  end
13
13
 
14
14
  def create_source(route, method_url, params, headers)
15
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
15
16
  route =~ method_url
16
- add_source_to(:customer, $1, params, customers)
17
+ add_source_to(:customer, $1, params, customers[stripe_account])
17
18
  end
18
19
 
19
20
  def retrieve_sources(route, method_url, params, headers)
21
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
20
22
  route =~ method_url
21
- retrieve_object_cards(:customer, $1, customers)
23
+ retrieve_object_cards(:customer, $1, customers[stripe_account])
22
24
  end
23
25
 
24
26
  def retrieve_source(route, method_url, params, headers)
27
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
25
28
  route =~ method_url
26
- customer = assert_existence :customer, $1, customers[$1]
29
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
27
30
 
28
31
  assert_existence :card, $2, get_card(customer, $2)
29
32
  end
30
33
 
31
34
  def delete_source(route, method_url, params, headers)
35
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
32
36
  route =~ method_url
33
- delete_card_from(:customer, $1, $2, customers)
37
+ delete_card_from(:customer, $1, $2, customers[stripe_account])
34
38
  end
35
39
 
36
40
  def update_source(route, method_url, params, headers)
41
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
37
42
  route =~ method_url
38
- customer = assert_existence :customer, $1, customers[$1]
43
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
39
44
 
40
45
  card = assert_existence :card, $2, get_card(customer, $2)
41
46
  card.merge!(params)
@@ -43,8 +48,9 @@ module StripeMock
43
48
  end
44
49
 
45
50
  def verify_source(route, method_url, params, headers)
51
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
46
52
  route =~ method_url
47
- customer = assert_existence :customer, $1, customers[$1]
53
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
48
54
 
49
55
  bank_account = assert_existence :bank_account, $2, verify_bank_account(customer, $2)
50
56
  bank_account
@@ -5,8 +5,9 @@ module StripeMock
5
5
  def Subscriptions.included(klass)
6
6
  klass.add_handler 'get /v1/subscriptions', :retrieve_subscriptions
7
7
  klass.add_handler 'post /v1/subscriptions', :create_subscription
8
- klass.add_handler 'get /v1/subscriptions/(.*)', :retrieve_subscription
8
+ klass.add_handler 'get /v1/subscriptions/((?!search).*)', :retrieve_subscription
9
9
  klass.add_handler 'post /v1/subscriptions/(.*)', :update_subscription
10
+ klass.add_handler 'get /v1/subscriptions/search', :search_subscriptions
10
11
  klass.add_handler 'delete /v1/subscriptions/(.*)', :cancel_subscription
11
12
 
12
13
  klass.add_handler 'post /v1/customers/(.*)/subscription(?:s)?', :create_customer_subscription
@@ -17,26 +18,29 @@ module StripeMock
17
18
  end
18
19
 
19
20
  def retrieve_customer_subscription(route, method_url, params, headers)
21
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
20
22
  route =~ method_url
21
23
 
22
- customer = assert_existence :customer, $1, customers[$1]
24
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
23
25
  subscription = get_customer_subscription(customer, $2)
24
26
 
25
27
  assert_existence :subscription, $2, subscription
26
28
  end
27
29
 
28
30
  def retrieve_customer_subscriptions(route, method_url, params, headers)
31
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
29
32
  route =~ method_url
30
33
 
31
- customer = assert_existence :customer, $1, customers[$1]
34
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
32
35
  customer[:subscriptions]
33
36
  end
34
37
 
35
38
  def create_customer_subscription(route, method_url, params, headers)
39
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
36
40
  route =~ method_url
37
41
 
38
42
  subscription_plans = get_subscription_plans_from_params(params)
39
- customer = assert_existence :customer, $1, customers[$1]
43
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
40
44
 
41
45
  if params[:source]
42
46
  new_card = get_card_by_token(params.delete(:source))
@@ -66,6 +70,16 @@ module StripeMock
66
70
  end
67
71
  end
68
72
 
73
+ if params[:promotion_code]
74
+ promotion_code_id = params[:promotion_code]
75
+
76
+ promotion_code = promotion_codes[promotion_code_id]
77
+
78
+ unless promotion_code
79
+ raise Stripe::InvalidRequestError.new("No such promotion code: #{promotion_code_id}", 'promotion_code', http_status: 400)
80
+ end
81
+ end
82
+
69
83
  subscriptions[subscription[:id]] = subscription
70
84
  add_subscription_to_customer(customer, subscription)
71
85
 
@@ -73,10 +87,10 @@ module StripeMock
73
87
  end
74
88
 
75
89
  def create_subscription(route, method_url, params, headers)
90
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
76
91
  if headers && headers[:idempotency_key]
77
92
  if subscriptions.any?
78
93
  original_subscription = subscriptions.values.find { |c| c[:idempotency_key] == headers[:idempotency_key]}
79
- puts original_subscription
80
94
  return subscriptions[original_subscription[:id]] if original_subscription
81
95
  end
82
96
  end
@@ -86,15 +100,7 @@ module StripeMock
86
100
 
87
101
  customer = params[:customer]
88
102
  customer_id = customer.is_a?(Stripe::Customer) ? customer[:id] : customer.to_s
89
- customer = assert_existence :customer, customer_id, customers[customer_id]
90
-
91
- if subscription_plans && customer
92
- subscription_plans.each do |plan|
93
- unless customer[:currency].to_s == plan[:currency].to_s
94
- raise Stripe::InvalidRequestError.new("Customer's currency of #{customer[:currency]} does not match plan's currency of #{plan[:currency]}", 'currency', http_status: 400)
95
- end
96
- end
97
- end
103
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
98
104
 
99
105
  if params[:source]
100
106
  new_card = get_card_by_token(params.delete(:source))
@@ -102,7 +108,7 @@ module StripeMock
102
108
  customer[:default_source] = new_card[:id]
103
109
  end
104
110
 
105
- allowed_params = %w(customer application_fee_percent coupon items metadata plan quantity source tax_percent trial_end trial_period_days current_period_start created prorate billing_cycle_anchor billing days_until_due idempotency_key)
111
+ allowed_params = %w(id customer application_fee_percent coupon items metadata plan quantity source tax_percent trial_end trial_period_days current_period_start created prorate billing_cycle_anchor billing days_until_due idempotency_key enable_incomplete_payments cancel_at_period_end default_tax_rates payment_behavior pending_invoice_item_interval default_payment_method collection_method off_session trial_from_plan proration_behavior backdate_start_date transfer_data expand automatic_tax payment_settings trial_settings promotion_code)
106
112
  unknown_params = params.keys - allowed_params.map(&:to_sym)
107
113
  if unknown_params.length > 0
108
114
  raise Stripe::InvalidRequestError.new("Received unknown parameter: #{unknown_params.join}", unknown_params.first.to_s, http_status: 400)
@@ -118,6 +124,10 @@ module StripeMock
118
124
  # Note: needs updating for subscriptions with multiple plans
119
125
  verify_card_present(customer, subscription_plans.first, subscription, params)
120
126
 
127
+ if params[:coupon] && params[:promotion_code]
128
+ raise Stripe::InvalidRequestError.new("You may only specify one of these parameters: coupon, promotion_code", "coupon", http_status: 400)
129
+ end
130
+
121
131
  if params[:coupon]
122
132
  coupon_id = params[:coupon]
123
133
 
@@ -133,6 +143,50 @@ module StripeMock
133
143
  end
134
144
  end
135
145
 
146
+ if params[:promotion_code]
147
+ promotion_code_id = params[:promotion_code]
148
+
149
+ promotion_code = promotion_codes[promotion_code_id]
150
+
151
+ unless promotion_code
152
+ raise Stripe::InvalidRequestError.new("No such promotion code: #{promotion_code_id}", 'promotion_code', http_status: 400)
153
+ end
154
+ end
155
+
156
+ if params[:trial_period_days]
157
+ subscription[:status] = 'trialing'
158
+ end
159
+
160
+ if params[:payment_behavior] == 'default_incomplete'
161
+ subscription[:status] = 'incomplete'
162
+ end
163
+
164
+ if params[:cancel_at_period_end]
165
+ subscription[:cancel_at_period_end] = true
166
+ subscription[:canceled_at] = Time.now.utc.to_i
167
+ end
168
+
169
+ if params[:transfer_data] && !params[:transfer_data].empty?
170
+ throw Stripe::InvalidRequestError.new(missing_param_message("transfer_data[destination]")) unless params[:transfer_data][:destination]
171
+ subscription[:transfer_data] = params[:transfer_data].dup
172
+ subscription[:transfer_data][:amount_percent] ||= 100
173
+ end
174
+
175
+ if (s = params[:expand]&.find { |s| s.start_with? 'latest_invoice' })
176
+ payment_intent = nil
177
+ unless subscription[:status] == 'trialing'
178
+ intent_status = subscription[:status] == 'incomplete' ? 'requires_payment_method' : 'succeeded'
179
+ intent = Data.mock_payment_intent({
180
+ status: intent_status,
181
+ amount: subscription[:plan][:amount],
182
+ currency: subscription[:plan][:currency]
183
+ })
184
+ payment_intent = s.include?('latest_invoice.payment_intent') ? intent : intent.id
185
+ end
186
+ invoice = Data.mock_invoice([], { payment_intent: payment_intent })
187
+ subscription[:latest_invoice] = invoice
188
+ end
189
+
136
190
  subscriptions[subscription[:id]] = subscription
137
191
  add_subscription_to_customer(customer, subscription)
138
192
 
@@ -146,22 +200,43 @@ module StripeMock
146
200
  end
147
201
 
148
202
  def retrieve_subscriptions(route, method_url, params, headers)
203
+ # stripe_account = headers && headers[:stripe_account] || Stripe.api_key
149
204
  route =~ method_url
150
205
 
151
- Data.mock_list_object(subscriptions.values, params)
152
- #customer = assert_existence :customer, $1, customers[$1]
153
- #customer[:subscriptions]
206
+ subs = subscriptions.values
207
+
208
+ case params[:status]
209
+ when nil
210
+ subs = subs.filter {|subscription| subscription[:status] != "canceled"}
211
+ when "all"
212
+ # Include all subscriptions
213
+ else
214
+ subs = subs.filter {|subscription| subscription[:status] == params[:status]}
215
+ end
216
+ if params[:current_period_end]
217
+ subs = filter_by_timestamp(subs, field: :current_period_end, value: params[:current_period_end])
218
+ end
219
+ if params[:current_period_start]
220
+ subs = filter_by_timestamp(subs, field: :current_period_start, value: params[:current_period_start])
221
+ end
222
+
223
+ Data.mock_list_object(subs, params)
154
224
  end
155
225
 
156
226
  def update_subscription(route, method_url, params, headers)
227
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
157
228
  route =~ method_url
158
229
 
230
+ if params[:billing_cycle_anchor] == 'now'
231
+ params[:billing_cycle_anchor] = Time.now.utc.to_i
232
+ end
233
+
159
234
  subscription_id = $2 ? $2 : $1
160
235
  subscription = assert_existence :subscription, subscription_id, subscriptions[subscription_id]
161
236
  verify_active_status(subscription)
162
237
 
163
238
  customer_id = subscription[:customer]
164
- customer = assert_existence :customer, customer_id, customers[customer_id]
239
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
165
240
 
166
241
  if params[:source]
167
242
  new_card = get_card_by_token(params.delete(:source))
@@ -191,17 +266,48 @@ module StripeMock
191
266
  raise Stripe::InvalidRequestError.new("No such coupon: #{coupon_id}", 'coupon', http_status: 400)
192
267
  end
193
268
  end
194
- verify_card_present(customer, subscription_plans.first, subscription)
195
269
 
196
- if subscription[:cancel_at_period_end]
270
+ if params[:promotion_code]
271
+ promotion_code_id = params[:promotion_code]
272
+
273
+ promotion_code = promotion_codes[promotion_code_id]
274
+
275
+ if promotion_code
276
+ # You can't apply a promotion code with amount restrictions on the Customer object or on a subscription
277
+ # update API call
278
+ if promotion_code[:restrictions][:minimum_amount]
279
+ raise Stripe::InvalidRequestError.new(
280
+ "This promotion code cannot be redeemed on a subcription update because it uses the `minimum_amount` restriction.",
281
+ "promotion_code",
282
+ http_status: 400
283
+ )
284
+ end
285
+ else
286
+ raise Stripe::InvalidRequestError.new("No such promotion code: #{promotion_code_id}", 'promotion_code', http_status: 400)
287
+ end
288
+ end
289
+
290
+ if params[:trial_period_days]
291
+ subscription[:status] = 'trialing'
292
+ end
293
+
294
+ if params[:cancel_at_period_end]
295
+ subscription[:cancel_at_period_end] = true
296
+ subscription[:canceled_at] = Time.now.utc.to_i
297
+ elsif params.has_key?(:cancel_at_period_end)
197
298
  subscription[:cancel_at_period_end] = false
198
299
  subscription[:canceled_at] = nil
199
300
  end
200
301
 
201
302
  params[:current_period_start] = subscription[:current_period_start]
202
303
  params[:trial_end] = params[:trial_end] || subscription[:trial_end]
304
+
305
+ plan_amount_was = subscription.dig(:plan, :amount)
306
+
203
307
  subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params)
204
308
 
309
+ verify_card_present(customer, subscription_plans.first, subscription, params) if plan_amount_was == 0 && subscription.dig(:plan, :amount) && subscription.dig(:plan, :amount) > 0
310
+
205
311
  # delete the old subscription, replace with the new subscription
206
312
  customer[:subscriptions][:data].reject! { |sub| sub[:id] == subscription[:id] }
207
313
  customer[:subscriptions][:data] << subscription
@@ -210,13 +316,14 @@ module StripeMock
210
316
  end
211
317
 
212
318
  def cancel_subscription(route, method_url, params, headers)
319
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
213
320
  route =~ method_url
214
321
 
215
322
  subscription_id = $2 ? $2 : $1
216
323
  subscription = assert_existence :subscription, subscription_id, subscriptions[subscription_id]
217
324
 
218
325
  customer_id = subscription[:customer]
219
- customer = assert_existence :customer, customer_id, customers[customer_id]
326
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
220
327
 
221
328
  cancel_params = { canceled_at: Time.now.utc.to_i }
222
329
  cancelled_at_period_end = (params[:at_period_end] == true)
@@ -237,6 +344,14 @@ module StripeMock
237
344
  subscription
238
345
  end
239
346
 
347
+ SEARCH_FIELDS = ["status"].freeze
348
+ def search_subscriptions(route, method_url, params, headers)
349
+ require_param(:query) unless params[:query]
350
+
351
+ results = search_results(subscriptions.values, params[:query], fields: SEARCH_FIELDS, resource_name: "subscriptions")
352
+ Data.mock_list_object(results, params)
353
+ end
354
+
240
355
  private
241
356
 
242
357
  def get_subscription_plans_from_params(params)
@@ -245,14 +360,17 @@ module StripeMock
245
360
  elsif params[:items]
246
361
  items = params[:items]
247
362
  items = items.values if items.respond_to?(:values)
248
- items.map { |item| item[:plan].to_s if item[:plan] }
363
+ items.map { |item| item[:plan] ? item[:plan] : item[:price] }
249
364
  else
250
365
  []
251
366
  end
367
+ plan_ids.compact!
252
368
  plan_ids.each do |plan_id|
253
369
  assert_existence :plan, plan_id, plans[plan_id]
370
+ rescue Stripe::InvalidRequestError
371
+ assert_existence :price, plan_id, prices[plan_id]
254
372
  end
255
- plan_ids.map { |plan_id| plans[plan_id] }
373
+ plan_ids.map { |plan_id| plans[plan_id] || prices[plan_id]}
256
374
  end
257
375
 
258
376
  # Ensure customer has card to charge unless one of the following criterias is met:
@@ -261,8 +379,11 @@ module StripeMock
261
379
  # 3) has billing set to send invoice
262
380
  def verify_card_present(customer, plan, subscription, params={})
263
381
  return if customer[:default_source]
382
+ return if customer[:invoice_settings][:default_payment_method]
264
383
  return if customer[:trial_end]
265
384
  return if params[:trial_end]
385
+ return if params[:payment_behavior] == 'default_incomplete'
386
+ return if subscription[:default_payment_method]
266
387
 
267
388
  plan_trial_period_days = plan[:trial_period_days] || 0
268
389
  plan_has_trial = plan_trial_period_days != 0 || plan[:amount] == 0 || plan[:trial_end]
@@ -281,7 +402,7 @@ module StripeMock
281
402
 
282
403
  return if params[:billing] == 'send_invoice'
283
404
 
284
- raise Stripe::InvalidRequestError.new('You must supply a valid card xoxo', nil, http_status: 400)
405
+ raise Stripe::InvalidRequestError.new('This customer has no attached payment source', nil, http_status: 400)
285
406
  end
286
407
 
287
408
  def verify_active_status(subscription)
@@ -8,6 +8,8 @@ module StripeMock
8
8
  end
9
9
 
10
10
  def create_token(route, method_url, params, headers)
11
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
12
+
11
13
  if params[:customer].nil? && params[:card].nil? && params[:bank_account].nil?
12
14
  raise Stripe::InvalidRequestError.new('You must supply either a card, customer, or bank account to create a token.', nil, http_status: 400)
13
15
  end
@@ -15,13 +17,13 @@ module StripeMock
15
17
  cus_id = params[:customer]
16
18
 
17
19
  if cus_id && params[:source]
18
- customer = assert_existence :customer, cus_id, customers[cus_id]
20
+ customer = assert_existence :customer, cus_id, customers[stripe_account][cus_id]
19
21
 
20
22
  # params[:card] is an id; grab it from the db
21
23
  customer_card = get_card(customer, params[:source])
22
24
  assert_existence :card, params[:source], customer_card
23
25
  elsif params[:card].is_a?(String)
24
- customer = assert_existence :customer, cus_id, customers[cus_id]
26
+ customer = assert_existence :customer, cus_id, customers[stripe_account][cus_id]
25
27
 
26
28
  # params[:card] is an id; grab it from the db
27
29
  customer_card = get_card(customer, params[:card])
@@ -32,7 +34,7 @@ module StripeMock
32
34
  params[:card][:last4] = params[:card][:number][-4,4]
33
35
  customer_card = params[:card]
34
36
  elsif params[:bank_account].is_a?(String)
35
- customer = assert_existence :customer, cus_id, customers[cus_id]
37
+ customer = assert_existence :customer, cus_id, customers[stripe_account][cus_id]
36
38
 
37
39
  # params[:bank_account] is an id; grab it from the db
38
40
  bank_account = verify_bank_account(customer, params[:bank_account])
@@ -41,7 +43,7 @@ module StripeMock
41
43
  # params[:card] is a hash of cc info; "Sanitize" the card number
42
44
  bank_account = params[:bank_account]
43
45
  else
44
- customer = assert_existence :customer, cus_id, customers[cus_id]
46
+ customer = assert_existence :customer, cus_id, customers[stripe_account][cus_id] || customers[Stripe.api_key][cus_id]
45
47
  customer_card = get_card(customer, customer[:default_source])
46
48
  end
47
49
 
@@ -45,7 +45,18 @@ module StripeMock
45
45
  raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", 'amount', http_status: 400)
46
46
  end
47
47
 
48
- transfers[id] = Data.mock_transfer(params.merge :id => id)
48
+ bal_trans_params = { amount: params[:amount].to_i, source: id }
49
+
50
+ balance_transaction_id = new_balance_transaction('txn', bal_trans_params)
51
+
52
+ transfers[id] = Data.mock_transfer(params.merge(id: id, balance_transaction: balance_transaction_id))
53
+
54
+ transfer = transfers[id].clone
55
+ if params[:expand] == ['balance_transaction']
56
+ transfer[:balance_transaction] = balance_transactions[balance_transaction_id]
57
+ end
58
+
59
+ transfer
49
60
  end
50
61
 
51
62
  def get_transfer(route, method_url, params, headers)
@@ -2,31 +2,146 @@ module StripeMock
2
2
  module RequestHandlers
3
3
  module ParamValidators
4
4
 
5
- def validate_create_plan_params(params)
5
+ def already_exists_message(obj_class)
6
+ "#{obj_class.to_s.split("::").last} already exists."
7
+ end
8
+
9
+ def not_found_message(obj_class, obj_id)
10
+ "No such #{obj_class.to_s.split("::").last.downcase}: #{obj_id}"
11
+ end
12
+
13
+ def missing_param_message(attr_name)
14
+ "Missing required param: #{attr_name}."
15
+ end
16
+
17
+ def invalid_integer_message(my_val)
18
+ "Invalid integer: #{my_val}"
19
+ end
20
+
21
+ #
22
+ # ProductValidator
23
+ #
24
+
25
+
26
+ def validate_create_product_params(params)
6
27
  params[:id] = params[:id].to_s
28
+ @base_strategy.create_product_params.keys.reject{ |k,_| k == :id }.each do |k|
29
+ raise Stripe::InvalidRequestError.new(missing_param_message(k), k) if params[k].nil?
30
+ end
31
+
32
+ if products[ params[:id] ]
33
+ raise Stripe::InvalidRequestError.new(already_exists_message(Stripe::Product), :id)
34
+ end
35
+ end
36
+
37
+ #
38
+ # PlanValidator
39
+ #
40
+
41
+ def missing_plan_amount_message
42
+ "Plans require an `amount` parameter to be set."
43
+ end
44
+
45
+ SUPPORTED_PLAN_INTERVALS = ["month", "year", "week", "day"]
46
+
47
+ def invalid_plan_interval_message
48
+ "Invalid interval: must be one of day, month, week, or year"
49
+ end
50
+
51
+ SUPPORTED_CURRENCIES = [
52
+ "usd", "aed", "afn", "all", "amd", "ang", "aoa", "ars", "aud", "awg", "azn", "bam", "bbd", "bdt", "bgn",
53
+ "bif", "bmd", "bnd", "bob", "brl", "bsd", "bwp", "bzd", "cad", "cdf", "chf", "clp", "cny", "cop", "crc",
54
+ "cve", "czk", "djf", "dkk", "dop", "dzd", "egp", "etb", "eur", "fjd", "fkp", "gbp", "gel", "gip", "gmd",
55
+ "gnf", "gtq", "gyd", "hkd", "hnl", "hrk", "htg", "huf", "idr", "ils", "inr", "isk", "jmd", "jpy", "kes",
56
+ "kgs", "khr", "kmf", "krw", "kyd", "kzt", "lak", "lbp", "lkr", "lrd", "lsl", "mad", "mdl", "mga", "mkd",
57
+ "mmk", "mnt", "mop", "mro", "mur", "mvr", "mwk", "mxn", "myr", "mzn", "nad", "ngn", "nio", "nok", "npr",
58
+ "nzd", "pab", "pen", "pgk", "php", "pkr", "pln", "pyg", "qar", "ron", "rsd", "rub", "rwf", "sar", "sbd",
59
+ "scr", "sek", "sgd", "shp", "sll", "sos", "srd", "std", "szl", "thb", "tjs", "top", "try", "ttd", "twd",
60
+ "tzs", "uah", "ugx", "uyu", "uzs", "vnd", "vuv", "wst", "xaf", "xcd", "xof", "xpf", "yer", "zar", "zmw",
61
+ "eek", "lvl", "svc", "vef", "ltl"
62
+ ]
63
+
64
+ def invalid_currency_message(my_val)
65
+ "Invalid currency: #{my_val.downcase}. Stripe currently supports these currencies: #{SUPPORTED_CURRENCIES.join(", ")}"
66
+ end
7
67
 
8
- @base_strategy.create_plan_params.keys.each do |name|
68
+ def validate_create_plan_params(params)
69
+ plan_id = params[:id].to_s
70
+ product_id = params[:product]
71
+
72
+ @base_strategy.create_plan_params.keys.each do |attr_name|
9
73
  message =
10
- if name == :amount
11
- "Plans require an `#{name}` parameter to be set."
74
+ if attr_name == :amount
75
+ "Plans require an `#{attr_name}` parameter to be set."
12
76
  else
13
- "Missing required param: #{name}."
77
+ "Missing required param: #{attr_name}."
14
78
  end
15
- raise Stripe::InvalidRequestError.new(message, name) if params[name].nil?
79
+ raise Stripe::InvalidRequestError.new(message, attr_name) if params[attr_name].nil?
80
+ end
81
+
82
+ if plans[plan_id]
83
+ message = already_exists_message(Stripe::Plan)
84
+ raise Stripe::InvalidRequestError.new(message, :id)
85
+ end
86
+
87
+ unless products[product_id]
88
+ message = not_found_message(Stripe::Product, product_id)
89
+ raise Stripe::InvalidRequestError.new(message, :product)
16
90
  end
17
91
 
18
- if plans[ params[:id] ]
19
- raise Stripe::InvalidRequestError.new("Plan already exists.", :id)
92
+ unless SUPPORTED_PLAN_INTERVALS.include?(params[:interval])
93
+ message = invalid_plan_interval_message
94
+ raise Stripe::InvalidRequestError.new(message, :interval)
95
+ end
96
+
97
+ unless SUPPORTED_CURRENCIES.include?(params[:currency])
98
+ message = invalid_currency_message(params[:currency])
99
+ raise Stripe::InvalidRequestError.new(message, :currency)
20
100
  end
21
101
 
22
102
  unless params[:amount].integer?
23
- raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", :amount)
103
+ message = invalid_integer_message(params[:amount])
104
+ raise Stripe::InvalidRequestError.new(message, :amount)
105
+ end
106
+
107
+ end
108
+
109
+ def validate_create_price_params(params)
110
+ price_id = params[:id].to_s
111
+
112
+ require_param(:currency) unless params[:currency]
113
+ unless params[:product] || params[:product_data]
114
+ raise Stripe::InvalidRequestError("Requires product or product_data")
115
+ end
116
+
117
+ product_id = params[:product] || create_product(nil, nil, params[:product_data], nil).id
118
+
119
+ if prices[price_id]
120
+ message = already_exists_message(Stripe::Price)
121
+ raise Stripe::InvalidRequestError.new(message, :id)
122
+ end
123
+
124
+ unless products[product_id]
125
+ message = not_found_message(Stripe::Product, product_id)
126
+ raise Stripe::InvalidRequestError.new(message, :product)
127
+ end
128
+
129
+ unless SUPPORTED_CURRENCIES.include?(params[:currency])
130
+ message = invalid_currency_message(params[:currency])
131
+ raise Stripe::InvalidRequestError.new(message, :currency)
132
+ end
133
+ end
134
+
135
+ def validate_list_prices_params(params)
136
+ if params[:lookup_keys] && !params[:lookup_keys].is_a?(Array)
137
+ raise Stripe::InvalidRequestError.new('Invalid array', :lookup_keys)
24
138
  end
25
139
  end
26
140
 
27
141
  def require_param(param_name)
28
142
  raise Stripe::InvalidRequestError.new("Missing required param: #{param_name}.", param_name.to_s, http_status: 400)
29
143
  end
144
+
30
145
  end
31
146
  end
32
147
  end
@@ -16,9 +16,9 @@ module StripeMock
16
16
  self.clear_data
17
17
  end
18
18
 
19
- def mock_request(*args)
19
+ def mock_request(*args, **kwargs)
20
20
  begin
21
- @instance.mock_request(*args)
21
+ @instance.mock_request(*args, **kwargs)
22
22
  rescue Stripe::InvalidRequestError => e
23
23
  {
24
24
  :error_raised => 'invalid_request',