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
@@ -1,79 +1,491 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe GoCardlessPro::Resources::CustomerBankAccount do
4
- describe 'initialising' do
5
- let(:data) do
6
- {
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 '#create' do
13
+ subject(:post_create_response) { client.customer_bank_accounts.create(params: new_resource) }
14
+ context 'with a valid request' do
15
+ let(:new_resource) do
16
+ {
17
+
18
+ 'account_holder_name' => 'account_holder_name-input',
19
+ 'account_number_ending' => 'account_number_ending-input',
20
+ 'bank_name' => 'bank_name-input',
21
+ 'country_code' => 'country_code-input',
22
+ 'created_at' => 'created_at-input',
23
+ 'currency' => 'currency-input',
24
+ 'enabled' => 'enabled-input',
25
+ 'id' => 'id-input',
26
+ 'links' => 'links-input',
27
+ 'metadata' => 'metadata-input'
28
+ }
29
+ end
30
+
31
+ before do
32
+ stub_request(:post, %r{.*api.gocardless.com/customer_bank_accounts})
33
+ .with(
34
+ body: {
35
+ 'customer_bank_accounts' => {
36
+
37
+ 'account_holder_name' => 'account_holder_name-input',
38
+ 'account_number_ending' => 'account_number_ending-input',
39
+ 'bank_name' => 'bank_name-input',
40
+ 'country_code' => 'country_code-input',
41
+ 'created_at' => 'created_at-input',
42
+ 'currency' => 'currency-input',
43
+ 'enabled' => 'enabled-input',
44
+ 'id' => 'id-input',
45
+ 'links' => 'links-input',
46
+ 'metadata' => 'metadata-input'
47
+ }
48
+ }
49
+ )
50
+ .to_return(
51
+ body: {
52
+ 'customer_bank_accounts' =>
53
+
54
+ {
55
+
56
+ 'account_holder_name' => 'account_holder_name-input',
57
+ 'account_number_ending' => 'account_number_ending-input',
58
+ 'bank_name' => 'bank_name-input',
59
+ 'country_code' => 'country_code-input',
60
+ 'created_at' => 'created_at-input',
61
+ 'currency' => 'currency-input',
62
+ 'enabled' => 'enabled-input',
63
+ 'id' => 'id-input',
64
+ 'links' => 'links-input',
65
+ 'metadata' => 'metadata-input'
66
+ }
67
+
68
+ }.to_json,
69
+ headers: response_headers
70
+ )
71
+ end
72
+
73
+ it 'creates and returns the resource' do
74
+ expect(post_create_response).to be_a(GoCardlessPro::Resources::CustomerBankAccount)
75
+ end
76
+ end
77
+
78
+ context 'with a request that returns a validation error' do
79
+ let(:new_resource) { {} }
80
+
81
+ before do
82
+ stub_request(:post, %r{.*api.gocardless.com/customer_bank_accounts}).to_return(
83
+ body: {
84
+ error: {
85
+ type: 'validation_failed',
86
+ code: 422,
87
+ errors: [
88
+ { message: 'test error message', field: 'test_field' }
89
+ ]
90
+ }
91
+ }.to_json,
92
+ headers: response_headers,
93
+ status: 422
94
+ )
95
+ end
96
+
97
+ it 'throws the correct error' do
98
+ expect { post_create_response }.to raise_error(GoCardlessPro::ValidationError)
99
+ end
100
+ end
101
+
102
+ context 'with a request that returns an idempotent creation conflict error' do
103
+ let(:id) { 'ID123' }
104
+
105
+ let(:new_resource) do
106
+ {
107
+
108
+ 'account_holder_name' => 'account_holder_name-input',
109
+ 'account_number_ending' => 'account_number_ending-input',
110
+ 'bank_name' => 'bank_name-input',
111
+ 'country_code' => 'country_code-input',
112
+ 'created_at' => 'created_at-input',
113
+ 'currency' => 'currency-input',
114
+ 'enabled' => 'enabled-input',
115
+ 'id' => 'id-input',
116
+ 'links' => 'links-input',
117
+ 'metadata' => 'metadata-input'
118
+ }
119
+ end
120
+
121
+ let!(:post_stub) do
122
+ stub_request(:post, %r{.*api.gocardless.com/customer_bank_accounts}).to_return(
123
+ body: {
124
+ error: {
125
+ type: 'invalid_state',
126
+ code: 409,
127
+ errors: [
128
+ {
129
+ message: 'A resource has already been created with this idempotency key',
130
+ reason: 'idempotent_creation_conflict',
131
+ links: {
132
+ conflicting_resource_id: id
133
+ }
134
+ }
135
+ ]
136
+ }
137
+ }.to_json,
138
+ headers: response_headers,
139
+ status: 409
140
+ )
141
+ end
142
+
143
+ let!(:get_stub) do
144
+ stub_url = "/customer_bank_accounts/#{id}"
145
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
146
+ .to_return(
147
+ body: {
148
+ 'customer_bank_accounts' => {
149
+
150
+ 'account_holder_name' => 'account_holder_name-input',
151
+ 'account_number_ending' => 'account_number_ending-input',
152
+ 'bank_name' => 'bank_name-input',
153
+ 'country_code' => 'country_code-input',
154
+ 'created_at' => 'created_at-input',
155
+ 'currency' => 'currency-input',
156
+ 'enabled' => 'enabled-input',
157
+ 'id' => 'id-input',
158
+ 'links' => 'links-input',
159
+ 'metadata' => 'metadata-input'
160
+ }
161
+ }.to_json,
162
+ headers: response_headers
163
+ )
164
+ end
165
+
166
+ it 'fetches the already-created resource' do
167
+ post_create_response
168
+ expect(post_stub).to have_been_requested
169
+ expect(get_stub).to have_been_requested
170
+ end
171
+ end
172
+ end
173
+
174
+ describe '#list' do
175
+ describe 'with no filters' do
176
+ subject(:get_list_response) { client.customer_bank_accounts.list }
177
+
178
+ before do
179
+ stub_request(:get, %r{.*api.gocardless.com/customer_bank_accounts}).to_return(
180
+ body: {
181
+ 'customer_bank_accounts' => [{
182
+
183
+ 'account_holder_name' => 'account_holder_name-input',
184
+ 'account_number_ending' => 'account_number_ending-input',
185
+ 'bank_name' => 'bank_name-input',
186
+ 'country_code' => 'country_code-input',
187
+ 'created_at' => 'created_at-input',
188
+ 'currency' => 'currency-input',
189
+ 'enabled' => 'enabled-input',
190
+ 'id' => 'id-input',
191
+ 'links' => 'links-input',
192
+ 'metadata' => 'metadata-input'
193
+ }],
194
+ meta: {
195
+ cursors: {
196
+ before: nil,
197
+ after: 'ABC123'
198
+ }
199
+ }
200
+ }.to_json,
201
+ headers: response_headers
202
+ )
203
+ end
204
+
205
+ it 'wraps each item in the resource class' do
206
+ expect(get_list_response.records.map(&:class).uniq.first).to eq(GoCardlessPro::Resources::CustomerBankAccount)
7
207
 
8
- 'account_holder_name' => 'account_holder_name-input',
208
+ expect(get_list_response.records.first.account_holder_name).to eq('account_holder_name-input')
9
209
 
10
- 'account_number_ending' => 'account_number_ending-input',
210
+ expect(get_list_response.records.first.account_number_ending).to eq('account_number_ending-input')
11
211
 
12
- 'bank_name' => 'bank_name-input',
212
+ expect(get_list_response.records.first.bank_name).to eq('bank_name-input')
13
213
 
14
- 'country_code' => 'country_code-input',
214
+ expect(get_list_response.records.first.country_code).to eq('country_code-input')
15
215
 
16
- 'created_at' => 'created_at-input',
216
+ expect(get_list_response.records.first.created_at).to eq('created_at-input')
17
217
 
18
- 'currency' => 'currency-input',
218
+ expect(get_list_response.records.first.currency).to eq('currency-input')
19
219
 
20
- 'enabled' => 'enabled-input',
220
+ expect(get_list_response.records.first.enabled).to eq('enabled-input')
21
221
 
22
- 'id' => 'id-input',
222
+ expect(get_list_response.records.first.id).to eq('id-input')
23
223
 
24
- 'links' => {
224
+ expect(get_list_response.records.first.metadata).to eq('metadata-input')
225
+ end
25
226
 
26
- 'customer' => 'customer-input'
227
+ it 'exposes the cursors for before and after' do
228
+ expect(get_list_response.before).to eq(nil)
229
+ expect(get_list_response.after).to eq('ABC123')
230
+ end
27
231
 
28
- },
232
+ specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') }
233
+ end
234
+ end
29
235
 
30
- 'metadata' => 'metadata-input'
236
+ describe '#all' do
237
+ let!(:first_response_stub) do
238
+ stub_request(:get, %r{.*api.gocardless.com/customer_bank_accounts$}).to_return(
239
+ body: {
240
+ 'customer_bank_accounts' => [{
31
241
 
32
- }
242
+ 'account_holder_name' => 'account_holder_name-input',
243
+ 'account_number_ending' => 'account_number_ending-input',
244
+ 'bank_name' => 'bank_name-input',
245
+ 'country_code' => 'country_code-input',
246
+ 'created_at' => 'created_at-input',
247
+ 'currency' => 'currency-input',
248
+ 'enabled' => 'enabled-input',
249
+ 'id' => 'id-input',
250
+ 'links' => 'links-input',
251
+ 'metadata' => 'metadata-input'
252
+ }],
253
+ meta: {
254
+ cursors: { after: 'AB345' },
255
+ limit: 1
256
+ }
257
+ }.to_json,
258
+ headers: response_headers
259
+ )
33
260
  end
