stripe-ruby-mock 3.0.0 → 3.1.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 (137) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rspec_tests.yml +38 -0
  3. data/.rspec +2 -1
  4. data/CHANGELOG.md +69 -0
  5. data/Gemfile +1 -0
  6. data/README.md +13 -5
  7. data/lib/stripe_mock/api/client.rb +1 -1
  8. data/lib/stripe_mock/api/errors.rb +3 -0
  9. data/lib/stripe_mock/api/instance.rb +1 -1
  10. data/lib/stripe_mock/api/webhooks.rb +66 -25
  11. data/lib/stripe_mock/client.rb +2 -1
  12. data/lib/stripe_mock/data/list.rb +42 -9
  13. data/lib/stripe_mock/data.rb +242 -31
  14. data/lib/stripe_mock/instance.rb +14 -3
  15. data/lib/stripe_mock/request_handlers/account_links.rb +15 -0
  16. data/lib/stripe_mock/request_handlers/accounts.rb +17 -6
  17. data/lib/stripe_mock/request_handlers/charges.rb +11 -4
  18. data/lib/stripe_mock/request_handlers/checkout_session.rb +179 -0
  19. data/lib/stripe_mock/request_handlers/customers.rb +35 -18
  20. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +1 -1
  21. data/lib/stripe_mock/request_handlers/events.rb +30 -3
  22. data/lib/stripe_mock/request_handlers/express_login_links.rb +15 -0
  23. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +6 -0
  24. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +36 -12
  25. data/lib/stripe_mock/request_handlers/invoices.rb +10 -4
  26. data/lib/stripe_mock/request_handlers/payment_intents.rb +19 -1
  27. data/lib/stripe_mock/request_handlers/payment_methods.rb +10 -24
  28. data/lib/stripe_mock/request_handlers/plans.rb +1 -1
  29. data/lib/stripe_mock/request_handlers/prices.rb +62 -0
  30. data/lib/stripe_mock/request_handlers/promotion_codes.rb +43 -0
  31. data/lib/stripe_mock/request_handlers/refunds.rb +13 -2
  32. data/lib/stripe_mock/request_handlers/setup_intents.rb +16 -9
  33. data/lib/stripe_mock/request_handlers/sources.rb +12 -6
  34. data/lib/stripe_mock/request_handlers/subscriptions.rb +120 -21
  35. data/lib/stripe_mock/request_handlers/tokens.rb +6 -4
  36. data/lib/stripe_mock/request_handlers/transfers.rb +12 -1
  37. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +33 -4
  38. data/lib/stripe_mock/server.rb +2 -2
  39. data/lib/stripe_mock/test_strategies/base.rb +62 -10
  40. data/lib/stripe_mock/version.rb +1 -1
  41. data/lib/stripe_mock/webhook_fixtures/account.updated.json +1 -1
  42. data/lib/stripe_mock/webhook_fixtures/balance.available.json +27 -15
  43. data/lib/stripe_mock/webhook_fixtures/charge.captured.json +143 -0
  44. data/lib/stripe_mock/webhook_fixtures/charge.dispute.created.json +63 -16
  45. data/lib/stripe_mock/webhook_fixtures/charge.failed.json +49 -120
  46. data/lib/stripe_mock/webhook_fixtures/charge.refund.updated.json +35 -0
  47. data/lib/stripe_mock/webhook_fixtures/charge.refunded.json +145 -50
  48. data/lib/stripe_mock/webhook_fixtures/charge.succeeded.json +114 -43
  49. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.json +79 -0
  50. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.payment_mode.json +53 -0
  51. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.setup_mode.json +45 -0
  52. data/lib/stripe_mock/webhook_fixtures/customer.created.json +37 -46
  53. data/lib/stripe_mock/webhook_fixtures/customer.deleted.json +36 -32
  54. data/lib/stripe_mock/webhook_fixtures/customer.source.created.json +31 -22
  55. data/lib/stripe_mock/webhook_fixtures/customer.source.updated.json +36 -25
  56. data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +135 -47
  57. data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +134 -45
  58. data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +135 -56
  59. data/lib/stripe_mock/webhook_fixtures/customer.updated.json +38 -47
  60. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +176 -49
  61. data/lib/stripe_mock/webhook_fixtures/invoice.finalized.json +171 -0
  62. data/lib/stripe_mock/webhook_fixtures/invoice.paid.json +171 -0
  63. data/lib/stripe_mock/webhook_fixtures/invoice.payment_action_required.json +171 -0
  64. data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +149 -83
  65. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +149 -90
  66. data/lib/stripe_mock/webhook_fixtures/invoice.upcoming.json +70 -0
  67. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +178 -50
  68. data/lib/stripe_mock/webhook_fixtures/invoiceitem.created.json +87 -13
  69. data/lib/stripe_mock/webhook_fixtures/invoiceitem.updated.json +88 -14
  70. data/lib/stripe_mock/webhook_fixtures/mandate.updated.json +34 -0
  71. data/lib/stripe_mock/webhook_fixtures/payment_intent.amount_capturable_updated.json +170 -0
  72. data/lib/stripe_mock/webhook_fixtures/payment_intent.canceled.json +73 -0
  73. data/lib/stripe_mock/webhook_fixtures/payment_intent.created.json +86 -0
  74. data/lib/stripe_mock/webhook_fixtures/payment_intent.payment_failed.json +225 -0
  75. data/lib/stripe_mock/webhook_fixtures/payment_intent.processing.json +162 -0
  76. data/lib/stripe_mock/webhook_fixtures/payment_intent.requires_action.json +191 -0
  77. data/lib/stripe_mock/webhook_fixtures/payment_intent.succeeded.json +196 -0
  78. data/lib/stripe_mock/webhook_fixtures/payment_link.created.json +47 -0
  79. data/lib/stripe_mock/webhook_fixtures/payment_link.updated.json +50 -0
  80. data/lib/stripe_mock/webhook_fixtures/payment_method.attached.json +63 -0
  81. data/lib/stripe_mock/webhook_fixtures/payment_method.detached.json +62 -0
  82. data/lib/stripe_mock/webhook_fixtures/payout.created.json +40 -0
  83. data/lib/stripe_mock/webhook_fixtures/payout.paid.json +40 -0
  84. data/lib/stripe_mock/webhook_fixtures/payout.updated.json +46 -0
  85. data/lib/stripe_mock/webhook_fixtures/plan.created.json +30 -13
  86. data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +30 -13
  87. data/lib/stripe_mock/webhook_fixtures/plan.updated.json +34 -14
  88. data/lib/stripe_mock/webhook_fixtures/price.created.json +42 -0
  89. data/lib/stripe_mock/webhook_fixtures/price.deleted.json +42 -0
  90. data/lib/stripe_mock/webhook_fixtures/price.updated.json +48 -0
  91. data/lib/stripe_mock/webhook_fixtures/product.created.json +19 -13
  92. data/lib/stripe_mock/webhook_fixtures/product.deleted.json +20 -14
  93. data/lib/stripe_mock/webhook_fixtures/product.updated.json +24 -15
  94. data/lib/stripe_mock/webhook_fixtures/quote.accepted.json +92 -0
  95. data/lib/stripe_mock/webhook_fixtures/quote.canceled.json +92 -0
  96. data/lib/stripe_mock/webhook_fixtures/quote.created.json +92 -0
  97. data/lib/stripe_mock/webhook_fixtures/quote.finalized.json +92 -0
  98. data/lib/stripe_mock/webhook_fixtures/setup_intent.canceled.json +46 -0
  99. data/lib/stripe_mock/webhook_fixtures/setup_intent.created.json +51 -0
  100. data/lib/stripe_mock/webhook_fixtures/setup_intent.setup_failed.json +100 -0
  101. data/lib/stripe_mock/webhook_fixtures/setup_intent.succeeded.json +46 -0
  102. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.canceled.json +119 -0
  103. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.created.json +114 -0
  104. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.released.json +111 -0
  105. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.updated.json +125 -0
  106. data/lib/stripe_mock/webhook_fixtures/tax_rate.created.json +32 -0
  107. data/lib/stripe_mock/webhook_fixtures/tax_rate.updated.json +37 -0
  108. data/lib/stripe_mock.rb +7 -0
  109. data/spec/instance_spec.rb +7 -7
  110. data/spec/integration_examples/completing_checkout_sessions_example.rb +37 -0
  111. data/spec/list_spec.rb +38 -0
  112. data/spec/readme_spec.rb +1 -1
  113. data/spec/server_spec.rb +4 -2
  114. data/spec/shared_stripe_examples/account_examples.rb +9 -1
  115. data/spec/shared_stripe_examples/account_link_examples.rb +16 -0
  116. data/spec/shared_stripe_examples/balance_examples.rb +6 -0
  117. data/spec/shared_stripe_examples/card_token_examples.rb +17 -21
  118. data/spec/shared_stripe_examples/checkout_session_examples.rb +99 -0
  119. data/spec/shared_stripe_examples/customer_examples.rb +49 -23
  120. data/spec/shared_stripe_examples/express_login_link_examples.rb +12 -0
  121. data/spec/shared_stripe_examples/invoice_examples.rb +29 -8
  122. data/spec/shared_stripe_examples/payment_intent_examples.rb +84 -0
  123. data/spec/shared_stripe_examples/payment_method_examples.rb +336 -67
  124. data/spec/shared_stripe_examples/price_examples.rb +223 -0
  125. data/spec/shared_stripe_examples/product_examples.rb +1 -9
  126. data/spec/shared_stripe_examples/promotion_code_examples.rb +68 -0
  127. data/spec/shared_stripe_examples/refund_examples.rb +13 -0
  128. data/spec/shared_stripe_examples/setup_intent_examples.rb +17 -0
  129. data/spec/shared_stripe_examples/subscription_examples.rb +361 -9
  130. data/spec/shared_stripe_examples/transfer_examples.rb +10 -1
  131. data/spec/shared_stripe_examples/webhook_event_examples.rb +51 -5
  132. data/spec/spec_helper.rb +4 -0
  133. data/spec/stripe_mock_spec.rb +2 -2
  134. data/spec/support/stripe_examples.rb +8 -1
  135. data/stripe-ruby-mock.gemspec +7 -2
  136. metadata +73 -12
  137. data/.travis.yml +0 -28
