paid 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -1
  3. data/.travis.yml +16 -0
  4. data/History.txt +4 -0
  5. data/README.md +58 -0
  6. data/Rakefile +9 -29
  7. data/VERSION +1 -0
  8. data/bin/paid-console +7 -0
  9. data/gemfiles/default-with-activesupport.gemfile +10 -0
  10. data/gemfiles/json.gemfile +12 -0
  11. data/gemfiles/yajl.gemfile +12 -0
  12. data/lib/paid.rb +129 -177
  13. data/lib/paid/account.rb +14 -1
  14. data/lib/paid/api_class.rb +336 -0
  15. data/lib/paid/api_list.rb +47 -0
  16. data/lib/paid/api_resource.rb +8 -25
  17. data/lib/paid/api_singleton.rb +5 -0
  18. data/lib/paid/customer.rb +36 -21
  19. data/lib/paid/errors/api_error.rb +6 -0
  20. data/lib/paid/event.rb +22 -1
  21. data/lib/paid/invoice.rb +16 -21
  22. data/lib/paid/plan.rb +18 -2
  23. data/lib/paid/subscription.rb +17 -11
  24. data/lib/paid/transaction.rb +19 -12
  25. data/lib/paid/util.rb +53 -106
  26. data/lib/paid/version.rb +1 -1
  27. data/paid.gemspec +10 -11
  28. data/tasks/api_test.rb +187 -0
  29. data/test/mock_resource.rb +69 -0
  30. data/test/paid/account_test.rb +41 -4
  31. data/test/paid/api_class_test.rb +412 -0
  32. data/test/paid/api_list_test.rb +17 -0
  33. data/test/paid/api_resource_test.rb +13 -343
  34. data/test/paid/api_singleton_test.rb +12 -0
  35. data/test/paid/authentication_test.rb +50 -0
  36. data/test/paid/customer_test.rb +189 -29
  37. data/test/paid/event_test.rb +74 -0
  38. data/test/paid/invoice_test.rb +101 -20
  39. data/test/paid/plan_test.rb +84 -8
  40. data/test/paid/status_codes_test.rb +63 -0
  41. data/test/paid/subscription_test.rb +100 -20
  42. data/test/paid/transaction_test.rb +110 -37
  43. data/test/paid/util_test.rb +15 -24
  44. data/test/test_data.rb +144 -93
  45. data/test/test_helper.rb +6 -4
  46. metadata +32 -26
  47. data/Gemfile.lock +0 -54
  48. data/README.rdoc +0 -35
  49. data/lib/data/ca-certificates.crt +0 -0
  50. data/lib/paid/alias.rb +0 -16
  51. data/lib/paid/api_operations/create.rb +0 -17
  52. data/lib/paid/api_operations/delete.rb +0 -11
  53. data/lib/paid/api_operations/list.rb +0 -17
  54. data/lib/paid/api_operations/update.rb +0 -57
  55. data/lib/paid/certificate_blacklist.rb +0 -55
  56. data/lib/paid/list_object.rb +0 -37
  57. data/lib/paid/paid_object.rb +0 -187
  58. data/lib/paid/singleton_api_resource.rb +0 -20
  59. data/lib/tasks/paid_tasks.rake +0 -4
  60. data/test/paid/alias_test.rb +0 -22
  61. data/test/paid/certificate_blacklist_test.rb +0 -18
  62. data/test/paid/list_object_test.rb +0 -16
  63. data/test/paid/paid_object_test.rb +0 -27
  64. data/test/paid/properties_test.rb +0 -103
data/lib/paid/customer.rb CHANGED
@@ -1,31 +1,46 @@
1
1
  module Paid
2
2
  class Customer < APIResource
3
- include Paid::APIOperations::Create
4
- include Paid::APIOperations::Delete
5
- include Paid::APIOperations::Update
6
- include Paid::APIOperations::List
3
+ # attributes :id and :object inherited from APIResource
4
+ attribute :name
5
+ attribute :email
6
+ attribute :description
7
+ attribute :external_id
8
+ attribute :aliases, APIList
9
+ attribute :phone
10
+ attribute :address_line1
11
+ attribute :address_line2
12
+ attribute :address_city
13
+ attribute :address_state
14
+ attribute :address_zip
15
+ attribute :allow_ach
16
+ attribute :allow_wire
17
+ attribute :allow_check
18
+ attribute :allow_credit_card
19
+ attribute :terms
20
+ attribute :billing_type
21
+ attribute :billing_cycle
22
+ attribute :stripe_customer_id
7
23
 
