paysio 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +9 -0
  3. data/CONTRIBUTORS +1 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +42 -0
  6. data/History.txt +4 -0
  7. data/LICENSE +21 -0
  8. data/README.rdoc +12 -0
  9. data/Rakefile +12 -0
  10. data/VERSION +1 -0
  11. data/bin/paysio +7 -0
  12. data/gemfiles/default-with-activesupport.gemfile +3 -0
  13. data/gemfiles/json.gemfile +4 -0
  14. data/gemfiles/yajl.gemfile +4 -0
  15. data/lib/data/ca-certificates.crt +3918 -0
  16. data/lib/paysio/account.rb +4 -0
  17. data/lib/paysio/api_operations/create.rb +16 -0
  18. data/lib/paysio/api_operations/delete.rb +11 -0
  19. data/lib/paysio/api_operations/list.rb +16 -0
  20. data/lib/paysio/api_operations/update.rb +15 -0
  21. data/lib/paysio/api_resource.rb +33 -0
  22. data/lib/paysio/charge.rb +39 -0
  23. data/lib/paysio/coupon.rb +7 -0
  24. data/lib/paysio/customer.rb +51 -0
  25. data/lib/paysio/errors/api_connection_error.rb +4 -0
  26. data/lib/paysio/errors/api_error.rb +4 -0
  27. data/lib/paysio/errors/authentication_error.rb +4 -0
  28. data/lib/paysio/errors/card_error.rb +11 -0
  29. data/lib/paysio/errors/invalid_request_error.rb +10 -0
  30. data/lib/paysio/errors/paysio_error.rb +20 -0
  31. data/lib/paysio/event.rb +5 -0
  32. data/lib/paysio/json.rb +21 -0
  33. data/lib/paysio/list_object.rb +14 -0
  34. data/lib/paysio/paysio_object.rb +159 -0
  35. data/lib/paysio/singleton_api_resource.rb +20 -0
  36. data/lib/paysio/util.rb +100 -0
  37. data/lib/paysio/version.rb +3 -0
  38. data/lib/paysio.rb +252 -0
  39. data/paysio.gemspec +28 -0
  40. data/test/test_helper.rb +162 -0
  41. data/test/test_paysio.rb +479 -0
  42. data/test/test_paysio_with_active_support.rb +2 -0
  43. metadata +193 -0