@@ -0,0 +1,179 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Checkout
4
+ module Session
5
+ def Session.included(klass)
6
+ klass.add_handler 'post /v1/checkout/sessions', :new_session
7
+ klass.add_handler 'get /v1/checkout/sessions', :list_checkout_sessions
8
+ klass.add_handler 'get /v1/checkout/sessions/([^/]*)', :get_checkout_session
9
+ klass.add_handler 'get /v1/checkout/sessions/([^/]*)/line_items', :list_line_items
10
+ end
11
+
12
+ def new_session(route, method_url, params, headers)
13
+ id = params[:id] || new_id('cs')
14
+
15
+ [:cancel_url, :success_url].each do |p|
16
+ require_param(p) if params[p].nil? || params[p].empty?
17
+ end
18
+
19
+ line_items = nil
20
+ if params[:line_items]
21
+ line_items = params[:line_items].each_with_index.map do |line_item, i|
22
+ throw Stripe::InvalidRequestError("Quantity is required. Add `quantity` to `line_items[#{i}]`") unless line_item[:quantity]
23
+ unless line_item[:price] || line_item[:price_data] || (line_item[:amount] && line_item[:currency] && line_item[:name])
24
+ throw Stripe::InvalidRequestError("Price or amount and currency is required. Add `price`, `price_data`, or `amount`, `currency` and `name` to `line_items[#{i}]`")
25
+ end
26
+ {
27
+ id: new_id("li"),
28
+ price: if line_item[:price]
29
+ line_item[:price]
30
+ elsif line_item[:price_data]
31
+ new_price(nil, nil, line_item[:price_data], nil)[:id]
32
+ else
33
+ new_price(nil, nil, {
34
+ unit_amount: line_item[:amount],
35
+ currency: line_item[:currency],
36
+ product_data: {
37
+ name: line_item[:name]
38
+ }
39
+ }, nil)[:id]
40
+ end,
41
+ quantity: line_item[:quantity]
42
+ }
43
+ end
44
+ end
45
+
46
+ amount = nil
47
+ currency = nil
48
+ if line_items
49
+ amount = 0
50
+
51
+ line_items.each do |line_item|
52
+ price = prices[line_item[:price]]
53
+
54
+ if price.nil?
55
+ raise StripeMock::StripeMockError.new("Price not found for ID: #{line_item[:price]}")
56
+ end
57
+
58
+ amount += (price[:unit_amount] * line_item[:quantity])
59
+ end
60
+
61
+ currency = prices[line_items.first[:price]][:currency]
62
+ end
63
+
64
+ payment_status = "unpaid"
65
+ payment_intent = nil
66
+ setup_intent = nil
67
+ case params[:mode]
68
+ when nil, "payment"
69
+ params[:customer] ||= new_customer(nil, nil, {email: params[:customer_email]}, nil)[:id]
70
+ require_param(:line_items) if params[:line_items].nil? || params[:line_items].empty?
71
+ payment_intent = new_payment_intent(nil, nil, {
72
+ amount: amount,
73
+ currency: currency,
74
+ customer: params[:customer],
75
+ payment_method_options: params[:payment_method_options],
76
+ payment_method_types: params[:payment_method_types]
77
+ }.merge(params[:payment_intent_data] || {}), nil)[:id]
78
+ checkout_session_line_items[id] = line_items
79
+ when "setup"
80
+ if !params[:line_items].nil? && !params[:line_items].empty?
81
+ throw Stripe::InvalidRequestError.new("You cannot pass `line_items` in `setup` mode", :line_items, http_status: 400)
82
+ end
83
+ setup_intent = new_setup_intent(nil, nil, {
84
+ customer: params[:customer],
85
+ payment_method_options: params[:payment_method_options],
86
+ payment_method_types: params[:payment_method_types]
87
+ }.merge(params[:setup_intent_data] || {}), nil)[:id]
88
+ payment_status = "no_payment_required"
89
+ when "subscription"
90
+ params[:customer] ||= new_customer(nil, nil, {email: params[:customer_email]}, nil)[:id]
91
+ require_param(:line_items) if params[:line_items].nil? || params[:line_items].empty?
92
+ checkout_session_line_items[id] = line_items
93
+ else
94
+ throw Stripe::InvalidRequestError.new("Invalid mode: must be one of payment, setup, or subscription", :mode, http_status: 400)
95
+ end
96
+
97
+ checkout_sessions[id] = {
98
+ id: id,
99
+ object: "checkout.session",
100
+ allow_promotion_codes: nil,
101
+ amount_subtotal: amount,
102
+ amount_total: amount,
103
+ automatic_tax: {
104
+ enabled: false,
105
+ status: nil
106
+ },
107
+ billing_address_collection: nil,
108
+ cancel_url: params[:cancel_url],
109
+ client_reference_id: nil,
110
+ currency: currency,
111
+ customer: params[:customer],
112
+ customer_details: nil,
113
+ customer_email: params[:customer_email],
114
+ livemode: false,
115
+ locale: nil,
116
+ metadata: params[:metadata],
117
+ mode: params[:mode],
118
+ payment_intent: payment_intent,
119
+ payment_method_options: params[:payment_method_options],
120
+ payment_method_types: params[:payment_method_types],
121
+ payment_status: payment_status,
122
+ setup_intent: setup_intent,
123
+ shipping: nil,
124
+ shipping_address_collection: nil,
125
+ submit_type: nil,
126
+ subscription: nil,
127
+ success_url: params[:success_url],
128
+ total_details: nil,
129
+ url: URI.join(StripeMock.checkout_base, id).to_s
130
+ }
131
+ end
132
+
133
+ def list_checkout_sessions(route, method_url, params, headers)
134
+ Data.mock_list_object(checkout_sessions.values)
135
+ end
136
+
137
+ def get_checkout_session(route, method_url, params, headers)
138
+ route =~ method_url
139
+ checkout_session = assert_existence :checkout_session, $1, checkout_sessions[$1]
140
+
141
+ checkout_session = checkout_session.clone
142
+ if params[:expand]&.include?('setup_intent') && checkout_session[:setup_intent]
143
+ checkout_session[:setup_intent] = setup_intents[checkout_session[:setup_intent]]
144
+ end
145
+ checkout_session
146
+ end
147
+
148
+ def list_line_items(route, method_url, params, headers)
149
+ route =~ method_url
150
+ checkout_session = assert_existence :checkout_session, $1, checkout_sessions[$1]
151
+
152
+ case checkout_session[:mode]
153
+ when "payment", "subscription"
154
+ line_items = assert_existence :checkout_session_line_items, $1, checkout_session_line_items[$1]
155
+ line_items.map do |line_item|
156
+ price = prices[line_item[:price]].clone
157
+
158
+ if price.nil?
159
+ raise StripeMock::StripeMockError.new("Price not found for ID: #{line_item[:price]}")
160
+ end
161
+
162
+ {
163
+ id: line_item[:id],
164
+ object: "item",
165
+ amount_subtotal: price[:unit_amount] * line_item[:quantity],
166
+ amount_total: price[:unit_amount] * line_item[:quantity],
167
+ currency: price[:currency],
168
+ price: price.clone,
169
+ quantity: line_item[:quantity]
170
+ }
171
+ end
172
+ else
173
+ throw Stripe::InvalidRequestError("Only payment and subscription sessions have line items")
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -12,6 +12,7 @@ module StripeMock
12
12
  end
