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
@@ -6,7 +6,8 @@ module StripeMock
6
6
  klass.add_handler 'post /v1/invoices', :new_invoice
7
7
  klass.add_handler 'get /v1/invoices/upcoming', :upcoming_invoice
8
8
  klass.add_handler 'get /v1/invoices/(.*)/lines', :get_invoice_line_items
9
- klass.add_handler 'get /v1/invoices/(.*)', :get_invoice
9
+ klass.add_handler 'get /v1/invoices/((?!search).*)', :get_invoice
10
+ klass.add_handler 'get /v1/invoices/search', :search_invoices
10
11
  klass.add_handler 'get /v1/invoices', :list_invoices
11
12
  klass.add_handler 'post /v1/invoices/(.*)/pay', :pay_invoice
12
13
  klass.add_handler 'post /v1/invoices/(.*)', :update_invoice
@@ -25,6 +26,14 @@ module StripeMock
25
26
  invoices[$1].merge!(params)
26
27
  end
27
28
 
29
+ SEARCH_FIELDS = ["currency", "customer", "number", "receipt_number", "subscription", "total"].freeze
30
+ def search_invoices(route, method_url, params, headers)
31
+ require_param(:query) unless params[:query]
32
+
33
+ results = search_results(invoices.values, params[:query], fields: SEARCH_FIELDS, resource_name: "invoices")
34
+ Data.mock_list_object(results, params)
35
+ end
36
+
28
37
  def list_invoices(route, method_url, params, headers)
29
38
  params[:offset] ||= 0
30
39
  params[:limit] ||= 10
@@ -53,16 +62,22 @@ module StripeMock
53
62
  route =~ method_url
54
63
  assert_existence :invoice, $1, invoices[$1]
55
64
  charge = invoice_charge(invoices[$1])
56
- invoices[$1].merge!(:paid => true, :attempted => true, :charge => charge[:id])
65
+ invoices[$1].merge!(
66
+ :paid => true,
67
+ :status => "paid",
68
+ :attempted => true,
69
+ :charge => charge[:id],
70
+ )
57
71
  end
58
72
 
59
- def upcoming_invoice(route, method_url, params, headers)
73
+ def upcoming_invoice(route, method_url, params, headers = {})
74
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
60
75
  route =~ method_url
61
- raise Stripe::InvalidRequestError.new('Missing required param: customer', nil, http_status: 400) if params[:customer].nil?
76
+ 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
77
  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
78
  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
79
 
65
- customer = customers[params[:customer]]
80
+ customer = customers[stripe_account][params[:customer]]
66
81
  assert_existence :customer, params[:customer], customer
67
82
 
68
83
  raise Stripe::InvalidRequestError.new("No upcoming invoices for customer: #{customer[:id]}", nil, http_status: 404) if customer[:subscriptions][:data].length == 0
@@ -99,7 +114,12 @@ module StripeMock
99
114
  invoice_lines = []
100
115
 
101
116
  if prorating
