currency_cloud 0.5 → 0.7

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +151 -8
  4. data/currency_cloud.gemspec +1 -0
  5. data/lib/currency_cloud.rb +14 -0
  6. data/lib/currency_cloud/actions/delete.rb +7 -1
  7. data/lib/currency_cloud/actions/save.rb +14 -0
  8. data/lib/currency_cloud/errors/api_error.rb +38 -2
  9. data/lib/currency_cloud/errors/error_utils.rb +14 -0
  10. data/lib/currency_cloud/errors/general_error.rb +5 -0
  11. data/lib/currency_cloud/errors/unexpected_error.rb +23 -1
  12. data/lib/currency_cloud/request_handler.rb +27 -20
  13. data/lib/currency_cloud/resource.rb +7 -1
  14. data/lib/currency_cloud/resources/balance.rb +3 -1
  15. data/lib/currency_cloud/resources/beneficiary.rb +2 -3
  16. data/lib/currency_cloud/resources/payment.rb +0 -2
  17. data/lib/currency_cloud/resources/settlement.rb +30 -9
  18. data/lib/currency_cloud/response_handler.rb +14 -15
  19. data/lib/currency_cloud/session.rb +4 -4
  20. data/lib/currency_cloud/version.rb +1 -1
  21. data/spec/currency_cloud/request_handler_spec.rb +32 -0
  22. data/spec/currency_cloud/resource_spec.rb +37 -0
  23. data/spec/currency_cloud_spec.rb +49 -5
  24. data/spec/integration/actions_spec.rb +28 -0
  25. data/spec/integration/errors_spec.rb +47 -0
  26. data/spec/integration/rates_spec.rb +1 -1
  27. data/spec/integration/settlements_spec.rb +67 -0
  28. data/spec/spec_helper.rb +3 -2
  29. data/spec/support/vcr_cassettes/Actions/can_first.yml +0 -36
  30. data/spec/support/vcr_cassettes/Actions/can_use_currency_to_retrieve_balance.yml +36 -0
  31. data/spec/support/vcr_cassettes/Actions/can_validate_beneficiaries.yml +38 -0
  32. data/spec/support/vcr_cassettes/Error/contains_full_details_for_api_error.yml +35 -0
  33. data/spec/support/vcr_cassettes/Error/is_raised_on_an_internal_server_error.yml +0 -34
  34. data/spec/support/vcr_cassettes/Rates/can_find.yml +3 -3
  35. data/spec/support/vcr_cassettes/Rates/can_provided_detailed_rate.yml +3 -3
  36. data/spec/support/vcr_cassettes/Reference/can_retrieve_beneficiary_required_details.yml +3 -3
  37. data/spec/support/vcr_cassettes/Reference/can_retrieve_conversion_dates.yml +3 -3
  38. data/spec/support/vcr_cassettes/Reference/can_retrieve_settlement_accounts.yml +3 -3
  39. data/spec/support/vcr_cassettes/Settlements/can_add_conversion.yml +102 -0
  40. data/spec/support/vcr_cassettes/Settlements/can_release.yml +69 -0
  41. data/spec/support/vcr_cassettes/Settlements/can_remove_conversion.yml +69 -0
  42. data/spec/support/vcr_cassettes/Settlements/can_unrelease.yml +69 -0
  43. metadata +26 -5
  44. data/Gemfile.lock +0 -99
  45. data/lib/currency_cloud/errors/config_error.rb +0 -5
@@ -11,13 +11,19 @@ module CurrencyCloud
11
11
  def self.actions(*actions)
12
12
  @actions ||= actions
13
13
  @actions.each do |action|
14
- self.class_eval do # self.class.instance_eval # metaclass.instance_eval
14
+ self.class_eval do
15
15
  action_module = CurrencyCloud::Actions.const_get(action.to_s.capitalize)
16
16
  self.extend(action_module)
17
17
  end
18
+
19
+
20
+ self.send(:include, CurrencyCloud::Actions::Save) if action == :update
21
+ self.send(:include, CurrencyCloud::Actions::InstanceDelete) if action == :delete
18
22
  end
19
23
  end
20
24
 
25
+ attr_reader :changed_attributes
26
+
21
27
  def initialize(object)
22
28
  @object = object
23
29
  @changed_attributes = Set.new
@@ -3,6 +3,8 @@ module CurrencyCloud
3
3
  resource :balances
4
4
  actions :find
5
5
 
6
- #TODO: currency
6
+ def self.currency(ccy)
7
+ get(ccy)
8
+ end
7
9
  end
