spreedly 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +4 -0
  3. data/HISTORY.md +5 -0
  4. data/README.md +362 -29
  5. data/Rakefile +32 -0
  6. data/lib/certs/cacert.pem +7815 -0
  7. data/lib/spreedly.rb +24 -282
  8. data/lib/spreedly/common/errors_parser.rb +15 -0
  9. data/lib/spreedly/common/fields.rb +90 -0
  10. data/lib/spreedly/connection.rb +40 -0
  11. data/lib/spreedly/environment.rb +176 -0
  12. data/lib/spreedly/error.rb +50 -0
  13. data/lib/spreedly/gateway.rb +10 -0
  14. data/lib/spreedly/model.rb +17 -0
  15. data/lib/spreedly/payment_methods/credit_card.rb +9 -0
  16. data/lib/spreedly/payment_methods/payment_method.rb +34 -0
  17. data/lib/spreedly/payment_methods/paypal.rb +7 -0
  18. data/lib/spreedly/payment_methods/sprel.rb +7 -0
  19. data/lib/spreedly/ssl_requester.rb +65 -0
  20. data/lib/spreedly/transactions/add_payment_method.rb +16 -0
  21. data/lib/spreedly/transactions/auth_purchase.rb +17 -0
  22. data/lib/spreedly/transactions/authorization.rb +7 -0
  23. data/lib/spreedly/transactions/capture.rb +14 -0
  24. data/lib/spreedly/transactions/gateway_transaction.rb +31 -0
  25. data/lib/spreedly/transactions/purchase.rb +7 -0
  26. data/lib/spreedly/transactions/redact_payment_method.rb +14 -0
  27. data/lib/spreedly/transactions/refund.rb +14 -0
  28. data/lib/spreedly/transactions/retain_payment_method.rb +14 -0
  29. data/lib/spreedly/transactions/transaction.rb +41 -0
  30. data/lib/spreedly/transactions/void.rb +9 -0
  31. data/lib/spreedly/urls.rb +64 -0
  32. data/lib/spreedly/version.rb +1 -1
  33. data/spreedly.gemspec +29 -0
  34. data/test/credentials/credentials.yml +9 -0
  35. data/test/credentials/test_credentials.rb +43 -0
  36. data/test/helpers/assertions.rb +29 -0
  37. data/test/helpers/communication_helper.rb +31 -0
  38. data/test/helpers/creation_helper.rb +26 -0
  39. data/test/helpers/stub_response.rb +18 -0
  40. data/test/remote/remote_add_credit_card_test.rb +62 -0
  41. data/test/remote/remote_add_gateway_test.rb +30 -0
  42. data/test/remote/remote_authorize_test.rb +48 -0
  43. data/test/remote/remote_capture_test.rb +71 -0
  44. data/test/remote/remote_find_gateway_test.rb +31 -0
  45. data/test/remote/remote_find_payment_method_test.rb +29 -0
  46. data/test/remote/remote_find_transaction_test.rb +33 -0
  47. data/test/remote/remote_list_transactions_test.rb +36 -0
  48. data/test/remote/remote_purchase_test.rb +69 -0
  49. data/test/remote/remote_redact_test.rb +38 -0
  50. data/test/remote/remote_refund_test.rb +65 -0
  51. data/test/remote/remote_retain_test.rb +39 -0
  52. data/test/remote/remote_void_test.rb +64 -0
  53. data/test/test_helper.rb +23 -0
  54. data/test/unit/add_credit_card_test.rb +74 -0
  55. data/test/unit/add_gateway_test.rb +26 -0
  56. data/test/unit/authorize_test.rb +87 -0
  57. data/test/unit/capture_test.rb +91 -0
  58. data/test/unit/environment_test.rb +18 -0
  59. data/test/unit/fields_test.rb +75 -0
  60. data/test/unit/find_gateway_test.rb +28 -0
  61. data/test/unit/find_payment_method_test.rb +90 -0
  62. data/test/unit/find_transaction_test.rb +31 -0
  63. data/test/unit/list_transactions_test.rb +46 -0
  64. data/test/unit/purchase_test.rb +95 -0
  65. data/test/unit/redact_payment_method_test.rb +51 -0
  66. data/test/unit/refund_test.rb +91 -0
  67. data/test/unit/response_stubs/add_credit_card_stubs.rb +43 -0
  68. data/test/unit/response_stubs/add_gateway_stubs.rb +39 -0
  69. data/test/unit/response_stubs/authorization_stubs.rb +139 -0
  70. data/test/unit/response_stubs/capture_stubs.rb +87 -0
  71. data/test/unit/response_stubs/find_gateway_stubs.rb +38 -0
  72. data/test/unit/response_stubs/find_payment_method_stubs.rb +108 -0
  73. data/test/unit/response_stubs/find_transaction_stubs.rb +43 -0
  74. data/test/unit/response_stubs/list_transactions_stubs.rb +110 -0
  75. data/test/unit/response_stubs/purchase_stubs.rb +139 -0
  76. data/test/unit/response_stubs/redact_payment_method_stubs.rb +54 -0
  77. data/test/unit/response_stubs/refund_stubs.rb +87 -0
  78. data/test/unit/response_stubs/retain_payment_method_stubs.rb +85 -0
  79. data/test/unit/response_stubs/void_stubs.rb +79 -0
  80. data/test/unit/retain_payment_method_test.rb +44 -0
  81. data/test/unit/timeout_test.rb +20 -0
  82. data/test/unit/void_test.rb +96 -0
  83. metadata +215 -29
  84. checksums.yaml +0 -15
  85. data/lib/spreedly/common.rb +0 -44
  86. data/lib/spreedly/mock.rb +0 -221
  87. data/lib/spreedly/test_hacks.rb +0 -27
