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.
- checksums.yaml +4 -4
- data/lib/gocardless_pro.rb +3 -0
- data/lib/gocardless_pro/client.rb +6 -1
- data/lib/gocardless_pro/resources/creditor_bank_account.rb +1 -2
- data/lib/gocardless_pro/resources/currency_exchange_rate.rb +44 -0
- data/lib/gocardless_pro/resources/customer_notification.rb +3 -5
- data/lib/gocardless_pro/resources/event.rb +2 -1
- data/lib/gocardless_pro/resources/mandate_import.rb +5 -8
- data/lib/gocardless_pro/resources/mandate_import_entry.rb +3 -5
- data/lib/gocardless_pro/resources/payout.rb +2 -0
- data/lib/gocardless_pro/resources/redirect_flow.rb +2 -0
- data/lib/gocardless_pro/resources/subscription.rb +38 -29
- data/lib/gocardless_pro/services/currency_exchange_rates_service.rb +67 -0
- data/lib/gocardless_pro/services/customers_service.rb +1 -2
- data/lib/gocardless_pro/services/instalment_schedules_service.rb +81 -5
- data/lib/gocardless_pro/services/mandates_service.rb +1 -1
- data/lib/gocardless_pro/services/payouts_service.rb +21 -0
- data/lib/gocardless_pro/services/subscriptions_service.rb +129 -0
- data/lib/gocardless_pro/version.rb +1 -1
- data/spec/resources/currency_exchange_rate_spec.rb +103 -0
- data/spec/resources/instalment_schedule_spec.rb +193 -1
- data/spec/resources/payout_spec.rb +45 -0
- data/spec/resources/redirect_flow_spec.rb +9 -0
- data/spec/resources/subscription_spec.rb +210 -0
- data/spec/services/currency_exchange_rates_service_spec.rb +223 -0
- data/spec/services/instalment_schedules_service_spec.rb +270 -1
- data/spec/services/payouts_service_spec.rb +74 -0
- data/spec/services/redirect_flows_service_spec.rb +9 -0
- data/spec/services/subscriptions_service_spec.rb +240 -0
- 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.
|
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
|
|