gocardless_pro 2.17.1 → 2.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gocardless_pro.rb +3 -0
  3. data/lib/gocardless_pro/client.rb +6 -1
  4. data/lib/gocardless_pro/resources/creditor_bank_account.rb +1 -2
  5. data/lib/gocardless_pro/resources/currency_exchange_rate.rb +44 -0
  6. data/lib/gocardless_pro/resources/customer_notification.rb +3 -5
  7. data/lib/gocardless_pro/resources/event.rb +2 -1
  8. data/lib/gocardless_pro/resources/mandate_import.rb +5 -8
  9. data/lib/gocardless_pro/resources/mandate_import_entry.rb +3 -5
  10. data/lib/gocardless_pro/resources/payout.rb +2 -0
  11. data/lib/gocardless_pro/resources/redirect_flow.rb +2 -0
  12. data/lib/gocardless_pro/resources/subscription.rb +38 -29
  13. data/lib/gocardless_pro/services/currency_exchange_rates_service.rb +67 -0
  14. data/lib/gocardless_pro/services/customers_service.rb +1 -2
  15. data/lib/gocardless_pro/services/instalment_schedules_service.rb +81 -5
  16. data/lib/gocardless_pro/services/mandates_service.rb +1 -1
  17. data/lib/gocardless_pro/services/payouts_service.rb +21 -0
  18. data/lib/gocardless_pro/services/subscriptions_service.rb +129 -0
  19. data/lib/gocardless_pro/version.rb +1 -1
  20. data/spec/resources/currency_exchange_rate_spec.rb +103 -0
  21. data/spec/resources/instalment_schedule_spec.rb +193 -1
  22. data/spec/resources/payout_spec.rb +45 -0
  23. data/spec/resources/redirect_flow_spec.rb +9 -0
  24. data/spec/resources/subscription_spec.rb +210 -0
  25. data/spec/services/currency_exchange_rates_service_spec.rb +223 -0
  26. data/spec/services/instalment_schedules_service_spec.rb +270 -1
  27. data/spec/services/payouts_service_spec.rb +74 -0
  28. data/spec/services/redirect_flows_service_spec.rb +9 -0
  29. data/spec/services/subscriptions_service_spec.rb +240 -0
  30. metadata +9 -3