8
- def generate_invoice(params={}, opts={})
9
- api_key, headers = Util.parse_opts(opts)
10
- response, api_key = Paid.request(
11
- :post, generate_invoice_url, api_key || @api_key, params, headers)
12
- # refresh_from(response, api_key)
13
- refresh_from({ :invoice => response }, api_key, true)
14
- invoice
15
- end
24
+ api_class_method :all, :get, :constructor => APIList.constructor(Customer)
25
+ api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
26
+ api_class_method :create, :post
27
+ api_class_method :by_alias, :get, "/v0/aliases/:alias", :arguments => [:alias]
28
+ api_class_method :by_external_id, :get, ":path/by_external_id/:external_id", :arguments => [:external_id]
16
29
 
17
- def invoices
18
- Invoice.all({ :customer => id }, @api_key)
19
- end
30
+ api_instance_method :save, :put, :default_params => :changed_attributes
31
+ api_instance_method :generate_invoice, :post, ":path/generate_invoice", :constructor => Invoice
20
32
 
21
- def transactions
22
- Transaction.all({ :customer => id }, @api_key)
23
- end
33
+ api_instance_method :invoices, :get, Invoice.path, :default_params => :customer_id_hash, :constructor => APIList.constructor(Invoice)
34
+ api_instance_method :transactions, :get, Transaction.path, :default_params => :customer_id_hash, :constructor => APIList.constructor(Transaction)
24
35
 
25
- private
36
+ def self.path
37
+ "/v0/customers"
38
+ end
26
39
 
27
- def generate_invoice_url
28
- api_url + '/generate_invoice'
40
+ def customer_id_hash
41
+ { :customer => self.id }
29
42
  end
43
+
44
+ APIClass.register_subclass(self, "customer")
30
45
  end
31
46
  end
@@ -1,4 +1,10 @@
1
1
  module Paid
2
2
  class APIError < PaidError
3
+
4
+ def self.generic(rcode, rbody)
5
+ self.new("Invalid response object from API: #{rbody.inspect} " +
6
+ "(HTTP response code was #{rcode})", rcode, rbody)
7
+ end
8
+
3
9
  end
4
10
  end
data/lib/paid/event.rb CHANGED
@@ -1,5 +1,26 @@
1
1
  module Paid
2
2
  class Event < APIResource
3
- include Paid::APIOperations::List
3
+ class Data
4
+ def self.construct(json={})
5
+ return nil if json.nil?
6
+ klass = APIClass.subclass_fetch(json[:object])
7
+ klass.construct(json)
8
+ end
9
+ end
10
+
11
+ # attributes :id and :object inherited from APIResource
12
+ attribute :created_at
13
+ attribute :type
14
+ attribute :data, Data
15
+
16
+ api_class_method :all, :get, :constructor => APIList.constructor(Event)
17
+ api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
18
+
19
+
20
+ def self.path
21
+ "/v0/events"
22
+ end
23
+
24
+ APIClass.register_subclass(self, "event")
4
25
  end
5
26
  end
data/lib/paid/invoice.rb CHANGED
@@ -1,31 +1,26 @@
1
1
  module Paid
2
2
  class Invoice < APIResource
3
- include Paid::APIOperations::List
4
- include Paid::APIOperations::Update
5
- include Paid::APIOperations::Create
3
+ # attributes :id and :object inherited from APIResource
4
+ attribute :summary
5
+ attribute :chase_schedule
6
+ attribute :next_chase_on
7
+ attribute :customer
8
+ attribute :issued_at
9
+ attribute :terms
10
+ attribute :url
6
11
 
7
- def issue(params={}, opts={})
8
- api_key, headers = Util.parse_opts(opts)
9
- response, api_key = Paid.request(
10
- :post, issue_url, api_key || @api_key, params, headers)
11
- refresh_from(response, api_key)
12
- end
12
+ api_class_method :all, :get, :constructor => APIList.constructor(Invoice)
13
+ api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
14
+ api_class_method :create, :post
13
15
 
14
- def mark_as_paid(params={}, opts={})
15
- api_key, headers = Util.parse_opts(opts)
16
- response, api_key = Paid.request(
17
- :post, mark_as_paid_url, api_key || @api_key, params, headers)
18
- refresh_from(response, api_key)
19
- end
16
+ api_instance_method :issue, :post, ":path/issue"
17
+ api_instance_method :mark_as_paid, :post, ":path/mark_as_paid" # requires :via in params
20
18
 
21
- private
22
19
 
23
- def issue_url
24
- api_url + '/issue'
20
+ def self.path
21
+ "/v0/invoices"
25
22
  end
26
23
 
27
- def mark_as_paid_url
28
- api_url + '/mark_as_paid'
29
- end
24
+ APIClass.register_subclass(self, "invoice")
30
25
  end
31
26
  end
data/lib/paid/plan.rb CHANGED
@@ -1,6 +1,22 @@
1
1
  module Paid