data/lib/spreedly.rb CHANGED
@@ -1,286 +1,28 @@
1
- require 'spreedly/common'
2
- require 'httparty'
3
-
4
- raise "Mock Spreedly already required!" if defined?(Spreedly::MOCK)
5
-
6
- =begin rdoc
7
- Provides a convenient wrapper around the http://spreedly.com API.
8
- Instead of mucking around with http you can just Spreedly.configure
9
- and Spreedly::Subscriber.find. Much of the functionality is hung off
10
- of the Spreedly::Subscriber class, and there's also a
11
- Spreedly::SubscriptionPlan class.
12
-
13
- One of the goals of this wrapper is to keep your tests fast while
14
- also keeping your app working. It does this by providing a drop-in
15
- replacement for the real Spreedly functionality that skips the
16
- network and gives you a simple (some might call it stupid)
17
- implementation that will work for 90% of your tests. At least we
18
- hope so.
19
-
20
- Help us make the mock work better by telling us when it doesn't work
21
- so we can improve it. Thanks!
22
-
23
- ==Example mock usage:
24
-
25
- if ENV["SPREEDLY"] == "REAL"
26
- require 'spreedly'
27
- else
28
- require 'spreedly/mock'
29
- end
30
- =end
1
+ require 'spreedly/ssl_requester'
2
+ require 'spreedly/urls'
3
+ require 'spreedly/environment'
4
+ require 'spreedly/connection'
5
+ require 'spreedly/common/fields'
6
+ require 'spreedly/common/errors_parser'
7
+ require 'spreedly/model'
8
+ require 'spreedly/payment_methods/payment_method'
9
+ require 'spreedly/payment_methods/credit_card'
10
+ require 'spreedly/payment_methods/paypal'
11
+ require 'spreedly/payment_methods/sprel'
12
+ require 'spreedly/transactions/transaction'
13
+ require 'spreedly/transactions/gateway_transaction'
14
+ require 'spreedly/transactions/add_payment_method'
15
+ require 'spreedly/transactions/auth_purchase'
16
+ require 'spreedly/transactions/purchase'
17
+ require 'spreedly/transactions/void'
18
+ require 'spreedly/transactions/authorization'
19
+ require 'spreedly/transactions/capture'
20
+ require 'spreedly/transactions/refund'
21
+ require 'spreedly/transactions/retain_payment_method'
22
+ require 'spreedly/transactions/redact_payment_method'
23
+ require 'spreedly/gateway'
24
+ require 'spreedly/error'
31
25
 
32
26
  module Spreedly
