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::PaymentsService 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.payments.create(params: new_resource) }
12
14
  context 'with a valid request' do
@@ -67,13 +69,36 @@ describe GoCardlessPro::Services::PaymentsService do
67
69
  }
68
70
 
69
71
  }.to_json,
70
- headers: { 'Content-Type' => 'application/json' }
72
+ headers: response_headers
71
73
  )
72
74
  end
73
75
 
74
76
  it 'creates and returns the resource' do
75
77
  expect(post_create_response).to be_a(GoCardlessPro::Resources::Payment)
76
78
  end
79
+
80
+ describe 'retry behaviour' do
81
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
82
+
83
+ it 'retries timeouts' do
84
+ stub = stub_request(:post, %r{.*api.gocardless.com/payments})
85
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
86
+
87
+ post_create_response
88
+ expect(stub).to have_been_requested.twice
89
+ end
90
+
91
+ it 'retries 5XX errors' do
92
+ stub = stub_request(:post, %r{.*api.gocardless.com/payments})
93
+ .to_return(status: 502,
94
+ headers: { 'Content-Type' => 'text/html' },
95
+ body: '<html><body>Response from Cloudflare</body></html>')
96
+ .then.to_return(status: 200, headers: response_headers)
97
+
98
+ post_create_response
99
+ expect(stub).to have_been_requested.twice
100
+ end
101
+ end
77
102
  end
78
103
 
79
104
  context 'with a request that returns a validation error' do
@@ -90,7 +115,7 @@ describe GoCardlessPro::Services::PaymentsService do
90
115
  ]
91
116
  }
92
117
  }.to_json,
93
- headers: { 'Content-Type' => 'application/json' },
118
+ headers: response_headers,
94
119
  status: 422
95
120
  )
96
121
  end
@@ -99,37 +124,114 @@ describe GoCardlessPro::Services::PaymentsService do
99
124
  expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
100
125
  end
101
126
  end
127
+
128
+ context 'with a request that returns an idempotent creation conflict error' do
129
+ let(:id) { 'ID123' }
130
+
131
+ let(:new_resource) do
132
+ {
133
+
134
+ 'amount' => 'amount-input',
135
+ 'amount_refunded' => 'amount_refunded-input',
136
+ 'charge_date' => 'charge_date-input',
137
+ 'created_at' => 'created_at-input',
138
+ 'currency' => 'currency-input',
139
+ 'description' => 'description-input',
140
+ 'id' => 'id-input',
141
+ 'links' => 'links-input',
142
+ 'metadata' => 'metadata-input',
143
+ 'reference' => 'reference-input',
144
+ 'status' => 'status-input'
145
+ }
146
+ end
147
+
148
+ let!(:post_stub) do
149
+ stub_request(:post, %r{.*api.gocardless.com/payments}).to_return(
150
+ body: {
151
+ error: {
152
+ type: 'invalid_state',
153
+ code: 409,
154
+ errors: [
155
+ {
156
+ message: 'A resource has already been created with this idempotency key',
157
+ reason: 'idempotent_creation_conflict',
158
+ links: {
159
+ conflicting_resource_id: id
160
+ }
161
+ }
162
+ ]
163
+ }
164
+ }.to_json,
165
+ headers: response_headers,
166
+ status: 409
167
+ )
168
+ end
169
+
170
+ let!(:get_stub) do
171
+ stub_url = "/payments/#{id}"
172
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
173
+ .to_return(
174
+ body: {
175
+ 'payments' => {
176
+
177
+ 'amount' => 'amount-input',
178
+ 'amount_refunded' => 'amount_refunded-input',
179
+ 'charge_date' => 'charge_date-input',
180
+ 'created_at' => 'created_at-input',
181
+ 'currency' => 'currency-input',
182
+ 'description' => 'description-input',
183
+ 'id' => 'id-input',
184
+ 'links' => 'links-input',
185
+ 'metadata' => 'metadata-input',
186
+ 'reference' => 'reference-input',
187
+ 'status' => 'status-input'
188
+ }
189
+ }.to_json,
190
+ headers: response_headers
191
+ )
192
+ end
193
+
194
+ it 'fetches the already-created resource' do
195
+ post_create_response
196
+ expect(post_stub).to have_been_requested
197
+ expect(get_stub).to have_been_requested
198
+ end
199
+ end
102
200
  end
103
201
 
104
202
  describe '#list' do
105
203
  describe 'with no filters' do
106
204
  subject(:get_list_response) { client.payments.list }
107
205
 
