stripe-ruby-mock 3.0.1 → 3.1.0.rc2

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 (49) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -5
  3. data/CHANGELOG.md +28 -15
  4. data/Gemfile +1 -0
  5. data/lib/stripe_mock.rb +4 -0
  6. data/lib/stripe_mock/api/client.rb +1 -1
  7. data/lib/stripe_mock/api/instance.rb +1 -1
  8. data/lib/stripe_mock/api/webhooks.rb +2 -0
  9. data/lib/stripe_mock/client.rb +2 -1
  10. data/lib/stripe_mock/data.rb +127 -25
  11. data/lib/stripe_mock/data/list.rb +31 -6
  12. data/lib/stripe_mock/instance.rb +7 -2
  13. data/lib/stripe_mock/request_handlers/account_links.rb +15 -0
  14. data/lib/stripe_mock/request_handlers/charges.rb +6 -4
  15. data/lib/stripe_mock/request_handlers/checkout_session.rb +16 -0
  16. data/lib/stripe_mock/request_handlers/customers.rb +22 -13
  17. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +1 -1
  18. data/lib/stripe_mock/request_handlers/express_login_links.rb +15 -0
  19. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +12 -7
  20. data/lib/stripe_mock/request_handlers/invoices.rb +4 -3
  21. data/lib/stripe_mock/request_handlers/payment_methods.rb +8 -5
  22. data/lib/stripe_mock/request_handlers/prices.rb +44 -0
  23. data/lib/stripe_mock/request_handlers/sources.rb +12 -6
  24. data/lib/stripe_mock/request_handlers/subscriptions.rb +29 -19
  25. data/lib/stripe_mock/request_handlers/tokens.rb +6 -4
  26. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +32 -0
  27. data/lib/stripe_mock/test_strategies/base.rb +26 -0
  28. data/lib/stripe_mock/version.rb +1 -1
  29. data/lib/stripe_mock/webhook_fixtures/balance.available.json +6 -0
  30. data/lib/stripe_mock/webhook_fixtures/payment_intent.payment_failed.json +186 -0
  31. data/lib/stripe_mock/webhook_fixtures/payment_intent.succeeded.json +164 -0
  32. data/spec/instance_spec.rb +4 -6
  33. data/spec/list_spec.rb +23 -0
  34. data/spec/server_spec.rb +4 -2
  35. data/spec/shared_stripe_examples/account_link_examples.rb +16 -0
  36. data/spec/shared_stripe_examples/balance_examples.rb +6 -0
  37. data/spec/shared_stripe_examples/card_token_examples.rb +17 -21
  38. data/spec/shared_stripe_examples/checkout_examples.rb +20 -1
  39. data/spec/shared_stripe_examples/customer_examples.rb +11 -13
  40. data/spec/shared_stripe_examples/express_login_link_examples.rb +12 -0
  41. data/spec/shared_stripe_examples/invoice_examples.rb +8 -8
  42. data/spec/shared_stripe_examples/payment_method_examples.rb +332 -68
  43. data/spec/shared_stripe_examples/price_examples.rb +183 -0
  44. data/spec/shared_stripe_examples/subscription_examples.rb +115 -8
  45. data/spec/spec_helper.rb +4 -0
  46. data/spec/stripe_mock_spec.rb +2 -2
  47. data/spec/support/stripe_examples.rb +5 -1
  48. data/stripe-ruby-mock.gemspec +6 -1
  49. metadata +25 -11
@@ -17,26 +17,29 @@ module StripeMock
17
17
  end
18
18
 
19
19
  def retrieve_customer_subscription(route, method_url, params, headers)
20
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
20
21
  route =~ method_url
21
22
 
22
- customer = assert_existence :customer, $1, customers[$1]
23
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
23
24
  subscription = get_customer_subscription(customer, $2)
24
25
 
25
26
  assert_existence :subscription, $2, subscription
26
27
  end
27
28
 
28
29
  def retrieve_customer_subscriptions(route, method_url, params, headers)
