spreedly_subscriptions 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.md ADDED
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ ## 1.4.0
4
+
5
+ * Add accessor for invoices
6
+ * Add accessor for last successful invoice
7
+
8
+ ## 1.3.6
9
+
10
+ * Modernize packaging
11
+ * Improve handling of bad credentials
12
+
13
+ ## 1.3.5
14
+
15
+ * Move to Spreedly repo.
16
+
17
+ ## 1.3.4
18
+
19
+ * Add result body to comp validation failure. [ntalbott]
20
+
21
+ ## 1.3.3
22
+
23
+ * Add support for return_url in Spreedly.edit_subscriber_url [jjthrash]
24
+ * Add support for return_url in Spreedly.subscribe_url [jjthrash]
25
+
26
+ ## 1.3.2
27
+
28
+ * Added methods on subscriber: update, allow_free_trial,
29
+ add_fee [mateuszzawisza]
30
+ * Fixing tests: generate spreedly url and edit url working with all
31
+ site names [mateuszzawisza]
32
+ * Add support for pre-population of the subscribe page via the subscribe
33
+ url. [davemcp]
34
+
35
+ ## 1.3.1
36
+
37
+ * Handle new error reporting when creating a subscriber.
38
+ * Use a more sane way of setting attributes in the mock, and make plans have a
39
+ plan type [seancribbs].
40
+ * Allow optional arguments to be passed when creating subscribers [jaknowlden].
41
+
42
+ ## 1.3.0 / 2009-06-04
43
+
44
+ * Properly handle invalid Subscriber lookup [karnowski].
45
+ * Added support for stopping auto renew [scottmotte, dsimard].
46
+
47
+ ## 1.2.2 / 2009-05-11
48
+
49
+ * Fixed an error in the README [karnowski].
50
+
51
+ ## 1.2.1 / 2009-04-30
52
+
53
+ * A few documentation tweaks.
54
+
55
+ ## 1.2.0 / 2009-04-30
56
+
57
+ * Added mock support for Subscriber#activate_free_trial [scottmotte].
58
+ * Added tests for Subscriber#activate_free_trial.
59
+
60
+ ## 1.1.0 / 2009-04-24
61
+
62
+ * Compatibility with the latest hoe and HTTParty [seancribbs].
63
+ * Added Subscriber.delete! [scottmotte].
64
+ * Added Subscriber#activate_free_trial [scottmotte].
65
+
66
+ ## 1.0.0 / 2009-03-17
67
+
68
+ * Initial release.
69
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2009-2013 Spreedly, Inc.. All Rights Reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Spreedly gem
2
+
3
+ * https://github.com/spreedly/spreedly_subscriptions_gem
4
+
5
+ ## Description
6
+
7
+ The spreedly_subscriptions gem provides a convenient Ruby wrapper for the Spreedly
8
+ Subscriptions API.
9
+
10
+ ## Features
11
+
12
+ * Makes it easy to get started.
13
+ * Fully tested.
14
+ * (Mostly) fully substitutable mock implementation for fast tests.
15
+ * Great example code.
16
+
17
+ ## Synopsis
18
+
19
+ # For real
20
+ require 'spreedly/subscriptions'
21
+ Spreedly::Subscriptions.configure('site short name', 'crazy hash token')
22
+ url = Spreedly::Subscriptions.subscribe_url('customer id', 'plan id')
23
+ subscriber = Spreedly::Subscriptions::Subscriber.find('customer id')
24
+ subscriber.active?
25
+
26
+ # For fast tests
27
+ require 'spreedly/subscriptions/mock'
28
+ Spreedly::Subscriptions.configure('site short name', 'crazy hash token')
29
+ url = Spreedly::Subscriptions.subscribe_url('customer id', 'plan id')
30
+ subscriber = Spreedly::Subscriptions::Subscriber.find('customer id')
31
+ subscriber.active?
32
+
33
+ Yup, they're exactly the same except for the require and the speed!
34
+
35
+ ## Requirements
36
+
37
+ * A Spreedly Subscriptions account.
38
+ * HTTParty
39
+
40
+ ## Install
41
+
42
+ $ gem install spreedly_subscriptions
@@ -0,0 +1,48 @@
1
+ Dir[File.dirname(__FILE__) + '/../../vendor/*'].each do |directory|
2
+ next unless File.directory?(directory)
3
+ $LOAD_PATH.unshift File.expand_path(directory + '/lib')
4
+ end
5
+
6
+ require 'uri'
7
+ require 'bigdecimal'
8
+
9
+ require 'spreedly/subscriptions/version'
10
+
11
+ module Spreedly
12
+ module Subscriptions
13
+
14
+ # Generates a subscribe url for the given user id and plan.
15
+ # Options:
16
+ # :screen_name => a screen name for the user (shows up in the admin UI)
17
+ # :email => pre-populate the email field
18
+ # :first_name => pre-populate the first name field
19
+ # :last_name => pre-populate the last name field
20
+ def self.subscribe_url(id, plan, options={})
21
+ %w(screen_name email first_name last_name return_url).each do |option|
22
+ options[option.to_sym] &&= URI.escape(options[option.to_sym])
23
+ end
24
+
25
+ screen_name = options.delete(:screen_name)
26
+ params = %w(email first_name last_name return_url).select{|e| options[e.to_sym]}.collect{|e| "#{e}=#{options[e.to_sym]}"}.join('&')
27
+
28
+ url = "https://spreedly.com/#{site_name}/subscribers/#{id}/subscribe/#{plan}"
29
+ url << "/#{screen_name}" if screen_name
30
+ url << '?' << params unless params == ''
31
+
32
+ url
33
+ end
34
+
35
+ # Generates an edit subscriber for the given subscriber token. The
36
+ # token is returned with the subscriber info (i.e. by
37
+ # Subscriber.find).
38
+ def self.edit_subscriber_url(token, return_url = nil)
39
+ "https://spreedly.com/#{site_name}/subscriber_accounts/#{token}" +
40
+ if return_url
41
+ "?return_url=#{URI.escape(return_url)}"
42
+ else
43
+ ''
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,224 @@
1
+ require 'spreedly/subscriptions/common'
2
+
3
+ raise "Real Spreedly already required!" if defined?(Spreedly::REAL)
4
+
5
+ module Spreedly
6
+ module Subscriptions
7
+ MOCK = "mock"
8
+
9
+ def self.configure(name, token)
10
+ @site_name = name
11
+ end
12
+
13
+ def self.site_name
14
+ @site_name
15
+ end
16
+
17
+ class Resource
18
+ def self.attributes
19
+ @attributes ||= {}
20
+ end
21
+
22
+ def self.attributes=(value)
23
+ @attributes = value
24
+ end
25
+
26
+ attr_reader :attributes
27
+ def initialize(params={})
28
+ @attributes = self.class.attributes.inject({}){|a,(k,v)| a[k.to_sym] = v.call; a}
29
+ params.each {|k,v| @attributes[k.to_sym] = v }
30
+ end
31
+
32
+ def id
33
+ @attributes[:id]
34
+ end
35
+
36
+ def method_missing(method, *args)
37
+ if method.to_s =~ /\?$/
38
+ send(method.to_s[0..-2], *args)
39
+ elsif @attributes.include?(method)
40
+ @attributes[method]
41
+ else
42
+ super
43
+ end
44
+ end
45
+ end
46
+
47
+ class Subscriber < Resource
48
+ self.attributes = {
49
+ :created_at => proc{Time.now},
50
+ :token => proc{(rand * 1000).round},
51
+ :active => proc{false},
52
+ :store_credit => proc{BigDecimal("0.0")},
53
+ :active_until => proc{nil},
54
+ :feature_level => proc{""},
55
+ :on_trial => proc{false},
56
+ :recurring => proc{false},
57
+ :eligible_for_free_trial => proc{false}
58
+ }
59
+
60
+ def self.wipe! # :nodoc: all
61
+ @subscribers = nil
62
+ end
63
+
64
+ def self.create!(id, *args) # :nodoc: all
65
+ optional_attrs = args.last.is_a?(::Hash) ? args.pop : {}
66
+ email, screen_name = args
67
+ sub = new({:customer_id => id, :email => email, :screen_name => screen_name}.merge(optional_attrs))
68
+
69
+ if subscribers[sub.id]
70
+ raise "Could not create subscriber: already exists."
71
+ end
72
+
73
+ subscribers[sub.id] = sub
74
+ sub
75
+ end
76
+
77
+ def self.delete!(id)
78
+ subscribers.delete(id)
79
+ end
80
+
81
+ def self.find(id)
82
+ subscribers[id]
83
+ end
84
+
85
+ def self.subscribers
86
+ @subscribers ||= {}
87
+ end
88
+
89
+ def self.all
90
+ @subscribers.values
91
+ end
92
+
93
+ def initialize(params={})
94
+ super
95
+ if !id || id == ''
96
+ raise "Could not create subscriber: Customer ID can't be blank."
97
+ end
98
+ @invoices ||= []
99
+ end
100
+
101
+ def id
102
+ @attributes[:customer_id]
103
+ end
104
+
105
+ def update(args)
106
+ args.each_pair do |key, value|
107
+ if @attributes.has_key?(key)
108
+ @attributes[key] = value
109
+ end
110
+ end
111
+ end
112
+
113
+ def comp(quantity, units, feature_level=nil)
114
+ raise "Could not comp subscriber: no longer exists." unless self.class.find(id)
115
+ raise "Could not comp subscriber: validation failed." unless units && quantity
116
+ current_active_until = (active_until || Time.now)
117
+ @attributes[:active_until] = case units
118
+ when 'days'
119
+ current_active_until + (quantity.to_i * 86400)
120
+ when 'months'
121
+ current_active_until + (quantity.to_i * 30 * 86400)
122
+ end
123
+ @attributes[:feature_level] = feature_level if feature_level
124
+ @attributes[:active] = true
125
+ end
126
+
127
+ def activate_free_trial(plan_id)
128
+ raise "Could not activate free trial for subscriber: validation failed. missing subscription plan id" unless plan_id
129
+ raise "Could not active free trial for subscriber: subscriber or subscription plan no longer exists." unless self.class.find(id) && SubscriptionPlan.find(plan_id)
130
+ 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." if (on_trial? and !eligible_for_free_trial?)
131
+ @attributes[:on_trial] = true
132
+ plan = SubscriptionPlan.find(plan_id)
133
+ comp(plan.duration_quantity, plan.duration_units, plan.feature_level)
134
+ end
135
+
136
+ def allow_free_trial
137
+ @attributes[:eligible_for_free_trial] = true
138
+ end
139
+
140
+ def stop_auto_renew
141
+ raise "Could not stop auto renew for subscriber: subscriber does not exist." unless self.class.find(id)
142
+ @attributes[:recurring] = false
143
+ end
144
+
145
+ def subscribe(plan_id, card_number="4222222222222")
146
+ plan = SubscriptionPlan.find(plan_id)
147
+ @invoices.unshift(Invoice.new(
148
+ amount: (@invoices.select{|invoice| invoice.closed?}.size > 0 ? 0 : plan.amount),
149
+ closed: false
150
+ ))
151
+
152
+ return unless card_number == "4222222222222"
153
+
154
+ @invoices.first.attributes[:closed] = true
155
+ @attributes[:recurring] = true
156
+ comp(plan.duration_quantity, plan.duration_units, plan.feature_level)
157
+ end
158
+
159
+ def add_fee(args)
160
+ raise "Unprocessable Entity" unless (args.keys & [:amount, :group, :name]).size == 3
161
+ raise "Unprocessable Entity" unless active?
162
+ nil
163
+ end
164
+
165
+ def invoices
166
+ @invoices
167
+ end
168
+
169
+ def last_successful_invoice
170
+ @invoices.detect{|invoice| invoice.closed?}
171
+ end
172
+ end
173
+
174
+ class Invoice < Resource
175
+ end
176
+
177
+ class SubscriptionPlan < Resource
178
+ self.attributes = {
179
+ :plan_type => proc{'regular'},
180
+ :feature_level => proc{''}
181
+ }
182
+
183
+ def self.all
184
+ plans.values
185
+ end
186
+
187
+ def self.find(id)
188
+ plans[id.to_i]
189
+ end
190
+
191
+ def self.plans
192
+ @plans ||= {
193
+ 1 => new(
194
+ :id => 1,
195
+ :name => 'Default mock plan',
196
+ :duration_quantity => 1,
197
+ :duration_units => 'days',
198
+ :amount => 6
199
+ ),
200
+ 2 => new(
201
+ :id => 2,
202
+ :name => 'Test Free Trial Plan',
203
+ :plan_type => 'free_trial',
204
+ :duration_quantity => 1,
205
+ :duration_units => 'days',
206
+ :amount => 11
207
+ ),
208
+ 3 => new(
209
+ :id => 3,
210
+ :name => 'Test Regular Plan',
211
+ :duration_quantity => 1,
212
+ :duration_units => 'days',
213
+ :amount => 17
214
+ )
215
+ }
216
+ end
217
+
218
+ def trial?
219
+ (plan_type == "free_trial")
220
+ end
221
+ end
222
+
223
+ end
224
+ end
@@ -0,0 +1,29 @@
1
+ require 'mechanize'
2
+
3
+ module Spreedly
4
+ module Subscriptions
5
+ class Subscriber
6
+ # This method is *strictly* for use when testing, and will
7
+ # probably only work against a test Spreedly site anyhow.
8
+ def subscribe(plan_id, card_number="4222222222222")
9
+ agent = Mechanize.new
10
+ page = agent.get(Spreedly::Subscriptions.subscribe_url(id, plan_id))
11
+ page = page.forms.first.submit
12
+ form = page.forms.first
13
+ form['credit_card[first_name]'] = 'Joe'
14
+ form['credit_card[last_name]'] = 'Bob'
15
+ form['subscriber[email]'] = 'joe@example.com'
16
+ form['credit_card[number]'] = card_number
17
+ form['credit_card[card_type]'] = 'visa'
18
+ form['credit_card[verification_value]'] = '234'
19
+ form['credit_card[month]'] = '1'
20
+ form['credit_card[year]'] = '2024'
21
+ page = form.click_button
22
+
23
+ if card_number == "4222222222222"
24
+ raise "Subscription didn't go through" unless page.title == "Thank you!"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module Spreedly
2
+ module Subscriptions
3
+ VERSION = "2.0.0"
4
+ end
5
+ end
@@ -0,0 +1,289 @@
1
+ require 'spreedly/subscriptions/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 https://spreedly.com/info/integration
8
+ API. Instead of mucking around with http you can just
9
+ Spreedly::Subscriptions.configure and Spreedly::Subscriber.find. Much of the
10
+ functionality is hung off of the Spreedly::Subscriptions::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/subscriptions'
27
+ else
28
+ require 'spreedly/subscriptions/mock'
29
+ end
30
+ =end
31
+
32
+ module Spreedly
33
+ module Subscriptions
34
+ REAL = "real" # :nodoc:
35
+
36
+ include HTTParty
37
+ headers 'Accept' => 'text/xml'
38
+ headers 'Content-Type' => 'text/xml'
39
+ format :xml
40
+
41
+ # Call this before you start using the API to set things up.
42
+ def self.configure(site_name, token)
43
+ base_uri "https://spreedly.com/api/v4/#{site_name}"
44
+ basic_auth token, 'X'
45
+ @site_name = site_name
46
+ end
47
+
48
+ def self.site_name # :nodoc:
49
+ @site_name
50
+ end
51
+
52
+ def self.to_xml_params(hash) # :nodoc:
53
+ hash.collect do |key, value|
54
+ tag = key.to_s.tr('_', '-')
55
+ result = "<#{tag}>"
56
+ if value.is_a?(Hash)
57
+ result << to_xml_params(value)
58
+ else
59
+ result << value.to_s
60
+ end
61
+ result << "</#{tag}>"
62
+ result
63
+ end.join('')
64
+ end
65
+
66
+ class Resource # :nodoc: all
67
+ def initialize(data)
68
+ @data = data
69
+ end
70
+
71
+ def id
72
+ @data["id"]
73
+ end
74
+
75
+ def method_missing(method, *args, &block)
76
+ if method.to_s =~ /\?$/
77
+ send(method.to_s[0..-2])
78
+ elsif @data.include?(method.to_s)
79
+ @data[method.to_s]
80
+ else
81
+ super
82
+ end
83
+ end
84
+ end
85
+
86
+ class Subscriber < Resource
87
+ # This will DELETE all the subscribers from the site.
88
+ #
89
+ # Only works for test sites (enforced on the Spreedly side).
90
+ def self.wipe!
91
+ Spreedly::Subscriptions.delete('/subscribers.xml')
92
+ end
93
+
94
+ # This will DELETE individual subscribers from the site. Pass in the customer_id.
95
+ #
96
+ # Only works for test sites (enforced on the Spreedly side).
97
+ def self.delete!(id)
98
+ Spreedly::Subscriptions.delete("/subscribers/#{id}.xml")
99
+ end
100
+
101
+ # Creates a new subscriber on Spreedly. The subscriber will NOT
102
+ # be active - they have to pay or you have to comp them for that
103
+ # to happen.
104
+ #
105
+ # Usage:
106
+ # Spreedly::Subscriptions.Subscriber.create!(id, email)
107
+ # Spreedly::Subscriptions.Subscriber.create!(id, email, screen_name)
108
+ # Spreedly::Subscriptions.Subscriber.create!(id, :email => email, :screen_name => screen_name)
109
+ # Spreedly::Subscriptions.Subscriber.create!(id, email, screen_name, :billing_first_name => first_name)
110
+ def self.create!(id, *args)
111
+ optional_attrs = args.last.is_a?(::Hash) ? args.pop : {}
112
+ email, screen_name = args
113
+ subscriber = {:customer_id => id, :email => email, :screen_name => screen_name}.merge(optional_attrs)
114
+ result = Spreedly::Subscriptions.post('/subscribers.xml', :body => Spreedly.to_xml_params(:subscriber => subscriber))
115
+ case result.code.to_s
116
+ when /2../
117
+ new(result['subscriber'])
118
+ when '403'
119
+ raise "Could not create subscriber: already exists."
120
+ when '422'
121
+ errors = [*result['errors']].collect{|e| e.last}
122
+ raise "Could not create subscriber: #{errors.join(', ')}"
123
+ else
124
+ raise "Could not create subscriber: result code #{result.code}."
125
+ end
126
+ end
127
+
128
+ # Looks a subscriber up by id.
129
+ def self.find(id)
130
+ xml = Spreedly::Subscriptions.get("/subscribers/#{id}.xml")
131
+ if [200, 404].include?(xml.code)
132
+ (xml.nil? || xml.empty? ? nil : new(xml['subscriber']))
133
+ else
134
+ raise "Could not find subscriber: result code #{xml.code}, body '#{xml.body}'"
135
+ end
136
+ end
137
+
138
+ # Returns all the subscribers in your site.
139
+ def self.all
140
+ Spreedly::Subscriptions.get('/subscribers.xml')['subscribers'].collect{|data| new(data)}
141
+ end
142
+
143
+ # Spreedly calls your id for the user the "customer id". This
144
+ # gives you a handy alias so you can just call it "id".
145
+ def id
146
+ customer_id
147
+ end
148
+
149
+ # Allows you to give a complimentary subscription (if the
150
+ # subscriber is inactive) or a complimentary time extension (if
151
+ # the subscriber is active). Automatically figures out which
152
+ # to do.
153
+ #
154
+ # Note: units must be one of "days" or "months" (Spreedly
155
+ # enforced).
156
+ def comp(quantity, units, feature_level=nil)
157
+ params = {:duration_quantity => quantity, :duration_units => units}
158
+ params[:feature_level] = feature_level if feature_level
159
+ raise "Feature level is required to comp an inactive subscriber" if !active? and !feature_level
160
+ endpoint = (active? ? "complimentary_time_extensions" : "complimentary_subscriptions")
161
+ result = Spreedly::Subscriptions.post("/subscribers/#{id}/#{endpoint}.xml", :body => Spreedly.to_xml_params(endpoint[0..-2] => params))
162
+ case result.code.to_s
163
+ when /2../
164
+ when '404'
165
+ raise "Could not comp subscriber: no longer exists."
166
+ when '422'
167
+ raise "Could not comp subscriber: validation failed (#{result.body})."
168
+ when '403'
169
+ raise "Could not comp subscriber: invalid comp type (#{endpoint})."
170
+ else
171
+ raise "Could not comp subscriber: result code #{result.code}."
172
+ end
173
+ end
174
+
175
+ # Activates a free trial on the subscriber.
176
+ # Requires plan_id of the free trial plan
177
+ def activate_free_trial(plan_id)
178
+ result = Spreedly::Subscriptions.post("/subscribers/#{id}/subscribe_to_free_trial.xml", :body =>
179
+ Spreedly::Subscriptions.to_xml_params(:subscription_plan => {:id => plan_id}))
180
+ case result.code.to_s
181
+ when /2../
182
+ when '404'
183
+ raise "Could not active free trial for subscriber: subscriber or subscription plan no longer exists."
184
+ when '422'
185
+ raise "Could not activate free trial for subscriber: validation failed. missing subscription plan id"
186
+ when '403'
187
+ 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."
188
+ else
189
+ raise "Could not activate free trial for subscriber: result code #{result.code}."
190
+ end
191
+ end
192
+
193
+ # Stop the auto renew of the subscriber such that their recurring subscription will no longer be renewed.
194
+ # usage: @subscriber.stop_auto_renew
195
+ def stop_auto_renew
196
+ result = Spreedly::Subscriptions.post("/subscribers/#{id}/stop_auto_renew.xml")
197
+ case result.code.to_s
198
+ when /2../
199
+ when '404'
200
+ raise "Could not stop auto renew for subscriber: subscriber does not exist."
201
+ else
202
+ raise "Could not stop auto renew for subscriber: result code #{result.code}."
203
+ end
204
+ end
205
+
206
+ # Update a Subscriber
207
+ # usage: @subscriber.update(:email => email, :screen_name => screen_name)
208
+ def update(args)
209
+ result = Spreedly::Subscriptions.put("/subscribers/#{id}.xml", :body => Spreedly.to_xml_params(:subscriber => args))
210
+
211
+ case result.code.to_s
212
+ when /2../
213
+ when '403'
214
+ raise "Could not update subscriber: new-customer-id is already in use."
215
+ when '404'
216
+ raise "Could not update subscriber: subscriber not found"
217
+ else
218
+ raise "Could not update subscriber: result code #{result.code}."
219
+ end
220
+ end
221
+
222
+ # Allow Another Free Trial
223
+ # usage: @subscriber.allow_free_trial
224
+ def allow_free_trial
225
+ result = Spreedly::Subscriptions.post("/subscribers/#{id}/allow_free_trial.xml")
226
+
227
+ case result.code.to_s
228
+ when /2../
229
+ else
230
+ raise "Could not allow subscriber to another trial: result code #{result.code}."
231
+ end
232
+ end
233
+
234
+ # Add a Fee to a Subscriber
235
+ # usage: @subscriber.add_fee(:amount => amount, :group => group_name, :description => description, :name => name)
236
+ def add_fee(args)
237
+ result = Spreedly::Subscriptions.post("/subscribers/#{id}/fees.xml", :body => Spreedly::Subscriptions.to_xml_params(:fee => args))
238
+
239
+ case result.code.to_s
240
+ when /2../
241
+ when '404'
242
+ raise "Not Found"
243
+ when '422'
244
+ raise "Unprocessable Entity - #{result.body}"
245
+ else
246
+ raise "Could not add fee to subscriber: result code #{result.code}."
247
+ end
248
+ end
249
+
250
+ # Get the invoices for the subscriber
251
+ def invoices
252
+ @invoices ||= @data["invoices"].collect{|i| Invoice.new(i)}
253
+ end
254
+
255
+ # Get the last successful invoice
256
+ def last_successful_invoice
257
+ invoices.detect do |invoice|
258
+ invoice.closed?
259
+ end
260
+ end
261
+ end
262
+
263
+ class Invoice < Resource
264
+ end
265
+
266
+ class SubscriptionPlan < Resource
267
+ # Returns all of the subscription plans defined in your site.
268
+ def self.all
269
+ xml = Spreedly::Subscriptions.get('/subscription_plans.xml')
270
+ if xml.code == 200
271
+ xml['subscription_plans'].collect{|data| new(data)}
272
+ else
273
+ raise "Could not list subscription plans: result code #{xml.code}, body '#{xml.body}'"
274
+ end
275
+ end
276
+
277
+ # Returns the subscription plan with the given id.
278
+ def self.find(id)
279
+ all.detect{|e| e.id.to_s == id.to_s}
280
+ end
281
+
282
+ # Convenience method for determining if this plan is a free trial plan or not.
283
+ def trial?
284
+ (plan_type == 'free_trial')
285
+ end
286
+ end
287
+
288
+ end
289
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spreedly_subscriptions
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 2.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Spreedly
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ none: false
21
+ name: httparty
22
+ type: :runtime
23
+ prerelease: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ none: false
30
+ - !ruby/object:Gem::Dependency
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ none: false
37
+ name: shoulda
38
+ type: :development
39
+ prerelease: false
40
+ requirement: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ none: false
46
+ - !ruby/object:Gem::Dependency
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ~>
50
+ - !ruby/object:Gem::Version
51
+ version: 2.5.1
52
+ none: false
53
+ name: mechanize
54
+ type: :development
55
+ prerelease: false
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: 2.5.1
61
+ none: false
62
+ description: This gem provides a convenient Ruby wrapper for the Spreedly Subscriptions
63
+ API.
64
+ email:
65
+ - nathaniel@spreedly.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - lib/spreedly/subscriptions/common.rb
71
+ - lib/spreedly/subscriptions/mock.rb
72
+ - lib/spreedly/subscriptions/test_hacks.rb
73
+ - lib/spreedly/subscriptions/version.rb
74
+ - lib/spreedly/subscriptions.rb
75
+ - README.md
76
+ - LICENSE.txt
77
+ - HISTORY.md
78
+ homepage: https://github.com/spreedly/spreedly_subscriptions_gem
79
+ licenses:
80
+ - MIT
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ none: false
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '1.8'
96
+ none: false
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 1.8.23
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Provides a Ruby wrapper for the Spreedly Subscriptions API.
103
+ test_files: []