paysio 1.0.0

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.
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