nedbank_api 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +120 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/nedbank_api.rb +39 -0
- data/lib/nedbank_api/api_wrapper.rb +29 -0
- data/lib/nedbank_api/authentications_api.rb +57 -0
- data/lib/nedbank_api/exceptions.rb +5 -0
- data/lib/nedbank_api/models/base_model.rb +21 -0
- data/lib/nedbank_api/models/intent_token.rb +36 -0
- data/lib/nedbank_api/models/payment.rb +10 -0
- data/lib/nedbank_api/models/payment_submission.rb +6 -0
- data/lib/nedbank_api/payments_api.rb +37 -0
- data/lib/nedbank_api/services/client.rb +18 -0
- data/lib/nedbank_api/services/configuration.rb +18 -0
- data/lib/nedbank_api/services/http.rb +39 -0
- data/lib/nedbank_api/version.rb +3 -0
- data/nedbank_api.gemspec +34 -0
- data/spec/authorisations_api_spec.rb +51 -0
- data/spec/client_spec.rb +16 -0
- data/spec/factories/client.rb +17 -0
- data/spec/factories/payment.rb +0 -0
- data/spec/nedbank_api_spec.rb +12 -0
- data/spec/payments_api_spec.rb +51 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/authentication/post_auth_token.json +0 -0
- data/spec/support/payment/post_intent_request.json +41 -0
- data/spec/support/payment/post_intent_response.json +47 -0
- data/spec/support/payment/post_payment_submission_request.json +55 -0
- data/spec/support/payment/post_payment_submission_response.json +16 -0
- metadata +264 -0
@@ -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,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
|
data/nedbank_api.gemspec
ADDED
@@ -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
|
data/spec/client_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
}
|