stripe-ruby-mock 3.1.0.rc3 → 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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec_tests.yml +38 -0
  3. data/.rspec +2 -1
  4. data/CHANGELOG.md +28 -0
  5. data/README.md +5 -3
  6. data/lib/stripe_mock/api/webhooks.rb +65 -26
  7. data/lib/stripe_mock/data.rb +80 -11
  8. data/lib/stripe_mock/instance.rb +6 -2
  9. data/lib/stripe_mock/request_handlers/accounts.rb +17 -6
  10. data/lib/stripe_mock/request_handlers/charges.rb +5 -0
  11. data/lib/stripe_mock/request_handlers/checkout_session.rb +158 -1
  12. data/lib/stripe_mock/request_handlers/events.rb +30 -3
  13. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +1 -0
  14. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +28 -9
  15. data/lib/stripe_mock/request_handlers/invoices.rb +6 -1
  16. data/lib/stripe_mock/request_handlers/payment_intents.rb +13 -2
  17. data/lib/stripe_mock/request_handlers/payment_methods.rb +5 -1
  18. data/lib/stripe_mock/request_handlers/prices.rb +18 -0
  19. data/lib/stripe_mock/request_handlers/promotion_codes.rb +43 -0
  20. data/lib/stripe_mock/request_handlers/refunds.rb +13 -2
  21. data/lib/stripe_mock/request_handlers/setup_intents.rb +16 -9
  22. data/lib/stripe_mock/request_handlers/subscriptions.rb +93 -4
  23. data/lib/stripe_mock/request_handlers/transfers.rb +12 -1
  24. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +5 -4
  25. data/lib/stripe_mock/test_strategies/base.rb +51 -24
  26. data/lib/stripe_mock/version.rb +1 -1
  27. data/lib/stripe_mock/webhook_fixtures/account.updated.json +1 -1
  28. data/lib/stripe_mock/webhook_fixtures/balance.available.json +26 -20
  29. data/lib/stripe_mock/webhook_fixtures/charge.captured.json +143 -0
  30. data/lib/stripe_mock/webhook_fixtures/charge.dispute.created.json +63 -16
  31. data/lib/stripe_mock/webhook_fixtures/charge.failed.json +49 -120
  32. data/lib/stripe_mock/webhook_fixtures/charge.refund.updated.json +35 -0
  33. data/lib/stripe_mock/webhook_fixtures/charge.refunded.json +145 -50
  34. data/lib/stripe_mock/webhook_fixtures/charge.succeeded.json +114 -43
  35. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.json +79 -0
  36. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.payment_mode.json +53 -0
  37. data/lib/stripe_mock/webhook_fixtures/checkout.session.completed.setup_mode.json +45 -0
  38. data/lib/stripe_mock/webhook_fixtures/customer.created.json +37 -46
  39. data/lib/stripe_mock/webhook_fixtures/customer.deleted.json +36 -32
  40. data/lib/stripe_mock/webhook_fixtures/customer.source.created.json +31 -22
  41. data/lib/stripe_mock/webhook_fixtures/customer.source.updated.json +36 -25
  42. data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +135 -47
  43. data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +134 -45
  44. data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +135 -56
  45. data/lib/stripe_mock/webhook_fixtures/customer.updated.json +38 -47
  46. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +176 -49
  47. data/lib/stripe_mock/webhook_fixtures/invoice.finalized.json +171 -0
  48. data/lib/stripe_mock/webhook_fixtures/invoice.paid.json +171 -0
  49. data/lib/stripe_mock/webhook_fixtures/invoice.payment_action_required.json +171 -0
  50. data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +149 -83
  51. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +149 -90
  52. data/lib/stripe_mock/webhook_fixtures/invoice.upcoming.json +70 -0
  53. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +178 -50
  54. data/lib/stripe_mock/webhook_fixtures/invoiceitem.created.json +87 -13
  55. data/lib/stripe_mock/webhook_fixtures/invoiceitem.updated.json +88 -14
  56. data/lib/stripe_mock/webhook_fixtures/mandate.updated.json +34 -0
  57. data/lib/stripe_mock/webhook_fixtures/payment_intent.amount_capturable_updated.json +170 -0
  58. data/lib/stripe_mock/webhook_fixtures/payment_intent.canceled.json +73 -0
  59. data/lib/stripe_mock/webhook_fixtures/payment_intent.created.json +86 -0
  60. data/lib/stripe_mock/webhook_fixtures/payment_intent.payment_failed.json +118 -79
  61. data/lib/stripe_mock/webhook_fixtures/payment_intent.processing.json +162 -0
  62. data/lib/stripe_mock/webhook_fixtures/payment_intent.requires_action.json +191 -0
  63. data/lib/stripe_mock/webhook_fixtures/payment_intent.succeeded.json +85 -53
  64. data/lib/stripe_mock/webhook_fixtures/payment_link.created.json +47 -0
  65. data/lib/stripe_mock/webhook_fixtures/payment_link.updated.json +50 -0
  66. data/lib/stripe_mock/webhook_fixtures/payment_method.attached.json +63 -0
  67. data/lib/stripe_mock/webhook_fixtures/payment_method.detached.json +62 -0
  68. data/lib/stripe_mock/webhook_fixtures/payout.created.json +40 -0
  69. data/lib/stripe_mock/webhook_fixtures/payout.paid.json +40 -0
  70. data/lib/stripe_mock/webhook_fixtures/payout.updated.json +46 -0
  71. data/lib/stripe_mock/webhook_fixtures/plan.created.json +30 -13
  72. data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +30 -13
  73. data/lib/stripe_mock/webhook_fixtures/plan.updated.json +34 -14
  74. data/lib/stripe_mock/webhook_fixtures/price.created.json +42 -0
  75. data/lib/stripe_mock/webhook_fixtures/price.deleted.json +42 -0
  76. data/lib/stripe_mock/webhook_fixtures/price.updated.json +48 -0
  77. data/lib/stripe_mock/webhook_fixtures/product.created.json +19 -13
  78. data/lib/stripe_mock/webhook_fixtures/product.deleted.json +20 -14
  79. data/lib/stripe_mock/webhook_fixtures/product.updated.json +24 -15
  80. data/lib/stripe_mock/webhook_fixtures/quote.accepted.json +92 -0
  81. data/lib/stripe_mock/webhook_fixtures/quote.canceled.json +92 -0
  82. data/lib/stripe_mock/webhook_fixtures/quote.created.json +92 -0
  83. data/lib/stripe_mock/webhook_fixtures/quote.finalized.json +92 -0
  84. data/lib/stripe_mock/webhook_fixtures/setup_intent.canceled.json +46 -0
  85. data/lib/stripe_mock/webhook_fixtures/setup_intent.created.json +51 -0
  86. data/lib/stripe_mock/webhook_fixtures/setup_intent.setup_failed.json +100 -0
  87. data/lib/stripe_mock/webhook_fixtures/setup_intent.succeeded.json +46 -0
  88. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.canceled.json +119 -0
  89. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.created.json +114 -0
  90. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.released.json +111 -0
  91. data/lib/stripe_mock/webhook_fixtures/subscription_schedule.updated.json +125 -0
  92. data/lib/stripe_mock/webhook_fixtures/tax_rate.created.json +32 -0
  93. data/lib/stripe_mock/webhook_fixtures/tax_rate.updated.json +37 -0
  94. data/lib/stripe_mock.rb +3 -1
  95. data/spec/instance_spec.rb +3 -1
  96. data/spec/integration_examples/completing_checkout_sessions_example.rb +37 -0
  97. data/spec/readme_spec.rb +1 -1
  98. data/spec/shared_stripe_examples/account_examples.rb +9 -1
  99. data/spec/shared_stripe_examples/checkout_session_examples.rb +99 -0
  100. data/spec/shared_stripe_examples/invoice_examples.rb +21 -0
  101. data/spec/shared_stripe_examples/payment_intent_examples.rb +74 -0
  102. data/spec/shared_stripe_examples/payment_method_examples.rb +32 -27
  103. data/spec/shared_stripe_examples/price_examples.rb +42 -2
  104. data/spec/shared_stripe_examples/promotion_code_examples.rb +68 -0
  105. data/spec/shared_stripe_examples/refund_examples.rb +13 -0
  106. data/spec/shared_stripe_examples/setup_intent_examples.rb +17 -0
  107. data/spec/shared_stripe_examples/subscription_examples.rb +213 -2
  108. data/spec/shared_stripe_examples/transfer_examples.rb +10 -1
  109. data/spec/shared_stripe_examples/webhook_event_examples.rb +51 -5
  110. data/spec/support/stripe_examples.rb +3 -1
  111. data/stripe-ruby-mock.gemspec +1 -1
  112. metadata +54 -11
  113. data/.travis.yml +0 -25
  114. data/lib/stripe_mock/request_handlers/checkout.rb +0 -15
  115. data/spec/shared_stripe_examples/checkout_examples.rb +0 -47