@@ -0,0 +1,223 @@
1
+ require 'spec_helper'
2
+
3
+ describe GoCardlessPro::Services::CurrencyExchangeRatesService 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.currency_exchange_rates.list }
15
+
16
+ let(:body) do
17
+ {
18
+ 'currency_exchange_rates' => [{
19
+
20
+ 'rate' => 'rate-input',
21
+ 'source' => 'source-input',
22
+ 'target' => 'target-input',
23
+ 'time' => 'time-input',
24
+ }],
25
+ meta: {
26
+ cursors: {
27
+ before: nil,
28
+ after: 'ABC123',
29
+ },
30
+ },
31
+ }.to_json
32
+ end
33
+
34
+ before do
35
+ stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates}).to_return(
36
+ body: body,
37
+ headers: response_headers
38
+ )
39
+ end
40
+
41
+ it 'wraps each item in the resource class' do
42
+ expect(get_list_response.records.map(&:class).uniq.first).to eq(GoCardlessPro::Resources::CurrencyExchangeRate)
43
+
44
+ expect(get_list_response.records.first.rate).to eq('rate-input')
45
+
46
+ expect(get_list_response.records.first.source).to eq('source-input')
47
+
48
+ expect(get_list_response.records.first.target).to eq('target-input')
49
+
50
+ expect(get_list_response.records.first.time).to eq('time-input')
51
+ end
52
+
53
+ it 'exposes the cursors for before and after' do
54
+ expect(get_list_response.before).to eq(nil)
55
+ expect(get_list_response.after).to eq('ABC123')
56
+ end
57
+
58
+ specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
59
+
60
+ describe 'retry behaviour' do
61
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
62
+
63
+ it 'retries timeouts' do
64
+ stub = stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates}).
65
+ to_timeout.then.to_return(status: 200, headers: response_headers, body: body)
66
+
67
+ get_list_response
68
+ expect(stub).to have_been_requested.twice
69
+ end
70
+
71
+ it 'retries 5XX errors' do
72
+ stub = stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates}).
73
+ to_return(status: 502,
74
+ headers: { 'Content-Type' => 'text/html' },
75
+ body: '<html><body>Response from Cloudflare</body></html>').
76
+ then.to_return(status: 200, headers: response_headers, body: body)
77
+
78
+ get_list_response
79
+ expect(stub).to have_been_requested.twice
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#all' do
86
+ let!(:first_response_stub) do
87
+ stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates$}).to_return(
88
+ body: {
89
+ 'currency_exchange_rates' => [{
90
+
91
+ 'rate' => 'rate-input',
92
+ 'source' => 'source-input',
93
+ 'target' => 'target-input',
94
+ 'time' => 'time-input',
95
+ }],
96
+ meta: {
97
+ cursors: { after: 'AB345' },
98
+ limit: 1,
99
+ },
100
+ }.to_json,
101
+ headers: response_headers
102
+ )
103
+ end
104
+
105
+ let!(:second_response_stub) do
106
+ stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates\?after=AB345}).to_return(
107
+ body: {
108
+ 'currency_exchange_rates' => [{
109
+
110
+ 'rate' => 'rate-input',
111
+ 'source' => 'source-input',
112
+ 'target' => 'target-input',
113
+ 'time' => 'time-input',
114
+ }],
115
+ meta: {
116
+ limit: 2,
117
+ cursors: {},
118
+ },
119
+ }.to_json,
120
+ headers: response_headers
121
+ )
122
+ end
123
+
124
+ it 'automatically makes the extra requests' do
125
+ expect(client.currency_exchange_rates.all.to_a.length).to eq(2)
126
+ expect(first_response_stub).to have_been_requested
127
+ expect(second_response_stub).to have_been_requested
128
+ end
129
+
130
+ describe 'retry behaviour' do
131
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
132
+
133
+ it 'retries timeouts' do
134
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates$}).to_return(
135
+ body: {
136
+ 'currency_exchange_rates' => [{
137
+
138
+ 'rate' => 'rate-input',
139
+ 'source' => 'source-input',
140
+ 'target' => 'target-input',
141
+ 'time' => 'time-input',
142
+ }],
143
+ meta: {
144
+ cursors: { after: 'AB345' },
145
+ limit: 1,
146
+ },
147
+ }.to_json,
148
+ headers: response_headers
149
+ )
150
+
151
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates\?after=AB345}).
152
+ to_timeout.then.
153
+ to_return(
154
+ body: {
155
+ 'currency_exchange_rates' => [{
156
+
157
+ 'rate' => 'rate-input',
158
+ 'source' => 'source-input',
159
+ 'target' => 'target-input',
160
+ 'time' => 'time-input',
161
+ }],
162
+ meta: {
163
+ limit: 2,
164
+ cursors: {},
165
+ },
166
+ }.to_json,
167
+ headers: response_headers
168
+ )
169
+
170
+ client.currency_exchange_rates.all.to_a
171
+
172
+ expect(first_response_stub).to have_been_requested
173
+ expect(second_response_stub).to have_been_requested.twice
174
+ end
175
+
176
+ it 'retries 5XX errors' do
177
+ first_response_stub = stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates$}).to_return(
178
+ body: {
179
+ 'currency_exchange_rates' => [{
180
+
181
+ 'rate' => 'rate-input',
182
+ 'source' => 'source-input',
183
+ 'target' => 'target-input',
184
+ 'time' => 'time-input',
185
+ }],
186
+ meta: {
187
+ cursors: { after: 'AB345' },
188
+ limit: 1,
189
+ },
190
+ }.to_json,
191
+ headers: response_headers
192
+ )
193
+
194
+ second_response_stub = stub_request(:get, %r{.*api.gocardless.com/currency_exchange_rates\?after=AB345}).
195
+ to_return(
196
+ status: 502,
197
+ body: '<html><body>Response from Cloudflare</body></html>',
198
+ headers: { 'Content-Type' => 'text/html' }
199
+ ).then.to_return(
200
+ body: {
201
+ 'currency_exchange_rates' => [{
202
+
203
+ 'rate' => 'rate-input',
204
+ 'source' => 'source-input',
205
+ 'target' => 'target-input',
206
+ 'time' => 'time-input',
207
+ }],
208
+ meta: {
209
+ limit: 2,
210
+ cursors: {},
211
+ },
212
+ }.to_json,
213
+ headers: response_headers
214
+ )
215
+
216
+ client.currency_exchange_rates.all.to_a
217
+
218
+ expect(first_response_stub).to have_been_requested
219
+ expect(second_response_stub).to have_been_requested.twice
220
+ end
221
+ end
222
+ end
223
+ end
@@ -10,7 +10,216 @@ describe GoCardlessPro::Services::InstalmentSchedulesService do
10
10
  let(:response_headers) { { 'Content-Type' => 'application/json' } }
11
11
 
12
12
  describe '#create' do
