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: CommunicationError, PayHyperError, ValidationError
Constant Summary collapse
- BASE_URLS =
{ :live => "http://api.local.local:3000", :sandbox => "http://sandbox-api.local.local:3001", }
- CURRENCIES =
%w{JOD USD BHD GBP EGP KWD OMR QAR SAR TRY AED PKR IQD}
- COUNTRY_CODES =
{ "JO" => "962", "SA" => "966", "IQ" => "964", "PK" => "92", }
- COUNTRIES =
COUNTRY_CODES.keys
- VERSION =
'0.4.4'
Class Method Summary collapse
-
.at_door!(name, phone, email, country, city, amount, currency, address = nil, invoice = nil, tag = nil, stickiness_id = nil, details = nil, location = nil) ⇒ Object
TODO: There is a lot of overlap with “in_store!”, refactor.
- .check_ani_authenticity(request) ⇒ Object
-
.in_store!(name, phone, email, country, amount, currency, tag = nil) ⇒ Object
TODO: There is a lot of overlap with “at_door!”, refactor.
- .make_call(endpoint, body) ⇒ Object
- .parse_notification(request) ⇒ Object
- .raise_if_not_setup! ⇒ Object
- .setup(access_key_id, access_key_secret, mode) ⇒ Object
Class Method Details
.at_door!(name, phone, email, country, city, amount, currency, address = nil, invoice = nil, tag = nil, stickiness_id = nil, details = nil, location = nil) ⇒ Object
TODO: There is a lot of overlap with “in_store!”, refactor.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/payhyper.rb', line 41 def self.at_door!(name, phone, email, country, city, amount, currency, address = nil, invoice = nil, tag = nil, stickiness_id = nil, details = nil, location = nil) # TODO: There is a lot of overlap with "in_store!", refactor. raise_if_not_setup! # == Clean-up fields == currency = currency.upcase if currency && currency.is_a?(String) country = country.upcase if country && country.is_a?(String) city = city.upcase if city && city.is_a?(String) if phone && phone.is_a?(String) phone = phone.gsub(/[^0-9]/, "") # Remove non-numeric characters. phone = phone.gsub(/^0*/, "") # Remove leading zeros. if country && COUNTRY_CODES[country] && !phone.start_with?(COUNTRY_CODES[country]) phone = COUNTRY_CODES[country] + phone # Add country code. end end # == Validate == raise ValidationError, "Country specified is incorrect or not supported." unless COUNTRIES.include?(country) raise ValidationError, "Incorrect amount, must be positive." if amount.to_i <= 0 raise ValidationError, "Currency is incorrect or not supported." unless CURRENCIES.include?(currency) raise ValidationError, "Incorrect phone, or not in a supported country." if phone.nil? || !phone.match(/\A#{COUNTRY_CODES[country]}[0-9]{8,#{15-COUNTRY_CODES[country].length}}\z/) raise ValidationError, "Invalid email." if email.nil? || !email.match(/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/) raise ValidationError, "Name is mandatory." if name.nil? || name.strip.length == 0 # == Do the request == make_call("/v1/at-door", { :name => name, :phone => phone, :email => email, :country => country, :city => city, :amount => amount, :currency => currency, :address => address, :invoice => invoice, :tag => tag, :stickiness_id => stickiness_id, :details => details, :location => location }) end |
.check_ani_authenticity(request) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/payhyper.rb', line 110 def self.check_ani_authenticity(request) = request.env["HTTP_AUTHORIZATION"] return false unless matches = .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") # TODO: security bug if the webserver doesn't check host headers. correct_signature = Security.sign(@access_key_secret, canonical_request_representation) return Security.secure_compare(correct_signature, input_signature) end |
.in_store!(name, phone, email, country, amount, currency, tag = nil) ⇒ Object
TODO: There is a lot of overlap with “at_door!”, refactor.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/payhyper.rb', line 65 def self.in_store!(name, phone, email, country, amount, currency, tag = nil) # TODO: There is a lot of overlap with "at_door!", refactor. raise_if_not_setup! # == Clean-up fields == currency = currency.upcase if currency && currency.is_a?(String) country = country.upcase if country && country.is_a?(String) if phone && phone.is_a?(String) phone = phone.gsub(/[^0-9]/, "") # Remove non-numeric characters. phone = phone.gsub(/^0*/, "") # Remove leading zeros. if country && COUNTRY_CODES[country] && !phone.start_with?(COUNTRY_CODES[country]) phone = COUNTRY_CODES[country] + phone # Add country code. end end # == Validate == raise ValidationError, "Country specified is incorrect or not supported." unless COUNTRIES.include?(country) raise ValidationError, "Incorrect amount, must be positive." if amount.to_i <= 0 raise ValidationError, "Currency is incorrect or not supported." unless CURRENCIES.include?(currency) raise ValidationError, "Incorrect phone, or not in a supported country." if phone.nil? || !phone.match(/\A962[0-9]{8,9}\z/) raise ValidationError, "Invalid email." if email.nil? || !email.match(/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/) raise ValidationError, "Name is mandatory." if name.nil? || name.strip.length == 0 # == Do the request == make_call("/v1/in-store", { :name => name, :phone => phone, :email => email, :country => country, :amount => amount, :currency => currency, :tag => tag }) end |
.make_call(endpoint, body) ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/payhyper.rb', line 125 def self.make_call(endpoint, body) body = JSON.dump(body) failure = "Unknown error" begin uri = URI.parse(@base_url + endpoint) 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 + endpoint, 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..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
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/payhyper.rb', line 88 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
106 107 108 |
# File 'lib/payhyper.rb', line 106 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
34 35 36 37 38 39 |
# File 'lib/payhyper.rb', line 34 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 |