13
13
 
14
14
  def new_customer(route, method_url, params, headers)
15
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
15
16
  params[:id] ||= new_id('cus')
16
17
  sources = []
17
18
 
@@ -29,7 +30,8 @@ module StripeMock
29
30
  params[:default_source] = sources.first[:id]
30
31
  end
31
32
 
32
- customers[ params[:id] ] = Data.mock_customer(sources, params)
33
+ customers[stripe_account] ||= {}
34
+ customers[stripe_account][params[:id]] = Data.mock_customer(sources, params)
33
35
 
34
36
  if params[:plan]
35
37
  plan_id = params[:plan].to_s
@@ -40,26 +42,30 @@ module StripeMock
40
42
  end
41
43
 
42
44
  subscription = Data.mock_subscription({ id: new_id('su') })
43
- subscription = resolve_subscription_changes(subscription, [plan], customers[ params[:id] ], params)
44
- add_subscription_to_customer(customers[ params[:id] ], subscription)
45
+ subscription = resolve_subscription_changes(subscription, [plan], customers[stripe_account][params[:id]], params)
46
+ add_subscription_to_customer(customers[stripe_account][params[:id]], subscription)
45
47
  subscriptions[subscription[:id]] = subscription
46
48
  elsif params[:trial_end]
47
49
  raise Stripe::InvalidRequestError.new('Received unknown parameter: trial_end', nil, http_status: 400)