8
10
  end
@@ -7,8 +7,7 @@ module CurrencyCloud
7
7
  actions :create, :retrieve, :find, :update, :delete
8
8
 
9
9
  def self.validate(params)
10
- new(request.get("#{self.resource}/validate", params))
11
- end
12
-
10
+ post('validate', params)
11
+ end
13
12
  end
14
13
  end
@@ -5,7 +5,5 @@ module CurrencyCloud
5
5
  resource :payments
6
6
 
7
7
  actions :create, :retrieve, :find, :delete, :update
8
-
9
- #TODO: authorize, authorizations
10
8
  end
11
9
  end
@@ -5,26 +5,47 @@ module CurrencyCloud
5
5
  resource :settlements
6
6
 
7
7
  actions :create, :retrieve, :find, :delete
8
-
8
+
9
9
  def add_conversion(conversion_id)
10
- # TODO: Should just update state of current object using a refresh method?
11
- new(request.post("#{self.resource}/#{self.id}/add_conversion", conversion_id: conversion_id))
10
+ update_attributes(Settlement.add_conversion(id, conversion_id))
12
11
  end
13
12
 
14
13
  def remove_conversion(conversion_id)
15
- # TODO: Should just update state of current object using a refresh method?
16
- new(request.post("#{self.resource}/#{self.id}/remove_conversion", conversion_id: conversion_id))
14
+ update_attributes(Settlement.remove_conversion(id, conversion_id))
17
15
  end
18
16
 
19
17
  def release
20
- # TODO: Should just update state of current object using a refresh method?
21
- new(request.post("#{self.resource}/#{self.id}/release"))
18
+ update_attributes(Settlement.release(id))
22
19
  end
23
20
 
24
21
  def unrelease
25
- # TODO: Should just update state of current object using a refresh method?
26
- new(request.post("#{self.resource}/#{self.id}/unrelease"))
22
+ update_attributes(Settlement.unrelease(id))
23
+ end
24
+
25
+ def self.add_conversion(settlement_id, conversion_id)
26
+ post("#{settlement_id}/add_conversion", conversion_id: conversion_id)
27
+ end
28
+
29
+ def self.remove_conversion(settlement_id, conversion_id)
30
+ post("#{settlement_id}/remove_conversion", conversion_id: conversion_id)
27
31
  end
28
32
 
33
+ def self.release(settlement_id)
34
+ post("#{settlement_id}/release")
35
+ end
36
+
37
+ def self.unrelease(settlement_id)
38
+ post("#{settlement_id}/unrelease")
39
+ end
40
+
41
+ private
42
+ def update_attributes(settlement)
43
+ self.conversion_ids = settlement.conversion_ids
44
+ self.status = settlement.status
45
+ self.entries = settlement.entries
46
+ self.updated_at = settlement.updated_at
47
+ self.released_at = settlement.released_at
48
+ self
49
+ end
29
50
  end
30
51
  end
@@ -2,17 +2,16 @@ module CurrencyCloud
2
2
 
3
3
  class ResponseHandler
4
4
 
5
- attr_reader :response
5
+ attr_reader :verb, :route, :params, :response
6
6
 
7
- def self.process(response)
8
- new(response).data
9
- end
10
-
11
- def initialize(response)
7
+ def initialize(verb, route, params, response)
8
+ @verb = verb
9
+ @route = route
10
+ @params = params
12
11
  @response = response
13
12
  end
14
13
 
15
- def data
14
+ def process
16
15
  if success?
17
16
  return parsed_response
18
17
  else
@@ -28,15 +27,15 @@ module CurrencyCloud
28
27
 
29
28
  def handle_failure
30
29
  error_class = case response.code
31
- when 400 then CurrencyCloud::BadRequestError
32
- when 401 then CurrencyCloud::AuthenticationError
33
- when 403 then CurrencyCloud::ForbiddenError
34
- when 404 then CurrencyCloud::NotFoundError
35
- when 429 then CurrencyCloud::TooManyRequestsError
36
- when 500 then CurrencyCloud::InternalApplicationError
37
- else CurrencyCloud::UnexpectedError
30
+ when 400 then BadRequestError
31
+ when 401 then AuthenticationError
32
+ when 403 then ForbiddenError
33
+ when 404 then NotFoundError
34
+ when 429 then TooManyRequestsError
35
+ when 500 then InternalApplicationError
38
36
  end
39
- raise error_class.new(response)
37
+ raise error_class.new(verb, route, params, response) if error_class
38
+ raise UnexpectedError.new(verb, route, params, response)
40
39
  end