@@ -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
@@ -7,6 +7,7 @@ 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,11 +13,15 @@ module StripeMock
13
13
  subscription[:items][:data] = plans.map do |plan|
14
14
  matching_item = items && items.detect { |item| [item[:price], item[:plan]].include? plan[:id] }
15
15
  if matching_item
16
- quantity = matching_item[:quantity] || 1
17
- id = matching_item[:id] || new_id('si')
18
- Data.mock_subscription_item({ plan: plan, quantity: quantity, id: id })
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)
19
21
  else
20
- Data.mock_subscription_item({ plan: plan, id: new_id('si') })
22
+ params = { plan: plan, id: new_id('si') }
23
+ params[:price] = plan if plan[:object] == "price"
24
+ Data.mock_subscription_item(params)
21
25
  end
22
26
  end
23
27
  subscription
@@ -46,10 +50,10 @@ module StripeMock
46
50
 
47
51
  if (((plan && plan[:trial_period_days]) || 0) == 0 && options[:trial_end].nil?) || options[:trial_end] == "now"
48
52
  end_time = options[:billing_cycle_anchor] || get_ending_time(start_time, plan)
49
- 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})
50
54
  else
51
55
  end_time = options[:trial_end] || (Time.now.utc.to_i + plan[:trial_period_days]*86400)
