stripe-ruby-mock 2.5.6 → 3.1.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/.travis.yml +6 -9
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile +1 -0
  6. data/README.md +17 -11
  7. data/lib/stripe_mock.rb +11 -0
  8. data/lib/stripe_mock/api/client.rb +1 -1
  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 +7 -0
  12. data/lib/stripe_mock/client.rb +2 -1
  13. data/lib/stripe_mock/data.rb +323 -13
  14. data/lib/stripe_mock/data/list.rb +42 -9
  15. data/lib/stripe_mock/instance.rb +52 -5
  16. data/lib/stripe_mock/request_handlers/account_links.rb +15 -0
  17. data/lib/stripe_mock/request_handlers/balance_transactions.rb +2 -2
  18. data/lib/stripe_mock/request_handlers/charges.rb +13 -12
  19. data/lib/stripe_mock/request_handlers/checkout.rb +15 -0
  20. data/lib/stripe_mock/request_handlers/checkout_session.rb +16 -0
  21. data/lib/stripe_mock/request_handlers/customers.rb +35 -18
  22. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +1 -1
  23. data/lib/stripe_mock/request_handlers/express_login_links.rb +15 -0
  24. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +14 -10
  25. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +22 -7
  26. data/lib/stripe_mock/request_handlers/helpers/token_helpers.rb +1 -1
  27. data/lib/stripe_mock/request_handlers/invoices.rb +11 -5
  28. data/lib/stripe_mock/request_handlers/payment_intents.rb +182 -0
  29. data/lib/stripe_mock/request_handlers/payment_methods.rb +120 -0
  30. data/lib/stripe_mock/request_handlers/plans.rb +1 -1
  31. data/lib/stripe_mock/request_handlers/prices.rb +44 -0
  32. data/lib/stripe_mock/request_handlers/products.rb +44 -0
  33. data/lib/stripe_mock/request_handlers/refunds.rb +6 -3
  34. data/lib/stripe_mock/request_handlers/setup_intents.rb +93 -0
  35. data/lib/stripe_mock/request_handlers/sources.rb +12 -6
  36. data/lib/stripe_mock/request_handlers/subscription_items.rb +36 -0
  37. data/lib/stripe_mock/request_handlers/subscriptions.rb +82 -41
  38. data/lib/stripe_mock/request_handlers/tax_rates.rb +36 -0
  39. data/lib/stripe_mock/request_handlers/tokens.rb +8 -6
  40. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +130 -9
  41. data/lib/stripe_mock/test_strategies/base.rb +68 -8
  42. data/lib/stripe_mock/test_strategies/live.rb +23 -12
  43. data/lib/stripe_mock/test_strategies/mock.rb +6 -2
  44. data/lib/stripe_mock/version.rb +1 -1
  45. data/lib/stripe_mock/webhook_fixtures/balance.available.json +6 -0
  46. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_reinstated.json +88 -0
  47. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_withdrawn.json +88 -0
  48. data/lib/stripe_mock/webhook_fixtures/charge.failed.json +166 -38
  49. data/lib/stripe_mock/webhook_fixtures/customer.created.json +1 -0
  50. data/lib/stripe_mock/webhook_fixtures/customer.updated.json +1 -0
  51. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +2 -1
  52. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +1 -1
  53. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +2 -1
  54. data/lib/stripe_mock/webhook_fixtures/payment_intent.payment_failed.json +186 -0
  55. data/lib/stripe_mock/webhook_fixtures/payment_intent.succeeded.json +164 -0
  56. data/lib/stripe_mock/webhook_fixtures/product.created.json +34 -0
  57. data/lib/stripe_mock/webhook_fixtures/product.deleted.json +34 -0
  58. data/lib/stripe_mock/webhook_fixtures/product.updated.json +38 -0
  59. data/spec/instance_spec.rb +10 -12
  60. data/spec/list_spec.rb +38 -0
  61. data/spec/server_spec.rb +6 -3
  62. data/spec/shared_stripe_examples/account_examples.rb +1 -1
  63. data/spec/shared_stripe_examples/account_link_examples.rb +16 -0
  64. data/spec/shared_stripe_examples/balance_examples.rb +6 -0
  65. data/spec/shared_stripe_examples/balance_transaction_examples.rb +3 -3
  66. data/spec/shared_stripe_examples/bank_examples.rb +3 -3
  67. data/spec/shared_stripe_examples/card_examples.rb +4 -4
  68. data/spec/shared_stripe_examples/card_token_examples.rb +17 -21
  69. data/spec/shared_stripe_examples/charge_examples.rb +32 -36
  70. data/spec/shared_stripe_examples/checkout_examples.rb +38 -0
  71. data/spec/shared_stripe_examples/coupon_examples.rb +1 -1
  72. data/spec/shared_stripe_examples/customer_examples.rb +103 -53
  73. data/spec/shared_stripe_examples/dispute_examples.rb +2 -2
  74. data/spec/shared_stripe_examples/error_mock_examples.rb +8 -7
  75. data/spec/shared_stripe_examples/express_login_link_examples.rb +12 -0
  76. data/spec/shared_stripe_examples/external_account_examples.rb +3 -3
  77. data/spec/shared_stripe_examples/invoice_examples.rb +43 -41
  78. data/spec/shared_stripe_examples/invoice_item_examples.rb +1 -1
  79. data/spec/shared_stripe_examples/payment_intent_examples.rb +147 -0
  80. data/spec/shared_stripe_examples/payment_method_examples.rb +449 -0
  81. data/spec/shared_stripe_examples/payout_examples.rb +2 -2
  82. data/spec/shared_stripe_examples/plan_examples.rb +135 -77
  83. data/spec/shared_stripe_examples/price_examples.rb +183 -0
  84. data/spec/shared_stripe_examples/product_examples.rb +155 -0
  85. data/spec/shared_stripe_examples/refund_examples.rb +41 -31
  86. data/spec/shared_stripe_examples/setup_intent_examples.rb +68 -0
  87. data/spec/shared_stripe_examples/subscription_examples.rb +546 -295
  88. data/spec/shared_stripe_examples/subscription_items_examples.rb +76 -0
  89. data/spec/shared_stripe_examples/tax_rate_examples.rb +42 -0
  90. data/spec/shared_stripe_examples/transfer_examples.rb +9 -9
  91. data/spec/shared_stripe_examples/webhook_event_examples.rb +11 -11
  92. data/spec/spec_helper.rb +7 -4
  93. data/spec/stripe_mock_spec.rb +4 -4
  94. data/spec/support/shared_contexts/stripe_validator_spec.rb +8 -0
  95. data/spec/support/stripe_examples.rb +12 -2
  96. data/stripe-ruby-mock.gemspec +8 -3
  97. metadata +81 -32
