gocardless_pro 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -4
  3. data/lib/gocardless_pro.rb +1 -0
  4. data/lib/gocardless_pro/api_service.rb +2 -0
  5. data/lib/gocardless_pro/client.rb +4 -3
  6. data/lib/gocardless_pro/error/invalid_state_error.rb +17 -0
  7. data/lib/gocardless_pro/middlewares/raise_gocardless_errors.rb +50 -0
  8. data/lib/gocardless_pro/request.rb +38 -1
  9. data/lib/gocardless_pro/resources/creditor.rb +2 -2
  10. data/lib/gocardless_pro/resources/creditor_bank_account.rb +11 -11
  11. data/lib/gocardless_pro/resources/customer_bank_account.rb +2 -2
  12. data/lib/gocardless_pro/resources/event.rb +2 -2
  13. data/lib/gocardless_pro/resources/mandate.rb +2 -2
  14. data/lib/gocardless_pro/resources/payment.rb +7 -7
  15. data/lib/gocardless_pro/resources/payout.rb +7 -6
  16. data/lib/gocardless_pro/resources/redirect_flow.rb +2 -2
  17. data/lib/gocardless_pro/resources/refund.rb +2 -2
  18. data/lib/gocardless_pro/resources/subscription.rb +2 -2
  19. data/lib/gocardless_pro/response.rb +2 -54
  20. data/lib/gocardless_pro/services/bank_details_lookups_service.rb +3 -0
  21. data/lib/gocardless_pro/services/creditor_bank_accounts_service.rb +31 -2
  22. data/lib/gocardless_pro/services/creditors_service.rb +21 -1
  23. data/lib/gocardless_pro/services/customer_bank_accounts_service.rb +34 -2
  24. data/lib/gocardless_pro/services/customers_service.rb +21 -1
  25. data/lib/gocardless_pro/services/events_service.rb +5 -0
  26. data/lib/gocardless_pro/services/mandate_pdfs_service.rb +3 -0
  27. data/lib/gocardless_pro/services/mandates_service.rb +47 -3
  28. data/lib/gocardless_pro/services/payments_service.rb +47 -3
  29. data/lib/gocardless_pro/services/payouts_service.rb +5 -0
  30. data/lib/gocardless_pro/services/redirect_flows_service.rb +28 -2
  31. data/lib/gocardless_pro/services/refunds_service.rb +21 -1
  32. data/lib/gocardless_pro/services/subscriptions_service.rb +34 -2
  33. data/lib/gocardless_pro/version.rb +1 -1
  34. data/spec/api_service_spec.rb +106 -0
  35. data/spec/middlewares/raise_gocardless_errors_spec.rb +98 -0
  36. data/spec/resources/bank_details_lookup_spec.rb +102 -19
  37. data/spec/resources/creditor_bank_account_spec.rb +416 -40
  38. data/spec/resources/creditor_spec.rb +414 -53
  39. data/spec/resources/customer_bank_account_spec.rb +452 -40
  40. data/spec/resources/customer_spec.rb +457 -45
  41. data/spec/resources/event_spec.rb +171 -72
  42. data/spec/resources/mandate_pdf_spec.rb +100 -17
  43. data/spec/resources/mandate_spec.rb +501 -44
  44. data/spec/resources/payment_spec.rb +531 -48
  45. data/spec/resources/payout_spec.rb +189 -45
  46. data/spec/resources/redirect_flow_spec.rb +277 -43
  47. data/spec/resources/refund_spec.rb +349 -34
  48. data/spec/resources/subscription_spec.rb +531 -53
  49. data/spec/response_spec.rb +12 -79
  50. data/spec/services/bank_details_lookups_service_spec.rb +67 -2
  51. data/spec/services/creditor_bank_accounts_service_spec.rb +309 -31
  52. data/spec/services/creditors_service_spec.rb +343 -33
  53. data/spec/services/customer_bank_accounts_service_spec.rb +335 -32
  54. data/spec/services/customers_service_spec.rb +364 -36
  55. data/spec/services/events_service_spec.rb +185 -24
  56. data/spec/services/mandate_pdfs_service_spec.rb +66 -2
  57. data/spec/services/mandates_service_spec.rb +341 -33
  58. data/spec/services/payments_service_spec.rb +355 -35
  59. data/spec/services/payouts_service_spec.rb +206 -26
  60. data/spec/services/redirect_flows_service_spec.rb +137 -7
  61. data/spec/services/refunds_service_spec.rb +301 -27
  62. data/spec/services/subscriptions_service_spec.rb +377 -38
  63. metadata +6 -3
@@ -7,6 +7,8 @@ describe GoCardlessPro::Services::RefundsService do
7
7
  )
8
8
  end
9
9
 
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
10
12
  describe '#create' do