30
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
29
31
  route =~ method_url
30
32
 
31
- customer = assert_existence :customer, $1, customers[$1]
33
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
32
34
  customer[:subscriptions]
33
35
  end
34
36
 
35
37
  def create_customer_subscription(route, method_url, params, headers)
38
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
36
39
  route =~ method_url
37
40
 
38
41
  subscription_plans = get_subscription_plans_from_params(params)
39
- customer = assert_existence :customer, $1, customers[$1]
42
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
40
43
 
41
44
  if params[:source]
42
45
  new_card = get_card_by_token(params.delete(:source))
@@ -73,10 +76,10 @@ module StripeMock
73
76
  end
74
77
 
75
78
  def create_subscription(route, method_url, params, headers)
79
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
76
80
  if headers && headers[:idempotency_key]
77
81
  if subscriptions.any?
78
82
  original_subscription = subscriptions.values.find { |c| c[:idempotency_key] == headers[:idempotency_key]}
79
- puts original_subscription
80
83
  return subscriptions[original_subscription[:id]] if original_subscription
81
84
  end
82
85
  end
@@ -86,15 +89,7 @@ module StripeMock
86
89
 
87
90
  customer = params[:customer]
88
91
  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
92
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
98
93
 
99
94
  if params[:source]
100
95
  new_card = get_card_by_token(params.delete(:source))
@@ -102,7 +97,7 @@ module StripeMock
102
97
  customer[:default_source] = new_card[:id]
103
98
  end
104
99
 
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 enable_incomplete_payments cancel_at_period_end default_tax_rates payment_behavior pending_invoice_item_interval)
100
+ 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 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 expand)
106
101
  unknown_params = params.keys - allowed_params.map(&:to_sym)
107
102
  if unknown_params.length > 0
108
103
  raise Stripe::InvalidRequestError.new("Received unknown parameter: #{unknown_params.join}", unknown_params.first.to_s, http_status: 400)
@@ -133,6 +128,10 @@ module StripeMock
133
128
  end
134
129
  end
135
130
 
131
+ if params[:trial_period_days]
132
+ subscription[:status] = 'trialing'
133
+ end
134
+
136
135
  if params[:cancel_at_period_end]
137
136
  subscription[:cancel_at_period_end] = true
138
137
  subscription[:canceled_at] = Time.now.utc.to_i
@@ -151,14 +150,16 @@ module StripeMock
151
150
  end
152
151
 
153
152
  def retrieve_subscriptions(route, method_url, params, headers)
153
+ # stripe_account = headers && headers[:stripe_account] || Stripe.api_key
154
154
  route =~ method_url
155
155
 
156
156
  Data.mock_list_object(subscriptions.values, params)
157
- #customer = assert_existence :customer, $1, customers[$1]
157
+ #customer = assert_existence :customer, $1, customers[stripe_account][$1]
158
158
  #customer[:subscriptions]
159
159
  end
160
160
 
161
161
  def update_subscription(route, method_url, params, headers)
162
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
162
163
  route =~ method_url
163
164
 
164
165
  subscription_id = $2 ? $2 : $1
@@ -166,7 +167,7 @@ module StripeMock
166
167
  verify_active_status(subscription)
167
168
 
168
169
  customer_id = subscription[:customer]
169
- customer = assert_existence :customer, customer_id, customers[customer_id]
170
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
170
171
 
171
172
  if params[:source]
172
173
  new_card = get_card_by_token(params.delete(:source))
@@ -197,6 +198,10 @@ module StripeMock
197
198
  end
198
199
  end
199
200
 
201
+ if params[:trial_period_days]
202
+ subscription[:status] = 'trialing'
203
+ end
204
+
200
205
  if params[:cancel_at_period_end]
201
206
  subscription[:cancel_at_period_end] = true
202
207
  subscription[:canceled_at] = Time.now.utc.to_i
@@ -222,13 +227,14 @@ module StripeMock
222
227
  end
223
228
 
224
229
  def cancel_subscription(route, method_url, params, headers)