41
40
 
42
41
  def parsed_response
@@ -7,11 +7,11 @@ module CurrencyCloud
7
7
  :uat => 'https://api-uat1.ccycloud.com'}
8
8
 
9
9
  attr_reader :environment, :login_id, :api_key
10
- attr_accessor :token
10
+ attr_accessor :token, :on_behalf_of
11
11
 
12
12
  def self.validate_environment(environment)
13
13
  unless Environments.keys.include?(environment)
14
- raise CurrencyCloud::ConfigError, "'#{environment}' is not a valid environment, must be one of: #{Environments.keys.join(", ")}"
14
+ raise CurrencyCloud::GeneralError, "'#{environment}' is not a valid environment, must be one of: #{Environments.keys.join(", ")}"
15
15
  end
16
16
  end
17
17
 
@@ -51,8 +51,8 @@ module CurrencyCloud
51
51
  private
52
52
  def validate
53
53
  self.class.validate_environment(environment)
54
- raise CurrencyCloud::ConfigError, "login_id must be set using CurrencyCloud.login_id=" unless login_id
55
- raise CurrencyCloud::ConfigError, "api_key must be set using CurrencyCloud.api_key=" unless api_key
54
+ raise CurrencyCloud::GeneralError, "login_id must be set using CurrencyCloud.login_id=" unless login_id
55
+ raise CurrencyCloud::GeneralError, "api_key must be set using CurrencyCloud.api_key=" unless api_key
56
56
  end
57
57
 
58
58
  def request
@@ -3,6 +3,6 @@
3
3
  module CurrencyCloud
4
4
 
5
5
  ApiVersion = 'v2' # API Version
6
- Version = '0.5' # Gem Version
6
+ Version = '0.7' # Gem Version
7
7
 
8
8
  end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'RequestHandler' do
4
+ let(:session) { CurrencyCloud::Session.new(:demonstration, nil, nil, '4df5b3e5882a412f148dcd08fa4e5b73')}
5
+ let(:response) { double('Response', code: 200, body: '{}') }
6
+
7
+ subject { CurrencyCloud::RequestHandler.new(session) }
8
+
9
+ describe 'with on_behalf_of parameter' do
10
+ it "adds it to parameters in HTTP call" do
11
+ session.on_behalf_of = "d1f7f5c2-4187-41da-88fc-b3ae40fa958f"
12
+
13
+ allow(HTTParty).to receive(:post) do |_, params|
14
+ expect(params).to include(:body)
15
+ expect(params[:body]).to include(on_behalf_of: "d1f7f5c2-4187-41da-88fc-b3ae40fa958f")
16
+ end.and_return(response)
17
+
18
+ subject.post('accounts/create', account_name: "Test Account")
19
+ end
20
+
21
+ it "ignores it if it's not a uuid" do
22
+ session.on_behalf_of = "nonsense variable"
23
+
24
+ allow(HTTParty).to receive(:post) do |_, params|
25
+ expect(params).to include(:body)
26
+ expect(params[:body]).to eq(account_name: "Test Account")
27
+ end.and_return(response)
28
+
29
+ subject.post('accounts/create', account_name: "Test Account")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Resource' do
4
+ before(:all) do
5
+ class Person < CurrencyCloud::Resource
6
+ resource :people
7
+ actions :update, :delete
8
+ end
9
+ end
10
+
11
+ describe "#save" do
12
+ it "only updates changed records" do
13
+ person = Person.new(id: 1, name: 'Richard', surname: 'Nienaber')
14
+ allow(person).to receive(:post).with(1, name: 'John')
15
+ person.name = 'John'
16
+
17
+ expect(person.save).to eq(person)
18
+ expect(person.changed_attributes).to eq(Set.new)
19
+ end
20
+
21
+ it "does nothing if nothing has changed" do
22
+ person = Person.new(id: 1, name: 'Richard', surname: 'Nienaber')
23
+
24
+ expect(person.save).to eq(person)
25
+ expect(person.changed_attributes).to eq(Set.new)
26
+ end
27
+ end
28
+
29
+ describe "#delete" do
30
+ it 'removes resource' do
31
+ person = Person.new(id: 1, name: 'Richard', surname: 'Nienaber')
32
+ allow(Person).to receive(:post).with('1/delete')
33
+
34
+ expect(person.delete).to eq(person)
35
+ end
36
+ end
37
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe CurrencyCloud do
4
4
 
5
- before(:each) do
5
+ before do
6
6
  CurrencyCloud.environment = nil