@@ -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,44 @@
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/(.*)', :get_price
9
+ klass.add_handler 'get /v1/prices', :list_prices
10
+ end
11
+
12
+ def new_price(route, method_url, params, headers)
13
+ params[:id] ||= new_id('price')
14
+ validate_create_price_params(params)
15
+ prices[ params[:id] ] = Data.mock_price(params)
16
+ end
17
+
18
+ def update_price(route, method_url, params, headers)
19
+ route =~ method_url
20
+ assert_existence :price, $1, prices[$1]
21
+ prices[$1].merge!(params)
22
+ end
23
+
24
+ def get_price(route, method_url, params, headers)
25
+ route =~ method_url
26
+ assert_existence :price, $1, prices[$1]
27
+ end
28
+
29
+ def list_prices(route, method_url, params, headers)
30
+ limit = params[:limit] ? params[:limit] : 10
31
+ price_data = prices.values
32
+ validate_list_prices_params(params)
33
+
34
+ if params.key?(:lookup_keys)
35
+ price_data.select! do |price|
36
+ params[:lookup_keys].include?(price[:lookup_key])
37
+ end
38
+ end
39
+
40
+ Data.mock_list_object(price_data.first(limit), params.merge!(limit: limit))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module Products
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
10
+ end
11
+
12
+ def create_product(_route, _method_url, params, _headers)
13
+ params[:id] ||= new_id('prod')
14
+ validate_create_product_params(params)
15
+ products[params[:id]] = Data.mock_product(params)
16
+ end
17
+
18
+ def retrieve_product(route, method_url, _params, _headers)
19
+ id = method_url.match(route).captures.first
20
+ assert_existence :product, id, products[id]
21
+ end
22
+
23
+ def update_product(route, method_url, params, _headers)
24
+ id = method_url.match(route).captures.first
25
+ product = assert_existence :product, id, products[id]
26
+
27
+ product.merge!(params)
28
+ end
29
+
30
+ def list_products(_route, _method_url, params, _headers)
31
+ limit = params[:limit] || 10
32
+ Data.mock_list_object(products.values.take(limit), params)
33
+ end
34
+
35
+ def destroy_product(route, method_url, _params, _headers)
36
+ id = method_url.match(route).captures.first
37
+ assert_existence :product, id, products[id]
38
+
39
+ products.delete(id)
40
+ { id: id, object: 'product', deleted: true }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -10,9 +10,12 @@ module StripeMock
10
10
  end