11
13
  subject(:post_create_response) { client.refunds.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -55,13 +57,36 @@ describe GoCardlessPro::Services::RefundsService do
55
57
  }
56
58
 
57
59
  }.to_json,
58
- headers: { 'Content-Type' => 'application/json' }
60
+ headers: response_headers
59
61
  )
60
62
  end
61
63
 
62
64
  it 'creates and returns the resource' do
63
65
  expect(post_create_response).to be_a(GoCardlessPro::Resources::Refund)
64
66
  end
67
+
68
+ describe 'retry behaviour' do
69
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
70
+
71
+ it 'retries timeouts' do
72
+ stub = stub_request(:post, %r{.*api.gocardless.com/refunds})
73
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
74
+
75
+ post_create_response
76
+ expect(stub).to have_been_requested.twice
77
+ end
78
+
79
+ it 'retries 5XX errors' do
80
+ stub = stub_request(:post, %r{.*api.gocardless.com/refunds})
81
+ .to_return(status: 502,
82
+ headers: { 'Content-Type' => 'text/html' },
83
+ body: '<html><body>Response from Cloudflare</body></html>')
84
+ .then.to_return(status: 200, headers: response_headers)
85
+
86
+ post_create_response
87
+ expect(stub).to have_been_requested.twice
88
+ end
89
+ end
65
90
  end
66
91
 
67
92
  context 'with a request that returns a validation error' do
@@ -78,7 +103,7 @@ describe GoCardlessPro::Services::RefundsService do
78
103
  ]
79
104
  }
80
105
  }.to_json,
81
- headers: { 'Content-Type' => 'application/json' },
106
+ headers: response_headers,
82
107
  status: 422
83
108
  )
84
109
  end
@@ -87,33 +112,102 @@ describe GoCardlessPro::Services::RefundsService do
87
112
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
88
113
  end
89
114
  end
115
+
116
+ context 'with a request that returns an idempotent creation conflict error' do
117
+ let(:id) { 'ID123' }
118
+
119
+ let(:new_resource) do
120
+ {
121
+
122
+ 'amount' => 'amount-input',
123
+ 'created_at' => 'created_at-input',
124
+ 'currency' => 'currency-input',
125
+ 'id' => 'id-input',
126
+ 'links' => 'links-input',
127
+ 'metadata' => 'metadata-input',
128
+ 'reference' => 'reference-input'
129
+ }
130
+ end
131
+
132
+ let!(:post_stub) do
133
+ stub_request(:post, %r{.*api.gocardless.com/refunds}).to_return(
134
+ body: {
135
+ error: {
136
+ type: 'invalid_state',
137
+ code: 409,
138
+ errors: [
139
+ {
140
+ message: 'A resource has already been created with this idempotency key',
141
+ reason: 'idempotent_creation_conflict',
142
+ links: {
143
+ conflicting_resource_id: id
144
+ }
145
+ }
146
+ ]
147
+ }
148
+ }.to_json,
149
+ headers: response_headers,
150
+ status: 409
151
+ )
152
+ end
153
+
154
+ let!(:get_stub) do
155
+ stub_url = "/refunds/#{id}"
156
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
157
+ .to_return(
158
+ body: {
159
+ 'refunds' => {
160
+
161
+ 'amount' => 'amount-input',
162
+ 'created_at' => 'created_at-input',
163
+ 'currency' => 'currency-input',
164
+ 'id' => 'id-input',
165
+ 'links' => 'links-input',
166
+ 'metadata' => 'metadata-input',
167
+ 'reference' => 'reference-input'
168
+ }
169
+ }.to_json,
170
+ headers: response_headers
171
+ )
172
+ end
173
+
174
+ it 'fetches the already-created resource' do
175
+ post_create_response
176
+ expect(post_stub).to have_been_requested
177
+ expect(get_stub).to have_been_requested
178
+ end
179
+ end
90
180
  end
91
181
 
92
182
  describe '#list' do
93
183
  describe 'with no filters' do
94
184
  subject(:get_list_response) { client.refunds.list }
95
185
 
