payment-highway 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +50 -0
- data/LICENSE +7 -0
- data/README.md +31 -0
- data/lib/payment_highway.rb +21 -0
- data/lib/payment_highway/api.rb +84 -0
- data/lib/payment_highway/batch_report.rb +13 -0
- data/lib/payment_highway/config.rb +20 -0
- data/lib/payment_highway/error.rb +4 -0
- data/lib/payment_highway/signer.rb +9 -0
- data/lib/payment_highway/version.rb +4 -0
- data/payment_highway.gemspec +24 -0
- data/spec/batch_report_spec.rb +89 -0
- data/spec/spec_helper.rb +4 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -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
data/Gemfile.lock
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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,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,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
|
data/spec/spec_helper.rb
ADDED
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
|