subscription_fu 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +0 -1
- data/README.md +4 -0
- data/UPDATING.md +19 -0
- data/app/models/subscription_fu/subscription.rb +32 -10
- data/app/models/subscription_fu/system_initiator.rb +7 -0
- data/app/models/subscription_fu/transaction.rb +14 -4
- data/lib/generators/subscription_fu/templates/migration.rb +6 -0
- data/lib/subscription_fu/models.rb +1 -6
- data/lib/subscription_fu/paypal.rb +6 -2
- data/lib/subscription_fu/railtie.rb +3 -0
- data/lib/subscription_fu/rake.tasks +6 -0
- data/lib/subscription_fu/version.rb +1 -1
- data/spec/app/db/migrate/20110701061927_subscription_fu_update_to_zero_three.rb +12 -0
- data/spec/app/db/schema.rb +6 -1
- data/spec/models/subscription_spec.rb +18 -8
- metadata +8 -20
data/Gemfile
CHANGED
data/README.md
CHANGED
data/UPDATING.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Updating Subscriptions for Rails
|
2
|
+
|
3
|
+
## From 0.2.x to 0.3.0
|
4
|
+
|
5
|
+
Add the new table subscription_system_initiators using a migration:
|
6
|
+
|
7
|
+
class SubscriptionFuUpdateToZeroThree < ActiveRecord::Migration
|
8
|
+
def self.up
|
9
|
+
create_table "subscription_system_initiators" do |t|
|
10
|
+
t.string "name"
|
11
|
+
t.string "description"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
def self.down
|
15
|
+
drop_table "subscription_system_initiators"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class SubscriptionFu::Subscription < ActiveRecord::Base
|
2
2
|
set_table_name :subscriptions
|
3
3
|
|
4
|
-
AVAILABLE_CANCEL_REASONS = %w( update cancel timeout admin )
|
4
|
+
AVAILABLE_CANCEL_REASONS = %w( update cancel gwcancel timeout admin )
|
5
5
|
|
6
6
|
default_scope order("created_at ASC", "id ASC")
|
7
7
|
|
@@ -18,10 +18,23 @@ class SubscriptionFu::Subscription < ActiveRecord::Base
|
|
18
18
|
validates :cancel_reason, :presence => true, :inclusion => AVAILABLE_CANCEL_REASONS, :if => :canceled?
|
19
19
|
|
20
20
|
scope :activated, where("subscriptions.activated_at IS NOT NULL")
|
21
|
+
scope :not_canceled, activated.where("subscriptions.canceled_at IS NULL")
|
22
|
+
scope :using_paypal, where("subscriptions.paypal_profile_id IS NOT NULL")
|
21
23
|
scope :current, lambda {|time| activated.where("subscriptions.starts_at <= ? AND (subscriptions.canceled_at IS NULL OR subscriptions.canceled_at > ?)", time, time) }
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
def self.sync_all_from_gateway
|
26
|
+
SubscriptionFu::Subscription.using_paypal.not_canceled.each do |s|
|
27
|
+
s.sync_from_gateway!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.build_for_initializing(plan_key, prev_sub = nil)
|
32
|
+
if prev_sub
|
33
|
+
start_time = prev_sub.successor_start_date(plan_key)
|
34
|
+
billing_start_time = prev_sub.end_date_when_canceled
|
35
|
+
else
|
36
|
+
billing_start_time = start_time = Time.now
|
37
|
+
end
|
25
38
|
new(:plan_key => plan_key, :starts_at => start_time, :billing_starts_at => billing_start_time, :prev_subscription => prev_sub)
|
26
39
|
end
|
27
40
|
|
@@ -74,11 +87,11 @@ class SubscriptionFu::Subscription < ActiveRecord::Base
|
|
74
87
|
Time.now
|
75
88
|
else
|
76
89
|
# otherwise they start with the next billing cycle
|
77
|
-
|
90
|
+
end_date_when_canceled
|
78
91
|
end
|
79
92
|
end
|
80
93
|
|
81
|
-
def
|
94
|
+
def end_date_when_canceled
|
82
95
|
# in case this plan was already canceled, this date takes
|
83
96
|
# precedence (there won't be a next billing time anymore).
|
84
97
|
canceled_at || next_billing_date || estimated_next_billing_date || Time.now
|
@@ -86,19 +99,28 @@ class SubscriptionFu::Subscription < ActiveRecord::Base
|
|
86
99
|
|
87
100
|
# billing API
|
88
101
|
|
89
|
-
def initiate_activation(
|
102
|
+
def initiate_activation(initiator)
|
90
103
|
gateway = (plan.free_plan? || sponsored?) ? 'nogw' : 'paypal'
|
91
|
-
transactions.create_activation(gateway,
|
104
|
+
transactions.create_activation(gateway, initiator).tap do |t|
|
92
105
|
if prev_subscription
|
93
106
|
to_cancel = [prev_subscription]
|
94
107
|
to_cancel.push(*prev_subscription.next_subscriptions.where("subscriptions.id <> ?", self).all)
|
95
|
-
to_cancel.each {|s| s.initiate_cancellation(
|
108
|
+
to_cancel.each {|s| s.initiate_cancellation(initiator, t) }
|
96
109
|
end
|
97
110
|
end
|
98
111
|
end
|
99
112
|
|
100
|
-
def initiate_cancellation(
|
101
|
-
transactions.create_cancellation(
|
113
|
+
def initiate_cancellation(initiator, activation_transaction)
|
114
|
+
transactions.create_cancellation(initiator, activation_transaction, self)
|
115
|
+
end
|
116
|
+
|
117
|
+
def sync_from_gateway!
|
118
|
+
if paypal?
|
119
|
+
if paypal_recurring_details[:status] == SubscriptionFu::Paypal::CANCELED_STATE
|
120
|
+
t = initiate_cancellation(SubscriptionFu::SystemInitiator.paypal_sync_initiator, nil)
|
121
|
+
t.complete(:effective => end_date_when_canceled, :reason => :gwcancel)
|
122
|
+
end
|
123
|
+
end
|
102
124
|
end
|
103
125
|
|
104
126
|
private
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class SubscriptionFu::SystemInitiator < ActiveRecord::Base
|
2
|
+
set_table_name :subscription_system_initiators
|
3
|
+
|
4
|
+
def self.paypal_sync_initiator
|
5
|
+
find_or_create_by_name("paypal sync", :description => "Updates subscription status based on status returned by Paypal")
|
6
|
+
end
|
7
|
+
end
|
@@ -54,8 +54,11 @@ class SubscriptionFu::Transaction < ActiveRecord::Base
|
|
54
54
|
update_attributes!(:status => "complete")
|
55
55
|
rescue Exception => err
|
56
56
|
if defined? ::ExceptionNotifier
|
57
|
-
data =
|
57
|
+
data = {:api_response => err.respond_to?(:response) ? err.response : nil, :subscription => subscription.inspect, :transaction => self.inspect}
|
58
58
|
::ExceptionNotifier::Notifier.background_exception_notification(err, :data => data).deliver
|
59
|
+
elsif defined? ::HoptoadNotifier
|
60
|
+
data = {:subscription => subscription.inspect, :transaction => self.inspect}
|
61
|
+
::HoptoadNotifier.notify(err, :parameters => data)
|
59
62
|
else
|
60
63
|
logger.warn(err)
|
61
64
|
logger.debug(err.backtrace.join("\n"))
|
@@ -125,10 +128,17 @@ class SubscriptionFu::Transaction < ActiveRecord::Base
|
|
125
128
|
end
|
126
129
|
|
127
130
|
def complete_cancellation_paypal(opts)
|
128
|
-
|
129
|
-
|
131
|
+
begin
|
132
|
+
SubscriptionFu::Paypal.express_request.renew!(sub_paypal_profile_id, :Cancel, :note => sub_cancel_reason)
|
133
|
+
rescue Paypal::Exception::APIError => err
|
134
|
+
if err.response.details.all?{|d| d.error_code == "11556"}
|
135
|
+
# 11556 - Invalid profile status for cancel action; profile should be active or suspended
|
136
|
+
logger.info("Got '#{err.response.details.inspect}' from paypal which indicates profile wasn't active (any more)...")
|
137
|
+
else
|
138
|
+
raise err
|
139
|
+
end
|
140
|
+
end
|
130
141
|
complete_cancellation(opts)
|
131
|
-
SubscriptionFu::Paypal.express_request.renew!(sub_paypal_profile_id, :Cancel, :note => sub_cancel_reason)
|
132
142
|
end
|
133
143
|
|
134
144
|
def complete_cancellation_nogw(opts)
|
@@ -29,10 +29,16 @@ class CreateSubscriptionFuTables < ActiveRecord::Migration
|
|
29
29
|
|
30
30
|
add_index "subscription_transactions", ["identifier"]
|
31
31
|
add_index "subscription_transactions", ["subscription_id"]
|
32
|
+
|
33
|
+
create_table "subscription_system_initiators" do |t|
|
34
|
+
t.string "name"
|
35
|
+
t.string "description"
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
def self.down
|
35
40
|
drop_table "subscriptions"
|
36
41
|
drop_table "subscription_transactions"
|
42
|
+
drop_table "subscription_system_initiators"
|
37
43
|
end
|
38
44
|
end
|
@@ -41,12 +41,7 @@ module SubscriptionFu
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def build_next_subscription(plan_key)
|
44
|
-
|
45
|
-
# TODO refactor
|
46
|
-
subscriptions.build_for_initializing(plan_key, active_subscription.successor_start_date(plan_key), active_subscription.successor_billing_start_date, active_subscription)
|
47
|
-
else
|
48
|
-
subscriptions.build_for_initializing(plan_key)
|
49
|
-
end
|
44
|
+
subscriptions.build_for_initializing(plan_key, active_subscription)
|
50
45
|
end
|
51
46
|
end
|
52
47
|
|
@@ -3,6 +3,9 @@ require "paypal"
|
|
3
3
|
module SubscriptionFu::Paypal
|
4
4
|
UTC_TZ = ActiveSupport::TimeZone.new("UTC")
|
5
5
|
|
6
|
+
CANCELED_STATE = "Cancelled"
|
7
|
+
ACTIVE_STATE = "Active"
|
8
|
+
|
6
9
|
def self.express_request
|
7
10
|
config = SubscriptionFu.config
|
8
11
|
::Paypal::Express::Request.new(
|
@@ -13,7 +16,8 @@ module SubscriptionFu::Paypal
|
|
13
16
|
|
14
17
|
def self.recurring_details(profile_id)
|
15
18
|
res = SubscriptionFu::Paypal.express_request.subscription(profile_id)
|
16
|
-
{ :
|
17
|
-
:
|
19
|
+
{ :status => res.recurring.status,
|
20
|
+
:next_billing_date => UTC_TZ.parse(res.recurring.summary.next_billing_date.to_s),
|
21
|
+
:last_payment_date => UTC_TZ.parse(res.recurring.summary.last_payment_date.to_s) }
|
18
22
|
end
|
19
23
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class SubscriptionFuUpdateToZeroThree < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table "subscription_system_initiators" do |t|
|
4
|
+
t.string "name"
|
5
|
+
t.string "description"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
drop_table "subscription_system_initiators"
|
11
|
+
end
|
12
|
+
end
|
data/spec/app/db/schema.rb
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended to check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(:version =>
|
13
|
+
ActiveRecord::Schema.define(:version => 20110701061927) do
|
14
14
|
|
15
15
|
create_table "initiators", :force => true do |t|
|
16
16
|
t.string "desc"
|
@@ -25,6 +25,11 @@ ActiveRecord::Schema.define(:version => 20110516070948) do
|
|
25
25
|
t.datetime "updated_at"
|
26
26
|
end
|
27
27
|
|
28
|
+
create_table "subscription_system_initiators", :force => true do |t|
|
29
|
+
t.string "name"
|
30
|
+
t.string "description"
|
31
|
+
end
|
32
|
+
|
28
33
|
create_table "subscription_transactions", :force => true do |t|
|
29
34
|
t.integer "subscription_id", :null => false
|
30
35
|
t.integer "initiator_id", :null => false
|
@@ -117,7 +117,7 @@ describe SubscriptionFu::Subscription do
|
|
117
117
|
it "should cancel previous sub with failure" do
|
118
118
|
sub = instance_variable_get("@#{sub_instance}").prev_subscription.reload
|
119
119
|
sub.canceled_at.should be_present
|
120
|
-
sub.transactions.last.status.should == "
|
120
|
+
sub.transactions.last.status.should == "complete"
|
121
121
|
end
|
122
122
|
end unless first_sub || prev_sub_is_free
|
123
123
|
context "complete with error in create" do
|
@@ -194,11 +194,11 @@ describe SubscriptionFu::Subscription do
|
|
194
194
|
end
|
195
195
|
|
196
196
|
context "active on paypal" do
|
197
|
-
before { mock_paypal_profile_details("fgsga564aa", "
|
197
|
+
before { mock_paypal_profile_details("fgsga564aa", "Active", "2010-01-10", "2010-02-10") }
|
198
198
|
it("should return next_billing_date") { @sub.next_billing_date.should == @next_billing }
|
199
199
|
it("should return last_billing_date") { @sub.last_billing_date.should == Time.parse("2010-01-10 00:00 UTC") }
|
200
200
|
it("should return estimated_next_billing_date") { @sub.estimated_next_billing_date.should == @next_billing }
|
201
|
-
it("should return
|
201
|
+
it("should return end_date_when_canceled") { @sub.end_date_when_canceled.should == @next_billing }
|
202
202
|
context "profess successor" do
|
203
203
|
before { at_time(@now) { @succ = @sub.subject.build_next_subscription('profess'); @succ.save! } }
|
204
204
|
should_build_valid_successor("profess", :now, :next_billing)
|
@@ -217,11 +217,11 @@ describe SubscriptionFu::Subscription do
|
|
217
217
|
end
|
218
218
|
|
219
219
|
context "canceled on paypal" do
|
220
|
-
before { mock_paypal_profile_details("fgsga564aa", "
|
220
|
+
before { mock_paypal_profile_details("fgsga564aa", "Cancelled", "2010-01-10", nil) }
|
221
221
|
it("should return next_billing_date") { @sub.next_billing_date.should be_nil }
|
222
222
|
it("should return last_billing_date") { @sub.last_billing_date.should == Time.parse("2010-01-10 00:00 UTC") }
|
223
223
|
it("should return estimated_next_billing_date") { @sub.estimated_next_billing_date.should == @next_billing }
|
224
|
-
it("should return
|
224
|
+
it("should return end_date_when_canceled") { @sub.end_date_when_canceled.should == @next_billing }
|
225
225
|
context "profess successor" do
|
226
226
|
before { at_time(@now) { @succ = @sub.subject.build_next_subscription('profess'); @succ.save! } }
|
227
227
|
should_build_valid_successor("profess", :now, :next_billing)
|
@@ -234,14 +234,24 @@ describe SubscriptionFu::Subscription do
|
|
234
234
|
before { at_time(@now) { @succ = @sub.subject.build_next_subscription('free'); @succ.save! } }
|
235
235
|
should_build_valid_successor("free", :next_billing, :next_billing)
|
236
236
|
end
|
237
|
+
context "sync" do
|
238
|
+
before { mock_paypal_delete_profile_with_error("fgsga564aa") }
|
239
|
+
before { at_time(@now) { @sub.sync_from_gateway! } }
|
240
|
+
it "should mark subscription as cancelled" do
|
241
|
+
@sub.reload.should be_canceled
|
242
|
+
@sub.canceled_at.should == Time.parse("2010-02-10 00:00 UTC")
|
243
|
+
@sub.cacnel_reason.should == "gwcacnel"
|
244
|
+
@sub.transactions.last.status.should == "complete"
|
245
|
+
end
|
246
|
+
end
|
237
247
|
end
|
238
248
|
|
239
249
|
context "canceled on paypal, no payments made" do
|
240
|
-
before { mock_paypal_profile_details("fgsga564aa", "
|
250
|
+
before { mock_paypal_profile_details("fgsga564aa", "Cancelled", nil, nil) }
|
241
251
|
it("should return next_billing_date") { @sub.next_billing_date.should be_nil }
|
242
252
|
it("should return last_billing_date") { @sub.last_billing_date.should be_nil }
|
243
253
|
it("should return estimated_next_billing_date") { @sub.estimated_next_billing_date.should be_nil }
|
244
|
-
it("should return
|
254
|
+
it("should return end_date_when_canceled") { at_time(@now) { @sub.end_date_when_canceled.should == @now } }
|
245
255
|
context "profess successor" do
|
246
256
|
before { at_time(@now) { @succ = @sub.subject.build_next_subscription('profess'); @succ.save! } }
|
247
257
|
should_build_valid_successor("profess", :now, :now)
|
@@ -258,7 +268,7 @@ describe SubscriptionFu::Subscription do
|
|
258
268
|
|
259
269
|
context "canceled on our side" do
|
260
270
|
before { @sub.update_attributes(:canceled_at => @next_billing, :cancel_reason => 'admin') }
|
261
|
-
it("should return
|
271
|
+
it("should return end_date_when_canceled") { @sub.end_date_when_canceled.should == @next_billing }
|
262
272
|
context "profess successor" do
|
263
273
|
before { at_time(@now) { @succ = @sub.subject.build_next_subscription('profess'); @succ.save! } }
|
264
274
|
should_build_valid_successor("profess", :now, :next_billing)
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subscription_fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 2
|
8
|
-
- 1
|
9
|
-
version: 0.2.1
|
4
|
+
prerelease:
|
5
|
+
version: 0.3.0
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Paul McMahon
|
@@ -15,7 +11,7 @@ autorequire:
|
|
15
11
|
bindir: bin
|
16
12
|
cert_chain: []
|
17
13
|
|
18
|
-
date: 2011-
|
14
|
+
date: 2011-07-01 00:00:00 +09:00
|
19
15
|
default_executable:
|
20
16
|
dependencies:
|
21
17
|
- !ruby/object:Gem::Dependency
|
@@ -26,10 +22,6 @@ dependencies:
|
|
26
22
|
requirements:
|
27
23
|
- - ">="
|
28
24
|
- !ruby/object:Gem::Version
|
29
|
-
segments:
|
30
|
-
- 3
|
31
|
-
- 0
|
32
|
-
- 3
|
33
25
|
version: 3.0.3
|
34
26
|
type: :runtime
|
35
27
|
version_requirements: *id001
|
@@ -41,10 +33,6 @@ dependencies:
|
|
41
33
|
requirements:
|
42
34
|
- - ~>
|
43
35
|
- !ruby/object:Gem::Version
|
44
|
-
segments:
|
45
|
-
- 0
|
46
|
-
- 3
|
47
|
-
- 0
|
48
36
|
version: 0.3.0
|
49
37
|
type: :runtime
|
50
38
|
version_requirements: *id002
|
@@ -62,8 +50,10 @@ files:
|
|
62
50
|
- LICENSE
|
63
51
|
- README.md
|
64
52
|
- Rakefile
|
53
|
+
- UPDATING.md
|
65
54
|
- app/models/subscription_fu/plan.rb
|
66
55
|
- app/models/subscription_fu/subscription.rb
|
56
|
+
- app/models/subscription_fu/system_initiator.rb
|
67
57
|
- app/models/subscription_fu/transaction.rb
|
68
58
|
- config/locales/en.yml
|
69
59
|
- examples/routes.rb
|
@@ -79,6 +69,7 @@ files:
|
|
79
69
|
- lib/subscription_fu/models.rb
|
80
70
|
- lib/subscription_fu/paypal.rb
|
81
71
|
- lib/subscription_fu/railtie.rb
|
72
|
+
- lib/subscription_fu/rake.tasks
|
82
73
|
- lib/subscription_fu/version.rb
|
83
74
|
- spec/app/.gitignore
|
84
75
|
- spec/app/Rakefile
|
@@ -103,6 +94,7 @@ files:
|
|
103
94
|
- spec/app/db/migrate/20110516061428_create_subjects.rb
|
104
95
|
- spec/app/db/migrate/20110516061443_create_initiators.rb
|
105
96
|
- spec/app/db/migrate/20110516070948_create_subscription_fu_tables.rb
|
97
|
+
- spec/app/db/migrate/20110701061927_subscription_fu_update_to_zero_three.rb
|
106
98
|
- spec/app/db/schema.rb
|
107
99
|
- spec/app/script/rails
|
108
100
|
- spec/factories/initiator.rb
|
@@ -129,21 +121,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
129
121
|
requirements:
|
130
122
|
- - ">="
|
131
123
|
- !ruby/object:Gem::Version
|
132
|
-
segments:
|
133
|
-
- 0
|
134
124
|
version: "0"
|
135
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
126
|
none: false
|
137
127
|
requirements:
|
138
128
|
- - ">="
|
139
129
|
- !ruby/object:Gem::Version
|
140
|
-
segments:
|
141
|
-
- 0
|
142
130
|
version: "0"
|
143
131
|
requirements: []
|
144
132
|
|
145
133
|
rubyforge_project: subscriptionfu
|
146
|
-
rubygems_version: 1.
|
134
|
+
rubygems_version: 1.6.2
|
147
135
|
signing_key:
|
148
136
|
specification_version: 3
|
149
137
|
summary: Rails support for handling free/paid subscriptions
|