52
- 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})
53
57
  end
54
58
 
55
59
  params
@@ -86,11 +90,13 @@ module StripeMock
86
90
  def get_ending_time(start_time, plan, intervals = 1)
87
91
  return start_time unless plan
88
92
 
89
- 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
90
96
  when "week"
91
- start_time + (604800 * (plan[:interval_count] || 1) * intervals)
97
+ start_time + (604800 * (interval_count) * intervals)
92
98
  when "month"
93
- (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
94
100
  when "year"
95
101
  (Time.at(start_time).to_datetime >> (12 * intervals)).to_time.to_i # max period is 1 year
96
102
  else
@@ -119,6 +125,19 @@ module StripeMock
119
125
  end
120
126
  total
121
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
122
141
  end
123
142
  end
124
143
  end
@@ -53,7 +53,12 @@ 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
64
  def upcoming_invoice(route, method_url, params, headers = {})
@@ -81,6 +81,10 @@ module StripeMock
81
81
  route =~ method_url
82
82
  payment_intent = assert_existence :payment_intent, $1, payment_intents[$1]
83
83
 
84
+ if params[:payment_method]
85
+ payment_intent[:payment_method] = params[:payment_method]
86
+ end
87
+
84
88
  succeeded_payment_intent(payment_intent)
85
89
  end
86
90
 
@@ -169,12 +173,19 @@ module StripeMock
169
173
  payment_intent[:status] = 'succeeded'
170
174
  btxn = new_balance_transaction('txn', { source: payment_intent[:id] })
171
175
 
172
- payment_intent[:charges][:data] << Data.mock_charge(
176
+ charge_id = new_id('ch')
177
+
178
+ charges[charge_id] = Data.mock_charge(
179
+ id: charge_id,
173
180
  balance_transaction: btxn,
181
+ payment_intent: payment_intent[:id],
174
182
  amount: payment_intent[:amount],
175
- currency: payment_intent[:currency]
183
+ currency: payment_intent[:currency],
184
+ payment_method: payment_intent[:payment_method]
176
185
  )
177
186
 
187
+ payment_intent[:charges][:data] << charges[charge_id].clone
188
+
178
189
  payment_intent
179
190
  end
180
191
  end
@@ -78,7 +78,11 @@ module StripeMock
78
78
 
79
79
  # post /v1/payment_methods/:id
80
80
  def update_payment_method(route, method_url, params, headers)
81
- allowed_params = [:billing_details, :card, :ideal, :sepa_debit, :metadata]
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
82
86
 
83
87
  id = method_url.match(route)[1]
84
88
 
@@ -11,6 +11,12 @@ module StripeMock
11
11
 
12
12
  def new_price(route, method_url, params, headers)
13
13
  params[:id] ||= new_id('price')
14
+
15
+ if params[:product_data]
16
+ params[:product] = create_product(nil, nil, params[:product_data], nil)[:id] unless params[:product]
17
+ params.delete(:product_data)
18
+ end
19
+
14
20
  validate_create_price_params(params)
15
21
  prices[ params[:id] ] = Data.mock_price(params)
16
22
  end
@@ -37,6 +43,18 @@ module StripeMock
37
43
  end
38
44
  end
39
45
 
46
+ if params.key?(:currency)
47
+ price_data.select! do |price|
48
+ params[:currency] == price[:currency]
49
+ end
50
+ end
51
+
52
+ if params.key?(:product)
53
+ price_data.select! do |price|
54
+ params[:product] == price[:product]
55
+ end
56
+ end
57
+
40
58
  Data.mock_list_object(price_data.first(limit), params.merge!(limit: limit))
41
59
  end
42
60
  end
@@ -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']
@@ -15,20 +15,22 @@ module StripeMock
15
15
  ]
16
16
 
17
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
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
24
  end
25
25
 
26
26
  def new_setup_intent(route, method_url, params, headers)
27
27
  id = new_id('si')
28
+ status = params[:payment_method] ? 'requires_action' : 'requires_payment_method'
28
29
 
29
30
  setup_intents[id] = Data.mock_setup_intent(
30
31
  params.merge(
31
- id: id
32
+ id: id,
33
+ status: status
32
34
  )
33
35
  )
34
36
 
@@ -40,7 +42,7 @@ module StripeMock
40
42
  id = $1
41
43
 
42
44
  setup_intent = assert_existence :setup_intent, id, setup_intents[id]
43
- setup_intents[id] = Util.rmerge(setup_intent, params.select{ |k,v| ALLOWED_PARAMS.include?(k)})
45
+ setup_intents[id] = Util.rmerge(setup_intent, params.select { |k, v| ALLOWED_PARAMS.include?(k) })
44
46
  end
45
47
 
46
48
  def get_setup_intents(route, method_url, params, headers)
@@ -50,7 +52,7 @@ module StripeMock
50
52
  clone = setup_intents.clone
51
53
 
52
54
  if params[:customer]
53
- clone.delete_if { |k,v| v[:customer] != params[:customer] }
55
+ clone.delete_if { |k, v| v[:customer] != params[:customer] }
54
56
  end
55
57
 
56
58
  Data.mock_list_object(clone.values, params)
@@ -62,6 +64,11 @@ module StripeMock
62
64
  setup_intent = assert_existence :setup_intent, setup_intent_id, setup_intents[setup_intent_id]
63
65
 
64
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
+
65
72
  setup_intent
66
73
  end
67
74
 
@@ -69,6 +69,16 @@ module StripeMock
69
69
  end
70
70
  end
71
71
 
72
+ if params[:promotion_code]
73
+ promotion_code_id = params[:promotion_code]
74
+
75
+ promotion_code = promotion_codes[promotion_code_id]
76
+
77
+ unless promotion_code
78
+ raise Stripe::InvalidRequestError.new("No such promotion code: #{promotion_code_id}", 'promotion_code', http_status: 400)
79
+ end
80
+ end
81
+
72
82
  subscriptions[subscription[:id]] = subscription
73
83
  add_subscription_to_customer(customer, subscription)
74
84
 
@@ -97,7 +107,7 @@ module StripeMock
97
107
  customer[:default_source] = new_card[:id]
98
108
  end
99
109
 
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)
110
+ 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)
101
111
  unknown_params = params.keys - allowed_params.map(&:to_sym)