48
50
  end
49
51
 
50
52
  if params[:coupon]
51
- coupon = coupons[ params[:coupon] ]
53
+ coupon = coupons[params[:coupon]]
52
54
  assert_existence :coupon, params[:coupon], coupon
53
-
54
- add_coupon_to_object(customers[params[:id]], coupon)
55
+ add_coupon_to_object(customers[stripe_account][params[:id]], coupon)
55
56
  end
56
57
 
57
- customers[ params[:id] ]
58
+ customers[stripe_account][params[:id]]
58
59
  end
59
60
 
60
61
  def update_customer(route, method_url, params, headers)
62
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
61
63
  route =~ method_url
62
- cus = assert_existence :customer, $1, customers[$1]
64
+ cus = assert_existence :customer, $1, customers[stripe_account][$1]
65
+
66
+ # get existing and pending metadata
67
+ metadata = cus.delete(:metadata) || {}
68
+ metadata_updates = params.delete(:metadata) || {}
63
69
 
64
70
  # Delete those params if their value is nil. Workaround of the problematic way Stripe serialize objects
65
71
  params.delete(:sources) if params[:sources] && params[:sources][:data].nil?
@@ -72,10 +78,13 @@ module StripeMock
72
78
  params.delete(:subscriptions) unless params[:subscriptions][:data].any?{ |v| !!v[:type]}