96
- before do
97
- stub_request(:get, %r{.*api.gocardless.com/refunds}).to_return(
98
- body: {
99
- 'refunds' => [{
186
+ let(:body) do
187
+ {
188
+ 'refunds' => [{
100
189
 
101
- 'amount' => 'amount-input',
102
- 'created_at' => 'created_at-input',
103
- 'currency' => 'currency-input',
104
- 'id' => 'id-input',
105
- 'links' => 'links-input',
106
- 'metadata' => 'metadata-input',
107
- 'reference' => 'reference-input'
108
- }],
109
- meta: {
110
- cursors: {
111
- before: nil,
112
- after: 'ABC123'
113
- }
190
+ 'amount' => 'amount-input',
191
+ 'created_at' => 'created_at-input',
192
+ 'currency' => 'currency-input',
193
+ 'id' => 'id-input',
194
+ 'links' => 'links-input',
195
+ 'metadata' => 'metadata-input',
196
+ 'reference' => 'reference-input'
197
+ }],
198
+ meta: {
199
+ cursors: {
200
+ before: nil,
201
+ after: 'ABC123'
114
202
  }
115
- }.to_json,
116
- headers: { 'Content-Type' => 'application/json' }
203
+ }
204
+ }.to_json
205
+ end
206
+
207
+ before do
208
+ stub_request(:get, %r{.*api.gocardless.com/refunds}).to_return(
209
+ body: body,
210
+ headers: response_headers
117
211
  )
118
212
  end
119
213
 
@@ -139,6 +233,29 @@ describe GoCardlessPro::Services::RefundsService do
139
233
  end
140
234
 
141
235
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
236
+
237
+ describe 'retry behaviour' do
238
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
239
+
240
+ it 'retries timeouts' do
241
+ stub = stub_request(:get, %r{.*api.gocardless.com/refunds})
242
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
243
+
244
+ get_list_response
245
+ expect(stub).to have_been_requested.twice
246
+ end
247
+
248
+ it 'retries 5XX errors' do
249
+ stub = stub_request(:get, %r{.*api.gocardless.com/refunds})
250
+ .to_return(status: 502,
251
+ headers: { 'Content-Type' => 'text/html' },
252
+ body: '<html><body>Response from Cloudflare</body></html>')
253
+ .then.to_return(status: 200, headers: response_headers, body: body)
254
+
255
+ get_list_response
256
+ expect(stub).to have_been_requested.twice
257
+ end
258
+ end
142
259
  end
143
260
  end
144
261
 
@@ -161,7 +278,7 @@ describe GoCardlessPro::Services::RefundsService do
161
278
  limit: 1
162
279
  }
163
280
  }.to_json,
164
- headers: { 'Content-Type' => 'application/json' }
281
+ headers: response_headers
165
282
  )
166
283
  end
167
284
 
@@ -183,7 +300,7 @@ describe GoCardlessPro::Services::RefundsService do
183
300
  cursors: {}
184
301
  }
185
302
  }.to_json,
186
- headers: { 'Content-Type' => 'application/json' }
303
+ headers: response_headers
187
304
  )
188
305
  end
189
306
 
@@ -192,6 +309,111 @@ describe GoCardlessPro::Services::RefundsService do
192
309
  expect(first_response_stub).to have_been_requested
193
310
  expect(second_response_stub).to have_been_requested
194
311
  end
312
+
313
+ describe 'retry behaviour' do
314
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
315
+
316
+ it 'retries timeouts' do
317
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds$}).to_return(
318
+ body: {
319
+ 'refunds' => [{
320
+
321
+ 'amount' => 'amount-input',
322
+ 'created_at' => 'created_at-input',
323
+ 'currency' => 'currency-input',
324
+ 'id' => 'id-input',
325
+ 'links' => 'links-input',
326
+ 'metadata' => 'metadata-input',
327
+ 'reference' => 'reference-input'
328
+ }],
329
+ meta: {
330
+ cursors: { after: 'AB345' },
331
+ limit: 1
332
+ }
333
+ }.to_json,
334
+ headers: response_headers
335
+ )
336
+
337
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds\?after=AB345})
338
+ .to_timeout.then
339
+ .to_return(
340
+ body: {
341
+ 'refunds' => [{
342
+
343
+ 'amount' => 'amount-input',
344
+ 'created_at' => 'created_at-input',
345
+ 'currency' => 'currency-input',
346
+ 'id' => 'id-input',
347
+ 'links' => 'links-input',
348
+ 'metadata' => 'metadata-input',
349
+ 'reference' => 'reference-input'
350
+ }],
351
+ meta: {
352
+ limit: 2,
353
+ cursors: {}
354
+ }
355
+ }.to_json,
356
+ headers: response_headers
357
+ )
358
+
359
+ client.refunds.all.to_a
360
+
361
+ expect(first_response_stub).to have_been_requested
362
+ expect(second_response_stub).to have_been_requested.twice
363
+ end
364
+
365
+ it 'retries 5XX errors' do
366
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds$}).to_return(
367
+ body: {
368
+ 'refunds' => [{
369
+
370
+ 'amount' => 'amount-input',
371
+ 'created_at' => 'created_at-input',
372
+ 'currency' => 'currency-input',
373
+ 'id' => 'id-input',
374
+ 'links' => 'links-input',
375
+ 'metadata' => 'metadata-input',
376
+ 'reference' => 'reference-input'
377
+ }],
378
+ meta: {
379
+ cursors: { after: 'AB345' },
380
+ limit: 1
381
+ }
382
+ }.to_json,
383
+ headers: response_headers
384
+ )
385
+
386
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/refunds\?after=AB345})
387
+ .to_return(
388
+ status: 502,
389
+ body: '<html><body>Response from Cloudflare</body></html>',
390
+ headers: { 'Content-Type' => 'text/html' }
391
+ ).then.to_return(
392
+ body: {
393
+ 'refunds' => [{
394
+
395
+ 'amount' => 'amount-input',
396
+ 'created_at' => 'created_at-input',
397
+ 'currency' => 'currency-input',
398
+ 'id' => 'id-input',
399
+ 'links' => 'links-input',
400
+ 'metadata' => 'metadata-input',
401
+ 'reference' => 'reference-input'
402
+ }],
403
+ meta: {
404
+ limit: 2,
405
+ cursors: {}
406
+ }
407
+ }.to_json,
408
+ headers: response_headers
409
+ )
410
+
411
+ client.refunds.all.to_a
412
+
413
+ expect(first_response_stub).to have_been_requested
414
+ expect(second_response_stub).to have_been_requested.twice
415
+ end
416
+ end
195
417
  end