2
2
  class Plan < APIResource
3
- include Paid::APIOperations::List
4
- include Paid::APIOperations::Create
3
+
4
+ # attributes :id and :object inherited from APIResource
5
+ attribute :name
6
+ attribute :description
7
+ attribute :interval
8
+ attribute :interval_count
9
+ attribute :amount
10
+ attribute :created_at
11
+
12
+ api_class_method :all, :get, :constructor => APIList.constructor(Plan)
13
+ api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
14
+ api_class_method :create, :post
15
+
16
+ def self.path
17
+ "/v0/plans"
18
+ end
19
+
20
+ APIClass.register_subclass(self, "plan")
5
21
  end
6
22
  end
@@ -1,19 +1,25 @@
1
1
  module Paid
2
2
  class Subscription < APIResource
3
- include Paid::APIOperations::List
4
- include Paid::APIOperations::Create
3
+ # attributes :id and :object inherited from APIResource
4
+ attribute :created_at
5
+ attribute :starts_on
6
+ attribute :next_transaction_on
7
+ attribute :plan, Plan
8
+ attribute :customer
9
+ attribute :started_at
10
+ attribute :ended_at
11
+ attribute :cancelled_at
5
12
 
6
- def cancel(params={}, opts={})
7
- api_key, headers = Util.parse_opts(opts)
8
- response, api_key = Paid.request(
9
- :post, cancel_url, api_key || @api_key, params, headers)
10
- refresh_from(response, api_key)
11
- end
13
+ api_class_method :all, :get, :constructor => APIList.constructor(Subscription)
14
+ api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
15
+ api_class_method :create, :post
12
16
 
13
- private
17
+ api_instance_method :cancel, :post, ":path/cancel"
14
18
 
15
- def cancel_url
16
- api_url + '/cancel'
19
+ def self.path
20
+ "/v0/subscriptions"
17
21
  end
22
+
23
+ APIClass.register_subclass(self, "subscription")
18
24
  end
19
25
  end
@@ -1,20 +1,27 @@
1
1
  module Paid
2
2
  class Transaction < APIResource
3
- include Paid::APIOperations::List
4
- include Paid::APIOperations::Create
5
- include Paid::APIOperations::Update
3
+ # attributes :id and :object inherited from APIResource
4
+ attribute :amount # req for create
5
+ attribute :description # req for create
6
+ attribute :customer # optional for create
7
+ attribute :alias # optional for create
8
+ attribute :paid # invalid for create
9
+ attribute :paid_on # optional for create
10
+ attribute :properties # optional for create
11
+ attribute :invoice # invalid for create
6
12
 
7
- def mark_as_paid(params={}, opts={})
8
- api_key, headers = Util.parse_opts(opts)
9
- response, api_key = Paid.request(
10
- :post, mark_as_paid_url, api_key || @api_key, params, headers)
11
- refresh_from(response, api_key)
12
- end
13
+ api_class_method :create, :post
14
+ api_class_method :retrieve, :get, ":path/:id", :arguments => [:id]
15
+ api_class_method :all, :get, :constructor => APIList.constructor(Transaction)
16
+
17
+ api_instance_method :save, :put, :default_params => changed_lambda
18
+ api_instance_method :mark_as_paid, :post, ":path/mark_as_paid"
13
19
 
14
- private
15
20
 
16
- def mark_as_paid_url
17
- api_url + '/mark_as_paid'
21
+ def self.path
22
+ "/v0/transactions"
18
23
  end
24
+
25
+ APIClass.register_subclass(self, "transaction")
19
26
  end
20
27
  end
data/lib/paid/util.rb CHANGED
@@ -1,130 +1,77 @@
1
1
  module Paid
2
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.object_classes
19
- @object_classes ||= {
20
- # data structures
21
- 'list' => ListObject,
22
-
23
- # business objects
24
- 'transaction' => Transaction,
25
- 'customer' => Customer,
26
- 'event' => Event,
27
- 'invoice' => Invoice,
28
- 'alias' => Alias,
29
- 'plan' => Plan,
30
- 'subscription' => Subscription
31
- }
32
- end
33
3
 
34
- def self.convert_to_paid_object(resp, api_key)
35
- case resp
36
- when Array
37
- resp.map { |i| convert_to_paid_object(i, api_key) }
38
- when Hash
39
- # Try converting to a known object class. If none available, fall back to generic PaidObject
40
- object_classes.fetch(resp[:object], PaidObject).construct_from(resp, api_key)
4
+ def self.query_string(params)
5
+ if params && params.any?
6
+ return query_array(params).join('&')
41
7
  else
42
- resp
8
+ return ""
43
9
  end
44
10
  end
45
11
 