11
11
 
12
12
  def new_refund(route, method_url, params, headers)
13
- if params[:idempotency_key] && refunds.any?
14
- original_refund = refunds.values.find { |c| c[:idempotency_key] == params[:idempotency_key]}
15
- return refunds[original_refund[:id]] if original_refund
13
+ if headers && headers[:idempotency_key]
14
+ params[:idempotency_key] = headers[:idempotency_key]
15
+ if refunds.any?
16
+ original_refund = refunds.values.find { |c| c[:idempotency_key] == headers[:idempotency_key]}
17
+ return refunds[original_refund[:id]] if original_refund
18
+ end
16
19
  end
17
20
 
18
21
  charge = assert_existence :charge, params[:charge], charges[params[:charge]]
@@ -0,0 +1,93 @@
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
+
29
+ setup_intents[id] = Data.mock_setup_intent(
30
+ params.merge(
31
+ id: id
32
+ )
33
+ )
34
+
35
+ setup_intents[id].clone
36
+ end
37
+
38
+ def update_setup_intent(route, method_url, params, headers)
39
+ route =~ method_url
40
+ id = $1
41
+
42
+ 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)})
44
+ end
45
+
46
+ def get_setup_intents(route, method_url, params, headers)
47
+ params[:offset] ||= 0
48
+ params[:limit] ||= 10
49
+
50
+ clone = setup_intents.clone
51
+
52
+ if params[:customer]
53
+ clone.delete_if { |k,v| v[:customer] != params[:customer] }
54
+ end
55
+
56
+ Data.mock_list_object(clone.values, params)
57
+ end
58
+
59
+ def get_setup_intent(route, method_url, params, headers)
60
+ route =~ method_url
61
+ setup_intent_id = $1 || params[:setup_intent]
62
+ setup_intent = assert_existence :setup_intent, setup_intent_id, setup_intents[setup_intent_id]
63
+
64
+ setup_intent = setup_intent.clone
65
+ setup_intent
66
+ end
67
+
68
+ def capture_setup_intent(route, method_url, params, headers)
69
+ route =~ method_url
70
+ setup_intent = assert_existence :setup_intent, $1, setup_intents[$1]
71
+
72
+ setup_intent[:status] = 'succeeded'
73
+ setup_intent
74
+ end
75
+
76
+ def confirm_setup_intent(route, method_url, params, headers)
77
+ route =~ method_url
78
+ setup_intent = assert_existence :setup_intent, $1, setup_intents[$1]
79
+
80
+ setup_intent[:status] = 'succeeded'
81
+ setup_intent
82
+ end
83
+
84
+ def cancel_setup_intent(route, method_url, params, headers)
85
+ route =~ method_url
86
+ setup_intent = assert_existence :setup_intent, $1, setup_intents[$1]
87
+
88
+ setup_intent[:status] = 'canceled'
89
+ setup_intent
90
+ end
91
+ end
92
+ end
93
+ end
@@ -12,30 +12,35 @@ module StripeMock
12
12
  end