102
- unused_amount = subscription[:plan][:amount] * subscription[:quantity] * (subscription[:current_period_end] - subscription_proration_date.to_i) / (subscription[:current_period_end] - subscription[:current_period_start])
117
+ unused_amount = (
118
+ subscription[:plan][:amount].to_f *
119
+ subscription[:quantity] *
120
+ (subscription[:current_period_end] - subscription_proration_date.to_i) / (subscription[:current_period_end] - subscription[:current_period_start])
121
+ ).ceil
122
+
103
123
  invoice_lines << Data.mock_line_item(
104
124
  id: new_id('ii'),
105
125
  amount: -unused_amount,
@@ -0,0 +1,202 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module PaymentIntents
4
+ ALLOWED_PARAMS = [:description, :metadata, :receipt_email, :shipping, :destination, :payment_method, :payment_method_types, :setup_future_usage, :transfer_data, :amount, :currency]
5
+
6
+ def PaymentIntents.included(klass)
7
+ klass.add_handler 'post /v1/payment_intents', :new_payment_intent
8
+ klass.add_handler 'get /v1/payment_intents', :get_payment_intents
9
+ klass.add_handler 'get /v1/payment_intents/((?!search).*)', :get_payment_intent
10
+ klass.add_handler 'get /v1/payment_intents/search', :search_payment_intents
11
+ klass.add_handler 'post /v1/payment_intents/(.*)/confirm', :confirm_payment_intent
12
+ klass.add_handler 'post /v1/payment_intents/(.*)/capture', :capture_payment_intent
13
+ klass.add_handler 'post /v1/payment_intents/(.*)/cancel', :cancel_payment_intent
14
+ klass.add_handler 'post /v1/payment_intents/(.*)', :update_payment_intent
15
+ end
16
+
17
+ def new_payment_intent(route, method_url, params, headers)
18
+ id = new_id('pi')
19
+
20
+ ensure_payment_intent_required_params(params)
21
+ status = case params[:amount]
22
+ when 3184 then 'requires_action'
23
+ when 3178 then 'requires_payment_method'
24
+ when 3055 then 'requires_capture'
25
+ else
26
+ 'succeeded'
27
+ end
28
+ last_payment_error = params[:amount] == 3178 ? last_payment_error_generator(code: 'card_declined', decline_code: 'insufficient_funds', message: 'Not enough funds.') : nil
29
+ payment_intents[id] = Data.mock_payment_intent(
30
+ params.merge(
31
+ id: id,
32
+ status: status,
33
+ last_payment_error: last_payment_error
34
+ )
35
+ )
36
+
37
+ if params[:confirm] && status == 'succeeded'
38
+ payment_intents[id] = succeeded_payment_intent(payment_intents[id])
39
+ end
40
+
41
+ payment_intents[id].clone
42
+ end
43
+
44
+ def update_payment_intent(route, method_url, params, headers)
45
+ route =~ method_url
46
+ id = $1
47
+
48
+ payment_intent = assert_existence :payment_intent, id, payment_intents[id]
49
+ payment_intents[id] = Util.rmerge(payment_intent, params.select{ |k,v| ALLOWED_PARAMS.include?(k)})
50
+ end
51
+
52
+ def get_payment_intents(route, method_url, params, headers)
53
+ params[:offset] ||= 0
54
+ params[:limit] ||= 10
55
+
56
+ clone = payment_intents.clone
57
+
58
+ if params[:customer]
59
+ clone.delete_if { |k,v| v[:customer] != params[:customer] }
60
+ end
61
+
62
+ Data.mock_list_object(clone.values, params)
63
+ end
64
+
65
+ def get_payment_intent(route, method_url, params, headers)
66
+ route =~ method_url
67
+ payment_intent_id = $1 || params[:payment_intent]
68
+ payment_intent = assert_existence :payment_intent, payment_intent_id, payment_intents[payment_intent_id]
69
+
70
+ payment_intent = payment_intent.clone
71
+ payment_intent
72
+ end
73
+
74
+ SEARCH_FIELDS = ["amount", "currency", "customer", "status"].freeze
75
+ def search_payment_intents(route, method_url, params, headers)
76
+ require_param(:query) unless params[:query]
77
+
78
+ results = search_results(payment_intents.values, params[:query], fields: SEARCH_FIELDS, resource_name: "payment_intents")
79
+ Data.mock_list_object(results, params)
80
+ end
81
+
82
+ def capture_payment_intent(route, method_url, params, headers)
83
+ route =~ method_url
84
+ payment_intent = assert_existence :payment_intent, $1, payment_intents[$1]
85
+
86
+ succeeded_payment_intent(payment_intent)
87
+ end
88
+
89
+ def confirm_payment_intent(route, method_url, params, headers)
90
+ route =~ method_url
91
+ payment_intent = assert_existence :payment_intent, $1, payment_intents[$1]
92
+
93
+ if params[:payment_method]
94
+ payment_intent[:payment_method] = params[:payment_method]
95
+ end
96
+
97
+ succeeded_payment_intent(payment_intent)
98
+ end
99
+
100
+ def cancel_payment_intent(route, method_url, params, headers)
101
+ route =~ method_url
102
+ payment_intent = assert_existence :payment_intent, $1, payment_intents[$1]
103
+
104
+ payment_intent[:status] = 'canceled'
105
+ payment_intent
106
+ end
107
+
108
+ private
109
+
110
+ def ensure_payment_intent_required_params(params)
111
+ if params[:amount].nil?
112
+ require_param(:amount)
113
+ elsif params[:currency].nil?
114
+ require_param(:currency)
115
+ elsif non_integer_charge_amount?(params)
116
+ raise Stripe::InvalidRequestError.new("Invalid integer: #{params[:amount]}", 'amount', http_status: 400)
117
+ elsif non_positive_charge_amount?(params)
118
+ raise Stripe::InvalidRequestError.new('Invalid positive integer', 'amount', http_status: 400)
119
+ end
120
+ end
121
+
122
+ def non_integer_charge_amount?(params)
123
+ params[:amount] && !params[:amount].is_a?(Integer)
124
+ end
125
+
126
+ def non_positive_charge_amount?(params)
127
+ params[:amount] && params[:amount] < 1
128
+ end
129
+
130
+ def last_payment_error_generator(code: nil, message: nil, decline_code: nil)
131
+ {
132
+ code: code,
133
+ doc_url: "https://stripe.com/docs/error-codes/payment-intent-authentication-failure",
134
+ message: message,
135
+ decline_code: decline_code,
136
+ payment_method: {
137
+ id: "pm_1EwXFA2eZvKYlo2C0tlY091l",
138
+ object: "payment_method",
139
+ billing_details: {
140
+ address: {
141
+ city: nil,
142
+ country: nil,
143
+ line1: nil,
144
+ line2: nil,
145
+ postal_code: nil,
146
+ state: nil
147
+ },
148
+ email: nil,
149
+ name: "seller_08072019090000",
150
+ phone: nil
151
+ },
152
+ card: {
153
+ brand: "visa",
154
+ checks: {
155
+ address_line1_check: nil,
156
+ address_postal_code_check: nil,
157
+ cvc_check: "unchecked"
158
+ },
159
+ country: "US",
160
+ exp_month: 12,
161
+ exp_year: 2021,
162
+ fingerprint: "LQBhEmJnItuj3mxf",
163
+ funding: "credit",
164
+ generated_from: nil,
165
+ last4: "1629",
166
+ three_d_secure_usage: {
167
+ supported: true
168
+ },
169
+ wallet: nil
170
+ },
171
+ created: 1563208900,
172
+ customer: nil,
173
+ livemode: false,
174
+ metadata: {},
175
+ type: "card"
176
+ },
177
+ type: "invalid_request_error"
178
+ }
179
+ end
180
+
181
+ def succeeded_payment_intent(payment_intent)
182
+ payment_intent[:status] = 'succeeded'
183
+ btxn = new_balance_transaction('txn', { source: payment_intent[:id] })
184
+
185
+ charge_id = new_id('ch')
186
+
187
+ charges[charge_id] = Data.mock_charge(
188
+ id: charge_id,
189
+ balance_transaction: btxn,
190
+ payment_intent: payment_intent[:id],
191
+ amount: payment_intent[:amount],
192
+ currency: payment_intent[:currency],
193
+ payment_method: payment_intent[:payment_method]
194
+ )
195
+
196
+ payment_intent[:charges][:data] << charges[charge_id].clone
197
+
198
+ payment_intent
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,124 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module PaymentMethods
4
+ ALLOWED_PARAMS = [:customer, :type]
5
+
6
+ def PaymentMethods.included(klass)
7
+ klass.add_handler 'post /v1/payment_methods', :new_payment_method
8
+ klass.add_handler 'get /v1/payment_methods/(.*)', :get_payment_method
9
+ klass.add_handler 'get /v1/payment_methods', :get_payment_methods
10
+ klass.add_handler 'post /v1/payment_methods/(.*)/attach', :attach_payment_method
11
+ klass.add_handler 'post /v1/payment_methods/(.*)/detach', :detach_payment_method
12
+ klass.add_handler 'post /v1/payment_methods/(.*)', :update_payment_method
13
+ end
14
+
15
+ # post /v1/payment_methods
16
+ def new_payment_method(route, method_url, params, headers)
17
+ id = new_id('pm')
18
+
19
+ ensure_payment_method_required_params(params)
20
+
21
+ payment_methods[id] = Data.mock_payment_method(
22
+ params.merge(
23
+ id: id
24
+ )
25
+ )
26
+
27
+ payment_methods[id].clone
28
+ end
29
+
30
+ #
31
+ # params: {:type=>"card", :customer=>"test_cus_3"}
32
+ #
33
+ # get /v1/payment_methods/:id
34
+ def get_payment_method(route, method_url, params, headers)
35
+ id = method_url.match(route)[1] || params[:payment_method]
36
+ payment_method = assert_existence :payment_method, id, payment_methods[id]
37
+
38
+ payment_method.clone
39
+ end
40
+
41
+ # get /v1/payment_methods
42
+ def get_payment_methods(route, method_url, params, headers)
43
+ params[:offset] ||= 0
44
+ params[:limit] ||= 10
45
+
46
+ clone = payment_methods.clone
47
+
48
+ if params[:customer]
49
+ clone.delete_if { |_k, v| v[:customer] != params[:customer] }
50
+ end
51
+
52
+ Data.mock_list_object(clone.values, params)
53
+ end
54
+
55
+ # post /v1/payment_methods/:id/attach
56
+ def attach_payment_method(route, method_url, params, headers)
57
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
58
+ allowed_params = [:customer]
59
+
60
+ id = method_url.match(route)[1]
61
+
62
+ assert_existence :customer, params[:customer], customers[stripe_account][params[:customer]]
63
+
64
+ payment_method = assert_existence :payment_method, id, payment_methods[id]
65
+ payment_methods[id] = Util.rmerge(payment_method, params.select { |k, _v| allowed_params.include?(k) })
66
+ payment_methods[id].clone
67
+ end
68
+
69
+ # post /v1/payment_methods/:id/detach
70
+ def detach_payment_method(route, method_url, params, headers)
71
+ id = method_url.match(route)[1]
72
+
73
+ payment_method = assert_existence :payment_method, id, payment_methods[id]
74
+ payment_method[:customer] = nil
75
+
76
+ payment_method.clone
77
+ end
78
+
79
+ # post /v1/payment_methods/:id
80
+ def update_payment_method(route, method_url, params, headers)
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
86
+
87
+ id = method_url.match(route)[1]
88
+
89
+ payment_method = assert_existence :payment_method, id, payment_methods[id]
90
+
91
+ if payment_method[:customer].nil?
92
+ raise Stripe::InvalidRequestError.new(
93
+ 'You must save this PaymentMethod to a customer before you can update it.',
94
+ nil,
95
+ http_status: 400
96
+ )
97
+ end
98
+
99
+ payment_methods[id] =
100
+ Util.rmerge(payment_method, params.select { |k, _v| allowed_params.include?(k)} )
101
+
102
+ payment_methods[id].clone
103
+ end
104
+
105
+ private
106
+
107
+ def ensure_payment_method_required_params(params)
108
+ require_param(:type) if params[:type].nil?
109
+
110
+ if invalid_type?(params[:type])
111
+ raise Stripe::InvalidRequestError.new(
112
+ 'Invalid type: must be one of card, ideal or sepa_debit',
113
+ nil,
114
+ http_status: 400
115
+ )
116
+ end
117
+ end
118
+
119
+ def invalid_type?(type)
120
+ !%w(card ideal sepa_debit).include?(type)
121
+ end
122
+ end
123
+ end
124
+ 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
@@ -0,0 +1,71 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Prices
4
+
5
+ def Prices.included(klass)
6
+ klass.add_handler 'post /v1/prices', :new_price
7
+ klass.add_handler 'post /v1/prices/(.*)', :update_price
8
+ klass.add_handler 'get /v1/prices/((?!search).*)', :get_price
9
+ klass.add_handler 'get /v1/prices/search', :search_prices
10
+ klass.add_handler 'get /v1/prices', :list_prices
11
+ end
12
+
13
+ def new_price(route, method_url, params, headers)
14
+ params[:id] ||= new_id('price')
15
+
16
+ if params[:product_data]
17
+ params[:product] = create_product(nil, nil, params[:product_data], nil)[:id] unless params[:product]
18
+ params.delete(:product_data)
19
+ end
20
+
21
+ validate_create_price_params(params)
22
+ prices[ params[:id] ] = Data.mock_price(params)
23
+ end
24
+
25
+ def update_price(route, method_url, params, headers)
26
+ route =~ method_url
27
+ assert_existence :price, $1, prices[$1]
28
+ prices[$1].merge!(params)
29
+ end
30
+
31
+ def get_price(route, method_url, params, headers)
32
+ route =~ method_url
33
+ assert_existence :price, $1, prices[$1]
34
+ end
35
+
36
+ def list_prices(route, method_url, params, headers)
37
+ limit = params[:limit] ? params[:limit] : 10
38
+ price_data = prices.values
39
+ validate_list_prices_params(params)
40
+
41
+ if params.key?(:lookup_keys)
42
+ price_data.select! do |price|
43
+ params[:lookup_keys].include?(price[:lookup_key])
44
+ end
45
+ end
46
+
47
+ if params.key?(:currency)
48
+ price_data.select! do |price|
49
+ params[:currency] == price[:currency]
50
+ end
51
+ end
52
+
53
+ if params.key?(:product)
54
+ price_data.select! do |price|
55
+ params[:product] == price[:product]
56
+ end
57
+ end
58
+
59
+ Data.mock_list_object(price_data.first(limit), params.merge!(limit: limit))
60
+ end
61
+
62
+ SEARCH_FIELDS = ["active", "currency", "lookup_key", "product", "type"].freeze
63
+ def search_prices(route, method_url, params, headers)
64
+ require_param(:query) unless params[:query]
65
+
66
+ results = search_results(prices.values, params[:query], fields: SEARCH_FIELDS, resource_name: "prices")
67
+ Data.mock_list_object(results, params)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -2,15 +2,17 @@ module StripeMock
2
2
  module RequestHandlers
3
3
  module Products
4
4
  def self.included(base)
5
- base.add_handler 'post /v1/products', :create_product
6
- base.add_handler 'get /v1/products/(.*)', :retrieve_product
7
- base.add_handler 'post /v1/products/(.*)', :update_product
8
- base.add_handler 'get /v1/products', :list_products
9
- base.add_handler 'delete /v1/products/(.*)', :destroy_product
5
+ base.add_handler 'post /v1/products', :create_product
6
+ base.add_handler 'get /v1/products/((?!search).*)', :retrieve_product
7
+ base.add_handler 'get /v1/products/search', :search_products
8
+ base.add_handler 'post /v1/products/(.*)', :update_product
9
+ base.add_handler 'get /v1/products', :list_products
10
+ base.add_handler 'delete /v1/products/(.*)', :destroy_product
10
11
  end
11
12
 
12
13
  def create_product(_route, _method_url, params, _headers)
13
14
  params[:id] ||= new_id('prod')
15
+ validate_create_product_params(params)
14
16
  products[params[:id]] = Data.mock_product(params)
15
17
  end
16
18
 
@@ -31,6 +33,14 @@ module StripeMock
31
33
  Data.mock_list_object(products.values.take(limit), params)
32
34
  end
33
35
 
36
+ SEARCH_FIELDS = ["active", "description", "name", "shippable", "url"].freeze
37
+ def search_products(route, method_url, params, headers)
38
+ require_param(:query) unless params[:query]
39
+
40
+ results = search_results(products.values, params[:query], fields: SEARCH_FIELDS, resource_name: "products")
41
+ Data.mock_list_object(results, params)
42
+ end
43
+
34
44
  def destroy_product(route, method_url, _params, _headers)
35
45
  id = method_url.match(route).captures.first
36
46
  assert_existence :product, id, products[id]
@@ -0,0 +1,43 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module PromotionCodes
4
+
5
+ def PromotionCodes.included(klass)
6
+ klass.add_handler 'post /v1/promotion_codes', :new_promotion_code
7
+ klass.add_handler 'post /v1/promotion_codes/([^/]*)', :update_promotion_code
8
+ klass.add_handler 'get /v1/promotion_codes/([^/]*)', :get_promotion_code
9
+ klass.add_handler 'get /v1/promotion_codes', :list_promotion_code
10
+ end
11
+
12
+ def new_promotion_code(route, method_url, params, headers)
13
+ params[:id] ||= new_id("promo")
14
+ raise Stripe::InvalidRequestError.new("Missing required param: coupon", "promotion_code", http_status: 400) unless params[:coupon]
15
+
16
+ if params[:restrictions]
17
+ if params[:restrictions][:minimum_amount] && !params[:restrictions][:minimum_amount_currency]
18
+ raise Stripe::InvalidRequestError.new(
19
+ "You must pass minimum_amount_currency when passing minimum_amount", "minimum_amount_currency", http_status: 400
20
+ )
21
+ end
22
+ end
23
+
24
+ promotion_codes[ params[:id] ] = Data.mock_promotion_code(params)
25
+ end
26
+
27
+ def update_promotion_code(route, method_url, params, headers)
28
+ route =~ method_url
29
+ assert_existence :promotion_code, $1, promotion_codes[$1]
30
+ promotion_codes[$1].merge!(params)
31
+ end
32
+
33
+ def get_promotion_code(route, method_url, params, headers)
34
+ route =~ method_url
35
+ assert_existence :promotion_code, $1, promotion_codes[$1]
36
+ end
37
+
38
+ def list_promotion_code(route, method_url, params, headers)
39
+ Data.mock_list_object(promotion_codes.values, params)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -18,7 +18,18 @@ module StripeMock
18
18
  end
19
19
  end
20
20
 
21
- charge = assert_existence :charge, params[:charge], charges[params[:charge]]
21
+ if params[:payment_intent]
22
+ payment_intent = assert_existence(
23
+ :payment_intent,
24
+ params[:payment_intent],
25
+ payment_intents[params[:payment_intent]]
26
+ )
27
+ charge = {}
28
+ else
29
+ charge = assert_existence :charge, params[:charge], charges[params[:charge]]
30
+ payment_intent = {}
31
+ end
32
+ params[:amount] ||= payment_intent[:amount]
22
33
  params[:amount] ||= charge[:amount]
23
34
  id = new_id('re')
24
35
  bal_trans_params = {
@@ -32,7 +43,7 @@ module StripeMock
32
43
  :id => id,
33
44
  :charge => charge[:id],
34
45
  )
35
- add_refund_to_charge(refund, charge)
46
+ add_refund_to_charge(refund, charge) unless charge.empty?
36
47
  refunds[id] = refund
37
48
 
38
49
  if params[:expand] == ['balance_transaction']
@@ -0,0 +1,100 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module SetupIntents
4
+ ALLOWED_PARAMS = [
5
+ :confirm,
6
+ :customer,
7
+ :description,
8
+ :metadata,
9
+ :on_behalf_of,
10
+ :payment_method,
11
+ :payment_method_options,
12
+ :payment_method_types,
13
+ :return_url,
14
+ :usage
15
+ ]
16
+
17
+ def SetupIntents.included(klass)
18
+ klass.add_handler 'post /v1/setup_intents', :new_setup_intent
19
+ klass.add_handler 'get /v1/setup_intents', :get_setup_intents
20
+ klass.add_handler 'get /v1/setup_intents/(.*)', :get_setup_intent
21
+ klass.add_handler 'post /v1/setup_intents/(.*)/confirm', :confirm_setup_intent
22
+ klass.add_handler 'post /v1/setup_intents/(.*)/cancel', :cancel_setup_intent
23
+ klass.add_handler 'post /v1/setup_intents/(.*)', :update_setup_intent
24
+ end
25
+
26
+ def new_setup_intent(route, method_url, params, headers)
27
+ id = new_id('si')
28
+ status = params[:payment_method] ? 'requires_action' : 'requires_payment_method'
29
+
30
+ setup_intents[id] = Data.mock_setup_intent(
31
+ params.merge(
32
+ id: id,
33
+ status: status
34
+ )
35
+ )
36
+
37
+ setup_intents[id].clone
38
+ end
39
+
40
+ def update_setup_intent(route, method_url, params, headers)
41
+ route =~ method_url
42
+ id = $1
43
+
44
+ setup_intent = assert_existence :setup_intent, id, setup_intents[id]
45
+ setup_intents[id] = Util.rmerge(setup_intent, params.select { |k, v| ALLOWED_PARAMS.include?(k) })
46
+ end
47
+
48
+ def get_setup_intents(route, method_url, params, headers)
49
+ params[:offset] ||= 0
50
+ params[:limit] ||= 10
51
+
52
+ clone = setup_intents.clone
53
+
54
+ if params[:customer]
55
+ clone.delete_if { |k, v| v[:customer] != params[:customer] }
56
+ end
57
+
58
+ Data.mock_list_object(clone.values, params)
59
+ end
60
+
61
+ def get_setup_intent(route, method_url, params, headers)
62
+ route =~ method_url
63
+ setup_intent_id = $1 || params[:setup_intent]
64
+ setup_intent = assert_existence :setup_intent, setup_intent_id, setup_intents[setup_intent_id]
65
+
66
+ setup_intent = setup_intent.clone
67
+
68
+ if params[:expand]&.include?("payment_method")
69
+ setup_intent[:payment_method] = assert_existence :payment_method, setup_intent[:payment_method], payment_methods[setup_intent[:payment_method]]
70
+ end
71
+
72
+ setup_intent
73
+ end
74
+
75
+ def capture_setup_intent(route, method_url, params, headers)
76
+ route =~ method_url
77
+ setup_intent = assert_existence :setup_intent, $1, setup_intents[$1]
78
+
79
+ setup_intent[:status] = 'succeeded'
80
+ setup_intent
81
+ end
82
+
83
+ def confirm_setup_intent(route, method_url, params, headers)
84
+ route =~ method_url
85
+ setup_intent = assert_existence :setup_intent, $1, setup_intents[$1]
86
+
87
+ setup_intent[:status] = 'succeeded'
88
+ setup_intent
89
+ end
90
+
91
+ def cancel_setup_intent(route, method_url, params, headers)
92
+ route =~ method_url
93
+ setup_intent = assert_existence :setup_intent, $1, setup_intents[$1]
94
+
95
+ setup_intent[:status] = 'canceled'
96
+ setup_intent
97
+ end
98
+ end
99
+ end
100
+ end