13
- subject(:post_create_response) { client.instalment_schedules.create(params: new_resource) }
13
+ subject(:post_create_response) { client.instalment_schedules.create_with_dates(params: new_resource) }
14
+ context 'with a valid request' do
15
+ let(:new_resource) do
16
+ {
17
+
18
+ 'created_at' => 'created_at-input',
19
+ 'currency' => 'currency-input',
20
+ 'id' => 'id-input',
21
+ 'links' => 'links-input',
22
+ 'metadata' => 'metadata-input',
23
+ 'name' => 'name-input',
24
+ 'payment_errors' => 'payment_errors-input',
25
+ 'status' => 'status-input',
26
+ 'total_amount' => 'total_amount-input',
27
+ }
28
+ end
29
+
30
+ before do
31
+ stub_request(:post, %r{.*api.gocardless.com/instalment_schedules}).
32
+ with(
33
+ body: {
34
+ 'instalment_schedules' => {
35
+
36
+ 'created_at' => 'created_at-input',
37
+ 'currency' => 'currency-input',
38
+ 'id' => 'id-input',
39
+ 'links' => 'links-input',
40
+ 'metadata' => 'metadata-input',
41
+ 'name' => 'name-input',
42
+ 'payment_errors' => 'payment_errors-input',
43
+ 'status' => 'status-input',
44
+ 'total_amount' => 'total_amount-input',
45
+ },
46
+ }
47
+ ).
48
+ to_return(
49
+ body: {
50
+ 'instalment_schedules' =>
51
+
52
+ {
53
+
54
+ 'created_at' => 'created_at-input',
55
+ 'currency' => 'currency-input',
56
+ 'id' => 'id-input',
57
+ 'links' => 'links-input',
58
+ 'metadata' => 'metadata-input',
59
+ 'name' => 'name-input',
60
+ 'payment_errors' => 'payment_errors-input',
61
+ 'status' => 'status-input',
62
+ 'total_amount' => 'total_amount-input',
63
+ },
64
+
65
+ }.to_json,
66
+ headers: response_headers
67
+ )
68
+ end
69
+
70
+ it 'creates and returns the resource' do
71
+ expect(post_create_response).to be_a(GoCardlessPro::Resources::InstalmentSchedule)
72
+ end
73
+
74
+ describe 'retry behaviour' do
75
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
76
+
77
+ it 'retries timeouts' do
78
+ stub = stub_request(:post, %r{.*api.gocardless.com/instalment_schedules}).
79
+ to_timeout.then.to_return(status: 200, headers: response_headers)
80
+
81
+ post_create_response
82
+ expect(stub).to have_been_requested.twice
83
+ end
84
+
85
+ it 'retries 5XX errors' do
86
+ stub = stub_request(:post, %r{.*api.gocardless.com/instalment_schedules}).
87
+ to_return(status: 502,
88
+ headers: { 'Content-Type' => 'text/html' },
89
+ body: '<html><body>Response from Cloudflare</body></html>').
90
+ then.to_return(status: 200, headers: response_headers)
91
+
92
+ post_create_response
93
+ expect(stub).to have_been_requested.twice
94
+ end
95
+ end
96
+ end
97
+
98
+ context 'with a request that returns a validation error' do
99
+ let(:new_resource) { {} }
100
+
101
+ before do
102
+ stub_request(:post, %r{.*api.gocardless.com/instalment_schedules}).to_return(
103
+ body: {
104
+ error: {
105
+ type: 'validation_failed',
106
+ code: 422,
107
+ errors: [
108
+ { message: 'test error message', field: 'test_field' },
109
+ ],
110
+ },
111
+ }.to_json,
112
+ headers: response_headers,
113
+ status: 422
114
+ )
115
+ end
116
+
117
+ it 'throws the correct error' do
118
+ expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
119
+ end
120
+ end
121
+
122
+ context 'with a request that returns an idempotent creation conflict error' do
123
+ let(:id) { 'ID123' }
124
+
125
+ let(:new_resource) do
126
+ {
127
+
128
+ 'created_at' => 'created_at-input',
129
+ 'currency' => 'currency-input',
130
+ 'id' => 'id-input',
131
+ 'links' => 'links-input',
132
+ 'metadata' => 'metadata-input',
133
+ 'name' => 'name-input',
134
+ 'payment_errors' => 'payment_errors-input',
135
+ 'status' => 'status-input',
136
+ 'total_amount' => 'total_amount-input',
137
+ }
138
+ end
139
+
140
+ let!(:post_stub) do
141
+ stub_request(:post, %r{.*api.gocardless.com/instalment_schedules}).to_return(
142
+ body: {
143
+ error: {
144
+ type: 'invalid_state',
145
+ code: 409,
146
+ errors: [
147
+ {
148
+ message: 'A resource has already been created with this idempotency key',
149
+ reason: 'idempotent_creation_conflict',
150
+ links: {
151
+ conflicting_resource_id: id,
152
+ },
153
+ },
154
+ ],
155
+ },
156
+ }.to_json,
157
+ headers: response_headers,
158
+ status: 409
159
+ )
160
+ end
161
+
162
+ let!(:get_stub) do
163
+ stub_url = "/instalment_schedules/#{id}"
164
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).
165
+ to_return(
166
+ body: {
167
+ 'instalment_schedules' => {
168
+
169
+ 'created_at' => 'created_at-input',
170
+ 'currency' => 'currency-input',
171
+ 'id' => 'id-input',
172
+ 'links' => 'links-input',
173
+ 'metadata' => 'metadata-input',
174
+ 'name' => 'name-input',
175
+ 'payment_errors' => 'payment_errors-input',
176
+ 'status' => 'status-input',
177
+ 'total_amount' => 'total_amount-input',
178
+ },
179
+ }.to_json,
180
+ headers: response_headers
181
+ )
182
+ end
183
+
184
+ context 'with default behaviour' do
185
+ it 'fetches the already-created resource' do
186
+ post_create_response
187
+ expect(post_stub).to have_been_requested
188
+ expect(get_stub).to have_been_requested
189
+ end
190
+ end
191
+
192
+ context 'with on_idempotency_conflict: :raise' do
193
+ let(:client) do
194
+ GoCardlessPro::Client.new(
195
+ access_token: 'SECRET_TOKEN',
196
+ on_idempotency_conflict: :raise
197
+ )
198
+ end
199
+
200
+ it 'raises an IdempotencyConflict error' do
201
+ expect { post_create_response }.
202
+ to raise_error(GoCardlessPro::IdempotencyConflict)
203
+ end
204
+ end
205
+
206
+ context 'with on_idempotency_conflict: :unknown' do
207
+ let(:client) do
208
+ GoCardlessPro::Client.new(
209
+ access_token: 'SECRET_TOKEN',
210
+ on_idempotency_conflict: :unknown
211
+ )
212
+ end
213
+
214
+ it 'raises an ArgumentError' do
215
+ expect { post_create_response }.to raise_error(ArgumentError)
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ describe '#create' do
222
+ subject(:post_create_response) { client.instalment_schedules.create_with_schedule(params: new_resource) }
14
223
  context 'with a valid request' do
