nedbank_api 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +7 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +120 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +46 -0
  11. data/Rakefile +10 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/nedbank_api.rb +39 -0
  15. data/lib/nedbank_api/api_wrapper.rb +29 -0
  16. data/lib/nedbank_api/authentications_api.rb +57 -0
  17. data/lib/nedbank_api/exceptions.rb +5 -0
  18. data/lib/nedbank_api/models/base_model.rb +21 -0
  19. data/lib/nedbank_api/models/intent_token.rb +36 -0
  20. data/lib/nedbank_api/models/payment.rb +10 -0
  21. data/lib/nedbank_api/models/payment_submission.rb +6 -0
  22. data/lib/nedbank_api/payments_api.rb +37 -0
  23. data/lib/nedbank_api/services/client.rb +18 -0
  24. data/lib/nedbank_api/services/configuration.rb +18 -0
  25. data/lib/nedbank_api/services/http.rb +39 -0
  26. data/lib/nedbank_api/version.rb +3 -0
  27. data/nedbank_api.gemspec +34 -0
  28. data/spec/authorisations_api_spec.rb +51 -0
  29. data/spec/client_spec.rb +16 -0
  30. data/spec/factories/client.rb +17 -0
  31. data/spec/factories/payment.rb +0 -0
  32. data/spec/nedbank_api_spec.rb +12 -0
  33. data/spec/payments_api_spec.rb +51 -0
  34. data/spec/spec_helper.rb +31 -0
  35. data/spec/support/authentication/post_auth_token.json +0 -0
  36. data/spec/support/payment/post_intent_request.json +41 -0
  37. data/spec/support/payment/post_intent_response.json +47 -0
  38. data/spec/support/payment/post_payment_submission_request.json +55 -0
  39. data/spec/support/payment/post_payment_submission_response.json +16 -0
  40. metadata +264 -0
