gocardless_pro 2.24.0 → 2.29.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +23 -4
  3. data/lib/gocardless_pro/api_service.rb +4 -0
  4. data/lib/gocardless_pro/client.rb +46 -1
  5. data/lib/gocardless_pro/error/authentication_error.rb +4 -0
  6. data/lib/gocardless_pro/error/permission_error.rb +4 -0
  7. data/lib/gocardless_pro/error/rate_limit_error.rb +4 -0
  8. data/lib/gocardless_pro/middlewares/raise_gocardless_errors.rb +12 -1
  9. data/lib/gocardless_pro/resources/bank_authorisation.rb +81 -0
  10. data/lib/gocardless_pro/resources/billing_request.rb +108 -0
  11. data/lib/gocardless_pro/resources/billing_request_flow.rb +72 -0
  12. data/lib/gocardless_pro/resources/billing_request_template.rb +68 -0
  13. data/lib/gocardless_pro/resources/block.rb +66 -0
  14. data/lib/gocardless_pro/resources/creditor.rb +2 -3
  15. data/lib/gocardless_pro/resources/event.rb +20 -0
  16. data/lib/gocardless_pro/resources/institution.rb +47 -0
  17. data/lib/gocardless_pro/resources/payer_authorisation.rb +131 -0
  18. data/lib/gocardless_pro/resources/payout_item.rb +4 -0
  19. data/lib/gocardless_pro/resources/redirect_flow.rb +2 -0
  20. data/lib/gocardless_pro/resources/scenario_simulator.rb +42 -0
  21. data/lib/gocardless_pro/resources/webhook.rb +62 -0
  22. data/lib/gocardless_pro/services/bank_authorisations_service.rb +80 -0
  23. data/lib/gocardless_pro/services/billing_request_flows_service.rb +70 -0
  24. data/lib/gocardless_pro/services/billing_request_templates_service.rb +131 -0
  25. data/lib/gocardless_pro/services/billing_requests_service.rb +352 -0
  26. data/lib/gocardless_pro/services/blocks_service.rb +223 -0
  27. data/lib/gocardless_pro/services/creditor_bank_accounts_service.rb +1 -5
  28. data/lib/gocardless_pro/services/creditors_service.rb +1 -3
  29. data/lib/gocardless_pro/services/currency_exchange_rates_service.rb +1 -1
  30. data/lib/gocardless_pro/services/customer_bank_accounts_service.rb +1 -5
  31. data/lib/gocardless_pro/services/customers_service.rb +1 -3
  32. data/lib/gocardless_pro/services/events_service.rb +1 -1
  33. data/lib/gocardless_pro/services/instalment_schedules_service.rb +1 -7
  34. data/lib/gocardless_pro/services/institutions_service.rb +56 -0
  35. data/lib/gocardless_pro/services/mandate_import_entries_service.rb +1 -1
  36. data/lib/gocardless_pro/services/mandate_imports_service.rb +0 -6
  37. data/lib/gocardless_pro/services/mandates_service.rb +1 -7
  38. data/lib/gocardless_pro/services/payer_authorisations_service.rb +202 -0
  39. data/lib/gocardless_pro/services/payments_service.rb +1 -7
  40. data/lib/gocardless_pro/services/payout_items_service.rb +1 -1
  41. data/lib/gocardless_pro/services/payouts_service.rb +1 -1
  42. data/lib/gocardless_pro/services/redirect_flows_service.rb +0 -4
  43. data/lib/gocardless_pro/services/refunds_service.rb +1 -3
  44. data/lib/gocardless_pro/services/scenario_simulators_service.rb +170 -0
  45. data/lib/gocardless_pro/services/subscriptions_service.rb +10 -13
  46. data/lib/gocardless_pro/services/tax_rates_service.rb +1 -1
  47. data/lib/gocardless_pro/services/webhooks_service.rb +111 -0
  48. data/lib/gocardless_pro/version.rb +1 -1
  49. data/lib/gocardless_pro.rb +30 -0
  50. data/spec/api_service_spec.rb +12 -1
  51. data/spec/middlewares/raise_gocardless_errors_spec.rb +30 -0
  52. data/spec/resources/bank_authorisation_spec.rb +259 -0
  53. data/spec/resources/billing_request_flow_spec.rb +219 -0
  54. data/spec/resources/billing_request_spec.rb +782 -0
  55. data/spec/resources/billing_request_template_spec.rb +502 -0
  56. data/spec/resources/block_spec.rb +560 -0
  57. data/spec/resources/institution_spec.rb +108 -0
  58. data/spec/resources/payer_authorisation_spec.rb +418 -0
  59. data/spec/resources/redirect_flow_spec.rb +9 -0
  60. data/spec/resources/scenario_simulator_spec.rb +63 -0
  61. data/spec/resources/webhook_spec.rb +323 -0
  62. data/spec/services/bank_authorisations_service_spec.rb +353 -0
  63. data/spec/services/billing_request_flows_service_spec.rb +253 -0
  64. data/spec/services/billing_request_templates_service_spec.rb +789 -0
  65. data/spec/services/billing_requests_service_spec.rb +1082 -0
  66. data/spec/services/blocks_service_spec.rb +823 -0
  67. data/spec/services/creditor_bank_accounts_service_spec.rb +0 -13
  68. data/spec/services/creditors_service_spec.rb +0 -13
  69. data/spec/services/customer_bank_accounts_service_spec.rb +0 -13
  70. data/spec/services/customers_service_spec.rb +0 -13
  71. data/spec/services/instalment_schedules_service_spec.rb +0 -26
  72. data/spec/services/institutions_service_spec.rb +232 -0
  73. data/spec/services/mandate_imports_service_spec.rb +0 -13
  74. data/spec/services/mandates_service_spec.rb +0 -13
  75. data/spec/services/payer_authorisations_service_spec.rb +559 -0
  76. data/spec/services/payments_service_spec.rb +0 -13
  77. data/spec/services/redirect_flows_service_spec.rb +9 -13
  78. data/spec/services/refunds_service_spec.rb +0 -13
  79. data/spec/services/scenario_simulators_service_spec.rb +74 -0
  80. data/spec/services/subscriptions_service_spec.rb +0 -13
  81. data/spec/services/webhooks_service_spec.rb +545 -0
  82. metadata +64 -7
