ribbon-flow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07298ede14e9864a03a7e433a43b9e387c540ee1
4
+ data.tar.gz: 757937ae1b637a82724df958ab7b6a0a641889dc
5
+ SHA512:
6
+ metadata.gz: a3950406361a0e8ac3f0e5ef27e3103466115a54ee6ab4dfd4dac89b34407a670ea5f9b7423ec03e98e8fd15064a1602bbcab940c859703afd7d1269b1fc637c
7
+ data.tar.gz: c1555ebc95da219a0f1d2b65b155039a49f8741eccd53d7086f712bb4fff39ecea6bf3248b051de1cff6a4fc6736c703d7b82ef2341f8469c7a7f07a6caa7feb
Binary file
@@ -0,0 +1,2 @@
1
+ ���)�&4��2�?A�� ��/��7�B����"I:���~�_u���ր��lh�ͷ���z�w�)c���d3p y��5PK��^�Y�6}�
2
+ �j��Sp�;�+����:��GY��%��� �#��'0C�uxF�e��Ԩ5��%Y�E��V&�\���W�v��z. �x^�\��ɩ�U}m�Z�
@@ -0,0 +1,31 @@
1
+
2
+ require 'flow/version'
3
+ require 'flow/errors'
4
+ require 'flow/config'
5
+ require 'flow/connection'
6
+ require 'flow/models'
7
+ require 'flow/response'
8
+
9
+ module Flow
10
+
11
+ def self.config
12
+ Flow::Config.instance
13
+ end
14
+
15
+ def self.connection
16
+ @__connection_cache ||= Flow::Connection.new(config.api_key, config.url)
17
+ end
18
+
19
+ def self.pool(pool_token)
20
+ Flow::Models::Pool.load(connection, pool_token)
21
+ end
22
+
23
+ def self.card(card_token)
24
+ Flow::Models::Card.load(connection, card_token)
25
+ end
26
+
27
+ def self.transaction(transaction_token)
28
+ Flow::Models::Transaction.load(connection, transaction_token)
29
+ end
30
+
31
+ end
@@ -0,0 +1,50 @@
1
+
2
+ require 'singleton'
3
+
4
+ module Flow
5
+ class Config
6
+ include Singleton
7
+
8
+ def initialize
9
+ @config = self.defaults
10
+ end
11
+
12
+ def defaults
13
+ {
14
+ url: 'https://flow.ribbon.co'.freeze,
15
+ api_key: nil
16
+ }
17
+ end
18
+
19
+ def method_missing(meth, *args, &block)
20
+ meth_str = meth.to_s
21
+
22
+ if /^(\w+)=$/.match(meth_str)
23
+ _set($1, args.first)
24
+ else
25
+ _get(meth)
26
+ end
27
+ end
28
+
29
+ def url=(url)
30
+ url = url[0..-2] if url[-1] == '/' # No trailing forward-slash
31
+ _set(:url, url)
32
+ end
33
+
34
+ def to_s
35
+ return @config.to_s
36
+ end
37
+
38
+ private
39
+
40
+ def _set(key, value)
41
+ # Changing the config value should require calling the
42
+ # _set method again.
43
+ return @config[key.to_sym] = value.dup.freeze
44
+ end
45
+
46
+ def _get(key)
47
+ return @config[key.to_sym]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,187 @@
1
+
2
+ require 'rest-client'
3
+ require 'openssl'
4
+ require 'securerandom'
5
+
6
+ module Flow
7
+
8
+ class Connection
9
+ attr_reader :url
10
+ attr_reader :access_key
11
+ attr_reader :version
12
+
13
+ attr_reader :scheme
14
+ attr_reader :host
15
+ attr_reader :port
16
+
17
+ def initialize(api_key, url='https://flow.ribbon.co', version=1)
18
+ @version = version
19
+
20
+ @url = (url[-1] == '/' ? url[0..-2] : url).dup.freeze # Remove trailing slash.
21
+
22
+ if /^([A-Za-z0-9]{20})\.([A-Za-z0-9]{43})$/.match(api_key)
23
+ @access_key = $1.dup.freeze
24
+ @signing_key = $2.dup.freeze
25
+ else
26
+ raise Flow::Errors::ConnectionError, "Invalid API Key format."
27
+ end
28
+
29
+ URI.parse(@url).tap do |uri|
30
+ @scheme = uri.scheme
31
+ @host = uri.host
32
+ @port = uri.port
33
+ end
34
+
35
+ # RestClient::Resource.new(
36
+ # 'https://example.com',
37
+ # :ssl_client_cert => OpenSSL::X509::Certificate.new(File.read("cert.pem")),
38
+ # :ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),
39
+ # :ssl_ca_file => "ca_certificate.pem",
40
+ # :verify_ssl => OpenSSL::SSL::VERIFY_PEER
41
+ # )
42
+
43
+ @api = RestClient::Resource.new(
44
+ @url,
45
+ verify_ssl: true,
46
+ headers: {
47
+ "Content-Type" => "application/json"
48
+ }
49
+ ) {|response, request, result| response}
50
+ end
51
+
52
+ def get(path, params={})
53
+ path = _build_path(path)
54
+ auth_header = _authorization_header(:get, path, params)
55
+ response = @api[path].get(authorization: auth_header, params: params)
56
+ Flow::Response.new(self, response)
57
+ end
58
+
59
+ def post(path, params={}, &block)
60
+ if block_given?
61
+ body_hash = block.call
62
+ if body_hash && !body_hash.empty?
63
+ body = body_hash.to_json
64
+ end
65
+ end
66
+
67
+ path = _build_path(path)
68
+ auth_header = _authorization_header(:post, path, params, body)
69
+ response = @api[path].post(body, authorization: auth_header, params: params)
70
+
71
+ Flow::Response.new(self, response)
72
+ end
73
+
74
+ private
75
+
76
+ def _signing_key
77
+ @signing_key
78
+ end
79
+
80
+ def _build_path(path)
81
+ path = '/' + path unless path[0] == '/'
82
+
83
+ if path.start_with?('/api/')
84
+ return path
85
+ else
86
+ return "/api/v#{version}#{path}"
87
+ end
88
+ end
89
+
90
+ def _gen_nonce
91
+ begin
92
+ nonce = SecureRandom.hex(8)
93
+ end until _valid_nonce?(nonce)
94
+
95
+ return nonce.freeze
96
+ end
97
+
98
+ def _valid_nonce?(nonce)
99
+ record = @__nonce_record ||= {}
100
+ nonce = nonce.to_s
101
+
102
+ # Try to limit the record store to about 2MB
103
+ # of data, assuming 16 single-byte characters
104
+ # and 4 byte timestamps.
105
+ # Supports 3,333 requests per second before exceeding
106
+ # the limit.
107
+ if record.length > 100_000
108
+ threshold_ts = (Time.now.to_i - 30).freeze
109
+ record.delete_if {|k,v| v < threshold_ts}
110
+ end
111
+
112
+ if record[nonce] && record[nonce] >= (Time.now.to_i - 30)
113
+ return false
114
+ else
115
+ record[nonce] = Time.now.to_i.freeze
116
+ return true
117
+ end
118
+ end
119
+
120
+ def _authorization_header(verb, path, query_hash, body=nil)
121
+ timestamp = Time.now.to_i.freeze
122
+ nonce = _gen_nonce
123
+
124
+ # Calculate request hash
125
+ path = _uri_encode(path[0] == '/' ? path : ('/' + path), false)
126
+ query_string = _hash_to_query_string(query_hash)
127
+ body_hash = _sha256(body.to_s)
128
+ request_hash = _sha256(
129
+ verb.to_s.upcase + "\n" +
130
+ host + "\n" +
131
+ path + "\n" +
132
+ query_string + "\n" +
133
+ body_hash
134
+ )
135
+
136
+ # Construct the message
137
+ message = timestamp.to_s + nonce + request_hash
138
+
139
+ signature = _hmac_sha256_hex(_signing_key, message)
140
+
141
+ return "RBN1-HMAC-SHA256 #{access_key};#{timestamp};#{nonce};#{signature}"
142
+ end
143
+
144
+ ######################################################################
145
+ #
146
+ # Encoding and signing helper methods
147
+ #
148
+ ######################################################################
149
+
150
+ def _hmac_sha256_hex(key, message)
151
+ digest = OpenSSL::Digest::SHA256.new
152
+ OpenSSL::HMAC.hexdigest(digest, key, message)
153
+ end
154
+
155
+ def _sha256(str)
156
+ ::OpenSSL::Digest::SHA256.hexdigest(str)
157
+ end
158
+
159
+ def _uri_encode(string, encode_fslash=true)
160
+ retval = ""
161
+
162
+ string.chars.each do |ch|
163
+ if (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
164
+ (ch >= '0' && ch <= '9') || ch == '-' || ch == '_' || ch == '.' || ch == '~'
165
+ retval << ch
166
+ elsif ch == '/'
167
+ retval << (encode_fslash ? '%2F' : ch)
168
+ else
169
+ retval << '%' << ch.bytes.first.to_s(16).upcase
170
+ end
171
+ end
172
+
173
+ return retval
174
+ end
175
+
176
+ def _hash_to_query_string(data)
177
+ pairs = []
178
+
179
+ data.sort{|a,b| a[0] <=> b[0]}.each do |key, value|
180
+ pairs << (_uri_encode(key.to_s) << '=' << _uri_encode(value.to_s))
181
+ end
182
+
183
+ pairs.join('&')
184
+ end
185
+ end
186
+
187
+ end
@@ -0,0 +1,12 @@
1
+
2
+
3
+ module Flow
4
+ module Errors
5
+
6
+ class FlowError < StandardError; end
7
+ class ConnectionError < FlowError; end
8
+ class ResponseError < FlowError; end
9
+ class LoadError < FlowError; end
10
+
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ require 'flow/models/model'
2
+ require 'flow/models/pool'
3
+ require 'flow/models/card'
4
+ require 'flow/models/transaction'
@@ -0,0 +1,39 @@
1
+ module Flow
2
+ module Models
3
+ class Card < Model
4
+
5
+ def self.load(connection, card_token)
6
+ r = connection.get("cards/#{card_token}")
7
+
8
+ raise Flow::Errors::LoadError.new(r.message) unless r.success?
9
+
10
+ return r.card
11
+ end
12
+
13
+ def ready?
14
+ self.status == 'ready'
15
+ end
16
+
17
+ def supported?
18
+ self.ready?
19
+ end
20
+
21
+ def unsupported?
22
+ self.status == 'unsupported'
23
+ end
24
+
25
+ def credit!(amount, options={})
26
+ r = connection.post("cards/#{self.token}/credits") do
27
+ {
28
+ amount: amount
29
+ }
30
+ end
31
+
32
+ raise Flow::Errors::ResponseError.new(r.message) unless r.success?
33
+
34
+ r.transaction
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+
2
+ module Flow
3
+ module Models
4
+
5
+ class Model
6
+ attr_reader :model_hash
7
+ attr_reader :connection
8
+
9
+ def initialize(connection, model_hash)
10
+ @connection = connection
11
+ @model_hash = model_hash
12
+ end
13
+
14
+ def method_missing(meth, *args, &block)
15
+ if model_hash.key?(meth.to_s)
16
+ return Model.load_model(meth, connection, model_hash[meth.to_s])
17
+ end
18
+
19
+ super
20
+ end
21
+
22
+ def self.load_model(name, connection, model_hash)
23
+ name = name.to_sym
24
+
25
+ case name
26
+ when :pool
27
+ return Flow::Models::Pool.new(connection, model_hash)
28
+ when :card
29
+ return Flow::Models::Card.new(connection, model_hash)
30
+ when :transaction
31
+ return Flow::Models::Transaction.new(connection, model_hash)
32
+ else
33
+ return model_hash
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Flow
2
+ module Models
3
+ class Pool < Model
4
+
5
+ def self.load(connection, pool_token)
6
+ r = connection.get("pools/#{pool_token}")
7
+
8
+ raise Flow::Errors::LoadError.new(r.message) unless r.success?
9
+
10
+ return r.pool
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ module Flow
2
+ module Models
3
+ class Transaction < Model
4
+
5
+ def self.load(connection, tx_token)
6
+ r = connection.get("transactions/#{tx_token}")
7
+
8
+ raise Flow::Errors::LoadError.new(r.message) unless r.success?
9
+
10
+ return r.transaction
11
+ end
12
+
13
+ def pending?
14
+ self.status == 'pending'
15
+ end
16
+
17
+ def scheduled?
18
+ self.status == 'scheduled'
19
+ end
20
+
21
+ def cleared?
22
+ self.status == 'cleared'
23
+ end
24
+
25
+ def failed?
26
+ self.status == 'failed'
27
+ end
28
+
29
+ def error?
30
+ self.status == 'error'
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+
2
+ require 'json'
3
+
4
+ module Flow
5
+
6
+ class Response
7
+ attr_reader :body
8
+ attr_reader :connection
9
+
10
+ def initialize(connection, rest_client_response)
11
+ @connection = connection
12
+ @response = rest_client_response
13
+
14
+ begin
15
+ body = @response.body
16
+ @body = JSON.parse(body) unless body.empty?
17
+ @body ||= {}
18
+ _deep_freeze(@body)
19
+ rescue JSON::ParserError => e
20
+ raise Flow::Errors::ResponseError, "Invalid JSON returned: #{e.message}"
21
+ end
22
+ end
23
+
24
+ def http_success?
25
+ @response.code >= 200 && @response.code < 300
26
+ end
27
+
28
+ def success?
29
+ body['status'] == 'success'
30
+ end
31
+
32
+ def error?
33
+ body['status'] == 'error'
34
+ end
35
+
36
+ def method_missing(meth, *args, &block)
37
+ if body.key?(meth.to_s)
38
+ return Flow::Models::Model.load_model(meth, connection, body[meth.to_s])
39
+ end
40
+
41
+ super
42
+ end
43
+
44
+
45
+ private
46
+
47
+ def _deep_freeze(obj)
48
+ if obj.respond_to?(:each)
49
+ obj.each do |elem|
50
+ _deep_freeze(elem)
51
+ end
52
+ end
53
+
54
+ obj.freeze
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module Flow
3
+ VERSION = '0.0.1'
4
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ribbon-flow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert Honer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDfDCCAmSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBCMRQwEgYDVQQDDAtlbmdp
14
+ bmVlcmluZzEWMBQGCgmSJomT8ixkARkWBnJpYmJvbjESMBAGCgmSJomT8ixkARkW
15
+ AmNvMB4XDTE0MDYwOTE2MjY0NFoXDTE1MDYwOTE2MjY0NFowQjEUMBIGA1UEAwwL
16
+ ZW5naW5lZXJpbmcxFjAUBgoJkiaJk/IsZAEZFgZyaWJib24xEjAQBgoJkiaJk/Is
17
+ ZAEZFgJjbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOqAiBev9e7J
18
+ D3X3A+SXpIbnG71jweN7Q7+QMhscA6+s8X91aG2cvNPpk7OSMaoU7Q81v+I9VMYu
19
+ n0JhOh/O86nYv0Ig/FKcDRzZIGgY0bjJV/5mokQ45xgseUTjJ3ocqOXncRPNx4xm
20
+ Am3eyb43aDqUFnowXcH84ZXMTFew+dekov6644pN14u8aXGWPmZxOQhTOi5+ESAe
21
+ +RON7v2xRq1OFcupEHeKHMhAMCbOy19NqTq/1yA8Pd940OfVRisCnXoQqOCP9H86
22
+ gakm8XcLrUdJupV6OxCMd5NZ3itRTFEWOMy6ZRkj2IVdFQ4ZPQjIHR2f253DF7BW
23
+ dUStbmkKuyECAwEAAaN9MHswCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
24
+ BBYEFPjdKqT6jDUZj755LAOopw41RGO/MCAGA1UdEQQZMBeBFWVuZ2luZWVyaW5n
25
+ QHJpYmJvbi5jbzAgBgNVHRIEGTAXgRVlbmdpbmVlcmluZ0ByaWJib24uY28wDQYJ
26
+ KoZIhvcNAQEFBQADggEBACmYnS5b0mBhurs/U+oTUiY6nL4/xTx1poZxUNJ9Emag
27
+ y/vnnbMxiZXXz4Y6vWdFiOg02N6BiBrvyjzkleOyw41lcNDj9pH6HyLFAKR4lU5V
28
+ HKaahXt474aWouDfvENcaINNtgLrPAqisgRckuyzfM/bU1a8Aj2OSLsSEh69giTi
29
+ 0WxI115SHQG+8CyzpRQIBteomCInG1ymSyvEUxpytSW39KBVKViB84Ss5vcMvtv7
30
+ grVI0T63q4/t9yjlCPkbK1VTObseWNPA2/7twecdkCVN3VaWEml4xf2KiOwnKDfk
31
+ aZOXvndbbL+k3uaxs/Fpfsi0AD02HiceGjSOZFd9Wyk=
32
+ -----END CERTIFICATE-----
33
+ date: 2014-06-09 00:00:00.000000000 Z
34
+ dependencies:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rest-client
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ description: With this gem, you can connect and perform any operation on Ribbon Flow.
50
+ It supports loading pool, card and transaction tokens. You can also easily credit
51
+ a card.
52
+ email: engineering@ribbon.co
53
+ executables: []
54
+ extensions: []
55
+ extra_rdoc_files: []
56
+ files:
57
+ - lib/flow/config.rb
58
+ - lib/flow/connection.rb
59
+ - lib/flow/errors.rb
60
+ - lib/flow/models/card.rb
61
+ - lib/flow/models/model.rb
62
+ - lib/flow/models/pool.rb
63
+ - lib/flow/models/transaction.rb
64
+ - lib/flow/models.rb
65
+ - lib/flow/response.rb
66
+ - lib/flow/version.rb
67
+ - lib/flow.rb
68
+ homepage: https://flow.ribbon.co
69
+ licenses:
70
+ - BSD
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.0.3
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Ruby SDK for the Ribbon Flow API.
92
+ test_files: []
Binary file