13
13
 
14
14
  def create_source(route, method_url, params, headers)
15
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
15
16
  route =~ method_url
16
- add_source_to(:customer, $1, params, customers)
17
+ add_source_to(:customer, $1, params, customers[stripe_account])
17
18
  end
18
19
 
19
20
  def retrieve_sources(route, method_url, params, headers)
21
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
20
22
  route =~ method_url
21
- retrieve_object_cards(:customer, $1, customers)
23
+ retrieve_object_cards(:customer, $1, customers[stripe_account])
22
24
  end
23
25
 
24
26
  def retrieve_source(route, method_url, params, headers)
27
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
25
28
  route =~ method_url
26
- customer = assert_existence :customer, $1, customers[$1]
29
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
27
30
 
28
31
  assert_existence :card, $2, get_card(customer, $2)
29
32
  end
30
33
 
31
34
  def delete_source(route, method_url, params, headers)
35
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
32
36
  route =~ method_url
33
- delete_card_from(:customer, $1, $2, customers)
37
+ delete_card_from(:customer, $1, $2, customers[stripe_account])
34
38
  end
35
39
 
36
40
  def update_source(route, method_url, params, headers)
41
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
37
42
  route =~ method_url
38
- customer = assert_existence :customer, $1, customers[$1]
43
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
39
44
 
40
45
  card = assert_existence :card, $2, get_card(customer, $2)
41
46
  card.merge!(params)
@@ -43,8 +48,9 @@ module StripeMock
43
48
  end
44
49
 
45
50
  def verify_source(route, method_url, params, headers)
51
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
46
52
  route =~ method_url
47
- customer = assert_existence :customer, $1, customers[$1]
53
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
48
54
 
49
55
  bank_account = assert_existence :bank_account, $2, verify_bank_account(customer, $2)
50
56
  bank_account
@@ -0,0 +1,36 @@
1
+ module StripeMock
2
+ module RequestHandlers
3
+ module SubscriptionItems
4
+
5
+ def SubscriptionItems.included(klass)
6
+ klass.add_handler 'get /v1/subscription_items', :retrieve_subscription_items
7
+ klass.add_handler 'post /v1/subscription_items/([^/]*)', :update_subscription_item
8
+ klass.add_handler 'post /v1/subscription_items', :create_subscription_items
9
+ end
10
+
11
+ def retrieve_subscription_items(route, method_url, params, headers)
12
+ route =~ method_url
13
+
14
+ require_param(:subscription) unless params[:subscription]
15
+
16
+ Data.mock_list_object(subscriptions_items, params)
17
+ end
18
+
19
+ def create_subscription_items(route, method_url, params, headers)
20
+ params[:id] ||= new_id('si')
21
+
22
+ require_param(:subscription) unless params[:subscription]
23
+ require_param(:plan) unless params[:plan]
24
+
25
+ subscriptions_items[params[:id]] = Data.mock_subscription_item(params.merge(plan: plans[params[:plan]]))
26
+ end
27
+
28
+ def update_subscription_item(route, method_url, params, headers)
29
+ route =~ method_url
30
+
31
+ subscription_item = assert_existence :subscription_item, $1, subscriptions_items[$1]
32
+ subscription_item.merge!(params.merge(plan: plans[params[:plan]]))
33
+ end
34
+ end
35
+ end
36
+ end
@@ -17,26 +17,29 @@ module StripeMock
17
17
  end
18
18
 
19
19
  def retrieve_customer_subscription(route, method_url, params, headers)
20
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
20
21
  route =~ method_url
21
22
 