34
261
 
35
- it 'can be initialized from an unenveloped response' do
36
- resource = described_class.new(data)
262
+ let!(:second_response_stub) do
263
+ stub_request(:get, %r{.*api.gocardless.com/customer_bank_accounts\?after=AB345}).to_return(
264
+ body: {
265
+ 'customer_bank_accounts' => [{
266
+
267
+ 'account_holder_name' => 'account_holder_name-input',
268
+ 'account_number_ending' => 'account_number_ending-input',
269
+ 'bank_name' => 'bank_name-input',
270
+ 'country_code' => 'country_code-input',
271
+ 'created_at' => 'created_at-input',
272
+ 'currency' => 'currency-input',
273
+ 'enabled' => 'enabled-input',
274
+ 'id' => 'id-input',
275
+ 'links' => 'links-input',
276
+ 'metadata' => 'metadata-input'
277
+ }],
278
+ meta: {
279
+ limit: 2,
280
+ cursors: {}
281
+ }
282
+ }.to_json,
283
+ headers: response_headers
284
+ )
285
+ end
37
286
 
38
- expect(resource.account_holder_name).to eq('account_holder_name-input')
287
+ it 'automatically makes the extra requests' do
288
+ expect(client.customer_bank_accounts.all.to_a.length).to eq(2)
289
+ expect(first_response_stub).to have_been_requested
290
+ expect(second_response_stub).to have_been_requested
291
+ end
292
+ end
39
293
 