196
418
 
197
419
  describe '#get' do
@@ -217,7 +439,7 @@ describe GoCardlessPro::Services::RefundsService do
217
439
  'reference' => 'reference-input'
218
440
  }
219
441
  }.to_json,
220
- headers: { 'Content-Type' => 'application/json' }
442
+ headers: response_headers
221
443
  )
222
444
  end
223
445
 
@@ -249,7 +471,7 @@ describe GoCardlessPro::Services::RefundsService do
249
471
  'reference' => 'reference-input'
250
472
  }
251
473
  }.to_json,
252
- headers: { 'Content-Type' => 'application/json' }
474
+ headers: response_headers
253
475
  )
254
476
  end
255
477
 
@@ -263,7 +485,7 @@ describe GoCardlessPro::Services::RefundsService do
263
485
  stub_url = '/refunds/:identity'.gsub(':identity', id)
264
486
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
265
487
  body: '',
266
- headers: { 'Content-Type' => 'application/json' }
488
+ headers: response_headers
267
489
  )
268
490
  end
269
491
 
@@ -279,6 +501,33 @@ describe GoCardlessPro::Services::RefundsService do
279
501
  expect { get_response }.to_not raise_error(/bad URI/)
280
502
  end
281
503
  end
504
+
505
+ describe 'retry behaviour' do
506
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
507
+
508
+ it 'retries timeouts' do
509
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
510
+
511
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
512
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
513
+
514
+ get_response
515
+ expect(stub).to have_been_requested.twice
516
+ end
517
+
518
+ it 'retries 5XX errors' do
519
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
520
+
521
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
522
+ .to_return(status: 502,
523
+ headers: { 'Content-Type' => 'text/html' },
524
+ body: '<html><body>Response from Cloudflare</body></html>')
525
+ .then.to_return(status: 200, headers: response_headers)
526
+
527
+ get_response
528
+ expect(stub).to have_been_requested.twice
529
+ end
530
+ end
282
531
  end
283
532
 
284
533
  describe '#update' do
@@ -303,7 +552,7 @@ describe GoCardlessPro::Services::RefundsService do
303
552
  'reference' => 'reference-input'
304
553
  }
305
554
  }.to_json,
306
- headers: { 'Content-Type' => 'application/json' }
555
+ headers: response_headers
307
556
  )
308
557
  end
309
558
 
@@ -311,6 +560,31 @@ describe GoCardlessPro::Services::RefundsService do
311
560
  expect(put_update_response).to be_a(GoCardlessPro::Resources::Refund)
312
561
  expect(stub).to have_been_requested
313
562
  end
563
+
564
+ describe 'retry behaviour' do
565
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
566
+
567
+ it 'retries timeouts' do
568
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
569
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
570
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
571
+
572
+ put_update_response
573
+ expect(stub).to have_been_requested.twice
574
+ end
575
+
576
+ it 'retries 5XX errors' do
577
+ stub_url = '/refunds/:identity'.gsub(':identity', id)
578
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
579
+ .to_return(status: 502,
580
+ headers: { 'Content-Type' => 'text/html' },
581
+ body: '<html><body>Response from Cloudflare</body></html>')
582
+ .then.to_return(status: 200, headers: response_headers)
583
+
584
+ put_update_response
585
+ expect(stub).to have_been_requested.twice
586
+ end
587
+ end
314
588
  end
315
589
  end
316
590
  end
@@ -7,6 +7,8 @@ describe GoCardlessPro::Services::SubscriptionsService do
7
7
  )
8
8
  end