108
- before do
109
- stub_request(:get, %r{.*api.gocardless.com/payments}).to_return(
110
- body: {
111
- 'payments' => [{
206
+ let(:body) do
207
+ {
208
+ 'payments' => [{
112
209
 
113
- 'amount' => 'amount-input',
114
- 'amount_refunded' => 'amount_refunded-input',
115
- 'charge_date' => 'charge_date-input',
116
- 'created_at' => 'created_at-input',
117
- 'currency' => 'currency-input',
118
- 'description' => 'description-input',
119
- 'id' => 'id-input',
120
- 'links' => 'links-input',
121
- 'metadata' => 'metadata-input',
122
- 'reference' => 'reference-input',
123
- 'status' => 'status-input'
124
- }],
125
- meta: {
126
- cursors: {
127
- before: nil,
128
- after: 'ABC123'
129
- }
210
+ 'amount' => 'amount-input',
211
+ 'amount_refunded' => 'amount_refunded-input',
212
+ 'charge_date' => 'charge_date-input',
213
+ 'created_at' => 'created_at-input',
214
+ 'currency' => 'currency-input',
215
+ 'description' => 'description-input',
216
+ 'id' => 'id-input',
217
+ 'links' => 'links-input',
218
+ 'metadata' => 'metadata-input',
219
+ 'reference' => 'reference-input',
220
+ 'status' => 'status-input'
221
+ }],
222
+ meta: {
223
+ cursors: {
224
+ before: nil,
225
+ after: 'ABC123'
130
226
  }
131
- }.to_json,
132
- headers: { 'Content-Type' => 'application/json' }
227
+ }
228
+ }.to_json
229
+ end
230
+
231
+ before do
232
+ stub_request(:get, %r{.*api.gocardless.com/payments}).to_return(
233
+ body: body,
234
+ headers: response_headers
133
235
  )
134
236
  end
135
237
 
@@ -163,6 +265,29 @@ describe GoCardlessPro::Services::PaymentsService do
163
265
  end
164
266
 
165
267
  specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
268
+
269
+ describe 'retry behaviour' do
270
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
271
+
272
+ it 'retries timeouts' do
273
+ stub = stub_request(:get, %r{.*api.gocardless.com/payments})
274
+ .to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
275
+
276
+ get_list_response
277
+ expect(stub).to have_been_requested.twice
278
+ end
279
+
280
+ it 'retries 5XX errors' do
281
+ stub = stub_request(:get, %r{.*api.gocardless.com/payments})
282
+ .to_return(status: 502,
283
+ headers: { 'Content-Type' => 'text/html' },
284
+ body: '<html><body>Response from Cloudflare</body></html>')
285
+ .then.to_return(status: 200, headers: response_headers, body: body)
286
+
287
+ get_list_response
288
+ expect(stub).to have_been_requested.twice
289
+ end
290
+ end
166
291
  end
167
292
  end
168
293
 
@@ -189,7 +314,7 @@ describe GoCardlessPro::Services::PaymentsService do
189
314
  limit: 1
190
315
  }
191
316
  }.to_json,
192
- headers: { 'Content-Type' => 'application/json' }
317
+ headers: response_headers
193
318
  )
194
319
  end
195
320
 
@@ -215,7 +340,7 @@ describe GoCardlessPro::Services::PaymentsService do
215
340
  cursors: {}
216
341
  }
217
342
  }.to_json,
218
- headers: { 'Content-Type' => 'application/json' }
343
+ headers: response_headers
219
344
  )
220
345
  end
221
346
 
@@ -224,6 +349,127 @@ describe GoCardlessPro::Services::PaymentsService do
224
349
  expect(first_response_stub).to have_been_requested
225
350
  expect(second_response_stub).to have_been_requested
226
351
  end