40
- expect(resource.account_number_ending).to eq('account_number_ending-input')
294
+ describe '#get' do
295
+ let(:id) { 'ID123' }
41
296
 
42
- expect(resource.bank_name).to eq('bank_name-input')
297
+ subject(:get_response) { client.customer_bank_accounts.get(id) }
43
298
 
44
- expect(resource.country_code).to eq('country_code-input')
299
+ context 'passing in a custom header' do
300
+ let!(:stub) do
301
+ stub_url = '/customer_bank_accounts/:identity'.gsub(':identity', id)
302
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/)
303
+ .with(headers: { 'Foo' => 'Bar' })
304
+ .to_return(
305
+ body: {
306
+ 'customer_bank_accounts' => {
45
307
 
46
- expect(resource.created_at).to eq('created_at-input')
308
+ 'account_holder_name' => 'account_holder_name-input',
309
+ 'account_number_ending' => 'account_number_ending-input',
310
+ 'bank_name' => 'bank_name-input',
311
+ 'country_code' => 'country_code-input',
312
+ 'created_at' => 'created_at-input',
313
+ 'currency' => 'currency-input',
314
+ 'enabled' => 'enabled-input',
315
+ 'id' => 'id-input',
316
+ 'links' => 'links-input',
317
+ 'metadata' => 'metadata-input'
318
+ }
319
+ }.to_json,
320
+ headers: response_headers
321
+ )
322
+ end
47
323
 