73
79
  end
74
80
  cus.merge!(params)
81
+ cus[:metadata] = {**metadata, **metadata_updates}
75
82
 
76
83
  if params[:source]
77
84
  if params[:source].is_a?(String)
78
85
  new_card = get_card_or_bank_by_token(params.delete(:source))
86
+ elsif params[:source].is_a?(Stripe::Token)
87
+ new_card = get_card_or_bank_by_token(params[:source][:id])
79
88
  elsif params[:source].is_a?(Hash)
80
89
  unless params[:source][:object] && params[:source][:number] && params[:source][:exp_month] && params[:source][:exp_year]
81
90
  raise Stripe::InvalidRequestError.new('You must supply a valid card', nil, http_status: 400)
@@ -87,31 +96,37 @@ module StripeMock
87
96
  end
88
97
 
89
98
  if params[:coupon]
90
- coupon = coupons[ params[:coupon] ]
91
- assert_existence :coupon, params[:coupon], coupon
99
+ if params[:coupon] == ''
100
+ delete_coupon_from_object(cus)
101
+ else
102
+ coupon = coupons[params[:coupon]]
103
+ assert_existence :coupon, params[:coupon], coupon
92
104
 
93
- add_coupon_to_object(cus, coupon)
105
+ add_coupon_to_object(cus, coupon)
106
+ end
94
107
  end
95
108
 
96
109
  cus
97
110
  end
98
111
 
99
112
  def delete_customer(route, method_url, params, headers)
113
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
100
114
  route =~ method_url
101
- assert_existence :customer, $1, customers[$1]
115
+ assert_existence :customer, $1, customers[stripe_account][$1]
102
116
 