230
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
225
231
  route =~ method_url
226
232
 
227
233
  subscription_id = $2 ? $2 : $1
228
234
  subscription = assert_existence :subscription, subscription_id, subscriptions[subscription_id]
229
235
 
230
236
  customer_id = subscription[:customer]
231
- customer = assert_existence :customer, customer_id, customers[customer_id]
237
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
232
238
 
233
239
  cancel_params = { canceled_at: Time.now.utc.to_i }
234
240
  cancelled_at_period_end = (params[:at_period_end] == true)
@@ -257,14 +263,17 @@ module StripeMock
257
263
  elsif params[:items]
258
264
  items = params[:items]
259
265
  items = items.values if items.respond_to?(:values)
260
- items.map { |item| item[:plan].to_s if item[:plan] }
266
+ items.map { |item| item[:plan] ? item[:plan] : item[:price] }
261
267
  else
262
268
  []
263
269
  end
270
+ plan_ids.compact!
264
271
  plan_ids.each do |plan_id|
265
272
  assert_existence :plan, plan_id, plans[plan_id]
273
+ rescue Stripe::InvalidRequestError
274
+ assert_existence :price, plan_id, prices[plan_id]
266
275
  end
267
- plan_ids.map { |plan_id| plans[plan_id] }
276
+ plan_ids.map { |plan_id| plans[plan_id] || prices[plan_id]}
268
277
  end
269
278
 
270
279
  # Ensure customer has card to charge unless one of the following criterias is met:
@@ -276,6 +285,7 @@ module StripeMock
276
285
  return if customer[:invoice_settings][:default_payment_method]
277
286
  return if customer[:trial_end]
278
287
  return if params[:trial_end]
288
+ return if subscription[:default_payment_method]
279
289
 
280
290
  plan_trial_period_days = plan[:trial_period_days] || 0
281
291
  plan_has_trial = plan_trial_period_days != 0 || plan[:amount] == 0 || plan[:trial_end]
@@ -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
 
@@ -110,9 +110,41 @@ module StripeMock
110
110
 
111
111
  end
112
112
 
113
+ def validate_create_price_params(params)
114
+ price_id = params[:id].to_s
115
+ product_id = params[:product]
116
+
117
+ @base_strategy.create_price_params.keys.each do |attr_name|
118
+ message = "Missing required param: #{attr_name}."
119
+ raise Stripe::InvalidRequestError.new(message, attr_name) if params[attr_name].nil?
120
+ end
121
+
122
+ if prices[price_id]
123
+ message = already_exists_message(Stripe::Price)
124
+ raise Stripe::InvalidRequestError.new(message, :id)
125
+ end
126
+
127
+ unless products[product_id]
128
+ message = not_found_message(Stripe::Product, product_id)
129
+ raise Stripe::InvalidRequestError.new(message, :product)
130
+ end
131
+
132
+ unless SUPPORTED_CURRENCIES.include?(params[:currency])
133
+ message = invalid_currency_message(params[:currency])
134
+ raise Stripe::InvalidRequestError.new(message, :currency)
135
+ end
136
+ end
137
+
138
+ def validate_list_prices_params(params)
139
+ if params[:lookup_keys] && !params[:lookup_keys].is_a?(Array)
140
+ raise Stripe::InvalidRequestError.new('Invalid array', :lookup_keys)
141
+ end
142
+ end
143
+
113
144
  def require_param(param_name)
114
145
  raise Stripe::InvalidRequestError.new("Missing required param: #{param_name}.", param_name.to_s, http_status: 400)
115
146
  end
147
+
116
148
  end
117
149
  end
118
150
  end
@@ -41,6 +41,15 @@ module StripeMock
41
41
  }.merge(params)
42
42
  end
43
43
 
44
+ def create_price(params={})
45
+ Stripe::Price.create create_price_params(params)
46
+ end
47
+
48
+ def create_price_params(params={})
49
+ {
50
+ :currency => StripeMock.default_currency,
51
+ }.merge(params)
52
+ end
44
53
 
