paid 0.1.0 → 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 (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