352
+
353
+ describe 'retry behaviour' do
354
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
355
+
356
+ it 'retries timeouts' do
357
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/payments$}).to_return(
358
+ body: {
359
+ 'payments' => [{
360
+
361
+ 'amount' => 'amount-input',
362
+ 'amount_refunded' => 'amount_refunded-input',
363
+ 'charge_date' => 'charge_date-input',
364
+ 'created_at' => 'created_at-input',
365
+ 'currency' => 'currency-input',
366
+ 'description' => 'description-input',
367
+ 'id' => 'id-input',
368
+ 'links' => 'links-input',
369
+ 'metadata' => 'metadata-input',
370
+ 'reference' => 'reference-input',
371
+ 'status' => 'status-input'
372
+ }],
373
+ meta: {
374
+ cursors: { after: 'AB345' },
375
+ limit: 1
376
+ }
377
+ }.to_json,
378
+ headers: response_headers
379
+ )
380
+
381
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/payments\?after=AB345})
382
+ .to_timeout.then
383
+ .to_return(
384
+ body: {
385
+ 'payments' => [{
386
+
387
+ 'amount' => 'amount-input',
388
+ 'amount_refunded' => 'amount_refunded-input',
389
+ 'charge_date' => 'charge_date-input',
390
+ 'created_at' => 'created_at-input',
391
+ 'currency' => 'currency-input',
392
+ 'description' => 'description-input',
393
+ 'id' => 'id-input',
394
+ 'links' => 'links-input',
395
+ 'metadata' => 'metadata-input',
396
+ 'reference' => 'reference-input',
397
+ 'status' => 'status-input'
398
+ }],
399
+ meta: {
400
+ limit: 2,
401
+ cursors: {}
402
+ }
403
+ }.to_json,
404
+ headers: response_headers
405
+ )
406
+
407
+ client.payments.all.to_a
408
+
409
+ expect(first_response_stub).to have_been_requested
410
+ expect(second_response_stub).to have_been_requested.twice
411
+ end
412
+
413
+ it 'retries 5XX errors' do
414
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/payments$}).to_return(
415
+ body: {
416
+ 'payments' => [{
417
+
418
+ 'amount' => 'amount-input',
419
+ 'amount_refunded' => 'amount_refunded-input',
420
+ 'charge_date' => 'charge_date-input',
421
+ 'created_at' => 'created_at-input',
422
+ 'currency' => 'currency-input',
423
+ 'description' => 'description-input',
424
+ 'id' => 'id-input',
425
+ 'links' => 'links-input',
426
+ 'metadata' => 'metadata-input',
427
+ 'reference' => 'reference-input',
428
+ 'status' => 'status-input'
429
+ }],
430
+ meta: {
431
+ cursors: { after: 'AB345' },
432
+ limit: 1
433
+ }
434
+ }.to_json,
435
+ headers: response_headers
436
+ )
437
+
438
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/payments\?after=AB345})
439
+ .to_return(
440
+ status: 502,
441
+ body: '<html><body>Response from Cloudflare</body></html>',
442
+ headers: { 'Content-Type' => 'text/html' }
443
+ ).then.to_return(
444
+ body: {
445
+ 'payments' => [{
446
+
447
+ 'amount' => 'amount-input',
448
+ 'amount_refunded' => 'amount_refunded-input',
449
+ 'charge_date' => 'charge_date-input',
450
+ 'created_at' => 'created_at-input',
451
+ 'currency' => 'currency-input',
452
+ 'description' => 'description-input',
453
+ 'id' => 'id-input',
454
+ 'links' => 'links-input',
455
+ 'metadata' => 'metadata-input',
456
+ 'reference' => 'reference-input',
457
+ 'status' => 'status-input'
458
+ }],
459
+ meta: {
460
+ limit: 2,
461
+ cursors: {}
462
+ }
463
+ }.to_json,
464
+ headers: response_headers
465
+ )
466
+
467
+ client.payments.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
+ end
227
473
  end
228
474
 
229
475
  describe '#get' do
@@ -253,7 +499,7 @@ describe GoCardlessPro::Services::PaymentsService do
253
499
  'status' => 'status-input'
254
500
  }
255
501
  }.to_json,
256
- headers: { 'Content-Type' => 'application/json' }
502
+ headers: response_headers
257
503
  )
258
504
  end
259
505
 
@@ -289,7 +535,7 @@ describe GoCardlessPro::Services::PaymentsService do
289
535
  'status' => 'status-input'
290
536
  }
291
537
  }.to_json,
292
- headers: { 'Content-Type' => 'application/json' }
538
+ headers: response_headers
293
539
  )
294
540
  end
295
541
 
@@ -303,7 +549,7 @@ describe GoCardlessPro::Services::PaymentsService do
303
549
  stub_url = '/payments/:identity'.gsub(':identity', id)
304
550
  stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
305
551
  body: '',
306
- headers: { 'Content-Type' => 'application/json' }
552
+ headers: response_headers
307
553
  )
308
554
  end
309
555
 
@@ -319,6 +565,33 @@ describe GoCardlessPro::Services::PaymentsService do
319
565
  expect { get_response }.to_not raise_error(/bad URI/)
320
566
  end
321
567
  end
568
+
569
+ describe 'retry behaviour' do
570
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
571
+
572
+ it 'retries timeouts' do
573
+ stub_url = '/payments/:identity'.gsub(':identity', id)
574
+
575
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
576
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
577
+
578
+ get_response
579
+ expect(stub).to have_been_requested.twice
580
+ end
581
+
582
+ it 'retries 5XX errors' do
583
+ stub_url = '/payments/:identity'.gsub(':identity', id)
584
+
585
+ stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/)
586
+ .to_return(status: 502,
587
+ headers: { 'Content-Type' => 'text/html' },
588
+ body: '<html><body>Response from Cloudflare</body></html>')
589
+ .then.to_return(status: 200, headers: response_headers)
590
+
591
+ get_response
592
+ expect(stub).to have_been_requested.twice
593
+ end
594
+ end
322
595
  end