22
- customer = assert_existence :customer, $1, customers[$1]
23
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
23
24
  subscription = get_customer_subscription(customer, $2)
24
25
 
25
26
  assert_existence :subscription, $2, subscription
26
27
  end
27
28
 
28
29
  def retrieve_customer_subscriptions(route, method_url, params, headers)
30
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
29
31
  route =~ method_url
30
32
 
31
- customer = assert_existence :customer, $1, customers[$1]
33
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
32
34
  customer[:subscriptions]
33
35
  end
34
36
 
35
37
  def create_customer_subscription(route, method_url, params, headers)
38
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
36
39
  route =~ method_url
37
40
 
38
41
  subscription_plans = get_subscription_plans_from_params(params)
39
- customer = assert_existence :customer, $1, customers[$1]
42
+ customer = assert_existence :customer, $1, customers[stripe_account][$1]
40
43
 
41
44
  if params[:source]
42
45
  new_card = get_card_by_token(params.delete(:source))
@@ -60,7 +63,7 @@ module StripeMock
60
63
  coupon = coupons[coupon_id]
61
64
 
62
65
  if coupon
63
- subscription[:discount] = Stripe::Util.convert_to_stripe_object({ coupon: coupon }, {})
66
+ add_coupon_to_object(subscription, coupon)
64
67
  else
65
68
  raise Stripe::InvalidRequestError.new("No such coupon: #{coupon_id}", 'coupon', http_status: 400)
66
69
  end
@@ -73,21 +76,20 @@ module StripeMock
73
76
  end
74
77
 
75
78
  def create_subscription(route, method_url, params, headers)
79
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
80
+ if headers && headers[:idempotency_key]
81
+ if subscriptions.any?
82
+ original_subscription = subscriptions.values.find { |c| c[:idempotency_key] == headers[:idempotency_key]}
83
+ return subscriptions[original_subscription[:id]] if original_subscription
84
+ end
85
+ end
76
86
  route =~ method_url
77
87
 
78
88
  subscription_plans = get_subscription_plans_from_params(params)
79
89
 
80
90
  customer = params[:customer]
81
91
  customer_id = customer.is_a?(Stripe::Customer) ? customer[:id] : customer.to_s
82
- customer = assert_existence :customer, customer_id, customers[customer_id]
83
-
84
- if subscription_plans && customer
85
- subscription_plans.each do |plan|
86
- unless customer[:currency].to_s == plan[:currency].to_s
87
- raise Stripe::InvalidRequestError.new("Customer's currency of #{customer[:currency]} does not match plan's currency of #{plan[:currency]}", 'currency', http_status: 400)
88
- end
89
- end
90
- end
92
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
91
93
 
92
94
  if params[:source]
93
95
  new_card = get_card_by_token(params.delete(:source))
@@ -95,7 +97,7 @@ module StripeMock
95
97
  customer[:default_source] = new_card[:id]
96
98
  end
97
99
 
98
- 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)
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)
99
101
  unknown_params = params.keys - allowed_params.map(&:to_sym)
100
102
  if unknown_params.length > 0
101
103
  raise Stripe::InvalidRequestError.new("Received unknown parameter: #{unknown_params.join}", unknown_params.first.to_s, http_status: 400)
@@ -103,6 +105,9 @@ module StripeMock
103
105
 
104
106
  subscription = Data.mock_subscription({ id: (params[:id] || new_id('su')) })
105
107
  subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params)
108
+ if headers[:idempotency_key]
109
+ subscription[:idempotency_key] = headers[:idempotency_key]
110
+ end
106
111
 
107
112
  # Ensure customer has card to charge if plan has no trial and is not free
108
113
  # Note: needs updating for subscriptions with multiple plans
@@ -117,12 +122,21 @@ module StripeMock
117
122
  coupon = coupons[coupon_id]
118
123
 
119
124
  if coupon
120
- subscription[:discount] = Stripe::Util.convert_to_stripe_object({ coupon: coupon }, {})
125
+ add_coupon_to_object(subscription, coupon)
121
126
  else