102
112
  if unknown_params.length > 0
103
113
  raise Stripe::InvalidRequestError.new("Received unknown parameter: #{unknown_params.join}", unknown_params.first.to_s, http_status: 400)
@@ -113,6 +123,10 @@ module StripeMock
113
123
  # Note: needs updating for subscriptions with multiple plans
114
124
  verify_card_present(customer, subscription_plans.first, subscription, params)
115
125
 
126
+ if params[:coupon] && params[:promotion_code]
127
+ raise Stripe::InvalidRequestError.new("You may only specify one of these parameters: coupon, promotion_code", "coupon", http_status: 400)
128
+ end
129
+
116
130
  if params[:coupon]
117
131
  coupon_id = params[:coupon]
118
132
 
@@ -128,15 +142,50 @@ module StripeMock
128
142
  end
129
143
  end
130
144
 
145
+ if params[:promotion_code]
146
+ promotion_code_id = params[:promotion_code]
147
+
148
+ promotion_code = promotion_codes[promotion_code_id]
149
+
150
+ unless promotion_code
151
+ raise Stripe::InvalidRequestError.new("No such promotion code: #{promotion_code_id}", 'promotion_code', http_status: 400)
152
+ end
153
+ end
154
+
131
155
  if params[:trial_period_days]
132
156
  subscription[:status] = 'trialing'