9
9
 
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
10
12
  describe '#create' do
11
13
  subject(:post_create_response) { client.subscriptions.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -82,13 +84,36 @@ describe GoCardlessPro::Services::SubscriptionsService do
82
84
  }
83
85
 
84
86
  }.to_json,
85
- headers: { 'Content-Type' => 'application/json' }
87
+ headers: response_headers
86
88
  )
87
89
  end
88
90
 
89
91
  it 'creates and returns the resource' do
90
92
  expect(post_create_response).to be_a(GoCardlessPro::Resources::Subscription)
91
93
  end
94
+
95
+ describe 'retry behaviour' do
96
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
97
+
98
+ it 'retries timeouts' do
99
+ stub = stub_request(:post, %r{.*api.gocardless.com/subscriptions})
100
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
101
+
102
+ post_create_response
103
+ expect(stub).to have_been_requested.twice
104
+ end
105
+
106
+ it 'retries 5XX errors' do
107
+ stub = stub_request(:post, %r{.*api.gocardless.com/subscriptions})
108
+ .to_return(status: 502,
109
+ headers: { 'Content-Type' => 'text/html' },
110
+ body: '<html><body>Response from Cloudflare</body></html>')
111
+ .then.to_return(status: 200, headers: response_headers)
112
+
113
+ post_create_response
114
+ expect(stub).to have_been_requested.twice
115
+ end
116
+ end
92
117
  end
93
118
 
94
119
  context 'with a request that returns a validation error' do
@@ -105,7 +130,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
105
130
  ]
106
131
  }
107
132
  }.to_json,
108
- headers: { 'Content-Type' => 'application/json' },
133
+ headers: response_headers,
109
134
  status: 422
110
135
  )
111
136
  end
@@ -114,42 +139,129 @@ describe GoCardlessPro::Services::SubscriptionsService do
114
139
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
115
140
  end
116
141
  end
142
+
143
+ context 'with a request that returns an idempotent creation conflict error' do
144
+ let(:id) { 'ID123' }
145
+
146
+ let(:new_resource) do
147
+ {
148
+
149
+ 'amount' => 'amount-input',
150
+ 'created_at' => 'created_at-input',
151
+ 'currency' => 'currency-input',
152
+ 'day_of_month' => 'day_of_month-input',
153
+ 'end_date' => 'end_date-input',
154
+ 'id' => 'id-input',
155
+ 'interval' => 'interval-input',
156
+ 'interval_unit' => 'interval_unit-input',
157
+ 'links' => 'links-input',
158
+ 'metadata' => 'metadata-input',
159
+ 'month' => 'month-input',
160
+ 'name' => 'name-input',
161
+ 'payment_reference' => 'payment_reference-input',
162
+ 'start_date' => 'start_date-input',
163
+ 'status' => 'status-input',
164
+ 'upcoming_payments' => 'upcoming_payments-input'
165
+ }
166
+ end
167
+
168
+ let!(:post_stub) do
169
+ stub_request(:post, %r{.*api.gocardless.com/subscriptions}).to_return(
170
+ body: {
171
+ error: {
172
+ type: 'invalid_state',
173
+ code: 409,
174
+ errors: [
175
+ {
176
+ message: 'A resource has already been created with this idempotency key',
177
+ reason: 'idempotent_creation_conflict',
178
+ links: {
179
+ conflicting_resource_id: id
180
+ }
181
+ }
182
+ ]
183
+ }
184
+ }.to_json,
185
+ headers: response_headers,
186
+ status: 409
187
+ )
188
+ end
189
+
190
+ let!(:get_stub) do
191
+ stub_url = "/subscriptions/#{id}"
192
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
193
+ .to_return(
194
+ body: {
195
+ 'subscriptions' => {
196
+
197
+ 'amount' => 'amount-input',
198
+ 'created_at' => 'created_at-input',
199
+ 'currency' => 'currency-input',
200
+ 'day_of_month' => 'day_of_month-input',
201
+ 'end_date' => 'end_date-input',
202
+ 'id' => 'id-input',
203
+ 'interval' => 'interval-input',
204
+ 'interval_unit' => 'interval_unit-input',
205
+ 'links' => 'links-input',
206
+ 'metadata' => 'metadata-input',
207
+ 'month' => 'month-input',
208
+ 'name' => 'name-input',
209
+ 'payment_reference' => 'payment_reference-input',
210
+ 'start_date' => 'start_date-input',
211
+ 'status' => 'status-input',
212
+ 'upcoming_payments' => 'upcoming_payments-input'
213
+ }
214
+ }.to_json,
215
+ headers: response_headers
216
+ )
217
+ end
218
+
219
+ it 'fetches the already-created resource' do
220
+ post_create_response
221
+ expect(post_stub).to have_been_requested
222
+ expect(get_stub).to have_been_requested
223
+ end
224
+ end
117
225
  end
