payment-highway 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3a4fe0337abc26665ec45dae9e0691a6bd2005b7aeede8a256737d8195eb5b71
4
+ data.tar.gz: a9bbcaf86fb0ce81c3e72d26da42b70656c3ff6280acb5f8762b46153b08568b
5
+ SHA512:
6
+ metadata.gz: 2e6036ce704755fe054e28cd814cbc5a9444ed3b61b73fbb30ee38e6022a7c445a732a5bc8c8e151d3024290dc899f94d6ce4fe95ae5e62297d7b28084e6ce8b
7
+ data.tar.gz: 8013ca17103e081cfdd248675253175326bcf2adf3b4b8d267108c4226c4a7ae791b67ef08e9bcb0b31c13f0f129888dcfeda6cf86e16de0c8238df0d131bf2a
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gemspec
8
+
9
+ group :development do
10
+ gem "rspec", "~> 3.8"
11
+ gem "webmock", "~> 3.4"
12
+ end
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ payment_highway (0.1)
5
+ faraday (~> 0.15)
6
+ openssl
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.5.2)
12
+ public_suffix (>= 2.0.2, < 4.0)
13
+ crack (0.4.3)
14
+ safe_yaml (~> 1.0.0)
15
+ diff-lcs (1.3)
16
+ faraday (0.15.4)
17
+ multipart-post (>= 1.2, < 3)
18
+ hashdiff (0.3.7)
19
+ multipart-post (2.0.0)
20
+ openssl (2.1.2)
21
+ public_suffix (3.0.3)
22
+ rspec (3.8.0)
23
+ rspec-core (~> 3.8.0)
24
+ rspec-expectations (~> 3.8.0)
25
+ rspec-mocks (~> 3.8.0)
26
+ rspec-core (3.8.0)
27
+ rspec-support (~> 3.8.0)
28
+ rspec-expectations (3.8.2)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.8.0)
31
+ rspec-mocks (3.8.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.8.0)
34
+ rspec-support (3.8.0)
35
+ safe_yaml (1.0.4)
36
+ webmock (3.4.2)
37
+ addressable (>= 2.3.6)
38
+ crack (>= 0.3.2)
39
+ hashdiff
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ payment_highway!
46
+ rspec (~> 3.8)
47
+ webmock (~> 3.4)
48
+
49
+ BUNDLED WITH
50
+ 1.16.4
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2018 Payment Highway
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # Payment Highway Ruby Library
2
+
3
+ This gem is ruby api client for Payment Highway
4
+
5
+ ## Examples
6
+ ### Batch Report fetching
7
+ You can find this in api documentation too: https://dev.paymenthighway.io/#daily-batch-report
8
+
9
+ ```ruby
10
+ config = PaymentHighway::Config.new(
11
+ account: 'test',
12
+ merchant: 'test_merchantId',
13
+ key: 'testKey',
14
+ secret: 'testSecret',
15
+ # Testing environment, remove this to use production
16
+ service: 'https://v1-hub-staging.sph-test-solinor.com'
17
+ )
18
+
19
+ report = PaymentHighway::BatchReport.new(
20
+ config: config,
21
+ date: Date.today
22
+ )
23
+
24
+ puts report.settlements.inspect
25
+ ```
26
+
27
+ ## TODO
28
+ Currently only batch reports are working
29
+
30
+ ## License
31
+ MIT
@@ -0,0 +1,21 @@
1
+ # Needed libraries
2
+ require 'faraday'
3
+ require 'date'
4
+ require 'securerandom'
5
+ require 'json'
6
+ require 'time'
7
+ require 'openssl'
8
+
9
+ # Version
10
+ require 'payment_highway/version'
11
+
12
+ # Support classes
13
+ require 'payment_highway/config'
14
+ require 'payment_highway/signer'
15
+ require 'payment_highway/error'
16
+
17
+ # Api client
18
+ require 'payment_highway/api'
19
+
20
+ # Support classes for api resources
21
+ require 'payment_highway/batch_report'
@@ -0,0 +1,84 @@
1
+ module PaymentHighway
2
+ class Api
3
+ def initialize(config:)
4
+ @config = config
5
+ end
6
+
7
+ def get_batch(date:)
8
+ get "/report/batch/#{date.strftime('%Y%m%d')}"
9
+ end
10
+
11
+ private
12
+
13
+ def get(resource)
14
+ response = Faraday.new(url: @config.service).get do |req|
15
+
16
+ req.headers['Content-Type'] = 'application/json'
17
+
18
+ # Add Payment Highway specific headers
19
+ signed_headers(
20
+ method: 'GET',
21
+ uri: resource,
22
+ config: @config,
23
+ values: [],
24
+ ).each do |header, value|
25
+ req.headers[header] = value
26
+ end
27
+
28
+ req.url resource
29
+ end
30
+
31
+ validate(response)
32
+
33
+ [response, JSON.parse(response.body)]
34
+ end
35
+
36
+ def signed_headers(method:, uri:, config:, values:, body: "", timestamp: Time.now.utc.strftime('%FT%TZ'), request_id: SecureRandom.uuid)
37
+ headers = {
38
+ 'sph-account': config.account,
39
+ 'sph-merchant': config.merchant,
40
+ 'sph-timestamp': timestamp,
41
+ 'sph-request-id': request_id,
42
+ 'sph-api-version': config.version,
43
+ }.compact # Removes optional nil parameters
44
+ headers.merge({ signature: Signer.signature(config: config, method: method, uri: uri, headers: headers, body: body) })
45
+ end
46
+
47
+ def validate(response)
48
+ # If server did not respond with 200 output the error message from the body
49
+ unless response.status == 200
50
+ raise Error, response.body
51
+ end
52
+
53
+ missing_mandatory_headers = [
54
+ 'sph-timestamp',
55
+ 'sph-request-id',
56
+ 'sph-response-id',
57
+ 'signature'
58
+ ] - response.env[:response_headers].keys
59
+ unless missing_mandatory_headers.empty?
60
+ raise Error, "Mandatory headers #{missing_mandatory_headers} are missing from response"
61
+ end
62
+ # SPEC: the timestamp must be checked and it must not differ more than five (5) minutes from the correct global UTC time
63
+ if (Time.now - Time.parse(response.env[:response_headers]['sph-timestamp'])) > 5 * 60
64
+ raise Error, "Latency between server and client was over 5 minutes. Results should not be trusted!"
65
+ end
66
+ # SPEC: Check that request and response headers were the same
67
+ if response.env[:response_headers]['sph-request-id'] != response.env[:request_headers]['sph-request-id']
68
+ raise Error, "sph-request-id mismatch! request: (#{response.env[:request_headers]['sph-request-id']}) was not response: (#{response.env[:response_headers]['sph-request-id']})"
69
+ end
70
+
71
+ # Calculate signature again here and we should end up to the same result
72
+ calculated_signature = Signer.signature(
73
+ config: @config,
74
+ method: response.env.method.to_s.upcase,
75
+ uri: response.env.url.path.to_s,
76
+ headers: response.env.response_headers.select {|h,v| h.start_with? 'sph-'}, # Only sph- headers are needed for signature
77
+ body: response.env.body
78
+ )
79
+ if response.env[:response_headers]['signature'] != calculated_signature
80
+ raise Error, "signature mismatch! sph-request-id: #{response.env[:response_headers]['sph-request-id']}"
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,13 @@
1
+ module PaymentHighway
2
+ class BatchReport
3
+ attr_reader :settlements, :result, :request_id, :response_id, :url
4
+ def initialize(config:, date:)
5
+ response, result = Api.new(config: config).get_batch(date: date)
6
+ @settlements = result['settlements']
7
+ @result = result['result']
8
+ @url = response.env.url.to_s
9
+ @request_id = response.env.response_headers['sph-request-id']
10
+ @response_id = response.env.response_headers['sph-response-id']
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module PaymentHighway
2
+ class Config
3
+ attr_accessor :account, :merchant, :key, :secret, :service, :version
4
+ def initialize(
5
+ account:,
6
+ key:,
7
+ secret:,
8
+ merchant: nil, # This is optional only and it's used if account has sub merchant
9
+ service: 'https://v1.api.paymenthighway.io',
10
+ version: PaymentHighway::API_VERSION
11
+ )
12
+ @account = account
13
+ @merchant = merchant
14
+ @key = key
15
+ @secret = secret
16
+ @service = service
17
+ @version = version
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ module PaymentHighway
2
+ class Error < ::RuntimeError
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module PaymentHighway
2
+ class Signer
3
+ def self.signature(config:, method:, uri:, headers:, body: "")
4
+ payload = ([method, uri] + Hash[headers.sort].map{|k,v| "#{k}:#{v}"} + [body]).join("\n")
5
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, config.secret, payload)
6
+ "SPH1 #{config.key} #{hmac}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module PaymentHighway
2
+ VERSION = '0.1.1'
3
+ API_VERSION = '20180927'
4
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path(File.join(__dir__,'lib'))
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require "payment_highway/version"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'payment-highway'
9
+ s.version = PaymentHighway::VERSION
10
+ s.summary = "Hola!"
11
+ s.description = "Custom payments for your custom app. Api client for Payment Highway"
12
+ s.authors = ['Payment Highway']
13
+ s.email = 'support@paymenthighway.fi'
14
+
15
+ s.homepage = 'https://github.com/paymenthighway/paymenthighway-rb'
16
+ s.license = 'MIT'
17
+
18
+ s.add_dependency("faraday", "~> 0.15")
19
+ s.add_dependency("openssl")
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- spec/*`.split("\n")
23
+ s.require_paths = ['lib']
24
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe PaymentHighway::BatchReport do
4
+
5
+ before(:each) do
6
+ @now = Time.parse('2018-11-20 12:55:15')
7
+ @request_id = '30cbbf47-12a1-4e02-875a-69eaa65c23a1'
8
+ @data = {"settlements"=>[], "result"=>{"code"=>100, "message"=>"OK"}}
9
+ @config = PaymentHighway::Config.new(
10
+ account: 'test',
11
+ merchant: 'test_merchantId',
12
+ key: 'testKey',
13
+ secret: 'testSecret'
14
+ )
15
+ @report_url = "#{@config.service}/report/batch/20181120"
16
+ # Mock uuid so that we will get the same value from mocked server
17
+ allow(SecureRandom).to receive(:uuid).and_return(@request_id)
18
+
19
+ # Mock time because otherwise signature calculation won't work
20
+ allow(Time).to receive(:now) { @now }
21
+
22
+ @valid_headers = {
23
+ 'sph-request-id': @request_id,
24
+ 'sph-timestamp': Time.now.utc.strftime('%FT%TZ'),
25
+ 'sph-response-id': '0bcae09d-cd72-4768-bb51-80962e96ac52',
26
+ 'signature': 'SPH1 testKey bd68d2d5453541c0c0deefc008df0d1eb0a0544590c60c1af7ef0e41b82c7802'
27
+ }
28
+ end
29
+
30
+ it "should throw exception from missing headers empty reports" do
31
+ stub_request(:get, @report_url).to_return( body: @data.to_json, headers: {})
32
+
33
+ expect {
34
+ described_class.new(config: @config, date: @now.to_date)
35
+ }.to raise_error( PaymentHighway::Error,
36
+ 'Mandatory headers ["sph-timestamp", "sph-request-id", "sph-response-id", "signature"] are missing from response'
37
+ )
38
+ end
39
+
40
+ it "should throw exception from mismatching request id" do
41
+ stub_request(:get, @report_url).to_return( body: @data.to_json, headers: @valid_headers.merge({
42
+ 'sph-request-id': '8cc1167b-eaf3-4394-893d-9e02f825311a'
43
+ }))
44
+
45
+ expect {
46
+ described_class.new(config: @config, date: @now.to_date)
47
+ }.to raise_error( PaymentHighway::Error, /request-id mismatch!/)
48
+ end
49
+
50
+ it "should throw exception from broken signature" do
51
+ stub_request(:get, @report_url).
52
+ to_return( body: @data.to_json, headers: @valid_headers.merge({
53
+ 'signature': 'SPH1 testKey 724651aab8aa41f9402b9de9dbfade5ac392b93f141a77b82df8d8e91d182e32'
54
+ }))
55
+
56
+ expect {
57
+ described_class.new(config: @config, date: @now.to_date)
58
+ }.to raise_error( PaymentHighway::Error, /signature mismatch!/)
59
+ end
60
+
61
+ it "should return empty reports on valid response" do
62
+ # Stub the request to batch reports
63
+ stub_request(:get, @report_url).
64
+ to_return(body: @data.to_json, headers: @valid_headers)
65
+
66
+ report = described_class.new(config: @config, date: @now.to_date)
67
+ expect(report.settlements).to eq(@data['settlements'])
68
+ expect(report.result).to eq(@data['result'])
69
+
70
+ expect(report.url).to eq(@report_url)
71
+ expect(report.request_id).to eq(@request_id)
72
+ expect(report.response_id).to eq(@valid_headers[:'sph-response-id'])
73
+ end
74
+
75
+ it "should return empty reports even without merchant" do
76
+ # Stub the request to batch reports
77
+ stub_request(:get, @report_url).
78
+ to_return(body: @data.to_json, headers: @valid_headers)
79
+
80
+ conf = PaymentHighway::Config.new(
81
+ account: 'test',
82
+ key: 'testKey',
83
+ secret: 'testSecret'
84
+ )
85
+ result = described_class.new(config: conf, date: @now.to_date)
86
+ expect(result.settlements).to eq(@data['settlements'])
87
+ expect(result.result).to eq(@data['result'])
88
+ end
89
+ end
@@ -0,0 +1,4 @@
1
+ require 'webmock/rspec'
2
+ require 'json'
3
+
4
+ require File.expand_path("../lib/payment_highway", __dir__)
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: payment-highway
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Payment Highway
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: openssl
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Custom payments for your custom app. Api client for Payment Highway
42
+ email: support@paymenthighway.fi
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - Gemfile
48
+ - Gemfile.lock
49
+ - LICENSE
50
+ - README.md
51
+ - lib/payment_highway.rb
52
+ - lib/payment_highway/api.rb
53
+ - lib/payment_highway/batch_report.rb
54
+ - lib/payment_highway/config.rb
55
+ - lib/payment_highway/error.rb
56
+ - lib/payment_highway/signer.rb
57
+ - lib/payment_highway/version.rb
58
+ - payment_highway.gemspec
59
+ - spec/batch_report_spec.rb
60
+ - spec/spec_helper.rb
61
+ homepage: https://github.com/paymenthighway/paymenthighway-rb
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.7.3
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Hola!
85
+ test_files:
86
+ - spec/batch_report_spec.rb
87
+ - spec/spec_helper.rb