103
- customers[$1] = {
104
- id: customers[$1][:id],
117
+ customers[stripe_account][$1] = {
118
+ id: customers[stripe_account][$1][:id],
105
119
  deleted: true
106
120
  }
107
121
  end
108
122
 
109
123
  def get_customer(route, method_url, params, headers)
124
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
110
125
  route =~ method_url
111
- customer = assert_existence :customer, $1, customers[$1]
126
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
112
127
 
113
128
  customer = customer.clone
114
- if params[:expand] == ['default_source']
129
+ if params[:expand] == ['default_source'] && customer[:sources][:data]
115
130
  customer[:default_source] = customer[:sources][:data].detect do |source|
116
131
  source[:id] == customer[:default_source]
117
132
  end
@@ -121,12 +136,14 @@ module StripeMock
121
136
  end
122
137
 
123
138
  def list_customers(route, method_url, params, headers)
124
- Data.mock_list_object(customers.values, params)
139
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
140
+ Data.mock_list_object(customers[stripe_account]&.values, params)
125
141
  end
126
142
 
127
143
  def delete_customer_discount(route, method_url, params, headers)
144
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
128
145
  route =~ method_url
129
- cus = assert_existence :customer, $1, customers[$1]
146
+ cus = assert_existence :customer, $1, customers[stripe_account][$1]
130
147
 
131
148
  cus[:discount] = nil
132
149
 
@@ -6,7 +6,7 @@ module StripeMock
6
6
  end
7
7
 
8
8
  def create_ephemeral_key(route, method_url, params, headers)
9
- Data.mock_ephemeral_key(params)
9
+ Data.mock_ephemeral_key(**params)
10
10
  end
11
11
  end
12
12
  end
@@ -4,7 +4,7 @@ module StripeMock
4
4
 
5
5
  def Events.included(klass)
6
6
  klass.add_handler 'get /v1/events/(.*)', :retrieve_event
7
- klass.add_handler 'get /v1/events', :list_events
7
+ klass.add_handler 'get /v1/events', :list_events
8
8
  end
9
9
 
10
10
  def retrieve_event(route, method_url, params, headers)
@@ -13,9 +13,36 @@ module StripeMock
13
13
  end
14
14
 
15
15
  def list_events(route, method_url, params, headers)
16
- Data.mock_list_object(events.values, params)
16
+ values = filter_by_created(events.values, params: params)
17
+ Data.mock_list_object(values, params)
17
18
  end
18
-
19
+
20
+ private
21
+
22
+ def filter_by_created(event_list, params:)
23
+ if params[:created].nil?
24
+ return event_list
25
+ end
26
+
27
+ if params[:created].is_a?(Hash)
28
+ if params[:created][:gt]
29
+ event_list = event_list.select { |event| event[:created] > params[:created][:gt].to_i }
30
+ end
31
+ if params[:created][:gte]
32
+ event_list = event_list.select { |event| event[:created] >= params[:created][:gte].to_i }
33
+ end
34
+ if params[:created][:lt]
35
+ event_list = event_list.select { |event| event[:created] < params[:created][:lt].to_i }
36
+ end
37
+ if params[:created][:lte]
38
+ event_list = event_list.select { |event| event[:created] <= params[:created][:lte].to_i }
39
+ end
40
+ else
41
+ event_list = event_list.select { |event| event[:created] == params[:created].to_i }
42
+ end
43
+ event_list
44
+ end
45
+
19
46
  end
20
47
  end
21
48
  end
@@ -0,0 +1,15 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module ExpressLoginLinks
4
+
5
+ def ExpressLoginLinks.included(klass)
6
+ klass.add_handler 'post /v1/accounts/(.*)/login_links', :new_account_login_link
7
+ end
8
+
9
+ def new_account_login_link(route, method_url, params, headers)
10
+ route =~ method_url
11
+ Data.mock_express_login_link(params)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -7,11 +7,17 @@ module StripeMock
7
7
  attrs[:coupon] = coupon
8
8
  attrs[:start] = Time.now.to_i
9
9
  attrs[:end] = (DateTime.now >> coupon[:duration_in_months].to_i).to_time.to_i if coupon[:duration] == 'repeating'
10
+ attrs[:id] = new_id("di")
10
11
  end
11
12
 
12
13
  object[:discount] = Stripe::Discount.construct_from(discount_attrs)
13
14
  object
14
15
  end
16
+
17
+ def delete_coupon_from_object(object)
18
+ object[:discount] = nil
19
+ object
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -11,12 +11,17 @@ module StripeMock
11
11
  items = options[:items]
12
12
  items = items.values if items.respond_to?(:values)
13
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 })
14
+ matching_item = items && items.detect { |item| [item[:price], item[:plan]].include? plan[:id] }
15
+ if matching_item
16
+ matching_item[:quantity] ||= 1
17
+ matching_item[:id] ||= new_id('si')
18
+ params = matching_item.merge(plan: plan)
19
+ params[:price] = plan if plan[:object] == "price"
20
+ Data.mock_subscription_item(params)
18
21
  else
19
- Data.mock_subscription_item({ plan: plan })
22
+ params = { plan: plan, id: new_id('si') }
23
+ params[:price] = plan if plan[:object] == "price"
24
+ Data.mock_subscription_item(params)
20
25
  end
21
26
  end
22
27
  subscription
@@ -32,7 +37,7 @@ module StripeMock
32
37
  start_time = options[:current_period_start] || now
33
38
  params = { customer: cus[:id], current_period_start: start_time, created: created_time }
34
39
  params.merge!({ :plan => (plans.size == 1 ? plans.first : nil) })
35
- keys_to_merge = /application_fee_percent|quantity|metadata|tax_percent|billing|days_until_due|default_tax_rates/
40
+ keys_to_merge = /application_fee_percent|quantity|metadata|tax_percent|billing|days_until_due|default_tax_rates|pending_invoice_item_interval|default_payment_method|collection_method/
36
41
  params.merge! options.select {|k,v| k =~ keys_to_merge}
37
42
 
38
43
  if options[:cancel_at_period_end] == true
@@ -45,10 +50,10 @@ module StripeMock
45
50
 
46
51
  if (((plan && plan[:trial_period_days]) || 0) == 0 && options[:trial_end].nil?) || options[:trial_end] == "now"
47
52
  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]})
53
+ params.merge!({status: 'active', current_period_end: end_time, trial_start: nil, trial_end: nil, billing_cycle_anchor: options[:billing_cycle_anchor] || created_time})
49
54
  else