48
- expect(resource.currency).to eq('currency-input')
324
+ subject(:get_response) do
325
+ client.customer_bank_accounts.get(id, headers: {
326
+ 'Foo' => 'Bar'
327
+ })
328
+ end
329
+
330
+ it 'includes the header' do
331
+ get_response
332
+ expect(stub).to have_been_requested
333
+ end
334
+ end
335
+
336
+ context 'when there is a customer_bank_account to return' do
337
+ before do
338
+ stub_url = '/customer_bank_accounts/:identity'.gsub(':identity', id)
339
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
340
+ body: {
341
+ 'customer_bank_accounts' => {
342
+
343
+ 'account_holder_name' => 'account_holder_name-input',
344
+ 'account_number_ending' => 'account_number_ending-input',
345
+ 'bank_name' => 'bank_name-input',
346
+ 'country_code' => 'country_code-input',
347
+ 'created_at' => 'created_at-input',
348
+ 'currency' => 'currency-input',
349
+ 'enabled' => 'enabled-input',
350
+ 'id' => 'id-input',
351
+ 'links' => 'links-input',
352
+ 'metadata' => 'metadata-input'
353
+ }
354
+ }.to_json,
355
+ headers: response_headers
356
+ )
357
+ end
358
+
359
+ it 'wraps the response in a resource' do
360
+ expect(get_response).to be_a(GoCardlessPro::Resources::CustomerBankAccount)
361
+ end
362
+ end
49
363
 
50
- expect(resource.enabled).to eq('enabled-input')
364
+ context 'when nothing is returned' do
365
+ before do
366
+ stub_url = '/customer_bank_accounts/:identity'.gsub(':identity', id)
367
+ stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return(
368
+ body: '',
369
+ headers: response_headers
370
+ )
371
+ end
51
372
 
52
- expect(resource.id).to eq('id-input')
373
+ it 'returns nil' do
374
+ expect(get_response).to be_nil
375
+ end
376
+ end
53
377
 
54
- expect(resource.links.customer).to eq('customer-input')
378
+ context "when an ID is specified which can't be included in a valid URI" do
379
+ let(:id) { '`' }
55
380
 
56
- expect(resource.metadata).to eq('metadata-input')
381
+ it "doesn't raise an error" do
382
+ expect { get_response }.to_not raise_error(/bad URI/)
383
+ end
57
384
  end
385
+ end
386
+
387
+ describe '#update' do
388
+ subject(:put_update_response) { client.customer_bank_accounts.update(id, params: update_params) }
389
+ let(:id) { 'ABC123' }
390
+
391
+ context 'with a valid request' do
392
+ let(:update_params) { { 'hello' => 'world' } }
393
+
394
+ let!(:stub) do
395
+ stub_url = '/customer_bank_accounts/:identity'.gsub(':identity', id)
396
+ stub_request(:put, /.*api.gocardless.com#{stub_url}/).to_return(
397
+ body: {
398
+ 'customer_bank_accounts' => {
399
+
400
+ 'account_holder_name' => 'account_holder_name-input',
401
+ 'account_number_ending' => 'account_number_ending-input',
402
+ 'bank_name' => 'bank_name-input',
403
+ 'country_code' => 'country_code-input',
404
+ 'created_at' => 'created_at-input',
405
+ 'currency' => 'currency-input',
406
+ 'enabled' => 'enabled-input',
407
+ 'id' => 'id-input',
408
+ 'links' => 'links-input',
409
+ 'metadata' => 'metadata-input'
410
+ }
411
+ }.to_json,
412
+ headers: response_headers
413
+ )
414
+ end
58
415
 