45
54
  def list_subscriptions(limit)
46
55
  Stripe::Subscription.list(limit: limit)
@@ -97,10 +106,27 @@ module StripeMock
97
106
  }.merge(params)
98
107
  end
99
108
 
109
+ def create_checkout_session_params(params = {})
110
+ {
111
+ payment_method_types: ['card'],
112
+ line_items: [{
113
+ name: 'T-shirt',
114
+ quantity: 1,
115
+ amount: 500,
116
+ currency: 'usd',
117
+ }],
118
+ }.merge(params)
119
+ end
120
+
121
+
100
122
  def create_coupon(params = {})
101
123
  Stripe::Coupon.create create_coupon_params(params)
102
124
  end
103
125
 
126
+ def create_checkout_session(params = {})
127
+ Stripe::Checkout::Session.create create_checkout_session_params(params)
128
+ end
129
+
104
130
  def delete_all_coupons
105
131
  coupons = Stripe::Coupon.list
106
132
  coupons.data.map(&:delete) if coupons.data.count > 0
@@ -1,4 +1,4 @@
1
1
  module StripeMock
2
2
  # stripe-ruby-mock version
3
- VERSION = "3.0.1"
3
+ VERSION = "3.1.0.rc2"
4
4
  end
@@ -18,6 +18,12 @@
18
18
  "currency": "usd"
19
19
  }
20
20
  ],
21
+ "instant_available": [
22
+ {
23
+ "amount": 0,
24
+ "currency": "usd"
25
+ }
26
+ ],
21
27
  "livemode": false,
22
28
  "object": "balance"
23
29
  }