50
55
  end_time = options[:trial_end] || (Time.now.utc.to_i + plan[:trial_period_days]*86400)
51
- params.merge!({status: 'trialing', current_period_end: end_time, trial_start: start_time, trial_end: end_time, billing_cycle_anchor: nil})
56
+ params.merge!({status: 'trialing', current_period_end: end_time, trial_start: start_time, trial_end: end_time, billing_cycle_anchor: options[:billing_cycle_anchor] || created_time})
52
57
  end
53
58
 
54
59
  params
@@ -85,11 +90,13 @@ module StripeMock
85
90
  def get_ending_time(start_time, plan, intervals = 1)
86
91
  return start_time unless plan
87
92
 
88
- case plan[:interval]
93
+ interval = plan[:interval] || plan.dig(:recurring, :interval)
94
+ interval_count = plan[:interval_count] || plan.dig(:recurring, :interval_count) || 1
95
+ case interval
89
96
  when "week"
90
- start_time + (604800 * (plan[:interval_count] || 1) * intervals)
97
+ start_time + (604800 * (interval_count) * intervals)
91
98
  when "month"
92
- (Time.at(start_time).to_datetime >> ((plan[:interval_count] || 1) * intervals)).to_time.to_i
99
+ (Time.at(start_time).to_datetime >> ((interval_count) * intervals)).to_time.to_i
93
100
  when "year"
94
101
  (Time.at(start_time).to_datetime >> (12 * intervals)).to_time.to_i # max period is 1 year
95
102
  else
@@ -111,9 +118,26 @@ module StripeMock
111
118
 
112
119
  def total_items_amount(items)
113
120
  total = 0
114
- items.each { |i| total += (i[:quantity] || 1) * i[:plan][:amount] }
121
+ items.each do |item|
122
+ quantity = item[:quantity] || 1
123
+ amount = item[:plan][:unit_amount] || item[:plan][:amount]
124
+ total += quantity * amount
125
+ end
115
126
  total
116
127
  end
128
+
129
+ def filter_by_timestamp(subscriptions, field:, value:)
130
+ if value.is_a?(Hash)
131
+ operator_mapping = { gt: :>, gte: :>=, lt: :<, lte: :<= }
132
+ subscriptions.filter do |sub|
133
+ sub[field].public_send(operator_mapping[value.keys[0]], value.values[0])
134
+ end
135
+ else
136
+ subscriptions.filter do |sub|
137
+ sub[field] == value
138
+ end
139
+ end
140
+ end
117
141
  end
118
142
  end
119
143
  end
@@ -53,16 +53,22 @@ module StripeMock
53
53
  route =~ method_url
54
54
  assert_existence :invoice, $1, invoices[$1]
55
55
  charge = invoice_charge(invoices[$1])
56
- invoices[$1].merge!(:paid => true, :attempted => true, :charge => charge[:id])
56
+ invoices[$1].merge!(
57
+ :paid => true,
58
+ :status => "paid",
59
+ :attempted => true,
60
+ :charge => charge[:id],
61
+ )
57
62
  end
58
63
 
59
- def upcoming_invoice(route, method_url, params, headers)
64
+ def upcoming_invoice(route, method_url, params, headers = {})
65
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
60
66
  route =~ method_url
61
- raise Stripe::InvalidRequestError.new('Missing required param: customer', nil, http_status: 400) if params[:customer].nil?
67
+ raise Stripe::InvalidRequestError.new('Missing required param: customer if subscription is not provided', nil, http_status: 400) if params[:customer].nil? && params[:subscription].nil?
62
68
  raise Stripe::InvalidRequestError.new('When previewing changes to a subscription, you must specify either `subscription` or `subscription_items`', nil, http_status: 400) if !params[:subscription_proration_date].nil? && params[:subscription].nil? && params[:subscription_plan].nil?
63
69
  raise Stripe::InvalidRequestError.new('Cannot specify proration date without specifying a subscription', nil, http_status: 400) if !params[:subscription_proration_date].nil? && params[:subscription].nil?
64
70
 
65
- customer = customers[params[:customer]]
71
+ customer = customers[stripe_account][params[:customer]]
66
72
  assert_existence :customer, params[:customer], customer
67
73
 
68
74
  raise Stripe::InvalidRequestError.new("No upcoming invoices for customer: #{customer[:id]}", nil, http_status: 404) if customer[:subscriptions][:data].length == 0
@@ -20,6 +20,7 @@ module StripeMock
20
20
  status = case params[:amount]
21
21
  when 3184 then 'requires_action'
22
22
  when 3178 then 'requires_payment_method'
23
+ when 3055 then 'requires_capture'
23
24
  else
24
25
  'succeeded'
25
26
  end
@@ -80,6 +81,10 @@ module StripeMock
80
81
  route =~ method_url