118
226
 
119
227
  describe '#list' do
120
228
  describe 'with no filters' do
121
229
  subject(:get_list_response) { client.subscriptions.list }
122
230
 
123
- before do
124
- stub_request(:get, %r{.*api.gocardless.com/subscriptions}).to_return(
125
- body: {
126
- 'subscriptions' => [{
231
+ let(:body) do
232
+ {
233
+ 'subscriptions' => [{
127
234
 
128
- 'amount' => 'amount-input',
129
- 'created_at' => 'created_at-input',
130
- 'currency' => 'currency-input',
131
- 'day_of_month' => 'day_of_month-input',
132
- 'end_date' => 'end_date-input',
133
- 'id' => 'id-input',
134
- 'interval' => 'interval-input',
135
- 'interval_unit' => 'interval_unit-input',
136
- 'links' => 'links-input',
137
- 'metadata' => 'metadata-input',
138
- 'month' => 'month-input',
139
- 'name' => 'name-input',
140
- 'payment_reference' => 'payment_reference-input',
141
- 'start_date' => 'start_date-input',
142
- 'status' => 'status-input',
143
- 'upcoming_payments' => 'upcoming_payments-input'
144
- }],
145
- meta: {
146
- cursors: {
147
- before: nil,
148
- after: 'ABC123'
149
- }
235
+ 'amount' => 'amount-input',
236
+ 'created_at' => 'created_at-input',
237
+ 'currency' => 'currency-input',
238
+ 'day_of_month' => 'day_of_month-input',
239
+ 'end_date' => 'end_date-input',
240
+ 'id' => 'id-input',
241
+ 'interval' => 'interval-input',
242
+ 'interval_unit' => 'interval_unit-input',
243
+ 'links' => 'links-input',
244
+ 'metadata' => 'metadata-input',
245
+ 'month' => 'month-input',
246
+ 'name' => 'name-input',
247
+ 'payment_reference' => 'payment_reference-input',
248
+ 'start_date' => 'start_date-input',
249
+ 'status' => 'status-input',
250
+ 'upcoming_payments' => 'upcoming_payments-input'
251
+ }],
252
+ meta: {
253
+ cursors: {
254
+ before: nil,
255
+ after: 'ABC123'
150
256
  }
151
- }.to_json,
152
- headers: { 'Content-Type' => 'application/json' }
257
+ }
258
+ }.to_json
259
+ end
260
+
261
+ before do
262
+ stub_request(:get, %r{.*api.gocardless.com/subscriptions}).to_return(
263
+ body: body,
264
+ headers: response_headers
153
265
  )
154
266
  end
155
267
 
@@ -193,6 +305,29 @@ describe GoCardlessPro::Services::SubscriptionsService do
193
305
  end
194
306
 
195
307
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
308
+
309
+ describe 'retry behaviour' do
310
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
311
+
312
+ it 'retries timeouts' do
313
+ stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions})
314
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
315
+
316
+ get_list_response
317
+ expect(stub).to have_been_requested.twice
318
+ end
319
+
320
+ it 'retries 5XX errors' do
321
+ stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions})
322
+ .to_return(status: 502,
323
+ headers: { 'Content-Type' => 'text/html' },
324
+ body: '<html><body>Response from Cloudflare</body></html>')
325
+ .then.to_return(status: 200, headers: response_headers, body: body)
326
+
327
+ get_list_response
328
+ expect(stub).to have_been_requested.twice
329
+ end
330
+ end
196
331
  end
197
332
  end
198
333
 
@@ -224,7 +359,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
224
359
  limit: 1
225
360
  }
226
361
  }.to_json,
227
- headers: { 'Content-Type' => 'application/json' }
362
+ headers: response_headers
228
363
  )
229
364
  end
230
365
 
@@ -255,7 +390,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
255
390
  cursors: {}
256
391
  }
257
392
  }.to_json,
258
- headers: { 'Content-Type' => 'application/json' }
393
+ headers: response_headers
259
394
  )
260
395
  end
261
396
 
@@ -264,6 +399,147 @@ describe GoCardlessPro::Services::SubscriptionsService do
264
399
  expect(first_response_stub).to have_been_requested
265
400
  expect(second_response_stub).to have_been_requested
266
401
  end