7
7
  CurrencyCloud.login_id = nil
8
8
  CurrencyCloud.api_key = nil
@@ -53,26 +53,70 @@ describe CurrencyCloud do
53
53
  it "raises an error if the environment is not set" do
54
54
  CurrencyCloud.environment = nil
55
55
  expect { CurrencyCloud.session }
56
- .to raise_error(CurrencyCloud::ConfigError, "'' is not a valid environment, must be one of: production, demonstration, uat")
56
+ .to raise_error(CurrencyCloud::GeneralError, "'' is not a valid environment, must be one of: production, demonstration, uat")
57
57
  end
58
58
 
59
59
  it "raises an error if the environment is invalid" do
60
60
  CurrencyCloud.environment = :invalid
61
61
  expect { CurrencyCloud.session }
62
- .to raise_error(CurrencyCloud::ConfigError, "'invalid' is not a valid environment, must be one of: production, demonstration, uat")
62
+ .to raise_error(CurrencyCloud::GeneralError, "'invalid' is not a valid environment, must be one of: production, demonstration, uat")
63
63
  end
64
64
 
65
65
  it "raises an error if the login_id is not set" do
66
66
  CurrencyCloud.environment = :demonstration
67
67
  expect { CurrencyCloud.session }
68
- .to raise_error(CurrencyCloud::ConfigError, "login_id must be set using CurrencyCloud.login_id=")
68
+ .to raise_error(CurrencyCloud::GeneralError, "login_id must be set using CurrencyCloud.login_id=")
69
69
  end
70
70
 
71
71
  it "raises an error if the api_key is not set" do
72
72
  CurrencyCloud.environment = :demonstration
73
73
  CurrencyCloud.login_id = 'test@example.com'
74
74
  expect { CurrencyCloud.session }
75
- .to raise_error(CurrencyCloud::ConfigError, "api_key must be set using CurrencyCloud.api_key=")
75
+ .to raise_error(CurrencyCloud::GeneralError, "api_key must be set using CurrencyCloud.api_key=")
76
76
  end
77
77
  end
78
+
79
+ describe "#on_behalf_of" do
80
+ before do
81
+ CurrencyCloud.environment = :demonstration
82
+ CurrencyCloud.token = '4df5b3e5882a412f148dcd08fa4e5b73'
83
+ CurrencyCloud.session.on_behalf_of = nil
84
+ end
85
+
86
+ it "sets the value on the session, and removes it when done" do
87
+ CurrencyCloud.on_behalf_of('c6ece846-6df1-461d-acaa-b42a6aa74045') do
88
+ expect(CurrencyCloud.session.on_behalf_of).to eq('c6ece846-6df1-461d-acaa-b42a6aa74045')
89
+ end
90
+ expect(CurrencyCloud.session.on_behalf_of).to be_nil
91
+ end
92
+
93
+ it "still removes the value from the session on error" do
94
+ expect do
95
+ CurrencyCloud.on_behalf_of('c6ece846-6df1-461d-acaa-b42a6aa74045') do
96
+ expect(CurrencyCloud.session.on_behalf_of).to eq('c6ece846-6df1-461d-acaa-b42a6aa74045')
97
+ raise 'Completed Expected error'
98
+ end
99
+ end.to raise_error('Completed Expected error')
100
+
101
+ expect(CurrencyCloud.session.on_behalf_of).to be_nil
102
+ end
103
+
104
+ it 'prevents reentrant usage' do
105
+ expect do
106
+ CurrencyCloud.on_behalf_of('c6ece846-6df1-461d-acaa-b42a6aa74045') do
107
+ CurrencyCloud.on_behalf_of('f57b2d33-652c-4589-a8ff-7762add2706d') do
108
+ raise "Should raise exception"
109
+ end
110
+ end
111
+ end.to raise_error(CurrencyCloud::GeneralError, "#on_behalf_of has already been set")
112
+ end
113
+
114
+ it 'prevents reentrant usage' do
115
+ expect do
116
+ CurrencyCloud.on_behalf_of('Richard Nienaber') do
117
+ raise "Should raise exception"
118
+ end
119
+ end.to raise_error(CurrencyCloud::GeneralError, "contact id for on behalf of is not a UUID")
120
+ end
121
+ end
78
122
  end
@@ -98,4 +98,32 @@ describe 'Actions', :vcr => true do
98
98
  expect(account.created_at).to eq('2015-04-24T15:57:55+00:00')