33
- REAL = "real" # :nodoc:
34
-
35
- include HTTParty
36
- headers 'Accept' => 'text/xml'
37
- headers 'Content-Type' => 'text/xml'
38
- format :xml
39
-
40
- # Call this before you start using the API to set things up.
41
- def self.configure(site_name, token)
42
- base_uri "https://spreedly.com/api/v4/#{site_name}"
43
- basic_auth token, 'X'
44
- @site_name = site_name
45
- end
46
-
47
- def self.site_name # :nodoc:
48
- @site_name
49
- end
50
-
51
- def self.to_xml_params(hash) # :nodoc:
52
- hash.collect do |key, value|
53
- tag = key.to_s.tr('_', '-')
54
- result = "<#{tag}>"
55
- if value.is_a?(Hash)
56
- result << to_xml_params(value)
57
- else
58
- result << value.to_s
59
- end
60
- result << "</#{tag}>"
61
- result
62
- end.join('')
63
- end
64
-
65
- class Resource # :nodoc: all
66
- def initialize(data)
67
- @data = data
68
- end
69
-
70
- def id
71
- @data["id"]
72
- end
73
-
74
- def method_missing(method, *args, &block)
75
- if method.to_s =~ /\?$/
76
- send(method.to_s[0..-2])
77
- elsif @data.include?(method.to_s)
78
- @data[method.to_s]
79
- else
80
- super
81
- end
82
- end
83
- end
84
-
85
- class Subscriber < Resource
86
- # This will DELETE all the subscribers from the site.
87
- #
88
- # Only works for test sites (enforced on the Spreedly side).
89
- def self.wipe!
90
- Spreedly.delete('/subscribers.xml')
91
- end
92
-
93
- # This will DELETE individual subscribers from the site. Pass in the customer_id.
94
- #
95
- # Only works for test sites (enforced on the Spreedly side).
96
- def self.delete!(id)
97
- Spreedly.delete("/subscribers/#{id}.xml")
98
- end
99
-
100
- # Creates a new subscriber on Spreedly. The subscriber will NOT
101
- # be active - they have to pay or you have to comp them for that
102
- # to happen.
103
- #
104
- # Usage:
105
- # Spreedly.Subscriber.create!(id, email)
106
- # Spreedly.Subscriber.create!(id, email, screen_name)
107
- # Spreedly.Subscriber.create!(id, :email => email, :screen_name => screen_name)
108
- # Spreedly.Subscriber.create!(id, email, screen_name, :billing_first_name => first_name)
109
- def self.create!(id, *args)
110
- optional_attrs = args.last.is_a?(::Hash) ? args.pop : {}
111
- email, screen_name = args
112
- subscriber = {:customer_id => id, :email => email, :screen_name => screen_name}.merge(optional_attrs)
113
- result = Spreedly.post('/subscribers.xml', :body => Spreedly.to_xml_params(:subscriber => subscriber))
114
- case result.code.to_s
115
- when /2../
116
- new(result['subscriber'])
117
- when '403'
118
- raise "Could not create subscriber: already exists."
119
- when '422'
120
- errors = [*result['errors']].collect{|e| e.last}
121
- raise "Could not create subscriber: #{errors.join(', ')}"
122
- else
123
- raise "Could not create subscriber: result code #{result.code}."
124
- end
125
- end
126
-
127
- # Looks a subscriber up by id.
128
- def self.find(id)
129
- xml = Spreedly.get("/subscribers/#{id}.xml")
130
- if [200, 404].include?(xml.code)
131
- (xml.nil? || xml.empty? ? nil : new(xml['subscriber']))
132
- else
133
- raise "Could not find subscriber: result code #{xml.code}, body '#{xml.body}'"
134
- end
135
- end
136
-
137
- # Returns all the subscribers in your site.
138
- def self.all
139
- Spreedly.get('/subscribers.xml')['subscribers'].collect{|data| new(data)}
140
- end
141
-
142
- # Spreedly calls your id for the user the "customer id". This
143
- # gives you a handy alias so you can just call it "id".
144
- def id
145
- customer_id
146
- end
147
-
148
- # Allows you to give a complimentary subscription (if the
149
- # subscriber is inactive) or a complimentary time extension (if
150
- # the subscriber is active). Automatically figures out which
151
- # to do.
152
- #
153
- # Note: units must be one of "days" or "months" (Spreedly
154
- # enforced).
155
- def comp(quantity, units, feature_level=nil)
156
- params = {:duration_quantity => quantity, :duration_units => units}
157
- params[:feature_level] = feature_level if feature_level
158
- raise "Feature level is required to comp an inactive subscriber" if !active? and !feature_level
159
- endpoint = (active? ? "complimentary_time_extensions" : "complimentary_subscriptions")
160
- result = Spreedly.post("/subscribers/#{id}/#{endpoint}.xml", :body => Spreedly.to_xml_params(endpoint[0..-2] => params))
161
- case result.code.to_s
162
- when /2../
163
- when '404'
164
- raise "Could not comp subscriber: no longer exists."
165
- when '422'
166
- raise "Could not comp subscriber: validation failed (#{result.body})."
167
- when '403'
168
- raise "Could not comp subscriber: invalid comp type (#{endpoint})."
169
- else
170
- raise "Could not comp subscriber: result code #{result.code}."
171
- end
172
- end
173
-
174
- # Activates a free trial on the subscriber.
175
- # Requires plan_id of the free trial plan
176
- def activate_free_trial(plan_id)
177
- result = Spreedly.post("/subscribers/#{id}/subscribe_to_free_trial.xml", :body =>
178
- Spreedly.to_xml_params(:subscription_plan => {:id => plan_id}))
179
- case result.code.to_s
180
- when /2../
181
- when '404'
182
- raise "Could not active free trial for subscriber: subscriber or subscription plan no longer exists."
183
- when '422'
184
- raise "Could not activate free trial for subscriber: validation failed. missing subscription plan id"
185
- when '403'
186
- raise "Could not activate free trial for subscriber: subscription plan either 1) isn't a free trial, 2) the subscriber is not eligible for a free trial, or 3) the subscription plan is not enabled."
187
- else
188
- raise "Could not activate free trial for subscriber: result code #{result.code}."
189
- end
190
- end
191
-
192
- # Stop the auto renew of the subscriber such that their recurring subscription will no longer be renewed.
193
- # usage: @subscriber.stop_auto_renew
194
- def stop_auto_renew
195
- result = Spreedly.post("/subscribers/#{id}/stop_auto_renew.xml")
196
- case result.code.to_s
197
- when /2../
198
- when '404'
199
- raise "Could not stop auto renew for subscriber: subscriber does not exist."
200
- else
201
- raise "Could not stop auto renew for subscriber: result code #{result.code}."
202
- end
203
- end
204
-
205
- # Update a Subscriber
206
- # usage: @subscriber.update(:email => email, :screen_name => screen_name)
207
- def update(args)
208
- result = Spreedly.put("/subscribers/#{id}.xml", :body => Spreedly.to_xml_params(:subscriber => args))
209
-
210
- case result.code.to_s
211
- when /2../
212
- when '403'
213
- raise "Could not update subscriber: new-customer-id is already in use."
214
- when '404'
215
- raise "Could not update subscriber: subscriber not found"
216
- else
217
- raise "Could not update subscriber: result code #{result.code}."
218
- end
219
- end
220
-
221
- # Allow Another Free Trial
222
- # usage: @subscriber.allow_free_trial
223
- def allow_free_trial
224
- result = Spreedly.post("/subscribers/#{id}/allow_free_trial.xml")
225
-
226
- case result.code.to_s
227
- when /2../
228
- else
229
- raise "Could not allow subscriber to another trial: result code #{result.code}."
230
- end
231
- end
232
-
233
- # Add a Fee to a Subscriber
234
- # usage: @subscriber.add_fee(:amount => amount, :group => group_name, :description => description, :name => name)
235
- def add_fee(args)
236
- result = Spreedly.post("/subscribers/#{id}/fees.xml", :body => Spreedly.to_xml_params(:fee => args))
237
-
238
- case result.code.to_s
239
- when /2../
240
- when '404'
241
- raise "Not Found"
242
- when '422'
243
- raise "Unprocessable Entity - #{result.body}"
244
- else
245
- raise "Could not add fee to subscriber: result code #{result.code}."
246
- end
247
- end
248
-
249
- # Get the invoices for the subscriber
250
- def invoices
251
- @invoices ||= @data["invoices"].collect{|i| Invoice.new(i)}
252
- end
253
-
254
- # Get the last successful invoice
255
- def last_successful_invoice
256
- invoices.detect do |invoice|
257
- invoice.closed?
258
- end
259
- end
260
- end
261
-
262
- class Invoice < Resource
263
- end
264
-
265
- class SubscriptionPlan < Resource
266
- # Returns all of the subscription plans defined in your site.
267
- def self.all
268
- xml = Spreedly.get('/subscription_plans.xml')
269
- if xml.code == 200
270
- xml['subscription_plans'].collect{|data| new(data)}
271
- else
272
- raise "Could not list subscription plans: result code #{xml.code}, body '#{xml.body}'"
273
- end
274
- end
275
-
276
- # Returns the subscription plan with the given id.
277
- def self.find(id)
278
- all.detect{|e| e.id.to_s == id.to_s}
279
- end
280
27
 
