gocardless_pro 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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