81
82
  payment_intent = assert_existence :payment_intent, $1, payment_intents[$1]
82
83
 
84
+ if params[:payment_method]
85
+ payment_intent[:payment_method] = params[:payment_method]
86
+ end
87
+
83
88
  succeeded_payment_intent(payment_intent)
84
89
  end
85
90
 
@@ -167,7 +172,20 @@ module StripeMock
167
172
  def succeeded_payment_intent(payment_intent)
168
173
  payment_intent[:status] = 'succeeded'
169
174
  btxn = new_balance_transaction('txn', { source: payment_intent[:id] })
170
- payment_intent[:charges][:data] << Data.mock_charge(balance_transaction: btxn)
175
+
176
+ charge_id = new_id('ch')
177
+
178
+ charges[charge_id] = Data.mock_charge(
179
+ id: charge_id,
180
+ balance_transaction: btxn,
181
+ payment_intent: payment_intent[:id],
182
+ amount: payment_intent[:amount],
183
+ currency: payment_intent[:currency],
184
+ payment_method: payment_intent[:payment_method]
185
+ )
186
+
187
+ payment_intent[:charges][:data] << charges[charge_id].clone
188
+
171
189
  payment_intent
172
190
  end
173
191
  end
@@ -1,7 +1,6 @@
1
1
  module StripeMock
2
2
  module RequestHandlers
3
3
  module PaymentMethods
4
-
5
4
  ALLOWED_PARAMS = [:customer, :type]
6
5
 
7
6
  def PaymentMethods.included(klass)
@@ -53,33 +52,14 @@ module StripeMock
53
52
  Data.mock_list_object(clone.values, params)
54
53
  end
55
54
 
56
- #
57
- # params: {:customer=>"test_cus_3"}
58
- #
59
- def attach_payment_method(route, method_url, params, headers)
60
- route =~ method_url
61
- id = $1
62
- payment_methods[id].merge!(params)
63
- payment_methods[id].clone
64
- end
65
-
66
- def detach_payment_method(route, method_url, params, headers)
67
-
68
- end
69
-
70
- def get_payment_method(route, method_url, params, headers)
71
- route =~ method_url
72
- id = $1
73
- assert_existence(:payment_method, $1, payment_methods[id])
74
- end
75
-
76
55
  # post /v1/payment_methods/:id/attach
77
56
  def attach_payment_method(route, method_url, params, headers)
57
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
78
58
  allowed_params = [:customer]
79
59
 
80
60
  id = method_url.match(route)[1]
81
61
 
82
- assert_existence :customer, params[:customer], customers[params[:customer]]
62
+ assert_existence :customer, params[:customer], customers[stripe_account][params[:customer]]
83
63
 
84
64
  payment_method = assert_existence :payment_method, id, payment_methods[id]
85
65
  payment_methods[id] = Util.rmerge(payment_method, params.select { |k, _v| allowed_params.include?(k) })
@@ -99,6 +79,10 @@ module StripeMock
99
79
  # post /v1/payment_methods/:id
100
80
  def update_payment_method(route, method_url, params, headers)
101
81
  allowed_params = [:billing_details, :card, :metadata]
82
+ disallowed_params = params.keys - allowed_params
83
+ unless disallowed_params.empty?
84
+ raise Stripe::InvalidRequestError.new("Received unknown parameter: #{disallowed_params.first}", disallowed_params.first)
85
+ end
102
86
 
103
87
  id = method_url.match(route)[1]
104
88
 
@@ -107,6 +91,7 @@ module StripeMock
107
91
  if payment_method[:customer].nil?
108
92
  raise Stripe::InvalidRequestError.new(
109
93
  'You must save this PaymentMethod to a customer before you can update it.',
94
+ nil,
110
95
  http_status: 400
111
96
  )
112
97
  end
@@ -124,14 +109,15 @@ module StripeMock
124
109
 
125
110
  if invalid_type?(params[:type])
126
111
  raise Stripe::InvalidRequestError.new(
127
- 'Invalid type: must be one of card or card_present',
112
+ 'Invalid type: must be one of card, ideal or sepa_debit',
113
+ nil,
128
114
  http_status: 400
129
115
  )
130
116
  end
131
117
  end
132
118
 
133
119
  def invalid_type?(type)
134
- !['card', 'card_present'].include?(type)
120
+ !%w(card ideal sepa_debit).include?(type)
135
121
  end
136
122
  end
137
123
  end
@@ -34,7 +34,7 @@ module StripeMock
34
34
 
35
35
  def list_plans(route, method_url, params, headers)
36
36
  limit = params[:limit] ? params[:limit] : 10
37
- Data.mock_list_object(plans.values.first(limit), limit: limit)
37
+ Data.mock_list_object(plans.values.first(limit), params.merge!(limit: limit))
38
38
  end
39
39
 
40
40
  end