281
- # Convenience method for determining if this plan is a free trial plan or not.
282
- def trial?
283
- (plan_type == 'free_trial')
284
- end
285
- end
286
28
  end
@@ -0,0 +1,15 @@
1
+ module Spreedly
2
+ module ErrorsParser
3
+
4
+ def errors_from(xml_doc)
5
+ xml_doc.xpath(".//errors/error").map do |each|
6
+ {
7
+ attribute: each.attributes['attribute'].to_s,
8
+ key: each.attributes['key'].to_s,
9
+ message: each.text
10
+ }
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,90 @@
1
+ module Spreedly
2
+ module Fields
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ def initialize_fields(xml_doc)
9
+ self.class.fields.each do |field|
10
+ value = xml_doc.at_xpath(".//#{field}").inner_html.strip
11
+ instance_variable_set("@#{field}", value)
12
+ end
13
+ end
14
+
15
+ def field_hash
16
+ self.class.fields.inject({}) do |hash, each|
17
+ hash[each] = send(each)
18
+ hash
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ def field(*fields_to_add)
24
+ options = fields_to_add.extract_options!
25
+ @fields ||= []
26
+ fields_to_add.each do |f|
27
+ @fields += [ f ]
28
+ add_accessor_for(f, options[:type])
29
+ end
30
+ end
31
+
32
+ def fields
33
+ @fields ||= []
34
+ end
35
+
36
+ def inherited(subclass)
37
+ subclass.instance_variable_set("@fields", instance_variable_get("@fields"))
38
+ end
39
+
40
+ private
41
+ def add_accessor_for(f, field_type)
42
+ case field_type
43
+ when :boolean
44
+ add_boolean_accessor(f)
45
+ when :date_time
46
+ add_date_time_accessor(f)
47
+ when :integer
48
+ add_integer_accessor(f)
49
+ when nil
50
+ attr_reader f
51
+ else
52
+ raise "Unknown field type '#{options[:type]}' for field '#{f}'"
53
+ end
54
+ end
55
+
56
+ def add_boolean_accessor(f)
57
+ define_method(f) do
58
+ return nil unless instance_variable_get("@#{f}")
59
+ "true" == instance_variable_get("@#{f}")
60
+ end
61
+ alias_method "#{f}?", f
62
+ end
63
+
64
+ def add_date_time_accessor(f)
65
+ define_method(f) do
66
+ Time.parse(instance_variable_get("@#{f}")) if instance_variable_get("@#{f}")
67
+ end
68
+ end
69
+
70
+ def add_integer_accessor(f)
71
+ define_method(f) do
72
+ return nil unless instance_variable_get("@#{f}")
73
+ instance_variable_get("@#{f}").to_i
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+
81
+ class Array
82
+ def extract_options!
83
+ if last.is_a?(Hash)
84
+ pop
85
+ else
86
+ {}
87
+ end
88
+ end
89
+ end
90
+
@@ -0,0 +1,40 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ module Spreedly
5
+ class Connection
6
+
7
+ attr_accessor :endpoint
8
+
9
+ def initialize(endpoint)
10
+ @endpoint = URI.parse(endpoint)
11
+ end
12
+
13
+ def request(method, body, headers = {})
14
+ case method
15
+ when :get
16
+ http.get(endpoint.request_uri, headers)
17
+ when :post
18
+ http.post(endpoint.request_uri, body, headers)
19
+ when :put
20
+ http.put(endpoint.request_uri, body, headers)
21
+ when :delete
22
+ http.delete(endpoint.request_uri, headers)
23
+ else
24
+ raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
25
+ end
26
+ end
27
+
28
+ private
29
+ def http
30
+ http = Net::HTTP.new(endpoint.host, endpoint.port)
31
+ http.open_timeout = 60
32
+ http.read_timeout = 60
33
+ http.use_ssl = true
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
35
+ http.ca_file = File.dirname(__FILE__) + '/../certs/cacert.pem'
36
+ http
37
+ end
38
+ end
39
+ end
40
+