46
- def self.file_readable(file)
47
- # This is nominally equivalent to File.readable?, but that can
48
- # report incorrect results on some more oddball filesystems
49
- # (such as AFS)
50
- begin
51
- File.open(file) { |f| }
52
- rescue
53
- false
54
- else
55
- true
56
- end
57
- end
58
-
59
- def self.symbolize_names(object)
60
- case object
61
- when Hash
62
- new_hash = {}
63
- object.each do |key, value|
64
- key = (key.to_sym rescue key) || key
65
- new_hash[key] = symbolize_names(value)
12
+ # Three major use cases (and nesting of them needs to be supported):
13
+ # { :a => { :b => "bvalue" } } => ["a[b]=bvalue"]
14
+ # { :a => [1, 2] } => ["a[]=1", "a[]=2"]
15
+ # { :a => "value" } => ["a=value"]
16
+ def self.query_array(params, key_prefix=nil)
17
+ ret = []
18
+ params.each do |key, value|
19
+ if params.is_a?(Array)
20
+ value = key
21
+ key = ''
66
22
  end
67
- new_hash
68
- when Array
69
- object.map { |value| symbolize_names(value) }
70
- else
71
- object
72
- end
73
- end
74
-
75
- def self.url_encode(key)
76
- URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
77
- end
23
+ key_suffix = escape(key)
24
+ full_key = key_prefix ? "#{key_prefix}[#{key_suffix}]" : key_suffix
78
25
 
79
- def self.flatten_params(params, parent_key=nil)
80
- result = []
81
- params.each do |key, value|
82
- calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
83
- if value.is_a?(Hash)
84
- result += flatten_params(value, calculated_key)
85
- elsif value.is_a?(Array)
86
- result += flatten_params_array(value, calculated_key)
26
+ if value.is_a?(Hash) || value.is_a?(Array)
27
+ # Handles the following cases:
28
+ # { :a => { :b => "bvalue" } } => ["a[b]=bvalue"]
29
+ # { :a => [1, 2] } => ["a[]=1", "a[]=2"]
30
+ ret += query_array(value, full_key)
87
31
  else
88
- result << [calculated_key, value]
32
+ # Handles the base case with just key and value:
33
+ # { :a => "value" } => ["a=value"]
34
+ ret << "#{full_key}=#{escape(value)}"
89
35
  end
90
36
  end
91
- result
37
+ ret
92
38
  end
93
39
 
94
- def self.flatten_params_array(value, calculated_key)
95
- result = []
96
- value.each do |elem|
97
- if elem.is_a?(Hash)
98
- result += flatten_params(elem, calculated_key)
99
- elsif elem.is_a?(Array)
100
- result += flatten_params_array(elem, calculated_key)
101
- else
102
- result << ["#{calculated_key}[]", elem]
40
+ def self.escape(val)
41
+ URI.escape(val.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
42
+ end
43
+
44
+ def self.symbolize_keys(obj)
45
+ if obj.is_a?(Hash)
46
+ ret = {}
47
+ obj.each do |key, value|
48
+ ret[(key.to_sym rescue key) || key] = symbolize_keys(value)
103
49
  end
50
+ return ret
51
+ elsif obj.is_a?(Array)
52
+ return obj.map{ |value| symbolize_keys(value) }
53
+ else
54
+ return obj
104
55
  end
105
- result
106
56
  end
107
57
 
108
- # The secondary opts argument can either be a string or hash
109
- # Turn this value into an api_key and a set of headers
110
- def self.parse_opts(opts)
111
- case opts
112
- when NilClass
113
- return nil, {}
114
- when String
115
- return opts, {}
116
- when Hash
117
- headers = {}
118
- if opts[:idempotency_key]
119
- headers[:idempotency_key] = opts[:idempotency_key]
58
+ def self.sorta_deep_clone(json)
59
+ if json.is_a?(Hash)
60
+ ret = {}
61
+ json.each do |k, v|
62
+ ret[k] = sorta_deep_clone(v)
120
63
  end
121
- if opts[:paid_account]
122
- headers[:paid_account] = opts[:paid_account]
123
- end
124
- return opts[:api_key], headers
64
+ ret
65
+ elsif json.is_a?(Array)
66
+ json.map{ |j| sorta_deep_clone(j) }
125
67
  else
126
- raise TypeError.new("parse_opts expects a string or a hash")
68
+ begin
69
+ json.dup
70
+ rescue
71
+ json
72
+ end
127
73
  end
128
74
  end
75
+
129
76
  end
130
77
  end
data/lib/paid/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Paid
2
- VERSION = "0.1.0"
2
+ VERSION = File.open(File.expand_path("../../../VERSION", __FILE__)).read()
3
3
  end