payhyper 0.1.0

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.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -0,0 +1,23 @@
1
+ module PayHyper
2
+ module JSON
3
+ if MultiJson.respond_to?(:dump)
4
+
5
+ def self.dump(*args)
6
+ MultiJson.dump(*args)
7
+ end
8
+ def self.load(*args)
9
+ MultiJson.load(*args)
10
+ end
11
+
12
+ else
13
+
14
+ def self.dump(*args)
15
+ MultiJson.encode(*args)
16
+ end
17
+ def self.load(*args)
18
+ MultiJson.decode(*args)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ module PayHyper
2
+ module Security
3
+
4
+ def self._sign(key, data)
5
+ OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), key, data)
6
+ end
7
+
8
+ def self.sign(key, data)
9
+ Base64.encode64(_sign(key, data)).gsub("\n", "")
10
+ end
11
+
12
+ def self.secure_compare(a, b)
13
+ return false if a.nil? || b.nil? || a.length.zero? || b.length.zero? || a.bytesize != b.bytesize
14
+ l = a.unpack "C#{a.bytesize}"
15
+ res = 0
16
+ b.each_byte { |byte| res |= byte ^ l.shift }
17
+ res == 0
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module PayHyper
2
+ VERSION = '0.1.0'
3
+ end
data/lib/payhyper.rb ADDED
@@ -0,0 +1,104 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ require 'rest_client'
5
+ require 'multi_json'
6
+
7
+ require 'payhyper/version'
8
+ require 'payhyper/json'
9
+ require 'payhyper/security'
10
+
11
+ module PayHyper
12
+
13
+ BASE_URLS = {
14
+ :live => "http://api.payhyper.com",
15
+ :sandbox => "http://api.sandbox.payhyper.com",
16
+ }
17
+
18
+ def self.setup(access_key_id, access_key_secret, mode)
19
+ raise PayHyperError, "Mode must be one of #{BASE_URLS.keys.map { |k| k.inspect }.join(" or ")}" unless BASE_URLS.keys.include?(mode)
20
+ @access_key_id = access_key_id
21
+ @access_key_secret = access_key_secret
22
+ @base_url = BASE_URLS[mode]
23
+ end
24
+
25
+ def self.raise_if_not_setup!
26
+ raise PayHyperError, "Must call setup() first" if @access_key_id.nil? || @access_key_secret.nil? || @base_url.nil?
27
+ end
28
+
29
+ def self.create_order!(params)
30
+ raise_if_not_setup!
31
+ # == Validate ==
32
+ raise ValidationError, "Incorrect amount." if params[:amount].to_i <= 0
33
+ raise ValidationError, "Incorrect email." if params[:email].nil? || !params[:email].match(/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/)
34
+ raise ValidationError, "Incorrect phone." if params[:phone].nil? || !params[:phone].match(/\A\+962[0-9]{8,9}\z/)
35
+ # == Do the request ==
36
+ body = JSON.dump({ :email => params[:email], :phone => params[:phone], :amount => params[:amount] })
37
+ failure = "Unknown error"
38
+ begin
39
+ uri = URI.parse(@base_url + "/v1/orders")
40
+ content_type = "application/json; charset=utf-8"
41
+ signature = Security.sign(@access_key_secret, ["POST", uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}", uri.request_uri, content_type, body.encode("UTF-8")].join("\n")) # Would be great if this could read directly from the request's headers.
42
+ res = RestClient.post(@base_url + "/v1/orders", body, :content_type => content_type, :authorization => "Hyper #{@access_key_id}:#{signature}")
43
+ failure = false
44
+ rescue RestClient::Exception => e # HTTP status codes not in 200-207, 301-303 and 307 result in a RestClient::Exception.
45
+ if e.http_code && e.http_code.to_i == 422 && e.http_body
46
+ raise ValidationError, JSON.load(e.http_body)["error"]
47
+ else
48
+ failure = "Remote returned HTTP status code #{e.http_code}"
49
+ end
50
+ rescue SocketError => e
51
+ if e.message.match(/getaddrinfo: (.*?)/)
52
+ failure = "Problem getting address information, possibly due to an incorrect domain name"
53
+ else
54
+ failure = "Unknown socket error"
55
+ end
56
+ rescue Errno::EINVAL => e
57
+ failure = "Incorrect host address"
58
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
59
+ failure = "Connection refused, or host unreachable"
60
+ rescue Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
61
+ failure = "Problem parsing HTTP response"
62
+ rescue EOFError, Errno::ECONNRESET => e
63
+ failure = "Remote closed connection unexpectedly"
64
+ end
65
+ if failure
66
+ raise CommunicationError, failure
67
+ else
68
+ JSON.load(res)
69
+ end
70
+ end
71
+
72
+ def self.check_ani_authenticity(request)
73
+ raise_if_not_setup!
74
+ authorization = request.env["HTTP_AUTHORIZATION"]
75
+ return false unless authorization
76
+ matches = authorization.match(/\A(.*) (.*):(.*)/)
77
+ return false unless matches
78
+ method, access_key_id, input_signature = matches.captures
79
+ return false unless method == "Hyper" && access_key_id && input_signature && access_key_id == @access_key_id
80
+ request.env["rack.input"].rewind # In case someone forgot to rewind the input.
81
+ body = request.env["rack.input"].read
82
+ request.env["rack.input"].rewind # Be nice to others.
83
+ canonical_request_representation = [request.env["REQUEST_METHOD"], request.env["HTTP_HOST"], request.env["PATH_INFO"], request.env["CONTENT_TYPE"], body].join("\n")
84
+ correct_signature = Security.sign(@access_key_secret, canonical_request_representation)
85
+ return Security.secure_compare(correct_signature, input_signature)
86
+ end
87
+
88
+ def self.parse_notification(request)
89
+ raise_if_not_setup!
90
+ if check_ani_authenticity(request)
91
+ request.env["rack.input"].rewind # In case someone forgot to rewind the input.
92
+ body = request.env["rack.input"].read
93
+ request.env["rack.input"].rewind # Be nice to others.
94
+ JSON.load(body)
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ class PayHyperError < StandardError; end
101
+ class AuthenticationError < PayHyperError; end
102
+ class ValidationError < PayHyperError; end
103
+ class CommunicationError < PayHyperError; end
104
+ end
data/payhyper.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'payhyper/version'
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ s.name = 'payhyper'
8
+ s.version = PayHyper::VERSION
9
+ s.date = '2013-05-12'
10
+ s.summary = 'The Ruby bindings of the Hyper API'
11
+ s.description = 'Hyper is an API for cash collection. See http://payhyper.com for more.'
12
+ s.authors = ["Sinan Taifour"]
13
+ s.email = 'sinan@payhyper.com'
14
+ s.homepage = 'http://payhyper.com'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.require_paths = ['lib']
18
+
19
+ s.add_dependency('rest-client', '~> 1.4')
20
+ s.add_dependency('multi_json', '>= 1.0.4', '< 2')
21
+
22
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: payhyper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sinan Taifour
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rest-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.4'
30
+ - !ruby/object:Gem::Dependency
31
+ name: multi_json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.0.4
38
+ - - <
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: 1.0.4
49
+ - - <
50
+ - !ruby/object:Gem::Version
51
+ version: '2'
52
+ description: Hyper is an API for cash collection. See http://payhyper.com for more.
53
+ email: sinan@payhyper.com
54
+ executables: []
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - Gemfile
59
+ - lib/payhyper.rb
60
+ - lib/payhyper/json.rb
61
+ - lib/payhyper/security.rb
62
+ - lib/payhyper/version.rb
63
+ - payhyper.gemspec
64
+ homepage: http://payhyper.com
65
+ licenses: []
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.24
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: The Ruby bindings of the Hyper API
88
+ test_files: []