@@ -0,0 +1,5 @@
1
+ module NedbankApi
2
+ module Exceptions
3
+ class TokenExpired < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module NedbankApi
2
+ module Models
3
+ class BaseModel < Delegator
4
+ attr_accessor :initialized_at
5
+
6
+ def initialize(obj)
7
+ super
8
+ @delegate_sd_obj = obj
9
+ self.initialized_at = Time.now
10
+ end
11
+
12
+ def __getobj__
13
+ @delegate_sd_obj
14
+ end
15
+
16
+ def __setobj__(obj)
17
+ @delegate_sd_obj = obj
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module NedbankApi
2
+ module Models
3
+ class IntentToken < BaseModel
4
+ ERRORS = {
5
+ token_expired: {
6
+ error: 'token_expired',
7
+ error_description: 'Intent Access Token has expired'
8
+ }
9
+ }
10
+
11
+ attr_accessor :token_expires_at,
12
+ :error,
13
+ :error_description
14
+
15
+ def token_expires_at
16
+ self.initialized_at + self.expires_in
17
+ end
18
+
19
+ def authenticated?
20
+ return false if self.access_token.nil?
21
+ !expired?
22
+ end
23
+
24
+ def expired?
25
+ return true if self.expires_in.nil?
26
+ raise Exceptions::TokenExpired if token_expires_at < Time.now
27
+ return false
28
+
29
+ rescue Exceptions::TokenExpired
30
+ self.error = ERRORS[:token_expired][:error]
31
+ self.error_description = ERRORS[:token_expired][:error_description ]
32
+ return true
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ module NedbankApi
2
+ module Models
3
+ class Payment < BaseModel
4
+
5
+ def created?
6
+ self.Data.present?
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module NedbankApi
2
+ module Models
3
+ class PaymentSubmission < BaseModel
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ module NedbankApi
2
+ class PaymentsApi < ApiWrapper
3
+ class << self
4
+ def create_intent(request_body: {})
5
+ http = Http.new(url: endpoint('/open-banking/payments'))
6
+
7
+ response = http.post(
8
+ headers: auth_headers,
9
+ body: request_body.to_json
10
+ )
11
+
12
+ return Models::Payment.new(json_to_object(response.body))
13
+ end
14
+
15
+ def submit_payment(request_body: {})
16
+ http = Http.new(url: endpoint('/open-banking/payment-submissions'))
17
+
18
+ response = http.post(
19
+ headers: auth_headers,
20
+ body: request_body.to_json
21
+ )
22
+
23
+ return Models::PaymentSubmission.new(json_to_object(response.body))
24
+ end
25
+
26
+ def get_payment_submission(payment_submission_id:)
27
+ http = Http.new(url: endpoint('/open-banking/payment-submissions/' + payment_submission_id))
28
+
29
+ response = http.get(
30
+ headers: auth_headers
31
+ )
32
+
33
+ return Models::PaymentSubmission.new(json_to_object(response.body))
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ module NedbankApi
2
+ class Client
3
+
4
+ def initialize(client_id:, client_secret:, api_base: DEFAULT_API_BASE)
5
+ NedbankApi.client_id = client_id
6
+ NedbankApi.client_secret = client_secret
7
+ NedbankApi.api_base = api_base unless api_base.nil?
8
+ end
9
+
10
+ def authentication
11
+ @_authentication ||= AuthenticationsApi.new
12
+ end
13
+
14
+ def payment
15
+ @_payment ||= PaymentsApi.new
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module NedbankApi
2
+ class Configuration
3
+
4
+ attr_accessor :client_id,
5
+ :client_secret,
6
+ :api_endpoint,
7
+ :oauth_redirect_url
8
+
9
+ API_ENDPOINT = 'https://api.nedbank.co.za/apimarket/sandbox'
10
+
11
+ def initialize
12
+ @client_id = nil
13
+ @client_secret = nil
14
+ @oauth_redirect_url = nil
15
+ @api_endpoint = API_ENDPOINT
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ module NedbankApi
2
+ class Http
3
+ def initialize(url:)
4
+ @url = URI(url)
5
+ end
6
+
7
+ def net_http
8
+ @net_http ||= Net::HTTP.new(@url.host, @url.port).tap do |http|
9
+ http.use_ssl = true
10
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
11
+ end
12
+ end
13
+
14
+ def get(body: {}, headers: {})
15
+ request = Net::HTTP::Get.new(@url)
16
+
17
+ headers.each do |key,value|
18
+ request[key] = value
19
+ end
20
+
21
+ request.body = body
22
+ response = net_http.request(request)
23
+
24
+ return response
25
+ end
26
+ def post(body: {}, headers: {})
27
+ request = Net::HTTP::Post.new(@url)
28
+
29
+ headers.each do |key,value|
30
+ request[key] = value
31
+ end
32
+
33
+ request.body = body
34
+ response = net_http.request(request)
35
+
36
+ return response
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module NedbankApi
2
+ VERSION = "0.2.2"
3
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'nedbank_api/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{nedbank_api}
8
+ s.version = NedbankApi::VERSION
9
+ s.date = %q{2019-09-11}
10
+ s.summary = %q{Nedbank API gem makes communicating with the Nedbank API quick and easy}
11
+ s.licenses = ['MIT']
12
+ s.authors = ['Jono Booth']
13
+ s.email = 'jonobth@gmail.com'
14
+ s.metadata = { 'source_code_uri' => 'https://github.com/jono-booth/nedbank_api' }
15
+
16
+ s.files = `git ls-files -z`.split("\x0")
17
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ s.test_files = s.files.grep(%r{^spec/})
19
+ s.require_paths = ['lib']
20
+
21
+ s.add_development_dependency 'bundler', '~> 1.5'
22
+ s.add_development_dependency 'rake', '~> 12.3'
23
+ s.add_development_dependency 'rspec', '~> 3.8'
24
+ s.add_development_dependency 'rspec-nc', '~> 0.3'
25
+ s.add_development_dependency 'webmock', '~> 3.7'
26
+ s.add_development_dependency 'factory_bot', '~> 5.0'
27
+ s.add_development_dependency 'guard', '~> 2.15'
28
+ s.add_development_dependency 'guard-rspec', '~> 4.7'
29
+ s.add_development_dependency 'pry', '~> 0.12'
30
+ s.add_development_dependency 'pry-remote', '~> 0.1'
31
+ s.add_development_dependency 'pry-nav', '~> 0.3'
32
+ s.add_development_dependency 'httplog', '~> 1.3'
33
+
34
+ end
@@ -0,0 +1,51 @@
1
+ RSpec.describe NedbankApi::AuthenticationsApi do
2
+ describe '.request_token_light' do
3
+ context 'a valid request' do
4
+ let(:response_body) { attributes_for :fake_client }
5
+
6
+ before :each do
7
+ stub_request(:post, "https://api.nedbank.co.za/apimarket/sandbox/nboauth/oauth20/token").
8
+ to_return(status: 200, body: response_body.to_json, headers: {})
9
+ end
10
+
11
+ it 'authenticates and sets the access token' do
12
+ token = NedbankApi::AuthenticationsApi.request_token_light
13
+ expect(NedbankApi.intent_token.access_token).to eq response_body[:access_token]
14
+ expect(token.authenticated?).to be true
15
+ end
16
+
17
+ context 'with an expired access token' do
18
+ it 'does not authenticate' do
19
+ token = NedbankApi::AuthenticationsApi.request_token_light
20
+ token.initialized_at = Time.now - 3599
21
+ expect(token.authenticated?).to be false
22
+ expect(token.error).to eq NedbankApi::Models::IntentToken::ERRORS[:token_expired][:error]
23
+ expect(token.error_description).to eq NedbankApi::Models::IntentToken::ERRORS[:token_expired][:error_description]
24
+ end
25
+ end
26
+ end
27
+
28
+ context 'given an error' do
29
+ let(:response_body) { attributes_for(:client_with_error, :invalid_client) }
30
+
31
+ before :each do
32
+ stub_request(:post, "https://api.nedbank.co.za/apimarket/sandbox/nboauth/oauth20/token").
33
+ to_return(status: 400, body: response_body.to_json, headers: {})
34
+ end
35
+
36
+ it 'does not authenticate' do
37
+ token = NedbankApi::AuthenticationsApi.request_token_light
38
+ expect(token.authenticated?).to be false
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '.authorise_url' do
44
+ let(:intent_id) { "GOODPEOPLEDRINKGOODBEER" }
45
+
46
+ it 'returns a payment auth url' do
47
+ authorisation_url = NedbankApi::AuthenticationsApi.authorisation_url(request_body: { intentid: intent_id })
48
+ expect(authorisation_url).to include "intentid=#{intent_id}"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ RSpec.describe NedbankApi::Client do
2
+ describe '.initialize' do
3
+
4
+ it 'sets up the client' do
5
+ NedbankApi::Client.new(
6
+ client_id: 'CLIENTID',
7
+ client_secret: 'CLIENTSECRET',
8
+ api_base: 'APIBASE'
9
+ )
10
+
11
+ expect(NedbankApi.client_id).to eq 'CLIENTID'
12
+ expect(NedbankApi.client_secret).to eq 'CLIENTSECRET'
13
+ expect(NedbankApi.api_base).to eq 'APIBASE'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ FactoryBot.define do
2
+ factory :fake_client, class: OpenStruct do
3
+ access_token { 'THISISBATCOUNTRY' }
4
+ client_id { 'NEVERTRUSTACOPINARAINCOAT' }
5
+ client_secret { 'TOOWEIRDTOLIVETORARETODIE' }
6
+ expires_in { 3599 }
7
+ api_url { 'https://api.nedbank.co.za/apimarket/sandbox' }
8
+
9
+ factory :client_with_error do
10
+ trait :invalid_client do
11
+ access_token { nil }
12
+ error_description { 'FBTOAU204E An invalid secret was provided for the client with identifier: [CLIENT_ID].' }
13
+ error { 'invalid_client' }
14
+ end
15
+ end
16
+ end
17
+ end
File without changes
@@ -0,0 +1,12 @@
1
+ RSpec.describe NedbankApi do
2
+ it "has a version number" do
3
+ expect(NedbankApi::VERSION).not_to be nil
4
+ end
5
+
6
+ let(:client) { build :fake_client }
7
+
8
+ it "sets the client id" do
9
+ NedbankApi.client_id = client.client_id
10
+ expect(NedbankApi.client_id).to eq client.client_id
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ RSpec.describe NedbankApi::PaymentsApi do
2
+ describe '.create_intent' do
3
+ let(:request_body) { File.read('spec/support/payment/post_intent_request.json') }
4
+ let(:response_body) { File.read('spec/support/payment/post_intent_response.json') }
5
+
6
+ before :each do
7
+ stub_request(:post, "https://api.nedbank.co.za/apimarket/sandbox/open-banking/payments").
8
+ to_return(status: 200, body: response_body)
9
+ end
10
+
11
+ it 'returns a payment object' do
12
+ payment = NedbankApi::PaymentsApi.create_intent(request_body: request_body)
13
+
14
+ expect(payment.Data.PaymentId).to eq JSON.parse(response_body)['Data']['PaymentId']
15
+ expect(payment.Data.Initiation.InstructedAmount.Amount).to eq JSON.parse(response_body)['Data']['Initiation']['InstructedAmount']['Amount']
16
+ end
17
+ end
18
+
19
+ describe '.submit_payment' do
20
+ let(:request_body) { File.read('spec/support/payment/post_payment_submission_request.json') }
21
+ let(:response_body) { File.read('spec/support/payment/post_payment_submission_response.json') }
22
+
23
+ before :each do
24
+ stub_request(:post, "https://api.nedbank.co.za/apimarket/sandbox/open-banking/payment-submissions").
25
+ to_return(status: 200, body: response_body)
26
+ end
27
+
28
+ it 'returns a payment submission object' do
29
+ submission = NedbankApi::PaymentsApi.submit_payment(request_body: request_body)
30
+ expect(submission.PaymentSubmissionId).to eq "62820068622336"
31
+ expect(submission.PaymentId).to eq "8124568155717632"
32
+ expect(submission.Status).to eq "AcceptedSettlementInProcess"
33
+ end
34
+ end
35
+
36
+ describe '.get_payment_submission' do
37
+ let(:payment_submission_id) { "62820068622336" }
38
+ let(:response_body) { File.read('spec/support/payment/post_payment_submission_response.json') }
39
+
40
+ before :each do
41
+ stub_request(:get, "https://api.nedbank.co.za/apimarket/sandbox/open-banking/payment-submissions/#{payment_submission_id}").
42
+ to_return(status: 200, body: response_body)
43
+ end
44
+
45
+ it 'returns a payment submission object' do
46
+ submission = NedbankApi::PaymentsApi.get_payment_submission(payment_submission_id: payment_submission_id)
47
+ expect(submission.PaymentSubmissionId).to eq payment_submission_id
48
+ expect(submission.Status).to eq "AcceptedSettlementInProcess"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ require "bundler/setup"
2
+ require "nedbank_api"
3
+ require 'pry'
4
+ require 'webmock/rspec'
5
+ require 'factory_bot'
6
+ require 'httplog'
7
+
8
+ RSpec.configure do |config|
9
+ # Enable flags like --only-failures and --next-failure
10
+ config.example_status_persistence_file_path = ".rspec_status"
11
+
12
+ # Disable RSpec exposing methods globally on `Module` and `main`
13
+ config.disable_monkey_patching!
14
+
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+
19
+ config.include FactoryBot::Syntax::Methods
20
+
21
+ config.before :all do
22
+ FactoryBot.find_definitions
23
+ end
24
+
25
+ WebMock.disable_net_connect!(allow_localhost: true)
26
+ end
27
+
28
+ HttpLog.configure do |config|
29
+ config.log_headers = false
30
+ config.color = :yellow
31
+ end
File without changes
@@ -0,0 +1,41 @@
1
+ {
2
+ "Data": {
3
+ "Initiation": {
4
+ "InstructionIdentification": "AAA4",
5
+ "EndToEndIdentification": "FRESCO.21302.GFX.24",
6
+ "InstructedAmount": {
7
+ "Amount": "55.60",
8
+ "Currency": "ZAR"
9
+ },
10
+ "CreditorAgent": {
11
+ "SchemeName": "ZASortCode",
12
+ "Identification": "470010"
13
+ },
14
+ "CreditorAccount": {
15
+ "SchemeName": "BBAN",
16
+ "Identification": "1028232942",
17
+ "Name": "ACME Inc",
18
+ "SecondaryIdentification": "0002"
19
+ },
20
+ "RemittanceInformation": {
21
+ "Unstructured": "Internal ops code 5120101",
22
+ "Reference": "FRESCO-101"
23
+ }
24
+ }
25
+ },
26
+ "Risk": {
27
+ "PaymentContextCode": "Other",
28
+ "MerchantCategoryCode": "5967",
29
+ "MerchantCustomerIdentification": "053598653254",
30
+ "DeliveryAddress": {
31
+ "AddressLine": [
32
+ "Flat 7"
33
+ ],
34
+ "StreetName": "Acacia Avenue",
35
+ "BuildingNumber": "27",
36
+ "PostCode": "GU31 2ZZ",
37
+ "TownName": "Sparsholt",
38
+ "Country": "UK"
39
+ }
40
+ }
41
+ }