@@ -0,0 +1,186 @@
1
+ {
2
+ "id": "evt_00000000000000",
3
+ "object": "event",
4
+ "api_version": "2018-02-28",
5
+ "created": 1578401135,
6
+ "data": {
7
+ "object": {
8
+ "id": "pi_00000000000000",
9
+ "object": "payment_intent",
10
+ "allowed_source_types": ["card", "sepa_debit"],
11
+ "amount": 200,
12
+ "amount_capturable": 0,
13
+ "amount_received": 0,
14
+ "application": null,
15
+ "application_fee_amount": null,
16
+ "canceled_at": null,
17
+ "cancellation_reason": null,
18
+ "capture_method": "automatic",
19
+ "charges": {
20
+ "object": "list",
21
+ "data": [
22
+ {
23
+ "id": "py_00000000000000",
24
+ "object": "charge",
25
+ "amount": 200,
26
+ "amount_refunded": 0,
27
+ "application": null,
28
+ "application_fee": null,
29
+ "application_fee_amount": null,
30
+ "balance_transaction": null,
31
+ "billing_details": {
32
+ "address": {
33
+ "city": null,
34
+ "country": null,
35
+ "line1": null,
36
+ "line2": null,
37
+ "postal_code": null,
38
+ "state": null
39
+ },
40
+ "email": "john.doe@example.com",
41
+ "name": "John Doe",
42
+ "phone": null
43
+ },
44
+ "captured": true,
45
+ "created": 1578401129,
46
+ "currency": "eur",
47
+ "customer": "cus_00000000000000",
48
+ "description": null,
49
+ "destination": "acct_00000000000000",
50
+ "dispute": null,
51
+ "disputed": false,
52
+ "failure_code": null,
53
+ "failure_message": null,
54
+ "fraud_details": {},
55
+ "invoice": null,
56
+ "livemode": false,
57
+ "metadata": {},
58
+ "on_behalf_of": null,
59
+ "order": null,
60
+ "outcome": {
61
+ "network_status": "approved_by_network",
62
+ "reason": null,
63
+ "risk_level": "not_assessed",
64
+ "seller_message": "Payment complete.",
65
+ "type": "authorized"
66
+ },
67
+ "paid": false,
68
+ "payment_intent": "pi_00000000000000",
69
+ "payment_method": "pm_00000000000000",
70
+ "payment_method_details": {
71
+ "sepa_debit": {
72
+ "bank_code": "37040044",
73
+ "branch_code": null,
74
+ "country": "DE",
75
+ "fingerprint": "00000000000000",
76
+ "last4": "3001",
77
+ "mandate": "mandate_00000000000000"
78
+ },
79
+ "type": "sepa_debit"
80
+ },
81
+ "receipt_email": null,
82
+ "receipt_number": null,
83
+ "receipt_url": "https://pay.stripe.com/receipts/acct_00000000000000/py_00000000000000/rcpt_00000000000000",
84
+ "refunded": false,
85
+ "refunds": {
86
+ "object": "list",
87
+ "data": [],
88
+ "has_more": false,
89
+ "total_count": 0,
90
+ "url": "/v1/charges/py_00000000000000/refunds"
91
+ },
92
+ "review": null,
93
+ "shipping": null,
94
+ "source": null,
95
+ "source_transfer": null,
96
+ "statement_descriptor": "ACME Corp",
97
+ "statement_descriptor_suffix": null,
98
+ "status": "failed",
99
+ "transfer_data": {
100
+ "amount": null,
101
+ "destination": "acct_00000000000000"
102
+ },
103
+ "transfer_group": "group_pi_00000000000000"
104
+ }
105
+ ],
106
+ "has_more": false,
107
+ "total_count": 1,
108
+ "url": "/v1/charges?payment_intent=pi_00000000000000"
109
+ },
110
+ "client_secret": "pi_00000000000000",
111
+ "confirmation_method": "automatic",
112
+ "created": 1578401129,
113
+ "currency": "eur",
114
+ "customer": "cus_00000000000000",
115
+ "description": null,
116
+ "invoice": null,
117
+ "last_payment_error": {
118
+ "code": "payment_intent_payment_attempt_failed",
119
+ "doc_url": "https://stripe.com/docs/error-codes/payment-intent-payment-attempt-failed",
120
+ "message": "The payment failed.",
121
+ "payment_method": {
122
+ "id": "pm_00000000000000",
123
+ "object": "payment_method",
124
+ "billing_details": {
125
+ "address": {
126
+ "city": null,
127
+ "country": null,
128
+ "line1": null,
129
+ "line2": null,
130
+ "postal_code": null,
131
+ "state": null
132
+ },
133
+ "email": "john.doe@example.com",
134
+ "name": "John Doe",
135
+ "phone": null
136
+ },
137
+ "created": 1578400666,
138
+ "customer": "cus_00000000000000",
139
+ "livemode": false,
140
+ "metadata": {},
141
+ "sepa_debit": {
142
+ "bank_code": "37040044",
143
+ "branch_code": "",
144
+ "country": "DE",
145
+ "fingerprint": "00000000000000",
146
+ "last4": "3001"
147
+ },
148
+ "type": "sepa_debit"
149
+ },
150
+ "type": "invalid_request_error"
151
+ },
152
+ "livemode": false,
153
+ "metadata": {},
154
+ "next_action": null,
155
+ "next_source_action": null,
156
+ "on_behalf_of": null,
157
+ "payment_method": null,
158
+ "payment_method_options": {
159
+ "card": {
160
+ "installments": null,
161
+ "request_three_d_secure": "automatic"
162
+ }
163
+ },
164
+ "payment_method_types": ["card", "sepa_debit"],
165
+ "receipt_email": null,
166
+ "review": null,
167
+ "setup_future_usage": null,
168
+ "shipping": null,
169
+ "source": null,
170
+ "statement_descriptor": "ACME Corp",
171
+ "statement_descriptor_suffix": null,
172
+ "status": "requires_source",
173
+ "transfer_data": {
174
+ "destination": "acct_00000000000000"
175
+ },
176
+ "transfer_group": null
177
+ }
178
+ },
179
+ "livemode": false,
180
+ "pending_webhooks": 3,
181
+ "request": {
182
+ "id": null,
183
+ "idempotency_key": null
184
+ },
185
+ "type": "payment_intent.payment_failed"
186
+ }