402
+
403
+ describe 'retry behaviour' do
404
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
405
+
406
+ it 'retries timeouts' do
407
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions$}).to_return(
408
+ body: {
409
+ 'subscriptions' => [{
410
+
411
+ 'amount' => 'amount-input',
412
+ 'created_at' => 'created_at-input',
413
+ 'currency' => 'currency-input',
414
+ 'day_of_month' => 'day_of_month-input',
415
+ 'end_date' => 'end_date-input',
416
+ 'id' => 'id-input',
417
+ 'interval' => 'interval-input',
418
+ 'interval_unit' => 'interval_unit-input',
419
+ 'links' => 'links-input',
420
+ 'metadata' => 'metadata-input',
421
+ 'month' => 'month-input',
422
+ 'name' => 'name-input',
423
+ 'payment_reference' => 'payment_reference-input',
424
+ 'start_date' => 'start_date-input',
425
+ 'status' => 'status-input',
426
+ 'upcoming_payments' => 'upcoming_payments-input'
427
+ }],
428
+ meta: {
429
+ cursors: { after: 'AB345' },
430
+ limit: 1
431
+ }
432
+ }.to_json,
433
+ headers: response_headers
434
+ )
435
+
436
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions\?after=AB345})
437
+ .to_timeout.then
438
+ .to_return(
439
+ body: {
440
+ 'subscriptions' => [{
441
+
442
+ 'amount' => 'amount-input',
443
+ 'created_at' => 'created_at-input',
444
+ 'currency' => 'currency-input',
445
+ 'day_of_month' => 'day_of_month-input',
446
+ 'end_date' => 'end_date-input',
447
+ 'id' => 'id-input',
448
+ 'interval' => 'interval-input',
449
+ 'interval_unit' => 'interval_unit-input',
450
+ 'links' => 'links-input',
451
+ 'metadata' => 'metadata-input',
452
+ 'month' => 'month-input',
453
+ 'name' => 'name-input',
454
+ 'payment_reference' => 'payment_reference-input',
455
+ 'start_date' => 'start_date-input',
456
+ 'status' => 'status-input',
457
+ 'upcoming_payments' => 'upcoming_payments-input'
458
+ }],
459
+ meta: {
460
+ limit: 2,
461
+ cursors: {}
462
+ }
463
+ }.to_json,
464
+ headers: response_headers
465
+ )
466
+
467
+ client.subscriptions.all.to_a
468
+
469
+ expect(first_response_stub).to have_been_requested
470
+ expect(second_response_stub).to have_been_requested.twice
471
+ end
472
+
473
+ it 'retries 5XX errors' do
474
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions$}).to_return(
475
+ body: {
476
+ 'subscriptions' => [{
477
+
478
+ 'amount' => 'amount-input',
479
+ 'created_at' => 'created_at-input',
480
+ 'currency' => 'currency-input',
481
+ 'day_of_month' => 'day_of_month-input',
482
+ 'end_date' => 'end_date-input',
483
+ 'id' => 'id-input',
484
+ 'interval' => 'interval-input',
485
+ 'interval_unit' => 'interval_unit-input',
486
+ 'links' => 'links-input',
487
+ 'metadata' => 'metadata-input',
488
+ 'month' => 'month-input',
489
+ 'name' => 'name-input',
490
+ 'payment_reference' => 'payment_reference-input',
491
+ 'start_date' => 'start_date-input',
492
+ 'status' => 'status-input',
493
+ 'upcoming_payments' => 'upcoming_payments-input'
494
+ }],
495
+ meta: {
496
+ cursors: { after: 'AB345' },
497
+ limit: 1
498
+ }
499
+ }.to_json,
500
+ headers: response_headers
501
+ )
502
+
503
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/subscriptions\?after=AB345})
504
+ .to_return(
505
+ status: 502,
506
+ body: '<html><body>Response from Cloudflare</body></html>',
507
+ headers: { 'Content-Type' => 'text/html' }
508
+ ).then.to_return(
509
+ body: {
510
+ 'subscriptions' => [{
511
+
512
+ 'amount' => 'amount-input',
513
+ 'created_at' => 'created_at-input',
514
+ 'currency' => 'currency-input',
515
+ 'day_of_month' => 'day_of_month-input',
516
+ 'end_date' => 'end_date-input',
517
+ 'id' => 'id-input',
518
+ 'interval' => 'interval-input',
519
+ 'interval_unit' => 'interval_unit-input',
520
+ 'links' => 'links-input',
521
+ 'metadata' => 'metadata-input',
522
+ 'month' => 'month-input',
523
+ 'name' => 'name-input',
524
+ 'payment_reference' => 'payment_reference-input',
525
+ 'start_date' => 'start_date-input',
526
+ 'status' => 'status-input',
527
+ 'upcoming_payments' => 'upcoming_payments-input'
528
+ }],
529
+ meta: {
530
+ limit: 2,
531
+ cursors: {}
532
+ }
533
+ }.to_json,
534
+ headers: response_headers
535
+ )
536
+
537
+ client.subscriptions.all.to_a
538
+
539
+ expect(first_response_stub).to have_been_requested
540
+ expect(second_response_stub).to have_been_requested.twice
541
+ end
542
+ end
267
543
  end
