spreedly_subscriptions 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.
- data/HISTORY.md +69 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/lib/spreedly/subscriptions/common.rb +48 -0
- data/lib/spreedly/subscriptions/mock.rb +224 -0
- data/lib/spreedly/subscriptions/test_hacks.rb +29 -0
- data/lib/spreedly/subscriptions/version.rb +5 -0
- data/lib/spreedly/subscriptions.rb +289 -0
- metadata +103 -0
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,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: []
|