122
127
  raise Stripe::InvalidRequestError.new("No such coupon: #{coupon_id}", 'coupon', http_status: 400)
123
128
  end
124
129
  end
125
130
 
131
+ if params[:trial_period_days]
132
+ subscription[:status] = 'trialing'
133
+ end
134
+
135
+ if params[:cancel_at_period_end]
136
+ subscription[:cancel_at_period_end] = true
137
+ subscription[:canceled_at] = Time.now.utc.to_i
138
+ end
139
+
126
140
  subscriptions[subscription[:id]] = subscription
127
141
  add_subscription_to_customer(customer, subscription)
128
142
 
@@ -136,14 +150,16 @@ module StripeMock
136
150
  end
137
151
 
138
152
  def retrieve_subscriptions(route, method_url, params, headers)
153
+ # stripe_account = headers && headers[:stripe_account] || Stripe.api_key
139
154
  route =~ method_url
140
155
 
141
156
  Data.mock_list_object(subscriptions.values, params)
142
- #customer = assert_existence :customer, $1, customers[$1]
157
+ #customer = assert_existence :customer, $1, customers[stripe_account][$1]
143
158
  #customer[:subscriptions]
144
159
  end
145
160
 
146
161
  def update_subscription(route, method_url, params, headers)
162
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
147
163
  route =~ method_url
148
164
 
149
165
  subscription_id = $2 ? $2 : $1
@@ -151,7 +167,7 @@ module StripeMock
151
167
  verify_active_status(subscription)
152
168
 
153
169
  customer_id = subscription[:customer]
154
- customer = assert_existence :customer, customer_id, customers[customer_id]
170
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
155
171
 
156
172
  if params[:source]
157
173
  new_card = get_card_by_token(params.delete(:source))
@@ -174,23 +190,35 @@ module StripeMock
174
190
 
175
191
  coupon = coupons[coupon_id]
176
192
  if coupon
177
- subscription[:discount] = Stripe::Util.convert_to_stripe_object({ coupon: coupon }, {})
193
+ add_coupon_to_object(subscription, coupon)
178
194
  elsif coupon_id == ""
179
- subscription[:discount] = Stripe::Util.convert_to_stripe_object(nil, {})
195
+ subscription[:discount] = nil
180
196
  else
181
197
  raise Stripe::InvalidRequestError.new("No such coupon: #{coupon_id}", 'coupon', http_status: 400)
182
198
  end
183
199
  end
184
- verify_card_present(customer, subscription_plans.first, subscription)
185
200
 
186
- if subscription[:cancel_at_period_end]
201
+ if params[:trial_period_days]
202
+ subscription[:status] = 'trialing'
203
+ end
204
+
205
+ if params[:cancel_at_period_end]
206
+ subscription[:cancel_at_period_end] = true
207
+ subscription[:canceled_at] = Time.now.utc.to_i
208
+ elsif params.has_key?(:cancel_at_period_end)
187
209
  subscription[:cancel_at_period_end] = false
188
210
  subscription[:canceled_at] = nil
189
211
  end
190
212
 
191
213
  params[:current_period_start] = subscription[:current_period_start]
214
+ params[:trial_end] = params[:trial_end] || subscription[:trial_end]
215
+
216
+ plan_amount_was = subscription.dig(:plan, :amount)
217
+
192
218
  subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params)
193
219
 
220
+ verify_card_present(customer, subscription_plans.first, subscription, params) if plan_amount_was == 0 && subscription.dig(:plan, :amount) && subscription.dig(:plan, :amount) > 0
221
+
194
222
  # delete the old subscription, replace with the new subscription
195
223
  customer[:subscriptions][:data].reject! { |sub| sub[:id] == subscription[:id] }
196
224
  customer[:subscriptions][:data] << subscription
@@ -199,13 +227,14 @@ module StripeMock
199
227
  end
200
228
 