99
99
  expect(account.updated_at).to eq('2015-04-24T15:57:55+00:00')
100
100
  end
101
+
102
+ it "can #validate beneficiaries" do
103
+ params = {
104
+ :bank_country => 'GB',
105
+ :currency => 'GBP',
106
+ :account_number => '12345678',
107
+ :routing_code_type_1 => 'sort_code',
108
+ :routing_code_value_1 => '123456',
109
+ :payment_types => ['regular']
110
+ }
111
+
112
+ beneficiary = CurrencyCloud::Beneficiary.validate(params)
113
+ expect(beneficiary).to be_a_kind_of(CurrencyCloud::Beneficiary)
114
+
115
+ expect(beneficiary.account_number).to include('12345678')
116
+ expect(beneficiary.payment_types).to include('regular')
117
+ end
118
+
119
+ it "can use #currency to retrieve balance" do
120
+ balance = CurrencyCloud::Balance.currency('GBP')
121
+
122
+ expect(balance).to be_a_kind_of(CurrencyCloud::Balance)
123
+
124
+ expect(balance.id).to eq("5a998e06-3eb7-46d6-ba58-f749864159ce")
125
+ expect(balance.amount).to eq('999866.78')
126
+ expect(balance.created_at).to eq('2014-12-04T09:50:35+00:00')
127
+ expect(balance.updated_at).to eq('2015-03-23T14:33:37+00:00')
128
+ end
101
129
  end
@@ -10,6 +10,40 @@ describe 'Error', :vcr => true do
10
10
  CurrencyCloud.token = nil
11
11
  end
12
12
 
13
+ it 'contains full details for api error' do
14
+ CurrencyCloud.login_id = 'non-existent-login-id'
15
+ CurrencyCloud.api_key = 'ef0fd50fca1fb14c1fab3a8436b9ecb57528f0'
16
+
17
+ error = nil
18
+ begin
19
+ CurrencyCloud.session
20
+ raise 'Should have failed'
21
+ rescue CurrencyCloud::BadRequestError => error
22
+ end
23
+
24
+ expected_error = %Q{CurrencyCloud::BadRequestError
25
+ ---
26
+ platform: #{error.platform}
27
+ request:
28
+ parameters:
29
+ login_id: non-existent-login-id
30
+ api_key: ef0fd50fca1fb14c1fab3a8436b9ecb57528f0
31
+ verb: post
32
+ url: https://devapi.thecurrencycloud.com/v2/authenticate/api
33
+ response:
34
+ status_code: 400
35
+ date: Wed, 29 Apr 2015 22:46:53 GMT
36
+ request_id: 2775253392756800903
37
+ errors:
38
+ - field: api_key
39
+ code: api_key_length_is_invalid
40
+ message: api_key should be 64 character(s) long
41
+ params:
42
+ length: 64
43
+ }
44
+ expect(error.to_s).to eq(expected_error)
45
+ end
46
+
13
47
  it 'is raised on a bad request' do
14
48
  CurrencyCloud.login_id = 'non-existent-login-id'
15
49
  CurrencyCloud.api_key = 'ef0fd50fca1fb14c1fab3a8436b9ecb57528f0'
@@ -66,6 +100,19 @@ describe 'Error', :vcr => true do
66
100
  rescue CurrencyCloud::UnexpectedError => error
67
101
  end
68
102
 
103
+ expected_error = %Q{CurrencyCloud::UnexpectedError
104
+ ---
105
+ platform: #{error.platform}
106
+ request:
107
+ parameters:
108
+ login_id: rjnienaber@gmail.com
109
+ api_key: ef0fd50fca1fb14c1fab3a8436b9ecb65f02f129fd87eafa45ded8ae257528f0
110
+ verb: post
111
+ url: https://devapi.thecurrencycloud.com/v2/authenticate/api
112
+ inner_error: Timeout::Error
113
+ }
114
+
115
+ expect(error.to_s).to eq(expected_error)
69
116
  expect(error.inner_error).to_not be_nil
70
117
  expect(error.inner_error.class).to eq(Timeout::Error)
71
118
  end
@@ -4,7 +4,7 @@ describe 'Rates', :vcr => true do
4
4
  before do
5
5
  CurrencyCloud.reset_session
6
6
  CurrencyCloud.environment = :demonstration
7
- CurrencyCloud.token = '242993ca94b9d1c6c1d8f7d3275a6f36'
7
+ CurrencyCloud.token = 'bbdd421bdda373ea69670c9101fa9197'
8
8
  end
9
9
 
10
10
  it 'can #find' do