Module: PayHyper

Defined in:
lib/payhyper.rb,
lib/payhyper/json.rb,
lib/payhyper/version.rb,
lib/payhyper/security.rb

Defined Under Namespace

Modules: JSON, Security Classes: AuthenticationError, CommunicationError, PayHyperError, ValidationError

Constant Summary collapse

BASE_URLS =
{
  :live => "http://api.local.local:3000",
  :sandbox => "http://api.sandbox.local.local:3001",
}
VERSION =
'0.1.1'

Class Method Summary collapse

Class Method Details

.check_ani_authenticity(request) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/payhyper.rb', line 80

def self.check_ani_authenticity(request)
  raise_if_not_setup!
  authorization = request.env["HTTP_AUTHORIZATION"]
  return false unless authorization
  matches = authorization.match(/\A(.*) (.*):(.*)/)
  return false unless matches
  method, access_key_id, input_signature = matches.captures
  return false unless method == "Hyper" && access_key_id && input_signature && access_key_id == @access_key_id
  request.env["rack.input"].rewind # In case someone forgot to rewind the input.
  body = request.env["rack.input"].read
  request.env["rack.input"].rewind # Be nice to others.
  canonical_request_representation = [request.env["REQUEST_METHOD"], request.env["HTTP_HOST"], request.env["PATH_INFO"], request.env["CONTENT_TYPE"], body].join("\n")
  correct_signature = Security.sign(@access_key_secret, canonical_request_representation)
  return Security.secure_compare(correct_signature, input_signature)
end

.create_order!(params) ⇒ Object

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/payhyper.rb', line 36

def self.create_order!(params)
  raise_if_not_setup!
  # == Validate ==
  raise ValidationError, "Incorrect amount." if params[:amount].to_i <= 0
  raise ValidationError, "Incorrect currency." unless %w{JOD USD BHD GBP EGP KWD QMR QAR SAR TRY AED}.include?(params[:currency].upcase)
  raise ValidationError, "Incorrect email." if params[:email].nil? || !params[:email].match(/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/)
  raise ValidationError, "Incorrect phone." if params[:phone].nil? || !params[:phone].match(/\A\+962[0-9]{8,9}\z/)
  # == Do the request ==
  body = JSON.dump({ :email => params[:email], :phone => params[:phone], :amount => params[:amount], :currency => params[:currency] })
  failure = "Unknown error"
  begin
    uri = URI.parse(@base_url + "/v1/orders")
    content_type = "application/json; charset=utf-8"
    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.
    res = RestClient.post(@base_url + "/v1/orders", body, :content_type => content_type, :authorization => "Hyper #{@access_key_id}:#{signature}")
    failure = false
  rescue RestClient::Exception => e # HTTP status codes not in 200-207, 301-303 and 307 result in a RestClient::Exception.
    if e.http_code && e.http_code.to_i == 422 && e.http_body
      raise ValidationError, JSON.load(e.http_body)["error"]
    else
      failure = "Remote returned HTTP status code #{e.http_code} (#{e.http_body})"
    end
  rescue SocketError => e
    if e.message.match(/getaddrinfo: (.*?)/)
      failure = "Problem getting address information, possibly due to an incorrect domain name"
    else
      failure = "Unknown socket error"
    end
  rescue Errno::EINVAL => e
    failure = "Incorrect host address"
  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
    failure = "Connection refused, or host unreachable"
  rescue Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
    failure = "Problem parsing HTTP response"
  rescue EOFError, Errno::ECONNRESET => e
    failure = "Remote closed connection unexpectedly"
  end
  if failure
    raise CommunicationError, failure
  else
    JSON.load(res)
  end
end

.parse_notification(request) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/payhyper.rb', line 96

def self.parse_notification(request)
  raise_if_not_setup!
  if check_ani_authenticity(request)
    request.env["rack.input"].rewind # In case someone forgot to rewind the input.
    body = request.env["rack.input"].read
    request.env["rack.input"].rewind # Be nice to others.
    JSON.load(body)
  else
    nil
  end
end

.raise_if_not_setup!Object

Raises:



32
33
34
# File 'lib/payhyper.rb', line 32

def self.raise_if_not_setup!
  raise PayHyperError, "Must call setup() first" if @access_key_id.nil? || @access_key_secret.nil? || @base_url.nil?
end

.setup(access_key_id, access_key_secret, mode) ⇒ Object

Raises:



25
26
27
28
29
30
# File 'lib/payhyper.rb', line 25

def self.setup(access_key_id, access_key_secret, mode)
  raise PayHyperError, "Mode must be one of #{BASE_URLS.keys.map { |k| k.inspect }.join(" or ")}" unless BASE_URLS.keys.include?(mode)
  @access_key_id = access_key_id
  @access_key_secret = access_key_secret
  @base_url = BASE_URLS[mode]
end