@@ -0,0 +1,1082 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoCardlessPro::Services::BillingRequestsService do
4
+ let(:client) do
5
+ GoCardlessPro::Client.new(
6
+ access_token: 'SECRET_TOKEN'
7
+ )
8
+ end
9
+
10
+ let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
+
12
+ describe '#list' do
13
+ describe 'with no filters' do
14
+ subject(:get_list_response) { client.billing_requests.list }
15
+
16
+ let(:body) do
17
+ {
18
+ 'billing_requests' => [{
19
+
20
+ 'actions' => 'actions-input',
21
+ 'created_at' => 'created_at-input',
22
+ 'id' => 'id-input',
23
+ 'links' => 'links-input',
24
+ 'mandate_request' => 'mandate_request-input',
25
+ 'metadata' => 'metadata-input',
26
+ 'payment_request' => 'payment_request-input',
27
+ 'resources' => 'resources-input',
28
+ 'status' => 'status-input',
29
+ }],
30
+ meta: {
31
+ cursors: {
32
+ before: nil,
33
+ after: 'ABC123',
34
+ },
35
+ },
36
+ }.to_json
37
+ end
38
+
39
+ before do
40
+ stub_request(:get, %r{.*api.gocardless.com/billing_requests}).to_return(
41
+ body: body,
42
+ headers: response_headers
43
+ )
44
+ end
45
+
46
+ it 'wraps each item in the resource class' do
47
+ expect(get_list_response.records.map(&:class).uniq.first).to eq(GoCardlessPro::Resources::BillingRequest)
48
+
49
+ expect(get_list_response.records.first.actions).to eq('actions-input')
50
+
51
+ expect(get_list_response.records.first.created_at).to eq('created_at-input')
52
+
53
+ expect(get_list_response.records.first.id).to eq('id-input')
54
+
55
+ expect(get_list_response.records.first.mandate_request).to eq('mandate_request-input')
56
+
57
+ expect(get_list_response.records.first.metadata).to eq('metadata-input')
58
+
59
+ expect(get_list_response.records.first.payment_request).to eq('payment_request-input')
60
+
61
+ expect(get_list_response.records.first.resources).to eq('resources-input')
62
+
63
+ expect(get_list_response.records.first.status).to eq('status-input')
64
+ end
65
+
66
+ it 'exposes the cursors for before and after' do
67
+ expect(get_list_response.before).to eq(nil)
68
+ expect(get_list_response.after).to eq('ABC123')
69
+ end
70
+
71
+ specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
72
+
73
+ describe 'retry behaviour' do
74
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
75
+
76
+ it 'retries timeouts' do
77
+ stub = stub_request(:get, %r{.*api.gocardless.com/billing_requests}).
78
+ to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
79
+
80
+ get_list_response
81
+ expect(stub).to have_been_requested.twice
82
+ end
83
+
84
+ it 'retries 5XX errors' do
85
+ stub = stub_request(:get, %r{.*api.gocardless.com/billing_requests}).
86
+ to_return(status: 502,
87
+ headers: { 'Content-Type' => 'text/html' },
88
+ body: '<html><body>Response from Cloudflare</body></html>').
89
+ then.to_return(status: 200, headers: response_headers, body: body)
90
+
91
+ get_list_response
92
+ expect(stub).to have_been_requested.twice
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#all' do
99
+ let!(:first_response_stub) do
100
+ stub_request(:get, %r{.*api.gocardless.com/billing_requests$}).to_return(
101
+ body: {
102
+ 'billing_requests' => [{
103
+
104
+ 'actions' => 'actions-input',
105
+ 'created_at' => 'created_at-input',
106
+ 'id' => 'id-input',
107
+ 'links' => 'links-input',
108
+ 'mandate_request' => 'mandate_request-input',
109
+ 'metadata' => 'metadata-input',
110
+ 'payment_request' => 'payment_request-input',
111
+ 'resources' => 'resources-input',
112
+ 'status' => 'status-input',
113
+ }],
114
+ meta: {
115
+ cursors: { after: 'AB345' },
116
+ limit: 1,
117
+ },
118
+ }.to_json,
119
+ headers: response_headers
120
+ )
121
+ end
122
+
123
+ let!(:second_response_stub) do
124
+ stub_request(:get, %r{.*api.gocardless.com/billing_requests\?after=AB345}).to_return(
125
+ body: {
126
+ 'billing_requests' => [{
127
+
128
+ 'actions' => 'actions-input',
129
+ 'created_at' => 'created_at-input',
130
+ 'id' => 'id-input',
131
+ 'links' => 'links-input',
132
+ 'mandate_request' => 'mandate_request-input',
133
+ 'metadata' => 'metadata-input',
134
+ 'payment_request' => 'payment_request-input',
135
+ 'resources' => 'resources-input',
136
+ 'status' => 'status-input',
137
+ }],
138
+ meta: {
139
+ limit: 2,
140
+ cursors: {},
141
+ },
142
+ }.to_json,
143
+ headers: response_headers
144
+ )
145
+ end
146
+
147
+ it 'automatically makes the extra requests' do
148
+ expect(client.billing_requests.all.to_a.length).to eq(2)
149
+ expect(first_response_stub).to have_been_requested
150
+ expect(second_response_stub).to have_been_requested
151
+ end
152
+
153
+ describe 'retry behaviour' do
154
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
155
+
156
+ it 'retries timeouts' do
157
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/billing_requests$}).to_return(
158
+ body: {
159
+ 'billing_requests' => [{
160
+
161
+ 'actions' => 'actions-input',
162
+ 'created_at' => 'created_at-input',
163
+ 'id' => 'id-input',
164
+ 'links' => 'links-input',
165
+ 'mandate_request' => 'mandate_request-input',
166
+ 'metadata' => 'metadata-input',
167
+ 'payment_request' => 'payment_request-input',
168
+ 'resources' => 'resources-input',
169
+ 'status' => 'status-input',
170
+ }],
171
+ meta: {
172
+ cursors: { after: 'AB345' },
173
+ limit: 1,
174
+ },
175
+ }.to_json,
176
+ headers: response_headers
177
+ )
178
+
179
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/billing_requests\?after=AB345}).
180
+ to_timeout.then.
181
+ to_return(
182
+ body: {
183
+ 'billing_requests' => [{
184
+
185
+ 'actions' => 'actions-input',
186
+ 'created_at' => 'created_at-input',
187
+ 'id' => 'id-input',
188
+ 'links' => 'links-input',
189
+ 'mandate_request' => 'mandate_request-input',
190
+ 'metadata' => 'metadata-input',
191
+ 'payment_request' => 'payment_request-input',
192
+ 'resources' => 'resources-input',
193
+ 'status' => 'status-input',
194
+ }],
195
+ meta: {
196
+ limit: 2,
197
+ cursors: {},
198
+ },
199
+ }.to_json,
200
+ headers: response_headers
201
+ )
202
+
203
+ client.billing_requests.all.to_a
204
+
205
+ expect(first_response_stub).to have_been_requested
206
+ expect(second_response_stub).to have_been_requested.twice
207
+ end
208
+
209
+ it 'retries 5XX errors' do
210
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/billing_requests$}).to_return(
211
+ body: {
212
+ 'billing_requests' => [{
213
+
214
+ 'actions' => 'actions-input',
215
+ 'created_at' => 'created_at-input',
216
+ 'id' => 'id-input',
217
+ 'links' => 'links-input',
218
+ 'mandate_request' => 'mandate_request-input',
219
+ 'metadata' => 'metadata-input',
220
+ 'payment_request' => 'payment_request-input',
221
+ 'resources' => 'resources-input',
222
+ 'status' => 'status-input',
223
+ }],
224
+ meta: {
225
+ cursors: { after: 'AB345' },
226
+ limit: 1,
227
+ },
228
+ }.to_json,
229
+ headers: response_headers
230
+ )
231
+
232
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/billing_requests\?after=AB345}).
233
+ to_return(
234
+ status: 502,
235
+ body: '<html><body>Response from Cloudflare</body></html>',
236
+ headers: { 'Content-Type' => 'text/html' }
237
+ ).then.to_return(
238
+ body: {
239
+ 'billing_requests' => [{
240
+
241
+ 'actions' => 'actions-input',
242
+ 'created_at' => 'created_at-input',
243
+ 'id' => 'id-input',
244
+ 'links' => 'links-input',
245
+ 'mandate_request' => 'mandate_request-input',
246
+ 'metadata' => 'metadata-input',
247
+ 'payment_request' => 'payment_request-input',
248
+ 'resources' => 'resources-input',
249
+ 'status' => 'status-input',
250
+ }],
251
+ meta: {
252
+ limit: 2,
253
+ cursors: {},
254
+ },
255
+ }.to_json,
256
+ headers: response_headers
257
+ )
258
+
259
+ client.billing_requests.all.to_a
260
+
261
+ expect(first_response_stub).to have_been_requested
262
+ expect(second_response_stub).to have_been_requested.twice
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#create' do
268
+ subject(:post_create_response) { client.billing_requests.create(params: new_resource) }
269
+ context 'with a valid request' do
270
+ let(:new_resource) do
271
+ {
272
+
273
+ 'actions' => 'actions-input',
274
+ 'created_at' => 'created_at-input',
275
+ 'id' => 'id-input',
276
+ 'links' => 'links-input',
277
+ 'mandate_request' => 'mandate_request-input',
278
+ 'metadata' => 'metadata-input',
279
+ 'payment_request' => 'payment_request-input',
280
+ 'resources' => 'resources-input',
281
+ 'status' => 'status-input',
282
+ }
283
+ end
284
+
285
+ before do
286
+ stub_request(:post, %r{.*api.gocardless.com/billing_requests}).
287
+ with(
288
+ body: {
289
+ 'billing_requests' => {
290
+
291
+ 'actions' => 'actions-input',
292
+ 'created_at' => 'created_at-input',
293
+ 'id' => 'id-input',
294
+ 'links' => 'links-input',
295
+ 'mandate_request' => 'mandate_request-input',
296
+ 'metadata' => 'metadata-input',
297
+ 'payment_request' => 'payment_request-input',
298
+ 'resources' => 'resources-input',
299
+ 'status' => 'status-input',
300
+ },
301
+ }
302
+ ).
303
+ to_return(
304
+ body: {
305
+ 'billing_requests' =>
306
+
307
+ {
308
+
309
+ 'actions' => 'actions-input',
310
+ 'created_at' => 'created_at-input',
311
+ 'id' => 'id-input',
312
+ 'links' => 'links-input',
313
+ 'mandate_request' => 'mandate_request-input',
314
+ 'metadata' => 'metadata-input',
315
+ 'payment_request' => 'payment_request-input',
316
+ 'resources' => 'resources-input',
317
+ 'status' => 'status-input',
318
+ },
319
+
320
+ }.to_json,
321
+ headers: response_headers
322
+ )
323
+ end
324
+
325
+ it 'creates and returns the resource' do
326
+ expect(post_create_response).to be_a(GoCardlessPro::Resources::BillingRequest)
327
+ end
328
+
329
+ describe 'retry behaviour' do
330
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
331
+
332
+ it 'retries timeouts' do
333
+ stub = stub_request(:post, %r{.*api.gocardless.com/billing_requests}).
334
+ to_timeout.then.to_return(status: 200, headers: response_headers)
335
+
336
+ post_create_response
337
+ expect(stub).to have_been_requested.twice
338
+ end
339
+
340
+ it 'retries 5XX errors' do
341
+ stub = stub_request(:post, %r{.*api.gocardless.com/billing_requests}).
342
+ to_return(status: 502,
343
+ headers: { 'Content-Type' => 'text/html' },
344
+ body: '<html><body>Response from Cloudflare</body></html>').
345
+ then.to_return(status: 200, headers: response_headers)
346
+
347
+ post_create_response
348
+ expect(stub).to have_been_requested.twice
349
+ end
350
+ end
351
+ end
352
+
353
+ context 'with a request that returns a validation error' do
354
+ let(:new_resource) { {} }
355
+
356
+ before do
357
+ stub_request(:post, %r{.*api.gocardless.com/billing_requests}).to_return(
358
+ body: {
359
+ error: {
360
+ type: 'validation_failed',
361
+ code: 422,
362
+ errors: [
363
+ { message: 'test error message', field: 'test_field' },
364
+ ],
365
+ },
366
+ }.to_json,
367
+ headers: response_headers,
368
+ status: 422
369
+ )
370
+ end
371
+
372
+ it 'throws the correct error' do
373
+ expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
374
+ end
375
+ end
376
+
377
+ context 'with a request that returns an idempotent creation conflict error' do
378
+ let(:id) { 'ID123' }
379
+
380
+ let(:new_resource) do
381
+ {
382
+
383
+ 'actions' => 'actions-input',
384
+ 'created_at' => 'created_at-input',
385
+ 'id' => 'id-input',
386
+ 'links' => 'links-input',
387
+ 'mandate_request' => 'mandate_request-input',
388
+ 'metadata' => 'metadata-input',
389
+ 'payment_request' => 'payment_request-input',
390
+ 'resources' => 'resources-input',
391
+ 'status' => 'status-input',
392
+ }
393
+ end
394
+
395
+ let!(:post_stub) do
396
+ stub_request(:post, %r{.*api.gocardless.com/billing_requests}).to_return(
397
+ body: {
398
+ error: {
399
+ type: 'invalid_state',
400
+ code: 409,
401
+ errors: [
402
+ {
403
+ message: 'A resource has already been created with this idempotency key',
404
+ reason: 'idempotent_creation_conflict',
405
+ links: {
406
+ conflicting_resource_id: id,
407
+ },
408
+ },
409
+ ],
410
+ },
411
+ }.to_json,
412
+ headers: response_headers,
413
+ status: 409
414
+ )
415
+ end
416
+
417
+ let!(:get_stub) do
418
+ stub_url = "/billing_requests/#{id}"
419
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).
420
+ to_return(
421
+ body: {
422
+ 'billing_requests' => {
423
+
424
+ 'actions' => 'actions-input',
425
+ 'created_at' => 'created_at-input',
426
+ 'id' => 'id-input',
427
+ 'links' => 'links-input',
428
+ 'mandate_request' => 'mandate_request-input',
429
+ 'metadata' => 'metadata-input',
430
+ 'payment_request' => 'payment_request-input',
431
+ 'resources' => 'resources-input',
432
+ 'status' => 'status-input',
433
+ },
434
+ }.to_json,
435
+ headers: response_headers
436
+ )
437
+ end
438
+
439
+ context 'with default behaviour' do
440
+ it 'fetches the already-created resource' do
441
+ post_create_response
442
+ expect(post_stub).to have_been_requested
443
+ expect(get_stub).to have_been_requested
444
+ end
445
+ end
446
+
447
+ context 'with on_idempotency_conflict: :raise' do
448
+ let(:client) do
449
+ GoCardlessPro::Client.new(
450
+ access_token: 'SECRET_TOKEN',
451
+ on_idempotency_conflict: :raise
452
+ )
453
+ end
454
+
455
+ it 'raises an IdempotencyConflict error' do
456
+ expect { post_create_response }.
457
+ to raise_error(GoCardlessPro::IdempotencyConflict)
458
+ end
459
+ end
460
+ end
461
+ end
462
+
463
+ describe '#get' do
464
+ let(:id) { 'ID123' }
465
+
466
+ subject(:get_response) { client.billing_requests.get(id) }
467
+
468
+ context 'passing in a custom header' do
469
+ let!(:stub) do
470
+ stub_url = '/billing_requests/:identity'.gsub(':identity', id)
471
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).
472
+ with(headers: { 'Foo' => 'Bar' }).
473
+ to_return(
474
+ body: {
475
+ 'billing_requests' => {
476
+
477
+ 'actions' => 'actions-input',
478
+ 'created_at' => 'created_at-input',
479
+ 'id' => 'id-input',
480
+ 'links' => 'links-input',
481
+ 'mandate_request' => 'mandate_request-input',
482
+ 'metadata' => 'metadata-input',
483
+ 'payment_request' => 'payment_request-input',
484
+ 'resources' => 'resources-input',
485
+ 'status' => 'status-input',
486
+ },
487
+ }.to_json,
488
+ headers: response_headers
489
+ )
490
+ end
491
+
492
+ subject(:get_response) do
493
+ client.billing_requests.get(id, headers: {
494
+ 'Foo' => 'Bar',
495
+ })
496
+ end
497
+
498
+ it 'includes the header' do
499
+ get_response
500
+ expect(stub).to have_been_requested
501
+ end
502
+ end
503
+
504
+ context 'when there is a billing_request to return' do
505
+ before do
506
+ stub_url = '/billing_requests/:identity'.gsub(':identity', id)
507
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
508
+ body: {
509
+ 'billing_requests' => {
510
+
511
+ 'actions' => 'actions-input',
512
+ 'created_at' => 'created_at-input',
513
+ 'id' => 'id-input',
514
+ 'links' => 'links-input',
515
+ 'mandate_request' => 'mandate_request-input',
516
+ 'metadata' => 'metadata-input',
517
+ 'payment_request' => 'payment_request-input',
518
+ 'resources' => 'resources-input',
519
+ 'status' => 'status-input',
520
+ },
521
+ }.to_json,
522
+ headers: response_headers
523
+ )
524
+ end
525
+
526
+ it 'wraps the response in a resource' do
527
+ expect(get_response).to be_a(GoCardlessPro::Resources::BillingRequest)
528
+ end
529
+ end
530
+
531
+ context 'when nothing is returned' do
532
+ before do
533
+ stub_url = '/billing_requests/:identity'.gsub(':identity', id)
534
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
535
+ body: '',
536
+ headers: response_headers
537
+ )
538
+ end
539
+
540
+ it 'returns nil' do
541
+ expect(get_response).to be_nil
542
+ end
543
+ end
544
+
545
+ context "when an ID is specified which can't be included in a valid URI" do
546
+ let(:id) { '`' }
547
+
548
+ it "doesn't raise an error" do
549
+ expect { get_response }.to_not raise_error(/bad URI/)
550
+ end
551
+ end
552
+
553
+ describe 'retry behaviour' do
554
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
555
+
556
+ it 'retries timeouts' do
557
+ stub_url = '/billing_requests/:identity'.gsub(':identity', id)
558
+
559
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/).
560
+ to_timeout.then.to_return(status: 200, headers: response_headers)
561
+
562
+ get_response
563
+ expect(stub).to have_been_requested.twice
564
+ end
565
+
566
+ it 'retries 5XX errors, other than 500s' do
567
+ stub_url = '/billing_requests/:identity'.gsub(':identity', id)
568
+
569
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/).
570
+ to_return(status: 502,
571
+ headers: { 'Content-Type' => 'text/html' },
572
+ body: '<html><body>Response from Cloudflare</body></html>').
573
+ then.to_return(status: 200, headers: response_headers)
574
+
575
+ get_response
576
+ expect(stub).to have_been_requested.twice
577
+ end
578
+
579
+ it 'retries 500 errors returned by the API' do
580
+ stub_url = '/billing_requests/:identity'.gsub(':identity', id)
581
+
582
+ gocardless_error = {
583
+ 'error' => {
584
+ 'message' => 'Internal server error',
585
+ 'documentation_url' => 'https://developer.gocardless.com/#gocardless',
586
+ 'errors' => [{
587
+ 'message' => 'Internal server error',
588
+ 'reason' => 'internal_server_error',
589
+ }],
590
+ 'type' => 'gocardless',
591
+ 'code' => 500,
592
+ 'request_id' => 'dummy_request_id',
593
+ 'id' => 'dummy_exception_id',
594
+ },
595
+ }
596
+
597
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/).
598
+ to_return(status: 500,
599
+ headers: response_headers,
600
+ body: gocardless_error.to_json).
601
+ then.to_return(status: 200, headers: response_headers)
602
+
603
+ get_response
604
+ expect(stub).to have_been_requested.twice
605
+ end
606
+ end
607
+ end
608
+
609
+ describe '#collect_customer_details' do
610
+ subject(:post_response) { client.billing_requests.collect_customer_details(resource_id) }
611
+
612
+ let(:resource_id) { 'ABC123' }
613
+
614
+ let!(:stub) do
615
+ # /billing_requests/%v/actions/collect_customer_details
616
+ stub_url = '/billing_requests/:identity/actions/collect_customer_details'.gsub(':identity', resource_id)
617
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
618
+ body: {
619
+ 'billing_requests' => {
620
+
621
+ 'actions' => 'actions-input',
622
+ 'created_at' => 'created_at-input',
623
+ 'id' => 'id-input',
624
+ 'links' => 'links-input',
625
+ 'mandate_request' => 'mandate_request-input',
626
+ 'metadata' => 'metadata-input',
627
+ 'payment_request' => 'payment_request-input',
628
+ 'resources' => 'resources-input',
629
+ 'status' => 'status-input',
630
+ },
631
+ }.to_json,
632
+ headers: response_headers
633
+ )
634
+ end
635
+
636
+ it 'wraps the response and calls the right endpoint' do
637
+ expect(post_response).to be_a(GoCardlessPro::Resources::BillingRequest)
638
+
639
+ expect(stub).to have_been_requested
640
+ end
641
+
642
+ describe 'retry behaviour' do
643
+ it "doesn't retry errors" do
644
+ stub_url = '/billing_requests/:identity/actions/collect_customer_details'.gsub(':identity', resource_id)
645
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/).
646
+ to_timeout
647
+
648
+ expect { post_response }.to raise_error(Faraday::ConnectionFailed)
649
+ expect(stub).to have_been_requested
650
+ end
651
+ end
652
+
653
+ context 'when the request needs a body and custom header' do
654
+ let(:body) { { foo: 'bar' } }
655
+ let(:headers) { { 'Foo' => 'Bar' } }
656
+ subject(:post_response) { client.billing_requests.collect_customer_details(resource_id, body, headers) }
657
+
658
+ let(:resource_id) { 'ABC123' }
659
+
660
+ let!(:stub) do
661
+ # /billing_requests/%v/actions/collect_customer_details
662
+ stub_url = '/billing_requests/:identity/actions/collect_customer_details'.gsub(':identity', resource_id)
663
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
664
+ with(
665
+ body: { foo: 'bar' },
666
+ headers: { 'Foo' => 'Bar' }
667
+ ).to_return(
668
+ body: {
669
+ 'billing_requests' => {
670
+
671
+ 'actions' => 'actions-input',
672
+ 'created_at' => 'created_at-input',
673
+ 'id' => 'id-input',
674
+ 'links' => 'links-input',
675
+ 'mandate_request' => 'mandate_request-input',
676
+ 'metadata' => 'metadata-input',
677
+ 'payment_request' => 'payment_request-input',
678
+ 'resources' => 'resources-input',
679
+ 'status' => 'status-input',
680
+ },
681
+ }.to_json,
682
+ headers: response_headers
683
+ )
684
+ end
685
+ end
686
+ end
687
+
688
+ describe '#collect_bank_account' do
689
+ subject(:post_response) { client.billing_requests.collect_bank_account(resource_id) }
690
+
691
+ let(:resource_id) { 'ABC123' }
692
+
693
+ let!(:stub) do
694
+ # /billing_requests/%v/actions/collect_bank_account
695
+ stub_url = '/billing_requests/:identity/actions/collect_bank_account'.gsub(':identity', resource_id)
696
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
697
+ body: {
698
+ 'billing_requests' => {
699
+
700
+ 'actions' => 'actions-input',
701
+ 'created_at' => 'created_at-input',
702
+ 'id' => 'id-input',
703
+ 'links' => 'links-input',
704
+ 'mandate_request' => 'mandate_request-input',
705
+ 'metadata' => 'metadata-input',
706
+ 'payment_request' => 'payment_request-input',
707
+ 'resources' => 'resources-input',
708
+ 'status' => 'status-input',
709
+ },
710
+ }.to_json,
711
+ headers: response_headers
712
+ )
713
+ end
714
+
715
+ it 'wraps the response and calls the right endpoint' do
716
+ expect(post_response).to be_a(GoCardlessPro::Resources::BillingRequest)
717
+
718
+ expect(stub).to have_been_requested
719
+ end
720
+
721
+ describe 'retry behaviour' do
722
+ it "doesn't retry errors" do
723
+ stub_url = '/billing_requests/:identity/actions/collect_bank_account'.gsub(':identity', resource_id)
724
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/).
725
+ to_timeout
726
+
727
+ expect { post_response }.to raise_error(Faraday::ConnectionFailed)
728
+ expect(stub).to have_been_requested
729
+ end
730
+ end
731
+
732
+ context 'when the request needs a body and custom header' do
733
+ let(:body) { { foo: 'bar' } }
734
+ let(:headers) { { 'Foo' => 'Bar' } }
735
+ subject(:post_response) { client.billing_requests.collect_bank_account(resource_id, body, headers) }
736
+
737
+ let(:resource_id) { 'ABC123' }
738
+
739
+ let!(:stub) do
740
+ # /billing_requests/%v/actions/collect_bank_account
741
+ stub_url = '/billing_requests/:identity/actions/collect_bank_account'.gsub(':identity', resource_id)
742
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
743
+ with(
744
+ body: { foo: 'bar' },
745
+ headers: { 'Foo' => 'Bar' }
746
+ ).to_return(
747
+ body: {
748
+ 'billing_requests' => {
749
+
750
+ 'actions' => 'actions-input',
751
+ 'created_at' => 'created_at-input',
752
+ 'id' => 'id-input',
753
+ 'links' => 'links-input',
754
+ 'mandate_request' => 'mandate_request-input',
755
+ 'metadata' => 'metadata-input',
756
+ 'payment_request' => 'payment_request-input',
757
+ 'resources' => 'resources-input',
758
+ 'status' => 'status-input',
759
+ },
760
+ }.to_json,
761
+ headers: response_headers
762
+ )
763
+ end
764
+ end
765
+ end
766
+
767
+ describe '#fulfil' do
768
+ subject(:post_response) { client.billing_requests.fulfil(resource_id) }
769
+
770
+ let(:resource_id) { 'ABC123' }
771
+
772
+ let!(:stub) do
773
+ # /billing_requests/%v/actions/fulfil
774
+ stub_url = '/billing_requests/:identity/actions/fulfil'.gsub(':identity', resource_id)
775
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
776
+ body: {
777
+ 'billing_requests' => {
778
+
779
+ 'actions' => 'actions-input',
780
+ 'created_at' => 'created_at-input',
781
+ 'id' => 'id-input',
782
+ 'links' => 'links-input',
783
+ 'mandate_request' => 'mandate_request-input',
784
+ 'metadata' => 'metadata-input',
785
+ 'payment_request' => 'payment_request-input',
786
+ 'resources' => 'resources-input',
787
+ 'status' => 'status-input',
788
+ },
789
+ }.to_json,
790
+ headers: response_headers
791
+ )
792
+ end
793
+
794
+ it 'wraps the response and calls the right endpoint' do
795
+ expect(post_response).to be_a(GoCardlessPro::Resources::BillingRequest)
796
+
797
+ expect(stub).to have_been_requested
798
+ end
799
+
800
+ describe 'retry behaviour' do
801
+ it "doesn't retry errors" do
802
+ stub_url = '/billing_requests/:identity/actions/fulfil'.gsub(':identity', resource_id)
803
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/).
804
+ to_timeout
805
+
806
+ expect { post_response }.to raise_error(Faraday::ConnectionFailed)
807
+ expect(stub).to have_been_requested
808
+ end
809
+ end
810
+
811
+ context 'when the request needs a body and custom header' do
812
+ let(:body) { { foo: 'bar' } }
813
+ let(:headers) { { 'Foo' => 'Bar' } }
814
+ subject(:post_response) { client.billing_requests.fulfil(resource_id, body, headers) }
815
+
816
+ let(:resource_id) { 'ABC123' }
817
+
818
+ let!(:stub) do
819
+ # /billing_requests/%v/actions/fulfil
820
+ stub_url = '/billing_requests/:identity/actions/fulfil'.gsub(':identity', resource_id)
821
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
822
+ with(
823
+ body: { foo: 'bar' },
824
+ headers: { 'Foo' => 'Bar' }
825
+ ).to_return(
826
+ body: {
827
+ 'billing_requests' => {
828
+
829
+ 'actions' => 'actions-input',
830
+ 'created_at' => 'created_at-input',
831
+ 'id' => 'id-input',
832
+ 'links' => 'links-input',
833
+ 'mandate_request' => 'mandate_request-input',
834
+ 'metadata' => 'metadata-input',
835
+ 'payment_request' => 'payment_request-input',
836
+ 'resources' => 'resources-input',
837
+ 'status' => 'status-input',
838
+ },
839
+ }.to_json,
840
+ headers: response_headers
841
+ )
842
+ end
843
+ end
844
+ end
845
+
846
+ describe '#confirm_payer_details' do
847
+ subject(:post_response) { client.billing_requests.confirm_payer_details(resource_id) }
848
+
849
+ let(:resource_id) { 'ABC123' }
850
+
851
+ let!(:stub) do
852
+ # /billing_requests/%v/actions/confirm_payer_details
853
+ stub_url = '/billing_requests/:identity/actions/confirm_payer_details'.gsub(':identity', resource_id)
854
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
855
+ body: {
856
+ 'billing_requests' => {
857
+
858
+ 'actions' => 'actions-input',
859
+ 'created_at' => 'created_at-input',
860
+ 'id' => 'id-input',
861
+ 'links' => 'links-input',
862
+ 'mandate_request' => 'mandate_request-input',
863
+ 'metadata' => 'metadata-input',
864
+ 'payment_request' => 'payment_request-input',
865
+ 'resources' => 'resources-input',
866
+ 'status' => 'status-input',
867
+ },
868
+ }.to_json,
869
+ headers: response_headers
870
+ )
871
+ end
872
+
873
+ it 'wraps the response and calls the right endpoint' do
874
+ expect(post_response).to be_a(GoCardlessPro::Resources::BillingRequest)
875
+
876
+ expect(stub).to have_been_requested
877
+ end
878
+
879
+ describe 'retry behaviour' do
880
+ it "doesn't retry errors" do
881
+ stub_url = '/billing_requests/:identity/actions/confirm_payer_details'.gsub(':identity', resource_id)
882
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/).
883
+ to_timeout
884
+
885
+ expect { post_response }.to raise_error(Faraday::ConnectionFailed)
886
+ expect(stub).to have_been_requested
887
+ end
888
+ end
889
+
890
+ context 'when the request needs a body and custom header' do
891
+ let(:body) { { foo: 'bar' } }
892
+ let(:headers) { { 'Foo' => 'Bar' } }
893
+ subject(:post_response) { client.billing_requests.confirm_payer_details(resource_id, body, headers) }
894
+
895
+ let(:resource_id) { 'ABC123' }
896
+
897
+ let!(:stub) do
898
+ # /billing_requests/%v/actions/confirm_payer_details
899
+ stub_url = '/billing_requests/:identity/actions/confirm_payer_details'.gsub(':identity', resource_id)
900
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
901
+ with(
902
+ body: { foo: 'bar' },
903
+ headers: { 'Foo' => 'Bar' }
904
+ ).to_return(
905
+ body: {
906
+ 'billing_requests' => {
907
+
908
+ 'actions' => 'actions-input',
909
+ 'created_at' => 'created_at-input',
910
+ 'id' => 'id-input',
911
+ 'links' => 'links-input',
912
+ 'mandate_request' => 'mandate_request-input',
913
+ 'metadata' => 'metadata-input',
914
+ 'payment_request' => 'payment_request-input',
915
+ 'resources' => 'resources-input',
916
+ 'status' => 'status-input',
917
+ },
918
+ }.to_json,
919
+ headers: response_headers
920
+ )
921
+ end
922
+ end
923
+ end
924
+
925
+ describe '#cancel' do
926
+ subject(:post_response) { client.billing_requests.cancel(resource_id) }
927
+
928
+ let(:resource_id) { 'ABC123' }
929
+
930
+ let!(:stub) do
931
+ # /billing_requests/%v/actions/cancel
932
+ stub_url = '/billing_requests/:identity/actions/cancel'.gsub(':identity', resource_id)
933
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
934
+ body: {
935
+ 'billing_requests' => {
936
+
937
+ 'actions' => 'actions-input',
938
+ 'created_at' => 'created_at-input',
939
+ 'id' => 'id-input',
940
+ 'links' => 'links-input',
941
+ 'mandate_request' => 'mandate_request-input',
942
+ 'metadata' => 'metadata-input',
943
+ 'payment_request' => 'payment_request-input',
944
+ 'resources' => 'resources-input',
945
+ 'status' => 'status-input',
946
+ },
947
+ }.to_json,
948
+ headers: response_headers
949
+ )
950
+ end
951
+
952
+ it 'wraps the response and calls the right endpoint' do
953
+ expect(post_response).to be_a(GoCardlessPro::Resources::BillingRequest)
954
+
955
+ expect(stub).to have_been_requested
956
+ end
957
+
958
+ describe 'retry behaviour' do
959
+ it "doesn't retry errors" do
960
+ stub_url = '/billing_requests/:identity/actions/cancel'.gsub(':identity', resource_id)
961
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/).
962
+ to_timeout
963
+
964
+ expect { post_response }.to raise_error(Faraday::ConnectionFailed)
965
+ expect(stub).to have_been_requested
966
+ end
967
+ end
968
+
969
+ context 'when the request needs a body and custom header' do
970
+ let(:body) { { foo: 'bar' } }
971
+ let(:headers) { { 'Foo' => 'Bar' } }
972
+ subject(:post_response) { client.billing_requests.cancel(resource_id, body, headers) }
973
+
974
+ let(:resource_id) { 'ABC123' }
975
+
976
+ let!(:stub) do
977
+ # /billing_requests/%v/actions/cancel
978
+ stub_url = '/billing_requests/:identity/actions/cancel'.gsub(':identity', resource_id)
979
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
980
+ with(
981
+ body: { foo: 'bar' },
982
+ headers: { 'Foo' => 'Bar' }
983
+ ).to_return(
984
+ body: {
985
+ 'billing_requests' => {
986
+
987
+ 'actions' => 'actions-input',
988
+ 'created_at' => 'created_at-input',
989
+ 'id' => 'id-input',
990
+ 'links' => 'links-input',
991
+ 'mandate_request' => 'mandate_request-input',
992
+ 'metadata' => 'metadata-input',
993
+ 'payment_request' => 'payment_request-input',
994
+ 'resources' => 'resources-input',
995
+ 'status' => 'status-input',
996
+ },
997
+ }.to_json,
998
+ headers: response_headers
999
+ )
1000
+ end
1001
+ end
1002
+ end
1003
+
1004
+ describe '#notify' do
1005
+ subject(:post_response) { client.billing_requests.notify(resource_id) }
1006
+
1007
+ let(:resource_id) { 'ABC123' }
1008
+
1009
+ let!(:stub) do
1010
+ # /billing_requests/%v/actions/notify
1011
+ stub_url = '/billing_requests/:identity/actions/notify'.gsub(':identity', resource_id)
1012
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
1013
+ body: {
1014
+ 'billing_requests' => {
1015
+
1016
+ 'actions' => 'actions-input',
1017
+ 'created_at' => 'created_at-input',
1018
+ 'id' => 'id-input',
1019
+ 'links' => 'links-input',
1020
+ 'mandate_request' => 'mandate_request-input',
1021
+ 'metadata' => 'metadata-input',
1022
+ 'payment_request' => 'payment_request-input',
1023
+ 'resources' => 'resources-input',
1024
+ 'status' => 'status-input',
1025
+ },
1026
+ }.to_json,
1027
+ headers: response_headers
1028
+ )
1029
+ end
1030
+
1031
+ it 'wraps the response and calls the right endpoint' do
1032
+ expect(post_response).to be_a(GoCardlessPro::Resources::BillingRequest)
1033
+
1034
+ expect(stub).to have_been_requested
1035
+ end
1036
+
1037
+ describe 'retry behaviour' do
1038
+ it "doesn't retry errors" do
1039
+ stub_url = '/billing_requests/:identity/actions/notify'.gsub(':identity', resource_id)
1040
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/).
1041
+ to_timeout
1042
+
1043
+ expect { post_response }.to raise_error(Faraday::ConnectionFailed)
1044
+ expect(stub).to have_been_requested
1045
+ end
1046
+ end
1047
+
1048
+ context 'when the request needs a body and custom header' do
1049
+ let(:body) { { foo: 'bar' } }
1050
+ let(:headers) { { 'Foo' => 'Bar' } }
1051
+ subject(:post_response) { client.billing_requests.notify(resource_id, body, headers) }
1052
+
1053
+ let(:resource_id) { 'ABC123' }
1054
+
1055
+ let!(:stub) do
1056
+ # /billing_requests/%v/actions/notify
1057
+ stub_url = '/billing_requests/:identity/actions/notify'.gsub(':identity', resource_id)
1058
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).
1059
+ with(
1060
+ body: { foo: 'bar' },
1061
+ headers: { 'Foo' => 'Bar' }
1062
+ ).to_return(
1063
+ body: {
1064
+ 'billing_requests' => {
1065
+
1066
+ 'actions' => 'actions-input',
1067
+ 'created_at' => 'created_at-input',
1068
+ 'id' => 'id-input',
1069
+ 'links' => 'links-input',
1070
+ 'mandate_request' => 'mandate_request-input',
1071
+ 'metadata' => 'metadata-input',
1072
+ 'payment_request' => 'payment_request-input',
1073
+ 'resources' => 'resources-input',
1074
+ 'status' => 'status-input',
1075
+ },
1076
+ }.to_json,
1077
+ headers: response_headers
1078
+ )
1079
+ end
1080
+ end
1081
+ end
1082
+ end