epopia-stripe-ruby-mock 2.5.8

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 (171) hide show
  1. checksums.yaml +7 -0
  2. data/.env +2 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +28 -0
  6. data/Gemfile +12 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +413 -0
  9. data/Rakefile +14 -0
  10. data/bin/stripe-mock-server +19 -0
  11. data/lib/stripe_mock.rb +95 -0
  12. data/lib/stripe_mock/api/account_balance.rb +14 -0
  13. data/lib/stripe_mock/api/bank_tokens.rb +13 -0
  14. data/lib/stripe_mock/api/card_tokens.rb +13 -0
  15. data/lib/stripe_mock/api/client.rb +41 -0
  16. data/lib/stripe_mock/api/conversion_rate.rb +14 -0
  17. data/lib/stripe_mock/api/debug.rb +11 -0
  18. data/lib/stripe_mock/api/errors.rb +65 -0
  19. data/lib/stripe_mock/api/global_id_prefix.rb +22 -0
  20. data/lib/stripe_mock/api/instance.rb +38 -0
  21. data/lib/stripe_mock/api/live.rb +15 -0
  22. data/lib/stripe_mock/api/server.rb +39 -0
  23. data/lib/stripe_mock/api/test_helpers.rb +24 -0
  24. data/lib/stripe_mock/api/webhooks.rb +88 -0
  25. data/lib/stripe_mock/client.rb +127 -0
  26. data/lib/stripe_mock/data.rb +1193 -0
  27. data/lib/stripe_mock/data/list.rb +73 -0
  28. data/lib/stripe_mock/error_queue.rb +27 -0
  29. data/lib/stripe_mock/errors/closed_client_connection_error.rb +9 -0
  30. data/lib/stripe_mock/errors/server_timeout_error.rb +12 -0
  31. data/lib/stripe_mock/errors/stripe_mock_error.rb +15 -0
  32. data/lib/stripe_mock/errors/uninitialized_instance_error.rb +9 -0
  33. data/lib/stripe_mock/errors/unstarted_state_error.rb +9 -0
  34. data/lib/stripe_mock/errors/unsupported_request_error.rb +4 -0
  35. data/lib/stripe_mock/instance.rb +237 -0
  36. data/lib/stripe_mock/request_handlers/accounts.rb +86 -0
  37. data/lib/stripe_mock/request_handlers/balance.rb +17 -0
  38. data/lib/stripe_mock/request_handlers/balance_transactions.rb +37 -0
  39. data/lib/stripe_mock/request_handlers/cards.rb +35 -0
  40. data/lib/stripe_mock/request_handlers/charges.rb +177 -0
  41. data/lib/stripe_mock/request_handlers/country_spec.rb +22 -0
  42. data/lib/stripe_mock/request_handlers/coupons.rb +35 -0
  43. data/lib/stripe_mock/request_handlers/customers.rb +137 -0
  44. data/lib/stripe_mock/request_handlers/disputes.rb +35 -0
  45. data/lib/stripe_mock/request_handlers/ephemeral_key.rb +13 -0
  46. data/lib/stripe_mock/request_handlers/events.rb +21 -0
  47. data/lib/stripe_mock/request_handlers/external_accounts.rb +55 -0
  48. data/lib/stripe_mock/request_handlers/helpers/bank_account_helpers.rb +14 -0
  49. data/lib/stripe_mock/request_handlers/helpers/card_helpers.rb +127 -0
  50. data/lib/stripe_mock/request_handlers/helpers/charge_helpers.rb +16 -0
  51. data/lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb +17 -0
  52. data/lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb +49 -0
  53. data/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +119 -0
  54. data/lib/stripe_mock/request_handlers/helpers/token_helpers.rb +44 -0
  55. data/lib/stripe_mock/request_handlers/invoice_items.rb +45 -0
  56. data/lib/stripe_mock/request_handlers/invoices.rb +177 -0
  57. data/lib/stripe_mock/request_handlers/orders.rb +80 -0
  58. data/lib/stripe_mock/request_handlers/payment_intents.rb +203 -0
  59. data/lib/stripe_mock/request_handlers/payment_methods.rb +112 -0
  60. data/lib/stripe_mock/request_handlers/payouts.rb +32 -0
  61. data/lib/stripe_mock/request_handlers/plans.rb +42 -0
  62. data/lib/stripe_mock/request_handlers/products.rb +43 -0
  63. data/lib/stripe_mock/request_handlers/recipients.rb +60 -0
  64. data/lib/stripe_mock/request_handlers/refunds.rb +91 -0
  65. data/lib/stripe_mock/request_handlers/sources.rb +55 -0
  66. data/lib/stripe_mock/request_handlers/subscription_items.rb +36 -0
  67. data/lib/stripe_mock/request_handlers/subscriptions.rb +296 -0
  68. data/lib/stripe_mock/request_handlers/tax_rates.rb +36 -0
  69. data/lib/stripe_mock/request_handlers/tokens.rb +75 -0
  70. data/lib/stripe_mock/request_handlers/transfers.rb +65 -0
  71. data/lib/stripe_mock/request_handlers/validators/param_validators.rb +32 -0
  72. data/lib/stripe_mock/server.rb +93 -0
  73. data/lib/stripe_mock/test_strategies/base.rb +81 -0
  74. data/lib/stripe_mock/test_strategies/live.rb +40 -0
  75. data/lib/stripe_mock/test_strategies/mock.rb +27 -0
  76. data/lib/stripe_mock/util.rb +44 -0
  77. data/lib/stripe_mock/version.rb +4 -0
  78. data/lib/stripe_mock/webhook_fixtures/account.application.deauthorized.json +12 -0
  79. data/lib/stripe_mock/webhook_fixtures/account.external_account.created.json +27 -0
  80. data/lib/stripe_mock/webhook_fixtures/account.external_account.deleted.json +27 -0
  81. data/lib/stripe_mock/webhook_fixtures/account.external_account.updated.json +27 -0
  82. data/lib/stripe_mock/webhook_fixtures/account.updated.json +26 -0
  83. data/lib/stripe_mock/webhook_fixtures/balance.available.json +25 -0
  84. data/lib/stripe_mock/webhook_fixtures/charge.dispute.closed.json +22 -0
  85. data/lib/stripe_mock/webhook_fixtures/charge.dispute.created.json +22 -0
  86. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_reinstated.json +88 -0
  87. data/lib/stripe_mock/webhook_fixtures/charge.dispute.funds_withdrawn.json +88 -0
  88. data/lib/stripe_mock/webhook_fixtures/charge.dispute.updated.json +25 -0
  89. data/lib/stripe_mock/webhook_fixtures/charge.failed.json +56 -0
  90. data/lib/stripe_mock/webhook_fixtures/charge.refunded.json +69 -0
  91. data/lib/stripe_mock/webhook_fixtures/charge.succeeded.json +55 -0
  92. data/lib/stripe_mock/webhook_fixtures/charge.updated.json +58 -0
  93. data/lib/stripe_mock/webhook_fixtures/coupon.created.json +23 -0
  94. data/lib/stripe_mock/webhook_fixtures/coupon.deleted.json +23 -0
  95. data/lib/stripe_mock/webhook_fixtures/customer.created.json +54 -0
  96. data/lib/stripe_mock/webhook_fixtures/customer.deleted.json +42 -0
  97. data/lib/stripe_mock/webhook_fixtures/customer.discount.created.json +28 -0
  98. data/lib/stripe_mock/webhook_fixtures/customer.discount.deleted.json +28 -0
  99. data/lib/stripe_mock/webhook_fixtures/customer.discount.updated.json +43 -0
  100. data/lib/stripe_mock/webhook_fixtures/customer.source.created.json +32 -0
  101. data/lib/stripe_mock/webhook_fixtures/customer.source.deleted.json +32 -0
  102. data/lib/stripe_mock/webhook_fixtures/customer.source.updated.json +36 -0
  103. data/lib/stripe_mock/webhook_fixtures/customer.subscription.created.json +66 -0
  104. data/lib/stripe_mock/webhook_fixtures/customer.subscription.deleted.json +65 -0
  105. data/lib/stripe_mock/webhook_fixtures/customer.subscription.trial_will_end.json +65 -0
  106. data/lib/stripe_mock/webhook_fixtures/customer.subscription.updated.json +78 -0
  107. data/lib/stripe_mock/webhook_fixtures/customer.updated.json +57 -0
  108. data/lib/stripe_mock/webhook_fixtures/invoice.created.json +71 -0
  109. data/lib/stripe_mock/webhook_fixtures/invoice.payment_failed.json +105 -0
  110. data/lib/stripe_mock/webhook_fixtures/invoice.payment_succeeded.json +112 -0
  111. data/lib/stripe_mock/webhook_fixtures/invoice.updated.json +74 -0
  112. data/lib/stripe_mock/webhook_fixtures/invoiceitem.created.json +21 -0
  113. data/lib/stripe_mock/webhook_fixtures/invoiceitem.deleted.json +21 -0
  114. data/lib/stripe_mock/webhook_fixtures/invoiceitem.updated.json +24 -0
  115. data/lib/stripe_mock/webhook_fixtures/plan.created.json +20 -0
  116. data/lib/stripe_mock/webhook_fixtures/plan.deleted.json +20 -0
  117. data/lib/stripe_mock/webhook_fixtures/plan.updated.json +23 -0
  118. data/lib/stripe_mock/webhook_fixtures/transfer.created.json +89 -0
  119. data/lib/stripe_mock/webhook_fixtures/transfer.failed.json +89 -0
  120. data/lib/stripe_mock/webhook_fixtures/transfer.paid.json +89 -0
  121. data/lib/stripe_mock/webhook_fixtures/transfer.updated.json +92 -0
  122. data/lib/trollop.rb +782 -0
  123. data/spec/_dummy/webhooks/dummy.event.json +6 -0
  124. data/spec/api/instance_spec.rb +30 -0
  125. data/spec/fixtures/create_refund.yml +126 -0
  126. data/spec/fixtures/stripe_webhooks/account.updated.json +7 -0
  127. data/spec/fixtures/stripe_webhooks/custom.account.updated.json +5 -0
  128. data/spec/instance_spec.rb +100 -0
  129. data/spec/integration_examples/charge_token_examples.rb +51 -0
  130. data/spec/integration_examples/customer_card_examples.rb +42 -0
  131. data/spec/integration_examples/prepare_error_examples.rb +38 -0
  132. data/spec/list_spec.rb +140 -0
  133. data/spec/readme_spec.rb +75 -0
  134. data/spec/server_spec.rb +139 -0
  135. data/spec/shared_stripe_examples/account_examples.rb +96 -0
  136. data/spec/shared_stripe_examples/balance_examples.rb +11 -0
  137. data/spec/shared_stripe_examples/balance_transaction_examples.rb +63 -0
  138. data/spec/shared_stripe_examples/bank_examples.rb +229 -0
  139. data/spec/shared_stripe_examples/bank_token_examples.rb +59 -0
  140. data/spec/shared_stripe_examples/card_examples.rb +307 -0
  141. data/spec/shared_stripe_examples/card_token_examples.rb +185 -0
  142. data/spec/shared_stripe_examples/charge_examples.rb +510 -0
  143. data/spec/shared_stripe_examples/country_specs_examples.rb +18 -0
  144. data/spec/shared_stripe_examples/coupon_examples.rb +85 -0
  145. data/spec/shared_stripe_examples/customer_examples.rb +453 -0
  146. data/spec/shared_stripe_examples/dispute_examples.rb +98 -0
  147. data/spec/shared_stripe_examples/ephemeral_key_examples.rb +17 -0
  148. data/spec/shared_stripe_examples/error_mock_examples.rb +162 -0
  149. data/spec/shared_stripe_examples/external_account_examples.rb +170 -0
  150. data/spec/shared_stripe_examples/extra_features_examples.rb +36 -0
  151. data/spec/shared_stripe_examples/invoice_examples.rb +524 -0
  152. data/spec/shared_stripe_examples/invoice_item_examples.rb +69 -0
  153. data/spec/shared_stripe_examples/payment_intent_examples.rb +131 -0
  154. data/spec/shared_stripe_examples/payment_method_examples.rb +175 -0
  155. data/spec/shared_stripe_examples/payout_examples.rb +68 -0
  156. data/spec/shared_stripe_examples/plan_examples.rb +194 -0
  157. data/spec/shared_stripe_examples/product_example.rb +65 -0
  158. data/spec/shared_stripe_examples/recipient_examples.rb +118 -0
  159. data/spec/shared_stripe_examples/refund_examples.rb +472 -0
  160. data/spec/shared_stripe_examples/subscription_examples.rb +1148 -0
  161. data/spec/shared_stripe_examples/subscription_items_examples.rb +75 -0
  162. data/spec/shared_stripe_examples/tax_rate_examples.rb +42 -0
  163. data/spec/shared_stripe_examples/transfer_examples.rb +130 -0
  164. data/spec/shared_stripe_examples/validation_examples.rb +19 -0
  165. data/spec/shared_stripe_examples/webhook_event_examples.rb +261 -0
  166. data/spec/spec_helper.rb +58 -0
  167. data/spec/stripe_mock_spec.rb +123 -0
  168. data/spec/support/stripe_examples.rb +42 -0
  169. data/spec/util_spec.rb +121 -0
  170. data/stripe-ruby-mock.gemspec +27 -0
  171. metadata +344 -0
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'Extra Features' do
4
+
5
+ it "can set the global id prefix" do
6
+ StripeMock.global_id_prefix = "custom_prefix_"
7
+ expect(StripeMock.global_id_prefix).to eq("custom_prefix_")
8
+
9
+ customer = Stripe::Customer.create
10
+ expect(customer.id).to match /^custom_prefix_cus/
11
+ StripeMock.global_id_prefix = nil
12
+ end
13
+
14
+ it "can set the global id prefix to nothing" do
15
+ StripeMock.global_id_prefix = ""
16
+ expect(StripeMock.global_id_prefix).to eq("")
17
+
18
+ customer = Stripe::Customer.create
19
+ expect(customer.id).to match /^cus/
20
+
21
+ # Support false
22
+ StripeMock.global_id_prefix = false
23
+ expect(StripeMock.global_id_prefix).to eq("")
24
+
25
+ customer = Stripe::Customer.create
26
+ expect(customer.id).to match /^cus/
27
+ StripeMock.global_id_prefix = nil
28
+ end
29
+
30
+ it "has a default global id prefix" do
31
+ StripeMock.global_id_prefix = nil
32
+ expect(StripeMock.global_id_prefix).to eq("test_")
33
+ customer = Stripe::Customer.create
34
+ expect(customer.id).to match /^test_cus/
35
+ end
36
+ end
@@ -0,0 +1,524 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'Invoice API' do
4
+
5
+ context "creating a new invoice" do
6
+ it "creates a stripe invoice" do
7
+ invoice = Stripe::Invoice.create
8
+ expect(invoice.id).to match(/^test_in/)
9
+ end
10
+
11
+ it "stores a created stripe invoice in memory" do
12
+ invoice = Stripe::Invoice.create
13
+ data = test_data_source(:invoices)
14
+ expect(data[invoice.id]).to_not be_nil
15
+ expect(data[invoice.id][:id]).to eq(invoice.id)
16
+ end
17
+ end
18
+
19
+ context "retrieving an invoice" do
20
+ it "retrieves a stripe invoice" do
21
+ original = Stripe::Invoice.create
22
+ invoice = Stripe::Invoice.retrieve(original.id)
23
+ expect(invoice.id).to eq(original.id)
24
+ end
25
+ end
26
+
27
+ context "updating an invoice" do
28
+ it "updates a stripe invoice" do
29
+ invoice = Stripe::Invoice.create(currency: "cad", statement_description: "orig-desc")
30
+ expect(invoice.currency).to eq("cad")
31
+ expect(invoice.statement_description).to eq("orig-desc")
32
+
33
+ invoice.currency = "usd"
34
+ invoice.statement_description = "new-desc"
35
+ invoice.save
36
+
37
+ invoice = Stripe::Invoice.retrieve(invoice.id)
38
+ expect(invoice.currency).to eq("usd")
39
+ expect(invoice.statement_description).to eq("new-desc")
40
+ end
41
+ end
42
+
43
+ context "retrieving a list of invoices" do
44
+ before do
45
+ @customer = Stripe::Customer.create(email: 'johnny@appleseed.com')
46
+ @invoice = Stripe::Invoice.create(customer: @customer.id)
47
+ @invoice2 = Stripe::Invoice.create
48
+ end
49
+
50
+ it "stores invoices for a customer in memory" do
51
+ expect(@customer.invoices.map(&:id)).to eq([@invoice.id])
52
+ end
53
+
54
+ it "stores all invoices in memory" do
55
+ expect(Stripe::Invoice.all.map(&:id).sort).to eq([@invoice.id, @invoice2.id].sort)
56
+ end
57
+
58
+ it "defaults count to 10 invoices" do
59
+ 11.times { Stripe::Invoice.create }
60
+ expect(Stripe::Invoice.all.count).to eq(10)
61
+ end
62
+
63
+ it "is marked as having more when more objects exist" do
64
+ 11.times { Stripe::Invoice.create }
65
+
66
+ expect(Stripe::Invoice.all.has_more).to eq(true)
67
+ end
68
+
69
+ context "when passing limit" do
70
+ it "gets that many invoices" do
71
+ expect(Stripe::Invoice.all(limit: 1).count).to eq(1)
72
+ end
73
+ end
74
+ end
75
+
76
+ context "paying an invoice" do
77
+ before do
78
+ @invoice = Stripe::Invoice.create
79
+ end
80
+
81
+ it 'updates attempted and paid flags' do
82
+ @invoice = @invoice.pay
83
+ expect(@invoice.attempted).to eq(true)
84
+ expect(@invoice.paid).to eq(true)
85
+ end
86
+
87
+ it 'creates a new charge object' do
88
+ expect{ @invoice.pay }.to change { Stripe::Charge.list.data.count }.by 1
89
+ end
90
+
91
+ it 'sets the charge attribute' do
92
+ @invoice = @invoice.pay
93
+ expect(@invoice.charge).to be_a String
94
+ expect(@invoice.charge.length).to be > 0
95
+ end
96
+
97
+ it 'charges the invoice customers default card' do
98
+ customer = Stripe::Customer.create({
99
+ source: stripe_helper.generate_card_token
100
+ })
101
+ customer_invoice = Stripe::Invoice.create({customer: customer})
102
+
103
+ customer_invoice.pay
104
+
105
+ expect(Stripe::Charge.list.data.first.customer).to eq customer.id
106
+ end
107
+ end
108
+
109
+ context "retrieving upcoming invoice" do
110
+ let(:customer) { Stripe::Customer.create(source: stripe_helper.generate_card_token) }
111
+ let(:coupon_amtoff) { stripe_helper.create_coupon(id: '100OFF', currency: 'usd', amount_off: 100_00, duration: 'repeating', duration_in_months: 6) }
112
+ let(:coupon_pctoff) { stripe_helper.create_coupon(id: '50%OFF', currency: 'usd', percent_off: 50, amount_off: nil, duration: 'repeating', duration_in_months: 6) }
113
+ let(:plan) { stripe_helper.create_plan(id: '50m', amount: 50_00, interval: 'month', name: '50m', currency: 'usd') }
114
+ let(:quantity) { 3 }
115
+ let(:subscription) { Stripe::Subscription.create(plan: plan.id, customer: customer.id, quantity: quantity) }
116
+
117
+ before(with_customer: true) { customer }
118
+ before(with_coupon_amtoff: true) { coupon_amtoff }
119
+ before(with_coupon_pctoff: true) { coupon_pctoff }
120
+ before(with_discount_amtoff: true) { customer.coupon = coupon_amtoff.id; customer.save }
121
+ before(with_discount_pctoff: true) { customer.coupon = coupon_pctoff.id; customer.save }
122
+ before(with_plan: true) { plan }
123
+ before(with_subscription: true) { subscription }
124
+
125
+ # after { subscription.delete rescue nil if @teardown_subscription }
126
+ # after { plan.delete rescue nil if @teardown_plan }
127
+ # after { coupon_pctoff.delete rescue nil if @teardown_coupon_pctoff }
128
+ # after { coupon_amtoff.delete rescue nil if @teardown_coupon_amtoff }
129
+ # after { customer.delete rescue nil if @teardown_customer }
130
+
131
+ describe 'parameter validation' do
132
+ it 'fails without parameters' do
133
+ expect { Stripe::Invoice.upcoming() }.to raise_error {|e|
134
+ expect(e).to be_a(ArgumentError) }
135
+ end
136
+
137
+ it 'fails without a valid customer' do
138
+ expect { Stripe::Invoice.upcoming(customer: 'whatever') }.to raise_error {|e|
139
+ expect(e).to be_a(Stripe::InvalidRequestError)
140
+ expect(e.message).to eq('No such customer: whatever') }
141
+ end
142
+
143
+ it 'fails without a customer parameter' do
144
+ expect { Stripe::Invoice.upcoming(gazebo: 'raindance') }.to raise_error {|e|
145
+ expect(e).to be_a(Stripe::InvalidRequestError)
146
+ expect(e.http_status).to eq(400)
147
+ expect(e.message).to eq('Missing required param: customer') }
148
+ end
149
+
150
+ it 'fails without a subscription' do
151
+ expect { Stripe::Invoice.upcoming(customer: customer.id) }.to raise_error {|e|
152
+ expect(e).to be_a(Stripe::InvalidRequestError)
153
+ expect(e.http_status).to eq(404)
154
+ expect(e.message).to eq("No upcoming invoices for customer: #{customer.id}") }
155
+ end
156
+ end
157
+
158
+ describe 'parameter validation' do
159
+ it 'fails without a subscription or subscription plan if subscription proration date is specified', live: true do
160
+ expect { Stripe::Invoice.upcoming(customer: customer.id, subscription_proration_date: Time.now.to_i) }.to raise_error do |e|
161
+ expect(e).to be_a Stripe::InvalidRequestError
162
+ expect(e.http_status).to eq 400
163
+ expect(e.message).to eq 'When previewing changes to a subscription, you must specify either `subscription` or `subscription_items`'
164
+ end
165
+ end
166
+
167
+ it 'fails without a subscription if proration date is specified', live: true, with_subscription: true do
168
+ expect { Stripe::Invoice.upcoming(customer: customer.id, subscription_plan: plan.id, subscription_proration_date: Time.now.to_i) }.to raise_error do |e|
169
+ expect(e).to be_a Stripe::InvalidRequestError
170
+ expect(e.http_status).to eq 400
171
+ expect(e.message).to eq 'Cannot specify proration date without specifying a subscription'
172
+ end
173
+ end
174
+ end
175
+
176
+ it 'considers current subscription', live: true, with_subscription: true do
177
+ # When
178
+ upcoming = Stripe::Invoice.upcoming(customer: customer.id)
179
+
180
+ # Then
181
+ expect(upcoming).to be_a Stripe::Invoice
182
+ expect(upcoming.customer).to eq(customer.id)
183
+ expect(upcoming.amount_due).to eq plan.amount * quantity
184
+ expect(upcoming.total).to eq(upcoming.lines.data[0].amount)
185
+ expect(upcoming.period_end).to eq(upcoming.lines.data[0].period.start)
186
+ expect(Time.at(upcoming.period_start).to_datetime >> 1).to eq(Time.at(upcoming.period_end).to_datetime) # +1 month
187
+ expect(Time.at(upcoming.period_start).to_datetime >> 2).to eq(Time.at(upcoming.lines.data[0].period.end).to_datetime) # +1 month
188
+ expect(upcoming.next_payment_attempt).to eq(upcoming.period_end + 3600) # +1 hour
189
+ expect(upcoming.subscription).to eq(subscription.id)
190
+ end
191
+
192
+ describe 'discounts' do
193
+ it 'considers a $ off discount', live: true, with_discount_amtoff: true, with_subscription: true do
194
+ # When
195
+ upcoming = Stripe::Invoice.upcoming(customer: customer.id)
196
+
197
+ # Then
198
+ expect(upcoming.discount).not_to be_nil
199
+ expect(upcoming.discount.coupon.id).to eq '100OFF'
200
+ expect(upcoming.discount.customer).to eq customer.id
201
+ expect(upcoming.discount.start).to be_within(5).of Time.now.to_i
202
+ expect(upcoming.discount.end).to be_within(5).of (Time.now.to_datetime >> 6).to_time.to_i
203
+ expect(upcoming.amount_due).to eq plan.amount * quantity - 100_00
204
+ expect(upcoming.subtotal).to eq(upcoming.lines.data[0].amount)
205
+ expect(upcoming.total).to eq upcoming.subtotal - 100_00
206
+ end
207
+
208
+ it 'considers a % off discount', live: true, with_discount_pctoff: true, with_subscription: true do
209
+ # When
210
+ upcoming = Stripe::Invoice.upcoming(customer: customer.id)
211
+
212
+ # Then
213
+ expect(upcoming.discount).not_to be_nil
214
+ expect(upcoming.discount.coupon.id).to eq '50%OFF'
215
+ expect(upcoming.discount.customer).to eq customer.id
216
+ expect(upcoming.discount.start).to be_within(5).of Time.now.to_i
217
+ expect(upcoming.discount.end).to be_within(5).of (Time.now.to_datetime >> 6).to_time.to_i
218
+ expect(upcoming.amount_due).to eq (plan.amount * quantity) * 0.5
219
+ expect(upcoming.subtotal).to eq(upcoming.lines.data[0].amount)
220
+ expect(upcoming.total).to eq upcoming.subtotal * 0.5
221
+ end
222
+ end
223
+
224
+ describe 'proration' do
225
+ shared_examples 'failing when proration date is outside of the subscription current period' do
226
+ it 'fails', live: true do
227
+ expect { Stripe::Invoice.upcoming(
228
+ customer: customer.id,
229
+ subscription: subscription.id,
230
+ subscription_plan: plan.id,
231
+ subscription_quantity: quantity,
232
+ subscription_proration_date: proration_date.to_i,
233
+ subscription_trial_end: nil
234
+ ) }.to raise_error {|e|
235
+ expect(e).to be_a(Stripe::InvalidRequestError)
236
+ expect(e.http_status).to eq(400)
237
+ expect(e.message).to eq('Cannot specify proration date outside of current subscription period') }
238
+ end
239
+ end
240
+
241
+ it_behaves_like 'failing when proration date is outside of the subscription current period' do
242
+ let(:proration_date) { subscription.current_period_start - 1 }
243
+ end
244
+
245
+ it_behaves_like 'failing when proration date is outside of the subscription current period' do
246
+ let(:proration_date) { subscription.current_period_end + 1 }
247
+ end
248
+
249
+ [false, true].each do |with_trial|
250
+ describe "prorating a subscription with a new plan, with_trial: #{with_trial}" do
251
+ let(:new_monthly_plan) { stripe_helper.create_plan(id: '100m', amount: 100_00, interval: 'month', name: '100m', currency: 'usd') }
252
+ let(:new_yearly_plan) { stripe_helper.create_plan(id: '100y', amount: 100_00, interval: 'year', name: '100y', currency: 'usd') }
253
+ let(:plan) { stripe_helper.create_plan(id: '50m', amount: 50_00, interval: 'month', name: '50m', currency: 'usd') }
254
+
255
+ it 'prorates while maintaining billing interval', live: true do
256
+ # Given
257
+ proration_date = Time.now + 5 * 24 * 3600 # 5 days later
258
+ new_quantity = 2
259
+ unused_amount = plan.amount * quantity * (subscription.current_period_end - proration_date.to_i) / (subscription.current_period_end - subscription.current_period_start)
260
+ remaining_amount = new_monthly_plan.amount * new_quantity * (subscription.current_period_end - proration_date.to_i) / (subscription.current_period_end - subscription.current_period_start)
261
+ prorated_amount_due = new_monthly_plan.amount * new_quantity - unused_amount + remaining_amount
262
+ credit_balance = 1000
263
+ customer.account_balance = -credit_balance
264
+ customer.save
265
+ query = { customer: customer.id, subscription: subscription.id, subscription_plan: new_monthly_plan.id, subscription_proration_date: proration_date.to_i, subscription_quantity: new_quantity }
266
+ query[:subscription_trial_end] = (DateTime.now >> 1).to_time.to_i if with_trial
267
+
268
+ # When
269
+ upcoming = Stripe::Invoice.upcoming(query)
270
+
271
+ # Then
272
+ expect(upcoming).to be_a Stripe::Invoice
273
+ expect(upcoming.customer).to eq(customer.id)
274
+ if with_trial
275
+ expect(upcoming.amount_due).to be_within(1).of 0
276
+ else
277
+ expect(upcoming.amount_due).to be_within(1).of prorated_amount_due - credit_balance
278
+ end
279
+ expect(upcoming.starting_balance).to eq -credit_balance
280
+ expect(upcoming.ending_balance).to be_nil
281
+ expect(upcoming.subscription).to eq(subscription.id)
282
+
283
+ if with_trial
284
+ expect(upcoming.lines.data.length).to eq(2)
285
+ else
286
+ expect(upcoming.lines.data.length).to eq(3)
287
+ end
288
+
289
+ expect(upcoming.lines.data[0].proration).to be_truthy
290
+ expect(upcoming.lines.data[0].plan.id).to eq '50m'
291
+ expect(upcoming.lines.data[0].amount).to be_within(1).of -unused_amount
292
+ expect(upcoming.lines.data[0].quantity).to eq quantity
293
+
294
+ unless with_trial
295
+ expect(upcoming.lines.data[1].proration).to be_truthy
296
+ expect(upcoming.lines.data[1].plan.id).to eq '100m'
297
+ expect(upcoming.lines.data[1].amount).to be_within(1).of remaining_amount
298
+ expect(upcoming.lines.data[1].quantity).to eq new_quantity
299
+ end
300
+
301
+ expect(upcoming.lines.data.last.proration).to be_falsey
302
+ expect(upcoming.lines.data.last.plan.id).to eq '100m'
303
+ expect(upcoming.lines.data.last.amount).to eq with_trial ? 0 : 20000
304
+ expect(upcoming.lines.data.last.quantity).to eq new_quantity
305
+ end
306
+
307
+ it 'prorates while changing billing intervals', live: true do
308
+ # Given
309
+ proration_date = Time.now + 5 * 24 * 3600 # 5 days later
310
+ new_quantity = 2
311
+ unused_amount = plan.amount * quantity * (subscription.current_period_end - proration_date.to_i) / (subscription.current_period_end - subscription.current_period_start)
312
+ prorated_amount_due = new_yearly_plan.amount * new_quantity - unused_amount
313
+ credit_balance = 1000
314
+ customer.account_balance = -credit_balance
315
+ customer.save
316
+ query = { customer: customer.id, subscription: subscription.id, subscription_plan: new_yearly_plan.id, subscription_proration_date: proration_date.to_i, subscription_quantity: new_quantity }
317
+ query[:subscription_trial_end] = (DateTime.now >> 1).to_time.to_i if with_trial
318
+
319
+ # When
320
+ upcoming = Stripe::Invoice.upcoming(query)
321
+
322
+ # Then
323
+ expect(upcoming).to be_a Stripe::Invoice
324
+ expect(upcoming.customer).to eq(customer.id)
325
+ if with_trial
326
+ expect(upcoming.amount_due).to eq 0
327
+ else
328
+ expect(upcoming.amount_due).to be_within(1).of prorated_amount_due - credit_balance
329
+ end
330
+ expect(upcoming.starting_balance).to eq -credit_balance
331
+ expect(upcoming.ending_balance).to be_nil
332
+ expect(upcoming.subscription).to eq(subscription.id)
333
+
334
+ expect(upcoming.lines.data[0].proration).to be_truthy
335
+ expect(upcoming.lines.data[0].plan.id).to eq '50m'
336
+ expect(upcoming.lines.data[0].amount).to be_within(1).of -unused_amount
337
+ expect(upcoming.lines.data[0].quantity).to eq quantity
338
+
339
+ expect(upcoming.lines.data[1].proration).to be_falsey
340
+ expect(upcoming.lines.data[1].plan.id).to eq '100y'
341
+ expect(upcoming.lines.data[1].amount).to eq with_trial ? 0 : 20000
342
+ expect(upcoming.lines.data[1].quantity).to eq new_quantity
343
+ end
344
+
345
+ # after { new_monthly_plan.delete rescue nil if @teardown_monthly_plan }
346
+ # after { new_yearly_plan.delete rescue nil if @teardown_yearly_plan }
347
+ end
348
+ end
349
+
350
+ shared_examples 'no proration is done' do
351
+ it 'generates a preview without performing an actual proration', live: true do
352
+ expect(preview.subtotal).to eq 150_00
353
+ # this is a future invoice (generted at the end of the current subscription cycle), rather than a proration invoice
354
+ expect(preview.created).to be_within(1).of subscription.current_period_end
355
+ expect(preview.period_start).to eq subscription.current_period_start
356
+ expect(preview.period_end).to eq subscription.current_period_end
357
+ expect(preview.lines.count).to eq 1
358
+ line = preview.lines.first
359
+ expect(line.type).to eq 'subscription'
360
+ expect(line.amount).to eq 150_00
361
+ # line period is for the NEXT subscription cycle
362
+ expect(line.period.start).to be_within(1).of subscription.current_period_end
363
+ expect(Time.at(line.period.end).month).to be_within(1).of (Time.at(subscription.current_period_end).to_datetime >> 1).month # +1 month
364
+ end
365
+ end
366
+
367
+ describe 'upcoming invoice with no proration parameters specified' do
368
+ let(:preview) do
369
+ Stripe::Invoice.upcoming(
370
+ customer: customer.id,
371
+ subscription: subscription.id
372
+ )
373
+ end
374
+
375
+ it_behaves_like 'no proration is done'
376
+ end
377
+
378
+ describe 'upcoming invoice with same subscription plan and quantity specified' do
379
+ let(:preview) do
380
+ proration_date = Time.now + 60
381
+ Stripe::Invoice.upcoming(
382
+ customer: customer.id,
383
+ subscription: subscription.id,
384
+ subscription_plan: plan.id,
385
+ subscription_quantity: quantity,
386
+ subscription_proration_date: proration_date.to_i,
387
+ subscription_trial_end: nil
388
+ )
389
+ end
390
+
391
+ it_behaves_like 'no proration is done'
392
+ end
393
+ end
394
+
395
+ it 'sets the start and end of billing periods correctly when plan has an interval_count' do
396
+ @oddplan = stripe_helper.create_plan(interval: "week", interval_count: 11)
397
+ @subscription = Stripe::Subscription.create(plan: @oddplan.id, customer: customer.id)
398
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
399
+
400
+ expect(@upcoming.period_start + 6652800).to eq(@upcoming.period_end) # 6652800 = +11 weeks
401
+ expect(@upcoming.period_end).to eq(@upcoming.lines.data[0].period.start)
402
+ expect(@upcoming.period_end + 6652800).to eq(@upcoming.lines.data[0].period.end) # 6652800 = +11 weeks
403
+ expect(@upcoming.next_payment_attempt).to eq(@upcoming.period_end + 3600) # +1 hour
404
+ end
405
+
406
+ it 'chooses the most recent of multiple subscriptions' do
407
+ @shortplan = stripe_helper.create_plan(id: 'a', interval: "week") # 1 week sub
408
+ @plainplan = stripe_helper.create_plan(id: 'b') # 1 month sub
409
+ @longplan = stripe_helper.create_plan(id: 'c', interval: "year") # 1 year sub
410
+
411
+ @plainsub = Stripe::Subscription.create(plan: @plainplan.id, customer: customer.id)
412
+ @shortsub = Stripe::Subscription.create(plan: @shortplan.id, customer: customer.id)
413
+ @longsub = Stripe::Subscription.create(plan: @longplan.id, customer: customer.id)
414
+
415
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
416
+
417
+ expect(@upcoming.period_start + 604800).to eq(@upcoming.period_end) # 604800 = 1 week
418
+ expect(@upcoming.period_end + 604800).to eq(@upcoming.lines.data[0].period.end) # 604800 = 1 week
419
+ expect(@upcoming.subscription).to eq(@shortsub.id)
420
+ end
421
+
422
+ it 'does not store the stripe invoice in memory since its only a preview', with_subscription: true do
423
+ invoice = Stripe::Invoice.upcoming(customer: customer.id)
424
+ data = test_data_source(:invoices)
425
+ expect(data[invoice.id]).to be_nil
426
+ end
427
+
428
+ context 'retrieving invoice line items' do
429
+ it 'returns all line items for created invoice' do
430
+ invoice = Stripe::Invoice.create(customer: customer.id)
431
+ line_items = invoice.lines.all
432
+
433
+ expect(invoice).to be_a Stripe::Invoice
434
+ expect(line_items.count).to eq(1)
435
+ expect(line_items.data[0].object).to eq('line_item')
436
+ expect(line_items.data[0].description).to eq('Test invoice item')
437
+ expect(line_items.data[0].type).to eq('invoiceitem')
438
+ end
439
+
440
+ it 'returns all line items for upcoming invoice' do
441
+ plan = stripe_helper.create_plan()
442
+ subscription = Stripe::Subscription.create(plan: plan.id, customer: customer.id)
443
+ upcoming = Stripe::Invoice.upcoming(customer: customer.id)
444
+ line_items = upcoming.lines
445
+
446
+ expect(upcoming).to be_a Stripe::Invoice
447
+ expect(line_items.count).to eq(1)
448
+ expect(line_items.data[0].object).to eq('line_item')
449
+ expect(line_items.data[0].description).to eq('Test invoice item')
450
+ expect(line_items.data[0].type).to eq('subscription')
451
+ end
452
+ end
453
+
454
+ context 'calculates month and year offsets correctly' do
455
+
456
+ it 'for one month plan on the 1st' do
457
+ @plan = stripe_helper.create_plan()
458
+ @sub = Stripe::Subscription.create(plan: @plan.id, customer: customer.id, current_period_start: Time.utc(2014,1,1,12).to_i)
459
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
460
+
461
+ expect(Time.at(@upcoming.period_start)).to eq(Time.utc(2014,1,1,12))
462
+ expect(Time.at(@upcoming.period_end)).to eq(Time.utc(2014,2,1,12))
463
+ expect(Time.at(@upcoming.lines.data[0].period.start)).to eq(Time.utc(2014,2,1,12))
464
+ expect(Time.at(@upcoming.lines.data[0].period.end)).to eq(Time.utc(2014,3,1,12))
465
+ end
466
+
467
+ it 'for one year plan on the 1st' do
468
+ @plan = stripe_helper.create_plan(interval: "year")
469
+ @sub = Stripe::Subscription.create(plan: @plan.id, customer: customer.id, current_period_start: Time.utc(2012,1,1,12).to_i)
470
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
471
+
472
+ expect(Time.at(@upcoming.period_start)).to eq(Time.utc(2012,1,1,12))
473
+ expect(Time.at(@upcoming.period_end)).to eq(Time.utc(2013,1,1,12))
474
+ expect(Time.at(@upcoming.lines.data[0].period.start)).to eq(Time.utc(2013,1,1,12))
475
+ expect(Time.at(@upcoming.lines.data[0].period.end)).to eq(Time.utc(2014,1,1,12))
476
+ end
477
+
478
+ it 'for one month plan on the 31st' do
479
+ @plan = stripe_helper.create_plan()
480
+ @sub = Stripe::Subscription.create(plan: @plan.id, customer: customer.id, current_period_start: Time.utc(2014,1,31,12).to_i)
481
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
482
+
483
+ expect(Time.at(@upcoming.period_start)).to eq(Time.utc(2014,1,31,12))
484
+ expect(Time.at(@upcoming.period_end)).to eq(Time.utc(2014,2,28,12))
485
+ expect(Time.at(@upcoming.lines.data[0].period.start)).to eq(Time.utc(2014,2,28,12))
486
+ expect(Time.at(@upcoming.lines.data[0].period.end)).to eq(Time.utc(2014,3,31,12))
487
+ end
488
+
489
+ it 'for one year plan on feb. 29th' do
490
+ @plan = stripe_helper.create_plan(interval: "year")
491
+ @sub = Stripe::Subscription.create(plan: @plan.id, customer: customer.id, current_period_start: Time.utc(2012,2,29,12).to_i)
492
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
493
+
494
+ expect(Time.at(@upcoming.period_start)).to eq(Time.utc(2012,2,29,12))
495
+ expect(Time.at(@upcoming.period_end)).to eq(Time.utc(2013,2,28,12))
496
+ expect(Time.at(@upcoming.lines.data[0].period.start)).to eq(Time.utc(2013,2,28,12))
497
+ expect(Time.at(@upcoming.lines.data[0].period.end)).to eq(Time.utc(2014,2,28,12))
498
+ end
499
+
500
+ it 'for two month plan on dec. 31st' do
501
+ @plan = stripe_helper.create_plan(interval_count: 2)
502
+ @sub = Stripe::Subscription.create(plan: @plan.id, customer: customer.id, current_period_start: Time.utc(2013,12,31,12).to_i)
503
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
504
+
505
+ expect(Time.at(@upcoming.period_start)).to eq(Time.utc(2013,12,31,12))
506
+ expect(Time.at(@upcoming.period_end)).to eq(Time.utc(2014, 2,28,12))
507
+ expect(Time.at(@upcoming.lines.data[0].period.start)).to eq(Time.utc(2014, 2,28,12))
508
+ expect(Time.at(@upcoming.lines.data[0].period.end)).to eq(Time.utc(2014, 4,30,12))
509
+ end
510
+
511
+ it 'for three month plan on nov. 30th' do
512
+ @plan = stripe_helper.create_plan(interval_count: 3)
513
+ @sub = Stripe::Subscription.create(plan: @plan.id, customer: customer.id, current_period_start: Time.utc(2013,11,30,12).to_i)
514
+ @upcoming = Stripe::Invoice.upcoming(customer: customer.id)
515
+
516
+ expect(Time.at(@upcoming.period_start)).to eq(Time.utc(2013,11,30,12))
517
+ expect(Time.at(@upcoming.period_end)).to eq(Time.utc(2014, 2,28,12))
518
+ expect(Time.at(@upcoming.lines.data[0].period.start)).to eq(Time.utc(2014, 2,28,12))
519
+ expect(Time.at(@upcoming.lines.data[0].period.end)).to eq(Time.utc(2014, 5,30,12))
520
+ end
521
+ end
522
+
523
+ end
524
+ end