59
- it 'can handle new attributes without erroring' do
60
- data['foo'] = 'bar'
61
- expect { described_class.new(data) }.to_not raise_error
416
+ it 'updates and returns the resource' do
417
+ expect(put_update_response).to be_a(GoCardlessPro::Resources::CustomerBankAccount)
418
+ expect(stub).to have_been_requested
419
+ end
62
420
  end
421
+ end
422
+
423
+ describe '#disable' do
424
+ subject(:post_response) { client.customer_bank_accounts.disable(resource_id) }
425
+
426
+ let(:resource_id) { 'ABC123' }
427
+
428
+ let!(:stub) do
429
+ # /customer_bank_accounts/%v/actions/disable
430
+ stub_url = '/customer_bank_accounts/:identity/actions/disable'.gsub(':identity', resource_id)
431
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/).to_return(
432
+ body: {
433
+ 'customer_bank_accounts' => {
63
434
 
64
- it 'can handle new link attributes without erroring' do
65
- data['links']['foo'] = 'bar'
66
- expect { described_class.new(data) }.to_not raise_error
435
+ 'account_holder_name' => 'account_holder_name-input',
436
+ 'account_number_ending' => 'account_number_ending-input',
437
+ 'bank_name' => 'bank_name-input',
438
+ 'country_code' => 'country_code-input',
439
+ 'created_at' => 'created_at-input',
440
+ 'currency' => 'currency-input',
441
+ 'enabled' => 'enabled-input',
442
+ 'id' => 'id-input',
443
+ 'links' => 'links-input',
444
+ 'metadata' => 'metadata-input'
445
+ }
446
+ }.to_json,
447
+ headers: response_headers
448
+ )
67
449
  end
68
450
 
69
- it 'can handle a nil links value' do
70
- data['links'] = nil
71
- expect { described_class.new(data).links }.to_not raise_error
451
+ it 'wraps the response and calls the right endpoint' do
452
+ expect(post_response).to be_a(GoCardlessPro::Resources::CustomerBankAccount)
453
+
454
+ expect(stub).to have_been_requested
72
455
  end
73
456
 
74
- describe '#to_h' do
75
- it 'returns a hash representing the resource' do
76
- expect(described_class.new(data).to_h).to eq(data)
457
+ context 'when the request needs a body and custom header' do
458
+ let(:body) { { foo: 'bar' } }
459
+ let(:headers) { { 'Foo' => 'Bar' } }
460
+ subject(:post_response) { client.customer_bank_accounts.disable(resource_id, body, headers) }
461
+
462
+ let(:resource_id) { 'ABC123' }
463
+
464
+ let!(:stub) do
465
+ # /customer_bank_accounts/%v/actions/disable
466
+ stub_url = '/customer_bank_accounts/:identity/actions/disable'.gsub(':identity', resource_id)
467
+ stub_request(:post, /.*api.gocardless.com#{stub_url}/)
468
+ .with(
469
+ body: { foo: 'bar' },
470
+ headers: { 'Foo' => 'Bar' }
471
+ ).to_return(
472
+ body: {
473
+ 'customer_bank_accounts' => {
474
+
475
+ 'account_holder_name' => 'account_holder_name-input',
476
+ 'account_number_ending' => 'account_number_ending-input',
477
+ 'bank_name' => 'bank_name-input',
478
+ 'country_code' => 'country_code-input',
479
+ 'created_at' => 'created_at-input',
480
+ 'currency' => 'currency-input',
481
+ 'enabled' => 'enabled-input',
482
+ 'id' => 'id-input',
483
+ 'links' => 'links-input',
484
+ 'metadata' => 'metadata-input'
485
+ }
486
+ }.to_json,
487
+ headers: response_headers
488
+ )
77
489
  end
78
490
  end
79
491
  end