268
544
 
269
545
  describe '#get' do
@@ -298,7 +574,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
298
574
  'upcoming_payments' => 'upcoming_payments-input'
299
575
  }
300
576
  }.to_json,
301
- headers: { 'Content-Type' => 'application/json' }
577
+ headers: response_headers
302
578
  )
303
579
  end
304
580
 
@@ -339,7 +615,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
339
615
  'upcoming_payments' => 'upcoming_payments-input'
340
616
  }
341
617
  }.to_json,
342
- headers: { 'Content-Type' => 'application/json' }
618
+ headers: response_headers
343
619
  )
344
620
  end
345
621
 
@@ -353,7 +629,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
353
629
  stub_url = '/subscriptions/:identity'.gsub(':identity', id)
354
630
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
355
631
  body: '',
356
- headers: { 'Content-Type' => 'application/json' }
632
+ headers: response_headers
357
633
  )
358
634
  end
359
635
 
@@ -369,6 +645,33 @@ describe GoCardlessPro::Services::SubscriptionsService do
369
645
  expect { get_response }.to_not raise_error(/bad URI/)
370
646
  end
371
647
  end
648
+
649
+ describe 'retry behaviour' do
650
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
651
+
652
+ it 'retries timeouts' do
653
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
654
+
655
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
656
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
657
+
658
+ get_response
659
+ expect(stub).to have_been_requested.twice
660
+ end
661
+
662
+ it 'retries 5XX errors' do
663
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
664
+
665
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
666
+ .to_return(status: 502,
667
+ headers: { 'Content-Type' => 'text/html' },
668
+ body: '<html><body>Response from Cloudflare</body></html>')
669
+ .then.to_return(status: 200, headers: response_headers)
670
+
671
+ get_response
672
+ expect(stub).to have_been_requested.twice
673
+ end
674
+ end
372
675
  end
373
676
 
374
677
  describe '#update' do
@@ -402,7 +705,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
402
705
  'upcoming_payments' => 'upcoming_payments-input'
403
706
  }
404
707
  }.to_json,
405
- headers: { 'Content-Type' => 'application/json' }
708
+ headers: response_headers
406
709
  )
407
710
  end
408
711
 
@@ -410,6 +713,31 @@ describe GoCardlessPro::Services::SubscriptionsService do
410
713
  expect(put_update_response).to be_a(GoCardlessPro::Resources::Subscription)
411
714
  expect(stub).to have_been_requested
412
715
  end
716
+
717
+ describe 'retry behaviour' do
718
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
719
+
720
+ it 'retries timeouts' do
721
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
722
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
723
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
724
+
725
+ put_update_response
726
+ expect(stub).to have_been_requested.twice
727
+ end
728
+
729
+ it 'retries 5XX errors' do
730
+ stub_url = '/subscriptions/:identity'.gsub(':identity', id)
731
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
732
+ .to_return(status: 502,
733
+ headers: { 'Content-Type' => 'text/html' },
734
+ body: '<html><body>Response from Cloudflare</body></html>')
735
+ .then.to_return(status: 200, headers: response_headers)
736
+
737
+ put_update_response
738
+ expect(stub).to have_been_requested.twice
739
+ end
740
+ end
413
741
  end
414
742
  end
415
743
 
@@ -443,7 +771,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
443
771
  'upcoming_payments' => 'upcoming_payments-input'
444
772
  }
445
773
  }.to_json,
446
- headers: { 'Content-Type' => 'application/json' }
774
+ headers: response_headers
447
775
  )
448
776
  end
449
777
 
@@ -453,6 +781,17 @@ describe GoCardlessPro::Services::SubscriptionsService do
453
781
  expect(stub).to have_been_requested
454
782
  end
455
783
 
784
+ describe 'retry behaviour' do
785
+ it "doesn't retry errors" do
786
+ stub_url = '/subscriptions/:identity/actions/cancel'.gsub(':identity', resource_id)
787
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/)
788
+ .to_timeout
789
+
790
+ expect { post_response }.to raise_error(Faraday::TimeoutError)
791
+ expect(stub).to have_been_requested
792
+ end
793
+ end
794
+
456
795
  context 'when the request needs a body and custom header' do
457
796
  let(:body) { { foo: 'bar' } }
458
797
  let(:headers) { { 'Foo' => 'Bar' } }
@@ -489,7 +828,7 @@ describe GoCardlessPro::Services::SubscriptionsService do
489
828
  'upcoming_payments' => 'upcoming_payments-input'
490
829
  }
491
830
  }.to_json,
492
- headers: { 'Content-Type' => 'application/json' }
831
+ headers: response_headers
493
832
  )
494
833
  end
495
834
  end