133
157
  end
134
158
 
159
+ if params[:payment_behavior] == 'default_incomplete'
160
+ subscription[:status] = 'incomplete'
161
+ end
162
+
135
163
  if params[:cancel_at_period_end]
136
164
  subscription[:cancel_at_period_end] = true
137
165
  subscription[:canceled_at] = Time.now.utc.to_i
138
166
  end
139
167
 
168
+ if params[:transfer_data] && !params[:transfer_data].empty?
169
+ throw Stripe::InvalidRequestError.new(missing_param_message("transfer_data[destination]")) unless params[:transfer_data][:destination]
170
+ subscription[:transfer_data] = params[:transfer_data].dup
171
+ subscription[:transfer_data][:amount_percent] ||= 100
172
+ end
173
+
174
+ if (s = params[:expand]&.find { |s| s.start_with? 'latest_invoice' })
175
+ payment_intent = nil
176
+ unless subscription[:status] == 'trialing'
177
+ intent_status = subscription[:status] == 'incomplete' ? 'requires_payment_method' : 'succeeded'
178
+ intent = Data.mock_payment_intent({
179
+ status: intent_status,
180
+ amount: subscription[:plan][:amount],
181
+ currency: subscription[:plan][:currency]
182
+ })
183
+ payment_intent = s.include?('latest_invoice.payment_intent') ? intent : intent.id
184
+ end
185
+ invoice = Data.mock_invoice([], { payment_intent: payment_intent })
186
+ subscription[:latest_invoice] = invoice
187
+ end
188
+
140
189
  subscriptions[subscription[:id]] = subscription
141
190
  add_subscription_to_customer(customer, subscription)
142
191
 
@@ -153,15 +202,34 @@ module StripeMock
153
202
  # stripe_account = headers && headers[:stripe_account] || Stripe.api_key