@@ -0,0 +1,4 @@
1
+ module Paysio
2
+ class Account < SingletonAPIResource
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module Paysio
2
+ module APIOperations
3
+ module Create
4
+ module ClassMethods
5
+ def create(params={}, api_key=nil)
6
+ response, api_key = Paysio.request(:post, self.url, api_key, params)
7
+ Util.convert_to_paysio_object(response, api_key)
8
+ end
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Paysio
2
+ module APIOperations
3
+ module Delete
4
+ def delete
5
+ response, api_key = Paysio.request(:delete, url, @api_key)
6
+ refresh_from(response, api_key)
7
+ self
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Paysio
2
+ module APIOperations
3
+ module List
4
+ module ClassMethods
5
+ def all(filters={}, api_key=nil)
6
+ response, api_key = Paysio.request(:get, url, api_key, filters)
7
+ Util.convert_to_paysio_object(response, api_key)
8
+ end
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Paysio
2
+ module APIOperations
3
+ module Update
4
+ def save
5
+ if @unsaved_values.length > 0
6
+ values = {}
7
+ @unsaved_values.each { |k| values[k] = @values[k] }
8
+ response, api_key = Paysio.request(:post, url, @api_key, values)
9
+ refresh_from(response, api_key)
10
+ end
11
+ self
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ module Paysio
2
+ class APIResource < PaysioObject
3
+ def self.class_name
4
+ self.name.split('::')[-1]
5
+ end
6
+
7
+ def self.url()
8
+ if self == APIResource
9
+ raise NotImplementedError.new('APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)')
10
+ end
11
+ "/v1/#{CGI.escape(class_name.downcase)}s"
12
+ end
13
+
14
+ def url
15
+ unless id = self['id']
16
+ raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
17
+ end
18
+ "#{self.class.url}/#{CGI.escape(id)}"
19
+ end
20
+
21
+ def refresh
22
+ response, api_key = Paysio.request(:get, url, @api_key, @retrieve_options)
23
+ refresh_from(response, api_key)
24
+ self
25
+ end
26
+
27
+ def self.retrieve(id, api_key=nil)
28
+ instance = self.new(id, api_key)
29
+ instance.refresh
30
+ instance
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ module Paysio
2
+ class Charge < APIResource
3
+ include Paysio::APIOperations::List
4
+ include Paysio::APIOperations::Create
5
+ include Paysio::APIOperations::Update
6
+
7
+ def refund(params={})
8
+ response, api_key = Paysio.request(:post, refund_url, @api_key, params)
9
+ refresh_from(response, api_key)
10
+ self
11
+ end
12
+
13
+ def capture(params={})
14
+ response, api_key = Paysio.request(:post, capture_url, @api_key, params)
15
+ refresh_from(response, api_key)
16
+ self
17
+ end
18
+
19
+ def update_dispute(params)
20
+ response, api_key = Paysio.request(:post, dispute_url, @api_key, params)
21
+ refresh_from({ :dispute => response }, api_key, true)
22
+ dispute
23
+ end
24
+
25
+ private
26
+
27
+ def refund_url
28
+ url + '/refund'
29
+ end
30
+
31
+ def capture_url
32
+ url + '/capture'
33
+ end
34
+
35
+ def dispute_url
36
+ url + '/dispute'
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Paysio
2
+ class Coupon < APIResource
3
+ include Paysio::APIOperations::Create
4
+ include Paysio::APIOperations::Delete
5
+ include Paysio::APIOperations::List
6
+ end
7
+ end
@@ -0,0 +1,51 @@
1
+ module Paysio
2
+ class Customer < APIResource
3
+ include Paysio::APIOperations::Create
4
+ include Paysio::APIOperations::Delete
5
+ include Paysio::APIOperations::Update
6
+ include Paysio::APIOperations::List
7
+
8
+ def add_invoice_item(params)
9
+ InvoiceItem.create(params.merge(:customer => id), @api_key)
10
+ end
11
+
12
+ def invoices
13
+ Invoice.all({ :customer => id }, @api_key)
14
+ end
15
+
16
+ def invoice_items
17
+ InvoiceItem.all({ :customer => id }, @api_key)
18
+ end
19
+
20
+ def charges
21
+ Charge.all({ :customer => id }, @api_key)
22
+ end
23
+
24
+ def cancel_subscription(params={})
25
+ response, api_key = Paysio.request(:delete, subscription_url, @api_key, params)
26
+ refresh_from({ :subscription => response }, api_key, true)
27
+ subscription
28
+ end
29
+
30
+ def update_subscription(params)
31
+ response, api_key = Paysio.request(:post, subscription_url, @api_key, params)
32
+ refresh_from({ :subscription => response }, api_key, true)
33
+ subscription
34
+ end
35
+
36
+ def delete_discount
37
+ Paysio.request(:delete, discount_url, @api_key)
38
+ refresh_from({ :discount => nil }, api_key, true)
39
+ end
40
+
41
+ private
42
+
43
+ def discount_url
44
+ url + '/discount'
45
+ end
46
+
47
+ def subscription_url
48
+ url + '/subscription'
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module Paysio
2
+ class APIConnectionError < PaysioError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Paysio
2
+ class APIError < PaysioError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Paysio
2
+ class AuthenticationError < PaysioError
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Paysio
2
+ class CardError < PaysioError
3
+ attr_reader :param, :code
4
+
5
+ def initialize(message, param, code, http_status=nil, http_body=nil, json_body=nil)
6
+ super(message, http_status, http_body, json_body)
7
+ @param = param
8
+ @code = code
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Paysio
2
+ class InvalidRequestError < PaysioError
3
+ attr_accessor :param
4
+
5
+ def initialize(message, param, http_status=nil, http_body=nil, json_body=nil)
6
+ super(message, http_status, http_body, json_body)
7
+ @param = param
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Paysio
2
+ class PaysioError < StandardError
3
+ attr_reader :message
4
+ attr_reader :http_status
5
+ attr_reader :http_body
6
+ attr_reader :json_body
7
+
8
+ def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
9
+ @message = message
10
+ @http_status = http_status
11
+ @http_body = http_body
12
+ @json_body = json_body
13
+ end
14
+
15
+ def to_s
16
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
17
+ "#{status_string}#{@message}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module Paysio
2
+ class Event < APIResource
3
+ include Paysio::APIOperations::List
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module Paysio
2
+ module JSON
3
+ if MultiJson.respond_to?(:dump)
4
+ def self.dump(*args)
5
+ MultiJson.dump(*args)
6
+ end
7
+
8
+ def self.load(*args)
9
+ MultiJson.load(*args)
10
+ end
11
+ else
12
+ def self.dump(*args)
13
+ MultiJson.encode(*args)
14
+ end
15
+
16
+ def self.load(*args)
17
+ MultiJson.decode(*args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Paysio
2
+ class ListObject < PaysioObject
3
+
4
+ def each(&blk)
5
+ self.data.each(&blk)
6
+ end
7
+
8
+ def all(filters={})
9
+ response, api_key = Paysio.request(:get, url, api_key, filters)
10
+ Util.convert_to_paysio_object(response, api_key)
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,159 @@
1
+ module Paysio
2
+ class PaysioObject
3
+ include Enumerable
4
+
5
+ attr_accessor :api_key
6
+ @@permanent_attributes = Set.new([:api_key])
7
+
8
+ # The default :id method is deprecated and isn't useful to us
9
+ if method_defined?(:id)
10
+ undef :id
11
+ end
12
+
13
+ def initialize(id=nil, api_key=nil)
14
+ # parameter overloading!
15
+ if id.kind_of?(Hash)
16
+ @retrieve_options = id.dup
17
+ @retrieve_options.delete(:id)
18
+ id = id[:id]
19
+ else
20
+ @retrieve_options = {}
21
+ end
22
+
23
+ @api_key = api_key
24
+ @values = {}
25
+ # This really belongs in APIResource, but not putting it there allows us
26
+ # to have a unified inspect method
27
+ @unsaved_values = Set.new
28
+ @transient_values = Set.new
29
+ self.id = id if id
30
+ end
31
+
32
+ def self.construct_from(values, api_key=nil)
33
+ obj = self.new(values[:id], api_key)
34
+ obj.refresh_from(values, api_key)
35
+ obj
36
+ end
37
+
38
+ def to_s(*args)
39
+ Paysio::JSON.dump(@values, :pretty => true)
40
+ end
41
+
42
+ def inspect()
43
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
44
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + Paysio::JSON.dump(@values, :pretty => true)
45
+ end
46
+
47
+ def refresh_from(values, api_key, partial=false)
48
+ @api_key = api_key
49
+
50
+ removed = partial ? Set.new : Set.new(@values.keys - values.keys)
51
+ added = Set.new(values.keys - @values.keys)
52
+ # Wipe old state before setting new. This is useful for e.g. updating a
53
+ # customer, where there is no persistent card parameter. Mark those values
54
+ # which don't persist as transient
55
+
56
+ instance_eval do
57
+ remove_accessors(removed)
58
+ add_accessors(added)
59
+ end
60
+ removed.each do |k|
61
+ @values.delete(k)
62
+ @transient_values.add(k)
63
+ @unsaved_values.delete(k)
64
+ end
65
+ values.each do |k, v|
66
+ @values[k] = Util.convert_to_paysio_object(v, api_key)
67
+ @transient_values.delete(k)
68
+ @unsaved_values.delete(k)
69
+ end
70
+ end
71
+
72
+ def [](k)
73
+ k = k.to_sym if k.kind_of?(String)
74
+ @values[k]
75
+ end
76
+
77
+ def []=(k, v)
78
+ send(:"#{k}=", v)
79
+ end
80
+
81
+ def keys
82
+ @values.keys
83
+ end
84
+
85
+ def values
86
+ @values.values
87
+ end
88
+
89
+ def to_json(*a)
90
+ Paysio::JSON.dump(@values)
91
+ end
92
+
93
+ def as_json(*a)
94
+ @values.as_json(*a)
95
+ end
96
+
97
+ def to_hash
98
+ @values
99
+ end
100
+
101
+ def each(&blk)
102
+ @values.each(&blk)
103
+ end
104
+
105
+ protected
106
+
107
+ def metaclass
108
+ class << self; self; end
109
+ end
110
+
111
+ def remove_accessors(keys)
112
+ metaclass.instance_eval do
113
+ keys.each do |k|
114
+ next if @@permanent_attributes.include?(k)
115
+ k_eq = :"#{k}="
116
+ remove_method(k) if method_defined?(k)
117
+ remove_method(k_eq) if method_defined?(k_eq)
118
+ end
119
+ end
120
+ end
121
+
122
+ def add_accessors(keys)
123
+ metaclass.instance_eval do
124
+ keys.each do |k|
125
+ next if @@permanent_attributes.include?(k)
126
+ k_eq = :"#{k}="
127
+ define_method(k) { @values[k] }
128
+ define_method(k_eq) do |v|
129
+ @values[k] = v
130
+ @unsaved_values.add(k)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def method_missing(name, *args)
137
+ # TODO: only allow setting in updateable classes.
138
+ if name.to_s.end_with?('=')
139
+ attr = name.to_s[0...-1].to_sym
140
+ @values[attr] = args[0]
141
+ @unsaved_values.add(attr)
142
+ add_accessors([attr])
143
+ return
144
+ else
145
+ return @values[name] if @values.has_key?(name)
146
+ end
147
+
148
+ begin
149
+ super
150
+ rescue NoMethodError => e
151
+ if @transient_values.include?(name)
152
+ raise NoMethodError.new(e.message + ". HINT: The '#{name}' attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Paysio's API, probably as a result of a save(). The attributes currently available on this object are: #{@values.keys.join(', ')}")
153
+ else
154
+ raise
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,20 @@
1
+ module Paysio
2
+ class SingletonAPIResource < APIResource
3
+ def self.url()
4
+ if self == SingletonAPIResource
5
+ raise NotImplementedError.new('SingletonAPIResource is an abstract class. You should perform actions on its subclasses (Account, etc.)')
6
+ end
7
+ "/v1/#{CGI.escape(class_name.downcase)}"
8
+ end
9
+
10
+ def url
11
+ self.class.url
12
+ end
13
+
14
+ def self.retrieve(api_key=nil)
15
+ instance = self.new(nil, api_key)
16
+ instance.refresh
17
+ instance
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,100 @@
1
+ module Paysio
2
+ module Util
3
+ def self.objects_to_ids(h)
4
+ case h
5
+ when APIResource
6
+ h.id
7
+ when Hash
8
+ res = {}
9
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
10
+ res
11
+ when Array
12
+ h.map { |v| objects_to_ids(v) }
13
+ else
14
+ h
15
+ end
16
+ end
17
+
18
+ def self.convert_to_paysio_object(resp, api_key)
19
+ types = {
20
+ 'charge' => Charge,
21
+ 'customer' => Customer,
22
+ 'coupon' => Coupon,
23
+ 'event' => Event,
24
+ 'list' => ListObject
25
+ }
26
+ case resp
27
+ when Array
28
+ resp.map { |i| convert_to_paysio_object(i, api_key) }
29
+ when Hash
30
+ # Try converting to a known object class. If none available, fall back to generic APIResource
31
+ if klass_name = resp[:object]
32
+ klass = types[klass_name]
33
+ end
34
+ klass ||= PaysioObject
35
+ klass.construct_from(resp, api_key)
36
+ else
37
+ resp
38
+ end
39
+ end
40
+
41
+ def self.file_readable(file)
42
+ begin
43
+ File.open(file) { |f| }
44
+ rescue
45
+ false
46
+ else
47
+ true
48
+ end
49
+ end
50
+
51
+ def self.symbolize_names(object)
52
+ case object
53
+ when Hash
54
+ new = {}
55
+ object.each do |key, value|
56
+ key = (key.to_sym rescue key) || key
57
+ new[key] = symbolize_names(value)
58
+ end
59
+ new
60
+ when Array
61
+ object.map { |value| symbolize_names(value) }
62
+ else
63
+ object
64
+ end
65
+ end
66
+
67
+ def self.url_encode(key)
68
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
69
+ end
70
+
71
+ def self.flatten_params(params, parent_key=nil)
72
+ result = []
73
+ params.each do |key, value|
74
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
75
+ if value.is_a?(Hash)
76
+ result += flatten_params(value, calculated_key)
77
+ elsif value.is_a?(Array)
78
+ result += flatten_params_array(value, calculated_key)
79
+ else
80
+ result << [calculated_key, value]
81
+ end
82
+ end
83
+ result
84
+ end
85
+
86
+ def self.flatten_params_array(value, calculated_key)
87
+ result = []
88
+ value.each do |elem|
89
+ if elem.is_a?(Hash)
90
+ result += flatten_params(elem, calculated_key)
91
+ elsif elem.is_a?(Array)
92
+ result += flatten_params_array(elem, calculated_key)
93
+ else
94
+ result << ["#{calculated_key}[]", elem]
95
+ end
96
+ end
97
+ result
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,3 @@
1
+ module Paysio
2
+ VERSION = '1.0.0'
3
+ end