201
229
  def cancel_subscription(route, method_url, params, headers)
230
+ stripe_account = headers && headers[:stripe_account] || Stripe.api_key
202
231
  route =~ method_url
203
232
 
204
233
  subscription_id = $2 ? $2 : $1
205
234
  subscription = assert_existence :subscription, subscription_id, subscriptions[subscription_id]
206
235
 
207
236
  customer_id = subscription[:customer]
208
- customer = assert_existence :customer, customer_id, customers[customer_id]
237
+ customer = assert_existence :customer, customer_id, customers[stripe_account][customer_id]
209
238
 
210
239
  cancel_params = { canceled_at: Time.now.utc.to_i }
211
240
  cancelled_at_period_end = (params[:at_period_end] == true)
@@ -234,36 +263,48 @@ module StripeMock
234
263
  elsif params[:items]
235
264
  items = params[:items]
236
265
  items = items.values if items.respond_to?(:values)
237
- items.map { |item| item[:plan].to_s if item[:plan] }
266
+ items.map { |item| item[:plan] ? item[:plan] : item[:price] }
238
267
  else
239
268
  []
240
269
  end
270
+ plan_ids.compact!
241
271
  plan_ids.each do |plan_id|
242
272
  assert_existence :plan, plan_id, plans[plan_id]
273
+ rescue Stripe::InvalidRequestError
274
+ assert_existence :price, plan_id, prices[plan_id]
243
275
  end
244
- plan_ids.map { |plan_id| plans[plan_id] }
276
+ plan_ids.map { |plan_id| plans[plan_id] || prices[plan_id]}
245
277
  end
246
278
 
279
+ # Ensure customer has card to charge unless one of the following criterias is met:
280
+ # 1) is in trial
281
+ # 2) is free
282
+ # 3) has billing set to send invoice
247
283
  def verify_card_present(customer, plan, subscription, params={})
248
- if customer[:default_source].nil? && customer[:trial_end].nil? &&
249
- (plan.nil? ||
250
- ((plan[:trial_period_days] || 0) == 0 &&
251
- plan[:amount] != 0 &&
252
- plan[:trial_end].nil?)) &&
253
- params[:trial_end].nil? &&
254
- (subscription.nil? || subscription[:trial_end].nil? || subscription[:trial_end] == 'now')
255
-
256
- if subscription[:items]
257
- trial = subscription[:items][:data].none? do |item|
258
- plan = item[:plan]
259
- (plan[:trial_period_days].nil? || plan[:trial_period_days] == 0) &&
260
- (plan[:trial_end].nil? || plan[:trial_end] == 'now')
261
- end
262
- return if trial
284
+ return if customer[:default_source]
285
+ return if customer[:invoice_settings][:default_payment_method]
286
+ return if customer[:trial_end]
287
+ return if params[:trial_end]
288
+ return if subscription[:default_payment_method]
289
+
290
+ plan_trial_period_days = plan[:trial_period_days] || 0
291
+ plan_has_trial = plan_trial_period_days != 0 || plan[:amount] == 0 || plan[:trial_end]
292
+ return if plan && plan_has_trial
293
+
294
+ return if subscription && subscription[:trial_end] && subscription[:trial_end] != 'now'
295
+
296
+ if subscription[:items]
297
+ trial = subscription[:items][:data].none? do |item|
298
+ plan = item[:plan]
299
+ (plan[:trial_period_days].nil? || plan[:trial_period_days] == 0) &&
300
+ (plan[:trial_end].nil? || plan[:trial_end] == 'now')
263
301
  end
264
-
265
- raise Stripe::InvalidRequestError.new('You must supply a valid card xoxo', nil, http_status: 400)
302
+ return if trial
266
303
  end
304
+
305
+ return if params[:billing] == 'send_invoice'
306
+
307
+ raise Stripe::InvalidRequestError.new('This customer has no attached payment source', nil, http_status: 400)
267
308
  end
268
309
 
269
310
  def verify_active_status(subscription)