154
203
  route =~ method_url
155
204
 
156
- Data.mock_list_object(subscriptions.values, params)
157
- #customer = assert_existence :customer, $1, customers[stripe_account][$1]
158
- #customer[:subscriptions]
205
+ subs = subscriptions.values
206
+
207
+ case params[:status]
208
+ when nil
209
+ subs = subs.filter {|subscription| subscription[:status] != "canceled"}
210
+ when "all"
211
+ # Include all subscriptions
212
+ else
213
+ subs = subs.filter {|subscription| subscription[:status] == params[:status]}
214
+ end
215
+ if params[:current_period_end]
216
+ subs = filter_by_timestamp(subs, field: :current_period_end, value: params[:current_period_end])
217
+ end
218
+ if params[:current_period_start]
219
+ subs = filter_by_timestamp(subs, field: :current_period_start, value: params[:current_period_start])
220
+ end
221
+
222
+ Data.mock_list_object(subs, params)
159
223
  end
160
224
 
161
225
  def update_subscription(route, method_url, params, headers)
162
226
  stripe_account = headers && headers[:stripe_account] || Stripe.api_key
163
227
  route =~ method_url
164
228
 
229
+ if params[:billing_cycle_anchor] == 'now'
230
+ params[:billing_cycle_anchor] = Time.now.utc.to_i
231
+ end
232
+
165
233
  subscription_id = $2 ? $2 : $1
166
234
  subscription = assert_existence :subscription, subscription_id, subscriptions[subscription_id]
167
235
  verify_active_status(subscription)
@@ -198,6 +266,26 @@ module StripeMock
198
266
  end
199
267
  end
200
268
 
269
+ if params[:promotion_code]
270
+ promotion_code_id = params[:promotion_code]
271
+
272
+ promotion_code = promotion_codes[promotion_code_id]
273
+
274
+ if promotion_code
275
+ # You can't apply a promotion code with amount restrictions on the Customer object or on a subscription
276
+ # update API call
277
+ if promotion_code[:restrictions][:minimum_amount]
278
+ raise Stripe::InvalidRequestError.new(
279
+ "This promotion code cannot be redeemed on a subcription update because it uses the `minimum_amount` restriction.",
280
+ "promotion_code",
281
+ http_status: 400
282
+ )
283
+ end
284
+ else
285
+ raise Stripe::InvalidRequestError.new("No such promotion code: #{promotion_code_id}", 'promotion_code', http_status: 400)
286
+ end
287
+ end
288
+
201
289
  if params[:trial_period_days]
202
290
  subscription[:status] = 'trialing'
203
291
  end
@@ -285,6 +373,7 @@ module StripeMock
285
373
  return if customer[:invoice_settings][:default_payment_method]
286
374
  return if customer[:trial_end]
287
375
  return if params[:trial_end]
376
+ return if params[:payment_behavior] == 'default_incomplete'
288
377
  return if subscription[:default_payment_method]
289
378
 
290
379
  plan_trial_period_days = plan[:trial_period_days] || 0
@@ -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)
@@ -108,13 +108,14 @@ module StripeMock
108
108
 
109
109
  def validate_create_price_params(params)
110
110
  price_id = params[:id].to_s
111
- product_id = params[:product]
112
111
 
113
- @base_strategy.create_price_params.keys.each do |attr_name|
114
- message = "Missing required param: #{attr_name}."
115
- raise Stripe::InvalidRequestError.new(message, attr_name) if params[attr_name].nil?
112
+ require_param(:currency) unless params[:currency]
113
+ unless params[:product] || params[:product_data]
114
+ raise Stripe::InvalidRequestError("Requires product or product_data")
116
115
  end
117
116
 
117
+ product_id = params[:product] || create_product(nil, nil, params[:product_data], nil).id
118
+
118
119
  if prices[price_id]
119
120
  message = already_exists_message(Stripe::Price)
120
121
  raise Stripe::InvalidRequestError.new(message, :id)