spreedly 1.4.0 → 2.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 (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
+