nedbank_api 0.2.2

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 (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
+ }