synapse_pay 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +16 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +54 -0
- data/Rakefile +8 -0
- data/VERSION +1 -0
- data/bin/synapse_pay-console +7 -0
- data/gemfiles/default-with-activesupport.gemfile +10 -0
- data/gemfiles/json.gemfile +12 -0
- data/gemfiles/yajl.gemfile +12 -0
- data/lib/synapse_pay.rb +78 -0
- data/lib/synapse_pay/apibits/api_client.rb +29 -0
- data/lib/synapse_pay/apibits/api_endpoint.rb +9 -0
- data/lib/synapse_pay/apibits/api_list.rb +88 -0
- data/lib/synapse_pay/apibits/api_method.rb +97 -0
- data/lib/synapse_pay/apibits/api_object.rb +52 -0
- data/lib/synapse_pay/apibits/api_resource.rb +127 -0
- data/lib/synapse_pay/apibits/headers_builder.rb +47 -0
- data/lib/synapse_pay/apibits/params_builder.rb +30 -0
- data/lib/synapse_pay/apibits/path_builder.rb +38 -0
- data/lib/synapse_pay/apibits/requester.rb +104 -0
- data/lib/synapse_pay/apibits/util.rb +51 -0
- data/lib/synapse_pay/client.rb +89 -0
- data/lib/synapse_pay/endpoints/bank_endpoint.rb +47 -0
- data/lib/synapse_pay/endpoints/bank_mfa_device_endpoint.rb +5 -0
- data/lib/synapse_pay/endpoints/bank_mfa_questions_endpoint.rb +5 -0
- data/lib/synapse_pay/endpoints/bank_status_endpoint.rb +11 -0
- data/lib/synapse_pay/endpoints/card_endpoint.rb +26 -0
- data/lib/synapse_pay/endpoints/deposit_endpoint.rb +23 -0
- data/lib/synapse_pay/endpoints/mass_pay_endpoint.rb +26 -0
- data/lib/synapse_pay/endpoints/order_endpoint.rb +44 -0
- data/lib/synapse_pay/endpoints/user_endpoint.rb +26 -0
- data/lib/synapse_pay/endpoints/wire_endpoint.rb +29 -0
- data/lib/synapse_pay/endpoints/withdrawal_endpoint.rb +17 -0
- data/lib/synapse_pay/errors/api_connection_error.rb +4 -0
- data/lib/synapse_pay/errors/api_error.rb +32 -0
- data/lib/synapse_pay/errors/authentication_error.rb +4 -0
- data/lib/synapse_pay/errors/synapse_pay_error.rb +13 -0
- data/lib/synapse_pay/nested_list.rb +32 -0
- data/lib/synapse_pay/resources/bank.rb +46 -0
- data/lib/synapse_pay/resources/bank_mfa_device.rb +32 -0
- data/lib/synapse_pay/resources/bank_mfa_questions.rb +28 -0
- data/lib/synapse_pay/resources/bank_status.rb +21 -0
- data/lib/synapse_pay/resources/card.rb +32 -0
- data/lib/synapse_pay/resources/deposit.rb +25 -0
- data/lib/synapse_pay/resources/mass_pay.rb +38 -0
- data/lib/synapse_pay/resources/order.rb +63 -0
- data/lib/synapse_pay/resources/user.rb +80 -0
- data/lib/synapse_pay/resources/wire.rb +31 -0
- data/lib/synapse_pay/resources/withdrawal.rb +29 -0
- data/lib/synapse_pay/version.rb +3 -0
- data/synapse_pay.gemspec +29 -0
- data/test/synapse_pay/api_list_test.rb +23 -0
- data/test/synapse_pay/api_method_test.rb +89 -0
- data/test/synapse_pay/headers_builder_test.rb +39 -0
- data/test/synapse_pay/params_builder_test.rb +57 -0
- data/test/synapse_pay/path_builder_test.rb +50 -0
- data/test/synapse_pay/requester_test.rb +86 -0
- data/test/synapse_pay/util_test.rb +51 -0
- data/test/test_data.rb +72 -0
- data/test/test_helper.rb +41 -0
- metadata +220 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
class APIObject
|
3
|
+
include Enumerable
|
4
|
+
attr_reader :json
|
5
|
+
|
6
|
+
def self.construct(json)
|
7
|
+
if json.is_a?(Array)
|
8
|
+
return json.map{ |a| APIObject.construct(a) }
|
9
|
+
elsif json.is_a?(Hash)
|
10
|
+
return APIObject.new(json)
|
11
|
+
else
|
12
|
+
return json
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(json=nil)
|
17
|
+
refresh_from(json)
|
18
|
+
end
|
19
|
+
|
20
|
+
def refresh_from(json={})
|
21
|
+
@json = Util.sorta_deep_clone(json)
|
22
|
+
@json.each do |k, v|
|
23
|
+
@json[k] = APIObject.construct(v)
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
@json.inspect
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_json(*args)
|
33
|
+
JSON.generate(@json)
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(name, *args, &blk)
|
37
|
+
if name.to_s.end_with?('=')
|
38
|
+
attr = name.to_s[0...-1].to_sym
|
39
|
+
@json[attr] = args[0]
|
40
|
+
else
|
41
|
+
if @json.respond_to?(name)
|
42
|
+
@json.send(name, *args, &blk)
|
43
|
+
elsif @json.has_key?(name.to_sym)
|
44
|
+
return @json[name.to_sym]
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
class APIResource
|
3
|
+
attr_reader :api_method
|
4
|
+
attr_reader :json
|
5
|
+
attr_reader :client
|
6
|
+
|
7
|
+
def initialize(json=nil, api_method=nil, client=nil)
|
8
|
+
refresh_from(json, api_method, client)
|
9
|
+
end
|
10
|
+
|
11
|
+
def refresh_from(json={}, api_method=nil, client=nil)
|
12
|
+
unless json.is_a?(Hash)
|
13
|
+
json = { :id => json }
|
14
|
+
end
|
15
|
+
json = Util.symbolize_keys(json)
|
16
|
+
|
17
|
+
# Clear or write over any old data
|
18
|
+
clear_api_attributes
|
19
|
+
@api_method = api_method
|
20
|
+
@json = Util.sorta_deep_clone(json)
|
21
|
+
@client = client
|
22
|
+
|
23
|
+
# Use json (not the @json, the cloned copy)
|
24
|
+
json.each do |k, v|
|
25
|
+
if self.class.api_attribute_names.include?(k.to_sym)
|
26
|
+
instance_variable_set("@#{k}", determine_api_attribute_value(k, v))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
34
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> Attributes: " + JSON.pretty_generate(inspect_api_attributes)
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect_nested
|
38
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
39
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}>"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_json(*args)
|
43
|
+
JSON.generate(api_attributes)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.api_attribute_names
|
47
|
+
@api_attributes.map(&:first)
|
48
|
+
end
|
49
|
+
|
50
|
+
def api_attributes
|
51
|
+
ret = {}
|
52
|
+
self.class.api_attribute_names.each do |attribute|
|
53
|
+
ret[attribute] = self.send(attribute)
|
54
|
+
end
|
55
|
+
ret
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect_api_attributes
|
59
|
+
ret = {}
|
60
|
+
api_attributes.each do |k, v|
|
61
|
+
if v.is_a?(APIResource)
|
62
|
+
ret[k] = v.inspect_nested
|
63
|
+
else
|
64
|
+
ret[k] = v
|
65
|
+
end
|
66
|
+
end
|
67
|
+
ret
|
68
|
+
end
|
69
|
+
|
70
|
+
# TODO(joncalhoun): Make this work for nested class construction.
|
71
|
+
def changed_api_attributes
|
72
|
+
ret = {}
|
73
|
+
self.api_attributes.each do |name, value|
|
74
|
+
if @json[name] != value
|
75
|
+
ret[name] = value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ret
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear_api_attributes
|
82
|
+
self.class.api_attribute_names.each do |name|
|
83
|
+
instance_variable_set("@#{name}", nil)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.determine_api_attribute_value(name, raw_value)
|
88
|
+
if @api_attributes[name] && @api_attributes[name].has_key?(:constructor)
|
89
|
+
klass = Util.constantize(@api_attributes[name][:constructor])
|
90
|
+
if(klass.respond_to?(:construct))
|
91
|
+
klass.construct(raw_value)
|
92
|
+
else
|
93
|
+
klass.new(raw_value)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
APIObject.construct(raw_value)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
def determine_api_attribute_value(name, raw_value)
|
100
|
+
self.class.determine_api_attribute_value(name, raw_value)
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def self.api_subclasses
|
105
|
+
return @api_subclasses ||= Set.new
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.api_subclass_fetch(name)
|
109
|
+
@api_subclasses_hash ||= {}
|
110
|
+
if @api_subclasses_hash.has_key?(name)
|
111
|
+
@api_subclasses_hash[name]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.register_api_subclass(subclass, name=nil)
|
116
|
+
@api_subclasses ||= Set.new
|
117
|
+
@api_subclasses << subclass
|
118
|
+
|
119
|
+
unless name.nil?
|
120
|
+
@api_subclasses_hash ||= {}
|
121
|
+
@api_subclasses_hash[name] = subclass
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
@api_attributes = {}
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
module HeadersBuilder
|
3
|
+
|
4
|
+
def self.build(headers)
|
5
|
+
headers ||= {}
|
6
|
+
default_headers.merge(headers)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.default_headers
|
10
|
+
headers = {
|
11
|
+
:user_agent => "SynapsePay/#{SynapsePay.api_version} RubyBindings/#{SynapsePay::VERSION}",
|
12
|
+
}.merge({
|
13
|
+
"Content-Type" => "application/json",
|
14
|
+
})
|
15
|
+
|
16
|
+
begin
|
17
|
+
headers.update(:x_synapse_pay_client_user_agent => JSON.generate(user_agent))
|
18
|
+
rescue => e
|
19
|
+
headers.update(:x_synapse_pay_client_raw_user_agent => user_agent.inspect,
|
20
|
+
:error => "#{e} (#{e.class})")
|
21
|
+
end
|
22
|
+
headers
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def self.user_agent
|
28
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
29
|
+
|
30
|
+
{
|
31
|
+
:bindings_version => SynapsePay::VERSION,
|
32
|
+
:lang => 'ruby',
|
33
|
+
:lang_version => lang_version,
|
34
|
+
:platform => RUBY_PLATFORM,
|
35
|
+
:publisher => 'synapse_pay',
|
36
|
+
:uname => get_uname
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.get_uname
|
41
|
+
`uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
|
42
|
+
rescue Errno::ENOMEM => ex # couldn't create subprocess
|
43
|
+
"uname lookup failed"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
module ParamsBuilder
|
3
|
+
|
4
|
+
def self.clean(params)
|
5
|
+
Util.symbolize_keys(params || {})
|
6
|
+
end
|
7
|
+
|
8
|
+
# Clean the params, and the hash to_merge, and then merge them.
|
9
|
+
# This ensures that we dont get something like { "id" => 123, :id => 321 }.
|
10
|
+
def self.merge(*args)
|
11
|
+
ret = {}
|
12
|
+
args.each do |arg|
|
13
|
+
ret = ret.merge(clean(arg))
|
14
|
+
end
|
15
|
+
ret
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.build(params, api_key=nil, auth_key=nil)
|
19
|
+
default_params.merge(clean(params))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.default_params
|
23
|
+
params = {
|
24
|
+
:client_id => SynapsePay.client_id,
|
25
|
+
:client_secret => SynapsePay.client_secret,
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
module PathBuilder
|
3
|
+
|
4
|
+
# Take a path like:
|
5
|
+
# ":path/:id/dogs/:dog_id"
|
6
|
+
# and convert it to:
|
7
|
+
# "#{object.path}/#{object.id}/dogs/#{params[:id]}" => "/objects/1/dogs/2"
|
8
|
+
#
|
9
|
+
# Path priority is:
|
10
|
+
# 1. Object - this will be a class or an instance of a class.
|
11
|
+
# 2. Params - this is a hash of key values. All keys *must* be symbolized.
|
12
|
+
def self.build(path, object, params)
|
13
|
+
ret = path.dup
|
14
|
+
if ret.include?(":")
|
15
|
+
matches = ret.scan(/:([^\/]*)/).flatten.map(&:to_sym)
|
16
|
+
missing = Set.new(matches)
|
17
|
+
|
18
|
+
matches.each do |match|
|
19
|
+
value = determine_value(match, object, params)
|
20
|
+
missing.delete(match) unless value.nil?
|
21
|
+
ret.sub!(match.inspect, "#{value}")
|
22
|
+
end
|
23
|
+
|
24
|
+
if missing.any?
|
25
|
+
raise ArgumentError.new("Could not determine the full URL. The following values of the path are missing: #{missing.to_a.join(', ')}. Try setting them in your params.")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
ret
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.determine_value(match, object, params)
|
32
|
+
value = object.send(match) if object && object.respond_to?(match)
|
33
|
+
value ||= params[match] if params && params.has_key?(match)
|
34
|
+
value
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
module Requester
|
3
|
+
|
4
|
+
def self.request(method, url, params, headers)
|
5
|
+
method = method.to_sym
|
6
|
+
url, params = prepare_params(method, url, params, headers)
|
7
|
+
request_opts = {
|
8
|
+
:method => method,
|
9
|
+
:url => url,
|
10
|
+
:headers => headers,
|
11
|
+
:payload => params,
|
12
|
+
|
13
|
+
:verify_ssl => false,
|
14
|
+
:open_timeout => 30,
|
15
|
+
:timeout => 60
|
16
|
+
}
|
17
|
+
|
18
|
+
execute_request(request_opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.execute_request(opts)
|
22
|
+
RestClient::Request.execute(opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get(url, params, headers)
|
26
|
+
self.request(:get, url, params, headers)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.delete(url, params, headers)
|
30
|
+
self.request(:delete, url, params, headers)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.put(url, params, headers)
|
34
|
+
self.request(:put, url, params, headers)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.post(url, params, headers)
|
38
|
+
self.request(:post, url, params, headers)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.prepare_params(method, url, params, headers)
|
42
|
+
if [:get, :head, :delete].include?(method)
|
43
|
+
unless params.empty?
|
44
|
+
url += URI.parse(url).query ? '&' : '?' + query_string(params)
|
45
|
+
end
|
46
|
+
params = nil
|
47
|
+
else
|
48
|
+
if headers["Content-Type"] == "application/json"
|
49
|
+
params = JSON.generate(params)
|
50
|
+
else
|
51
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
52
|
+
if !RestClient::Payload.has_file?(params)
|
53
|
+
params = query_string(params)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
[url, params]
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.query_string(params)
|
61
|
+
params ||= {}
|
62
|
+
if params.any?
|
63
|
+
query_array(params).join('&')
|
64
|
+
else
|
65
|
+
""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Three major use cases (and nesting of them needs to be supported):
|
70
|
+
# { :a => { :b => "bvalue" } } => ["a[b]=bvalue"]
|
71
|
+
# { :a => [1, 2] } => ["a[]=1", "a[]=2"]
|
72
|
+
# { :a => "value" } => ["a=value"]
|
73
|
+
def self.query_array(params, key_prefix=nil)
|
74
|
+
ret = []
|
75
|
+
params.each do |key, value|
|
76
|
+
if params.is_a?(Array)
|
77
|
+
value = key
|
78
|
+
key = ''
|
79
|
+
end
|
80
|
+
key_suffix = escape(key)
|
81
|
+
full_key = key_prefix ? "#{key_prefix}[#{key_suffix}]" : key_suffix
|
82
|
+
|
83
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
84
|
+
# Handles the following cases:
|
85
|
+
# { :a => { :b => "bvalue" } } => ["a[b]=bvalue"]
|
86
|
+
# { :a => [1, 2] } => ["a[]=1", "a[]=2"]
|
87
|
+
ret += query_array(value, full_key)
|
88
|
+
elsif value.is_a?(APIObject)
|
89
|
+
ret += query_array(value.json, full_key)
|
90
|
+
else
|
91
|
+
# Handles the base case with just key and value:
|
92
|
+
# { :a => "value" } => ["a=value"]
|
93
|
+
ret << "#{full_key}=#{escape(value)}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
ret
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.escape(val)
|
100
|
+
URI.escape(val.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SynapsePay
|
2
|
+
module Util
|
3
|
+
|
4
|
+
def self.symbolize_keys(obj)
|
5
|
+
if obj.is_a?(Hash)
|
6
|
+
ret = {}
|
7
|
+
obj.each do |key, value|
|
8
|
+
ret[(key.to_sym rescue key) || key] = symbolize_keys(value)
|
9
|
+
end
|
10
|
+
return ret
|
11
|
+
elsif obj.is_a?(Array)
|
12
|
+
return obj.map{ |value| symbolize_keys(value) }
|
13
|
+
else
|
14
|
+
return obj
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.sorta_deep_clone(json)
|
19
|
+
if json.is_a?(Hash)
|
20
|
+
ret = {}
|
21
|
+
json.each do |k, v|
|
22
|
+
ret[k] = sorta_deep_clone(v)
|
23
|
+
end
|
24
|
+
ret
|
25
|
+
elsif json.is_a?(Array)
|
26
|
+
json.map{ |j| sorta_deep_clone(j) }
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
json.dup
|
30
|
+
rescue
|
31
|
+
json
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.constantize(str, prefix=false)
|
37
|
+
str = str.to_s
|
38
|
+
begin
|
39
|
+
str.split('::').reduce(Module, :const_get)
|
40
|
+
rescue NameError => e
|
41
|
+
if prefix
|
42
|
+
raise e
|
43
|
+
else
|
44
|
+
p = "#{self.name}".split("::").first
|
45
|
+
constantize("#{p}::#{str}", true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|