15
224
  let(:new_resource) do
16
225
  {
@@ -619,6 +828,66 @@ describe GoCardlessPro::Services::InstalmentSchedulesService do
619
828
  end
620
829
  end
621
830
 
831
+ describe '#update' do
832
+ subject(:put_update_response) { client.instalment_schedules.update(id, params: update_params) }
833
+ let(:id) { 'ABC123' }
834
+
835
+ context 'with a valid request' do
836
+ let(:update_params) { { 'hello' => 'world' } }
837
+
838
+ let!(:stub) do
839
+ stub_url = '/instalment_schedules/:identity'.gsub(':identity', id)
840
+ stub_request(:put, /.*api.gocardless.com#{stub_url}/).to_return(
841
+ body: {
842
+ 'instalment_schedules' => {
843
+
844
+ 'created_at' => 'created_at-input',
845
+ 'currency' => 'currency-input',
846
+ 'id' => 'id-input',
847
+ 'links' => 'links-input',
848
+ 'metadata' => 'metadata-input',
849
+ 'name' => 'name-input',
850
+ 'payment_errors' => 'payment_errors-input',
851
+ 'status' => 'status-input',
852
+ 'total_amount' => 'total_amount-input',
853
+ },
854
+ }.to_json,
855
+ headers: response_headers
856
+ )
857
+ end
858
+
859
+ it 'updates and returns the resource' do
860
+ expect(put_update_response).to be_a(GoCardlessPro::Resources::InstalmentSchedule)
861
+ expect(stub).to have_been_requested
862
+ end
863
+
864
+ describe 'retry behaviour' do
865
+ before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) }
866
+
867
+ it 'retries timeouts' do
868
+ stub_url = '/instalment_schedules/:identity'.gsub(':identity', id)
869
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/).
870
+ to_timeout.then.to_return(status: 200, headers: response_headers)
871
+
872
+ put_update_response
873
+ expect(stub).to have_been_requested.twice
874
+ end
875
+
876
+ it 'retries 5XX errors' do
877
+ stub_url = '/instalment_schedules/:identity'.gsub(':identity', id)
878
+ stub = stub_request(:put, /.*api.gocardless.com#{stub_url}/).
879
+ to_return(status: 502,
880
+ headers: { 'Content-Type' => 'text/html' },
881
+ body: '<html><body>Response from Cloudflare</body></html>').
882
+ then.to_return(status: 200, headers: response_headers)
883
+
884
+ put_update_response
885
+ expect(stub).to have_been_requested.twice
886
+ end
887
+ end
888
+ end
889
+ end
890
+
622
891
  describe '#cancel' do
623
892
  subject(:post_response) { client.instalment_schedules.cancel(resource_id) }
624
893