323
596
 
324
597
  describe '#update' do
@@ -347,7 +620,7 @@ describe GoCardlessPro::Services::PaymentsService do
347
620
  'status' => 'status-input'
348
621
  }
349
622
  }.to_json,
350
- headers: { 'Content-Type' => 'application/json' }
623
+ headers: response_headers
351
624
  )
352
625
  end
353
626
 
@@ -355,6 +628,31 @@ describe GoCardlessPro::Services::PaymentsService do
355
628
  expect(put_update_response).to be_a(GoCardlessPro::Resources::Payment)
356
629
  expect(stub).to have_been_requested
357
630
  end
631
+
632
+ describe 'retry behaviour' do
633
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
634
+
635
+ it 'retries timeouts' do
636
+ stub_url = '/payments/:identity'.gsub(':identity', id)
637
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
638
+ .to_timeout.then.to_return(status: 200, headers: response_headers)
639
+
640
+ put_update_response
641
+ expect(stub).to have_been_requested.twice
642
+ end
643
+
644
+ it 'retries 5XX errors' do
645
+ stub_url = '/payments/:identity'.gsub(':identity', id)
646
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/)
647
+ .to_return(status: 502,
648
+ headers: { 'Content-Type' => 'text/html' },
649
+ body: '<html><body>Response from Cloudflare</body></html>')
650
+ .then.to_return(status: 200, headers: response_headers)
651
+
652
+ put_update_response
653
+ expect(stub).to have_been_requested.twice
654
+ end
655
+ end
358
656
  end
359
657
  end
360
658
 
@@ -383,7 +681,7 @@ describe GoCardlessPro::Services::PaymentsService do
383
681
  'status' => 'status-input'
384
682
  }
385
683
  }.to_json,
386
- headers: { 'Content-Type' => 'application/json' }
684
+ headers: response_headers
387
685
  )
388
686
  end
389
687
 
@@ -393,6 +691,17 @@ describe GoCardlessPro::Services::PaymentsService do
393
691
  expect(stub).to have_been_requested
394
692
  end
395
693
 
694
+ describe 'retry behaviour' do
695
+ it "doesn't retry errors" do
696
+ stub_url = '/payments/:identity/actions/cancel'.gsub(':identity', resource_id)
697
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/)
698
+ .to_timeout
699
+
700
+ expect { post_response }.to raise_error(Faraday::TimeoutError)
701
+ expect(stub).to have_been_requested
702
+ end
703
+ end
704
+
396
705
  context 'when the request needs a body and custom header' do
397
706
  let(:body) { { foo: 'bar' } }
398
707
  let(:headers) { { 'Foo' => 'Bar' } }
@@ -424,7 +733,7 @@ describe GoCardlessPro::Services::PaymentsService do
424
733
  'status' => 'status-input'
425
734
  }
426
735
  }.to_json,
427
- headers: { 'Content-Type' => 'application/json' }
736
+ headers: response_headers
428
737
  )
429
738
  end
430
739
  end
@@ -455,7 +764,7 @@ describe GoCardlessPro::Services::PaymentsService do
455
764
  'status' => 'status-input'
456
765
  }
457
766
  }.to_json,
458
- headers: { 'Content-Type' => 'application/json' }
767
+ headers: response_headers
459
768
  )
460
769
  end
461
770
 
@@ -465,6 +774,17 @@ describe GoCardlessPro::Services::PaymentsService do
465
774
  expect(stub).to have_been_requested
466
775
  end
467
776
 
777
+ describe 'retry behaviour' do
778
+ it "doesn't retry errors" do
779
+ stub_url = '/payments/:identity/actions/retry'.gsub(':identity', resource_id)
780
+ stub = stub_request(:post, /.*api.gocardless.com#{stub_url}/)
781
+ .to_timeout
782
+
783
+ expect { post_response }.to raise_error(Faraday::TimeoutError)
784
+ expect(stub).to have_been_requested
785
+ end
786
+ end
787
+
468
788
  context 'when the request needs a body and custom header' do
469
789
  let(:body) { { foo: 'bar' } }
470
790
  let(:headers) { { 'Foo' => 'Bar' } }
@@ -496,7 +816,7 @@ describe GoCardlessPro::Services::PaymentsService do
496
816
  'status' => 'status-input'
497
817
  }
498
818
  }.to_json,
499
- headers: { 'Content-Type' => 'application/json' }
819
+ headers: response_headers
500
820
  )
501
821
  end
502
822
  end