class Scanpay::Client
Public Class Methods
new(apikey)
click to toggle source
# File lib/scanpay.rb, line 17 def initialize(apikey) @https = HTTPClient.new @https.ssl_config.set_default_paths @https.connect_timeout = 20 # seconds @apikey = apikey @headers = { 'authorization' => 'basic ' + Base64.strict_encode64(apikey), 'x-sdk' => 'Ruby-1.1.0/' + RUBY_VERSION, 'content-type' => 'application/json', } end
Public Instance Methods
charge(subid, data, opts={})
click to toggle source
# File lib/scanpay.rb, line 103 def charge(subid, data, opts={}) if !subid.is_a? Integer raise ArgumentError, 'first argument is not an integer' end return request("/v1/subscribers/#{subid}/charge", data, opts); end
generateIdempotencyKey()
click to toggle source
# File lib/scanpay.rb, line 38 def generateIdempotencyKey() return SecureRandom.base64(32).delete('=') end
handlePing(body='', signature='')
click to toggle source
handlePing: Convert body to JSON and validate signature
# File lib/scanpay.rb, line 93 def handlePing(body='', signature='') digest = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), @apikey, body) is_valid = timesafe_equals(Base64.strict_encode64(digest), signature) raise 'invalid signature' unless is_valid o = JSON.parse(body) return o if (o['shopid'].is_a? Integer) && (o['seq'].is_a? Integer) raise 'invalid ping' end
newURL(data, opts={})
click to toggle source
newURL: Create a new payment link
# File lib/scanpay.rb, line 76 def newURL(data, opts={}) o = request('/v1/new', data, opts) return o['url'] if o['url'].is_a? String raise 'invalid response from server' end
renew(subid, data, opts={})
click to toggle source
# File lib/scanpay.rb, line 110 def renew(subid, data, opts={}) if !subid.is_a? Integer raise ArgumentError, 'first argument is not an integer' end o = request("/v1/subscribers/#{subid}/renew", data, opts); return o['url'] if o['url'].is_a? String raise 'invalid response from server' end
seq(num, opts={})
click to toggle source
seq: Get array of changes since the reqested sequence number
# File lib/scanpay.rb, line 83 def seq(num, opts={}) if !num.is_a? Integer raise ArgumentError, 'first argument is not an integer' end o = request('/v1/seq/' + num.to_s, nil, opts) return o if (o['changes'].kind_of? Array) && (o['seq'].is_a? Integer) raise 'invalid response from server' end
timesafe_equals(a, b)
click to toggle source
# File lib/scanpay.rb, line 29 def timesafe_equals(a, b) return false if a.bytesize != b.bytesize neq = 0 a.bytes.each_index { |i| neq |= a.bytes[i] ^ b.bytes[i]; } return neq === 0 end
Private Instance Methods
request(path, data, opts={})
click to toggle source
# File lib/scanpay.rb, line 42 def request(path, data, opts={}) hostname = opts['hostname'] || 'api.scanpay.dk' headers = @headers.clone # Let merchant override HTTP headers if opts.has_key? 'headers' opts['headers'].each do |key, value| headers[key.downcase] = value end end res = (data === nil) ? @https.get('https://' + hostname + path, nil, headers) : @https.post('https://' + hostname + path, data.to_json, headers) if headers['idempotency-key'] idem = res.header['idempotency-status'][0] case idem when 'OK' # Do nothing when 'ERROR' raise "server failed to provide idempotency: #{res.reason}" when '' raise "missing response idempotency status: #{res.reason}" else raise "unknown idempotency status '#{idem}': #{res.reason}" end end raise IdempotentResponseException, res.reason if res.code != 200 begin return JSON.parse(res.body) rescue JSON::ParserError => e raise IdempotentResponseException, "invalid json response: #{e.message}" end end