redline 0.1.4 → 0.2.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/.gitignore +2 -1
- data/README.rdoc +43 -20
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/lib/redline.rb +14 -5
- data/lib/redline/billing/base.rb +11 -0
- data/lib/redline/customer/base.rb +18 -0
- data/lib/redline/{customer.rb → customer/instance.rb} +0 -11
- data/lib/redline/customer/settings.rb +12 -0
- data/lib/redline/subscription/base.rb +16 -0
- data/lib/redline/subscription/instance.rb +42 -0
- data/lib/redline/subscription/settings.rb +24 -0
- data/redline.gemspec +20 -8
- data/spec/billing_spec.rb +121 -0
- data/spec/customer_spec.rb +76 -0
- data/spec/db/models.rb +34 -2
- data/spec/db/schema.rb +35 -14
- data/spec/redline_spec.rb +35 -63
- data/spec/subscription_spec.rb +237 -0
- metadata +17 -5
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -2,7 +2,18 @@
|
|
2
2
|
|
3
3
|
Braintree via Redline: riding the rails
|
4
4
|
|
5
|
-
Redline helps you with Braintree on Rails, it automatically makes creation/deletion/update scripts for your model, translating and syncing them with a Braintree customer profile.
|
5
|
+
Redline helps you with Braintree on Rails, it automatically makes customer creation/deletion/update scripts for your model, translating and syncing them with a Braintree customer profile.
|
6
|
+
Redline also includes a lightweight manual subscription billing system, if you desire that functionality.
|
7
|
+
|
8
|
+
=== Please note: this repository may be a prerelease version, please read the README of the version you have installed.
|
9
|
+
|
10
|
+
== Heads up
|
11
|
+
|
12
|
+
=== This software is delivered "as is" without warranty. As with any piece of code, please study it if you include it in your project, especially for such a critical component of your application.
|
13
|
+
|
14
|
+
While I believe the tests cover the code well, there may be certain cases that I have not expirienced or predicted. This is a manual billing script, so dreadful things may occur; please help out and add to the tests if you see, or god forbid expirience, any problems.
|
15
|
+
|
16
|
+
It is my intention to convert to gateway handled subscriptions, once Braintree adds this functionality to their gem.
|
6
17
|
|
7
18
|
== Getting started
|
8
19
|
|
@@ -15,34 +26,46 @@ Run your install task:
|
|
15
26
|
|
16
27
|
$ rake gems:install
|
17
28
|
|
18
|
-
Configure Braintree normally in an initializer:
|
19
|
-
|
20
|
-
Braintree::Configuration.environment = CONFIG[:braintree][:environment].to_sym
|
21
|
-
Braintree::Configuration.merchant_id = CONFIG[:braintree][:merchant_id]
|
22
|
-
Braintree::Configuration.public_key = CONFIG[:braintree][:public_key]
|
23
|
-
Braintree::Configuration.private_key = CONFIG[:braintree][:private_key]
|
29
|
+
Configure Braintree normally in an initializer and add the following magic columns to a model of your choice (e.g, User):
|
24
30
|
|
25
|
-
|
31
|
+
add_column :users, :customer_id, :integer # Required
|
32
|
+
|
33
|
+
add_column :users, :subscription_key, :string # Required if User has_a_subscription
|
34
|
+
add_column :users, :paid_until, :date # Required if User has_a_subscription
|
35
|
+
add_column :users, :trial_until, :date # Required if User has_a_subscription and trial length is greater than 0.days
|
26
36
|
|
27
|
-
|
37
|
+
And the following definition calls:
|
28
38
|
|
29
|
-
|
30
|
-
|
39
|
+
class User < ActiveRecord::Base
|
40
|
+
|
41
|
+
has_a_braintree_customer
|
42
|
+
|
43
|
+
has_a_subscription do
|
44
|
+
plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
|
45
|
+
end
|
46
|
+
|
31
47
|
end
|
32
48
|
|
33
|
-
|
49
|
+
And then run this daily:
|
34
50
|
|
35
|
-
|
51
|
+
User.run_billing!
|
36
52
|
|
37
|
-
|
38
|
-
acts_as_braintree_customer :business_name => :company, :firstname => :first_name, :lastname => :last_name
|
39
|
-
attr_accessible :firstname, :lastname, :website, :email, :business_name, :password, :password_confirmation
|
40
|
-
end
|
53
|
+
== More advanced configuration
|
41
54
|
|
42
|
-
|
43
|
-
james.customer #=> <Braintree::Customer id: "9999999", first_name: "James", last_name: "Daniels", company: "MarginLeft, LLC", email: "james@jamesdaniels.net", website: "http://www.marginleft.com", ..., addresses: [], credit_cards: []>
|
55
|
+
If you don't like the default settings or your user needs a field mapping, you can override many assumptions RedLine makes:
|
44
56
|
|
45
|
-
|
57
|
+
class User < ActiveRecord::Base
|
58
|
+
has_a_braintree_customer do
|
59
|
+
attribute_map :firstname => :first_name, :lastname => :last_name
|
60
|
+
end
|
61
|
+
|
62
|
+
has_a_subscription do
|
63
|
+
plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
|
64
|
+
default_plan :medium
|
65
|
+
billing_frequency 30.days, :grace_period => 7.days
|
66
|
+
free_trial 30.days, :reminder => 7.days
|
67
|
+
end
|
68
|
+
end
|
46
69
|
|
47
70
|
== Note on Patches/Pull Requests
|
48
71
|
|
data/Rakefile
CHANGED
@@ -5,8 +5,8 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "redline"
|
8
|
-
gem.summary = %Q{Braintree
|
9
|
-
gem.description = %Q{
|
8
|
+
gem.summary = %Q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
|
9
|
+
gem.description = %Q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
|
10
10
|
gem.email = "james@marginleft.com"
|
11
11
|
gem.homepage = "http://github.com/jamesdaniels/redline"
|
12
12
|
gem.authors = ["James Daniels"]
|
@@ -21,6 +21,11 @@ rescue LoadError
|
|
21
21
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
22
|
end
|
23
23
|
|
24
|
+
begin
|
25
|
+
require 'metric_fu'
|
26
|
+
rescue
|
27
|
+
end
|
28
|
+
|
24
29
|
require 'spec/rake/spectask'
|
25
30
|
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
31
|
spec.libs << 'lib' << 'spec'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/redline.rb
CHANGED
@@ -2,12 +2,21 @@ require 'rubygems'
|
|
2
2
|
require 'active_record'
|
3
3
|
|
4
4
|
module RedLine
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
autoload :Customer, 'redline/customer/base'
|
7
|
+
autoload :Subscription, 'redline/subscription/base'
|
8
|
+
autoload :Billing, 'redline/billing/base'
|
9
|
+
|
10
|
+
def has_a_braintree_customer
|
11
|
+
include RedLine::Customer
|
12
|
+
yield if block_given?
|
13
|
+
set_default_customer_options
|
14
|
+
end
|
7
15
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
16
|
+
def has_a_subscription
|
17
|
+
include RedLine::Subscription
|
18
|
+
yield if block_given?
|
19
|
+
set_default_subscription_options
|
11
20
|
end
|
12
21
|
|
13
22
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RedLine
|
2
|
+
module Customer
|
3
|
+
autoload :Settings, 'redline/customer/settings'
|
4
|
+
autoload :InstanceMethods, 'redline/customer/instance'
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ActiveSupport::Memoizable
|
8
|
+
extend RedLine::Customer::Settings
|
9
|
+
include InstanceMethods
|
10
|
+
memoize :customer
|
11
|
+
before_create :create_customer
|
12
|
+
before_update :update_customer
|
13
|
+
before_destroy :delete_customer
|
14
|
+
cattr_accessor :braintree_customer
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,16 +1,5 @@
|
|
1
1
|
module RedLine
|
2
2
|
module Customer
|
3
|
-
def self.included(base)
|
4
|
-
base.class_eval do
|
5
|
-
send :extend, ActiveSupport::Memoizable
|
6
|
-
send :include, InstanceMethods
|
7
|
-
memoize :customer
|
8
|
-
before_create :create_customer
|
9
|
-
before_update :update_customer
|
10
|
-
before_destroy :delete_customer
|
11
|
-
cattr_accessor :braintree_customer
|
12
|
-
end
|
13
|
-
end
|
14
3
|
module InstanceMethods
|
15
4
|
def braintree_customer_attributes
|
16
5
|
wanted_attributes = Braintree::Customer._create_signature.reject{|a| a.is_a? Hash}.reject{|a| a == :id}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module RedLine
|
2
|
+
module Subscription
|
3
|
+
autoload :Settings, 'redline/subscription/settings'
|
4
|
+
autoload :InstanceMethods, 'redline/subscription/instance'
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
extend ActiveSupport::Memoizable
|
8
|
+
extend RedLine::Subscription::Settings
|
9
|
+
extend RedLine::Billing
|
10
|
+
include InstanceMethods
|
11
|
+
before_create :set_plan
|
12
|
+
cattr_accessor :subscription_plans, :trial_settings, :billing_settings, :default_subscription_plan
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RedLine
|
2
|
+
module Subscription
|
3
|
+
module InstanceMethods
|
4
|
+
def plan
|
5
|
+
self.class.subscription_plans[(subscription_key || self.class.default_subscription_plan).to_sym]
|
6
|
+
end
|
7
|
+
def plan=(plan)
|
8
|
+
if (plan.is_a?(String) || plan.is_a?(Symbol)) && self.class.subscription_plans.keys.include?(plan.to_sym)
|
9
|
+
self.subscription_key = plan
|
10
|
+
elsif plan.is_a?(Hash)
|
11
|
+
self.subscription_key = self.class.subscription_plans.index(plan)
|
12
|
+
end
|
13
|
+
self.paid_until = Date.today
|
14
|
+
self.trial_until ||= Date.today + self.class.trial_settings[:period] if trial?
|
15
|
+
end
|
16
|
+
def set_plan
|
17
|
+
self.subscription_key ||= self.class.default_subscription_plan
|
18
|
+
end
|
19
|
+
def pay
|
20
|
+
self.customer.sale!(:amount => ("%0.2f" % plan[:price]), :options => {:submit_for_settlement => true})
|
21
|
+
self.paid_until = Date.today + self.class.billing_settings[:period] - (past_due || 0)
|
22
|
+
end
|
23
|
+
def past_grace?
|
24
|
+
past_due && past_due >= self.class.billing_settings[:grace_period]
|
25
|
+
end
|
26
|
+
def paid_plan?
|
27
|
+
plan[:price] > 0
|
28
|
+
end
|
29
|
+
def trial?
|
30
|
+
self.class.trial_settings[:period] > 0.days
|
31
|
+
end
|
32
|
+
def next_due
|
33
|
+
paid_plan? && [trial? && trial_until || nil, paid_until, Date.today].compact.max
|
34
|
+
end
|
35
|
+
def past_due
|
36
|
+
due_date = [trial? && trial_until || nil, paid_until].compact.max
|
37
|
+
amount = (due_date && (Date.today - due_date) || 0).days
|
38
|
+
paid_plan? && amount > 0 && amount
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RedLine
|
2
|
+
module Subscription
|
3
|
+
module Settings
|
4
|
+
def set_default_subscription_options
|
5
|
+
billing_frequency 30.days unless billing_settings
|
6
|
+
free_trial 30.days unless trial_settings
|
7
|
+
default_plan ((send :subscription_plans).to_a.sort_by{|a| a[1][:price]}.first.first) unless default_subscription_plan
|
8
|
+
end
|
9
|
+
def plans(subscriptions = {})
|
10
|
+
include RedLine::Subscription
|
11
|
+
self.subscription_plans = subscriptions
|
12
|
+
end
|
13
|
+
def free_trial(period, options = {})
|
14
|
+
self.trial_settings = {:period => period, :reminder => 5.days}.merge(options)
|
15
|
+
end
|
16
|
+
def billing_frequency(period, options = {})
|
17
|
+
self.billing_settings = {:period => period, :grace_period => 5.days}.merge(options)
|
18
|
+
end
|
19
|
+
def default_plan(subscription_plan)
|
20
|
+
self.default_subscription_plan = subscription_plan
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/redline.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{redline}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["James Daniels"]
|
12
|
-
s.date = %q{2010-02-
|
13
|
-
s.description = %q{
|
12
|
+
s.date = %q{2010-02-10}
|
13
|
+
s.description = %q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
|
14
14
|
s.email = %q{james@marginleft.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
@@ -24,25 +24,37 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/redline.rb",
|
27
|
-
"lib/redline/
|
27
|
+
"lib/redline/billing/base.rb",
|
28
|
+
"lib/redline/customer/base.rb",
|
29
|
+
"lib/redline/customer/instance.rb",
|
30
|
+
"lib/redline/customer/settings.rb",
|
31
|
+
"lib/redline/subscription/base.rb",
|
32
|
+
"lib/redline/subscription/instance.rb",
|
33
|
+
"lib/redline/subscription/settings.rb",
|
28
34
|
"redline.gemspec",
|
35
|
+
"spec/billing_spec.rb",
|
36
|
+
"spec/customer_spec.rb",
|
29
37
|
"spec/db/database.yml",
|
30
38
|
"spec/db/models.rb",
|
31
39
|
"spec/db/schema.rb",
|
32
40
|
"spec/redline_spec.rb",
|
33
41
|
"spec/spec.opts",
|
34
|
-
"spec/spec_helper.rb"
|
42
|
+
"spec/spec_helper.rb",
|
43
|
+
"spec/subscription_spec.rb"
|
35
44
|
]
|
36
45
|
s.homepage = %q{http://github.com/jamesdaniels/redline}
|
37
46
|
s.rdoc_options = ["--charset=UTF-8"]
|
38
47
|
s.require_paths = ["lib"]
|
39
48
|
s.rubygems_version = %q{1.3.5}
|
40
|
-
s.summary = %q{Braintree
|
49
|
+
s.summary = %q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
|
41
50
|
s.test_files = [
|
42
|
-
"spec/
|
51
|
+
"spec/billing_spec.rb",
|
52
|
+
"spec/customer_spec.rb",
|
53
|
+
"spec/db/models.rb",
|
43
54
|
"spec/db/schema.rb",
|
44
55
|
"spec/redline_spec.rb",
|
45
|
-
"spec/spec_helper.rb"
|
56
|
+
"spec/spec_helper.rb",
|
57
|
+
"spec/subscription_spec.rb"
|
46
58
|
]
|
47
59
|
|
48
60
|
if s.respond_to? :specification_version then
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe User do
|
4
|
+
|
5
|
+
def valid_user
|
6
|
+
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
7
|
+
@user ||= User.new(
|
8
|
+
:first_name => 'James',
|
9
|
+
:last_name => 'Daniels',
|
10
|
+
:email => 'james@marginleft.com',
|
11
|
+
:unused_attribute => 'unused'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def mock_customer
|
16
|
+
@customer ||= mock(Braintree::Customer, :id => 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should run billing' do
|
20
|
+
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
21
|
+
Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).exactly(3).and_return(mock_customer)
|
22
|
+
Braintree::Customer.should_receive('update!').twice.and_return(mock_customer)
|
23
|
+
users = User.subscription_plans.keys.map do |plan|
|
24
|
+
user = User.new(
|
25
|
+
:first_name => 'James',
|
26
|
+
:last_name => 'Daniels',
|
27
|
+
:email => 'james@marginleft.com',
|
28
|
+
:unused_attribute => 'unused'
|
29
|
+
)
|
30
|
+
user.plan = plan
|
31
|
+
if plan == :mild
|
32
|
+
user.should_receive(:next_due).and_return(nil)
|
33
|
+
user.should_not_receive(:pay)
|
34
|
+
else
|
35
|
+
user.should_receive(:next_due).and_return(Date.today)
|
36
|
+
user.should_receive(:pay).and_return(true)
|
37
|
+
end
|
38
|
+
user.save
|
39
|
+
user
|
40
|
+
end
|
41
|
+
User.stub!(:all).and_return(users)
|
42
|
+
User.run_billing!
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should run billing with failures' do
|
46
|
+
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
47
|
+
Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).once.and_return(mock_customer)
|
48
|
+
Braintree::Customer.should_receive('update!').once.and_return(mock_customer)
|
49
|
+
user = User.new(
|
50
|
+
:first_name => 'James',
|
51
|
+
:last_name => 'Daniels',
|
52
|
+
:email => 'james@marginleft.com',
|
53
|
+
:unused_attribute => 'unused'
|
54
|
+
)
|
55
|
+
user.plan = :medium
|
56
|
+
user.paid_until = Date.today - 1
|
57
|
+
user.trial_until = Date.today - 1
|
58
|
+
user.stub!('customer').and_return(mock_customer)
|
59
|
+
user.stub!(:pay).and_raise(ArgumentError)
|
60
|
+
user.save
|
61
|
+
User.stub!(:all).and_return([user])
|
62
|
+
User.run_billing!
|
63
|
+
user.next_due.should eql(Date.today)
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'paid bill' do
|
67
|
+
(0).upto(5) do |x|
|
68
|
+
it "should push the due date 30 days when #{x} days past due" do
|
69
|
+
user = User.new
|
70
|
+
mock_customer = mock(Braintree::Customer, :id => 1)
|
71
|
+
user.stub!('customer').and_return(mock_customer)
|
72
|
+
mock_customer.should_receive('sale!').with({:amount=>"10.00", :options=>{:submit_for_settlement=>true}}).and_return(true)
|
73
|
+
user.plan = :spicy
|
74
|
+
user.paid_until = Date.today - x.days
|
75
|
+
user.trial_until = Date.today - 30.days
|
76
|
+
user.pay
|
77
|
+
user.next_due.should eql(Date.today + 30.days - x.days)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe ComplexUser do
|
85
|
+
|
86
|
+
describe 'paid bill' do
|
87
|
+
(0).upto(5) do |x|
|
88
|
+
it "should push the due date 30 days when #{x} days past due" do
|
89
|
+
user = ComplexUser.new
|
90
|
+
mock_customer = mock(Braintree::Customer, :id => 1)
|
91
|
+
user.stub!('customer').and_return(mock_customer)
|
92
|
+
mock_customer.should_receive('sale!').with({:amount=>"5.00", :options=>{:submit_for_settlement=>true}}).and_return(true)
|
93
|
+
user.plan = :medium
|
94
|
+
user.paid_until = Date.today - x.days
|
95
|
+
user.trial_until = Date.today - 31.days
|
96
|
+
user.pay
|
97
|
+
user.next_due.should eql(Date.today + 31.days - x.days)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe UserWithoutTrial do
|
105
|
+
|
106
|
+
describe 'paid bill' do
|
107
|
+
(0).upto(5) do |x|
|
108
|
+
it "should push the due date 30 days when #{x} days past due" do
|
109
|
+
user = UserWithoutTrial.new
|
110
|
+
mock_customer = mock(Braintree::Customer, :id => 1)
|
111
|
+
user.stub!('customer').and_return(mock_customer)
|
112
|
+
mock_customer.should_receive('sale!').with({:amount=>"10.00", :options=>{:submit_for_settlement=>true}}).and_return(true)
|
113
|
+
user.plan = :spicy
|
114
|
+
user.paid_until = Date.today - x.days
|
115
|
+
user.pay
|
116
|
+
user.next_due.should eql(Date.today + 30.days - x.days)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe User do
|
4
|
+
|
5
|
+
def valid_user
|
6
|
+
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
7
|
+
@user ||= User.new(
|
8
|
+
:first_name => 'James',
|
9
|
+
:last_name => 'Daniels',
|
10
|
+
:email => 'james@marginleft.com',
|
11
|
+
:unused_attribute => 'unused'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def mock_customer
|
16
|
+
@customer ||= mock(Braintree::Customer, :id => 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should have a empty braintree_customer" do
|
20
|
+
User.braintree_customer.should eql({})
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should inherit the instance methods" do
|
24
|
+
expected_methods = %w(braintree_customer_attributes customer create_customer update_customer delete_customer)
|
25
|
+
(User.instance_methods & expected_methods).sort.should eql(expected_methods.sort)
|
26
|
+
end
|
27
|
+
it "should have proper braintree attributes" do
|
28
|
+
valid_user.braintree_customer_attributes.should eql({:first_name=>"James", :last_name=>"Daniels", :email=>"james@marginleft.com"})
|
29
|
+
end
|
30
|
+
it "should fire Braintree::Customer.create!" do
|
31
|
+
Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).and_return(mock_customer)
|
32
|
+
valid_user.save!
|
33
|
+
valid_user.reload
|
34
|
+
valid_user.customer_id.should eql(mock_customer.id)
|
35
|
+
end
|
36
|
+
it "should fire Braintree::Customer.update!" do
|
37
|
+
Braintree::Customer.stub!('create!').and_return(mock_customer)
|
38
|
+
Braintree::Customer.should_receive('update!').with(mock_customer.id, valid_user.braintree_customer_attributes.merge(:email => 'james@jamesdaniels.net')).and_return(true)
|
39
|
+
valid_user.save!
|
40
|
+
valid_user.update_attributes(:email => 'james@jamesdaniels.net')
|
41
|
+
valid_user.reload
|
42
|
+
valid_user.customer_id.should eql(mock_customer.id)
|
43
|
+
end
|
44
|
+
it "should fire Braintree::Customer.delete" do
|
45
|
+
Braintree::Customer.stub!('create!').and_return(mock_customer)
|
46
|
+
Braintree::Customer.stub!('find').and_return(true)
|
47
|
+
Braintree::Customer.should_receive('delete').with(mock_customer.id).and_return(true)
|
48
|
+
valid_user.save!
|
49
|
+
valid_user.destroy
|
50
|
+
end
|
51
|
+
it "should fire Braintree::Customer.find" do
|
52
|
+
Braintree::Customer.stub!('create!').and_return(mock_customer)
|
53
|
+
Braintree::Customer.stub!('update!').and_return(true)
|
54
|
+
Braintree::Customer.should_receive('find').with(mock_customer.id).and_return(true)
|
55
|
+
valid_user.save!
|
56
|
+
valid_user.customer
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ComplexUser do
|
62
|
+
|
63
|
+
it "should have a empty braintree_customer" do
|
64
|
+
ComplexUser.braintree_customer.should eql({:firstname=>:first_name, :lastname=>:last_name})
|
65
|
+
end
|
66
|
+
it "should have proper braintree attributes" do
|
67
|
+
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
68
|
+
ComplexUser.new(
|
69
|
+
:firstname => 'James',
|
70
|
+
:lastname => 'Daniels',
|
71
|
+
:email => 'james@marginleft.com',
|
72
|
+
:unused_attribute => 'unused'
|
73
|
+
).braintree_customer_attributes.should eql({:first_name=>"James", :last_name=>"Daniels", :email=>"james@marginleft.com"})
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
data/spec/db/models.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
class User < ActiveRecord::Base
|
2
2
|
|
3
|
-
|
3
|
+
has_a_braintree_customer
|
4
|
+
|
5
|
+
has_a_subscription do
|
6
|
+
plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
|
7
|
+
end
|
4
8
|
|
5
9
|
validates_presence_of :first_name, :last_name, :unused_attribute, :email
|
6
10
|
|
@@ -8,8 +12,36 @@ end
|
|
8
12
|
|
9
13
|
class ComplexUser < ActiveRecord::Base
|
10
14
|
|
11
|
-
|
15
|
+
has_a_braintree_customer do
|
16
|
+
attribute_map :firstname => :first_name, :lastname => :last_name
|
17
|
+
end
|
18
|
+
|
19
|
+
has_a_subscription do
|
20
|
+
plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
|
21
|
+
default_plan :medium
|
22
|
+
billing_frequency 31.days, :grace_period => 6.days
|
23
|
+
free_trial 31.days, :reminder => 6.days
|
24
|
+
end
|
12
25
|
|
13
26
|
validates_presence_of :firstname, :lastname, :unused_attribute, :email
|
14
27
|
|
28
|
+
end
|
29
|
+
|
30
|
+
class UserWithoutTrial < ActiveRecord::Base
|
31
|
+
|
32
|
+
has_a_braintree_customer do
|
33
|
+
attribute_map :firstname => :first_name, :lastname => :last_name
|
34
|
+
end
|
35
|
+
|
36
|
+
has_a_subscription do
|
37
|
+
plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
|
38
|
+
free_trial 0.days
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class UserWithoutSubscription < ActiveRecord::Base
|
44
|
+
|
45
|
+
has_a_braintree_customer
|
46
|
+
|
15
47
|
end
|
data/spec/db/schema.rb
CHANGED
@@ -1,16 +1,37 @@
|
|
1
1
|
ActiveRecord::Schema.define(:version => 1) do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
2
|
+
create_table :users, :force => true do |t|
|
3
|
+
t.column :first_name, :string
|
4
|
+
t.column :last_name, :string
|
5
|
+
t.column :email, :string
|
6
|
+
t.column :unused_attribute, :string
|
7
|
+
t.column :customer_id, :integer
|
8
|
+
t.column :subscription_key, :string
|
9
|
+
t.column :trial_until, :date
|
10
|
+
t.column :paid_until, :date
|
11
|
+
end
|
12
|
+
create_table :complex_users, :force => true do |t|
|
13
|
+
t.column :firstname, :string
|
14
|
+
t.column :lastname, :string
|
15
|
+
t.column :email, :string
|
16
|
+
t.column :unused_attribute, :string
|
17
|
+
t.column :customer_id, :integer
|
18
|
+
t.column :subscription_key, :string
|
19
|
+
t.column :trial_until, :date
|
20
|
+
t.column :paid_until, :date
|
21
|
+
end
|
22
|
+
create_table :user_without_trials, :force => true do |t|
|
23
|
+
t.column :firstname, :string
|
24
|
+
t.column :lastname, :string
|
25
|
+
t.column :email, :string
|
26
|
+
t.column :unused_attribute, :string
|
27
|
+
t.column :customer_id, :integer
|
28
|
+
t.column :subscription_key, :string
|
29
|
+
t.column :paid_until, :date
|
30
|
+
end
|
31
|
+
create_table :user_without_subscription, :force => true do |t|
|
32
|
+
t.column :firstname, :string
|
33
|
+
t.column :lastname, :string
|
34
|
+
t.column :email, :string
|
35
|
+
t.column :customer_id, :integer
|
36
|
+
end
|
16
37
|
end
|
data/spec/redline_spec.rb
CHANGED
@@ -1,70 +1,42 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
6
|
-
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
7
|
-
@user ||= User.new(
|
8
|
-
:first_name => 'James',
|
9
|
-
:last_name => 'Daniels',
|
10
|
-
:email => 'james@marginleft.com',
|
11
|
-
:unused_attribute => 'unused'
|
12
|
-
)
|
13
|
-
end
|
14
|
-
|
15
|
-
def mock_customer
|
16
|
-
@customer ||= mock(Braintree::Customer, :id => 1)
|
17
|
-
end
|
18
|
-
|
19
|
-
it "should have a empty braintree_customer" do
|
20
|
-
User.braintree_customer.should eql({})
|
21
|
-
end
|
22
|
-
it "should inherit the instance methods" do
|
23
|
-
expected_methods = %w(braintree_customer_attributes customer create_customer update_customer delete_customer)
|
24
|
-
(User.instance_methods & expected_methods).sort.should eql(expected_methods.sort)
|
25
|
-
end
|
26
|
-
it "should have proper braintree attributes" do
|
27
|
-
valid_user.braintree_customer_attributes.should eql({:first_name=>"James", :last_name=>"Daniels", :email=>"james@marginleft.com"})
|
28
|
-
end
|
29
|
-
it "should fire Braintree::Customer.create!" do
|
30
|
-
Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).and_return(mock_customer)
|
31
|
-
valid_user.save!
|
32
|
-
valid_user.customer_id.should eql(mock_customer.id)
|
33
|
-
end
|
34
|
-
it "should fire Braintree::Customer.update!" do
|
35
|
-
Braintree::Customer.stub!('create!').and_return(mock_customer)
|
36
|
-
Braintree::Customer.should_receive('update!').with(mock_customer.id, valid_user.braintree_customer_attributes.merge(:email => 'james@jamesdaniels.net')).and_return(true)
|
37
|
-
valid_user.save!
|
38
|
-
valid_user.update_attributes(:email => 'james@jamesdaniels.net')
|
39
|
-
valid_user.customer_id.should eql(mock_customer.id)
|
40
|
-
end
|
41
|
-
it "should fire Braintree::Customer.delete" do
|
42
|
-
Braintree::Customer.stub!('create!').and_return(mock_customer)
|
43
|
-
Braintree::Customer.stub!('find').and_return(true)
|
44
|
-
Braintree::Customer.should_receive('delete').with(mock_customer.id).and_return(true)
|
45
|
-
valid_user.save!
|
46
|
-
valid_user.destroy
|
47
|
-
end
|
48
|
-
it "should fire Braintree::Customer.find" do
|
49
|
-
Braintree::Customer.stub!('create!').and_return(mock_customer)
|
50
|
-
Braintree::Customer.stub!('update!').and_return(true)
|
51
|
-
Braintree::Customer.should_receive('find').with(mock_customer.id).and_return(true)
|
52
|
-
valid_user.save!
|
53
|
-
valid_user.customer
|
3
|
+
describe ActiveRecord do
|
4
|
+
it 'should extend our module' do
|
5
|
+
ActiveRecord::Base.is_a?(RedLine).should be_true
|
54
6
|
end
|
55
7
|
end
|
56
8
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
:
|
67
|
-
|
68
|
-
|
9
|
+
[User, ComplexUser, UserWithoutTrial, UserWithoutSubscription].each do |klass|
|
10
|
+
describe klass do
|
11
|
+
it 'should extend our module' do
|
12
|
+
klass.is_a?(RedLine).should be_true
|
13
|
+
end
|
14
|
+
it 'should respond to a customer' do
|
15
|
+
klass.respond_to?(:has_a_braintree_customer).should be_true
|
16
|
+
end
|
17
|
+
it 'should respond to a subscription' do
|
18
|
+
klass.respond_to?(:has_a_subscription).should be_true
|
19
|
+
end
|
20
|
+
it 'should include RedLine::Customer' do
|
21
|
+
klass.include?(RedLine::Customer).should be_true
|
22
|
+
end
|
23
|
+
it 'should extend RedLine::Customer::Settings' do
|
24
|
+
klass.is_a?(RedLine::Customer::Settings).should be_true
|
25
|
+
end
|
26
|
+
it 'should include RedLine::Customer::InstanceMethods' do
|
27
|
+
klass.include?(RedLine::Customer::InstanceMethods).should be_true
|
28
|
+
end
|
29
|
+
it "should#{(klass == UserWithoutSubscription) && ' not' || ''} include RedLine::Subscription" do
|
30
|
+
klass.include?(RedLine::Subscription).should eql(klass != UserWithoutSubscription)
|
31
|
+
end
|
32
|
+
it "should#{(klass == UserWithoutSubscription) && ' not' || ''} extend RedLine::Subscription::Settings" do
|
33
|
+
klass.is_a?(RedLine::Subscription::Settings).should eql(klass != UserWithoutSubscription)
|
34
|
+
end
|
35
|
+
it "should#{(klass == UserWithoutSubscription) && ' not' || ''} include RedLine::Subscription::InstanceMethods" do
|
36
|
+
klass.include?(RedLine::Subscription::InstanceMethods).should eql(klass != UserWithoutSubscription)
|
37
|
+
end
|
38
|
+
it "should#{(klass == UserWithoutSubscription) && ' not' || ''} include RedLine::Billing" do
|
39
|
+
klass.is_a?(RedLine::Billing).should eql(klass != UserWithoutSubscription)
|
40
|
+
end
|
69
41
|
end
|
70
42
|
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe User do
|
4
|
+
|
5
|
+
def valid_user
|
6
|
+
Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
|
7
|
+
@user ||= User.new(
|
8
|
+
:first_name => 'James',
|
9
|
+
:last_name => 'Daniels',
|
10
|
+
:email => 'james@marginleft.com',
|
11
|
+
:unused_attribute => 'unused'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
def mock_customer
|
16
|
+
@customer ||= mock(Braintree::Customer, :id => 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should have a default plan' do
|
20
|
+
Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).and_return(mock_customer)
|
21
|
+
user = valid_user
|
22
|
+
user.save
|
23
|
+
user.reload
|
24
|
+
user.subscription_key = :mild
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should have possible subscription plans' do
|
28
|
+
User.subscription_plans.should == {:mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}}
|
29
|
+
end
|
30
|
+
it 'should have a default plan' do
|
31
|
+
User.default_subscription_plan.should eql(:mild)
|
32
|
+
end
|
33
|
+
it 'should have billing settings' do
|
34
|
+
User.billing_settings.should == {:period => 30.days, :grace_period => 5.days}
|
35
|
+
end
|
36
|
+
it 'should have trial settings' do
|
37
|
+
User.trial_settings.should == {:period => 30.days, :reminder=>5.days}
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'should handle trial period' do
|
41
|
+
it 'sets trial' do
|
42
|
+
user = User.new
|
43
|
+
user.plan = :mild
|
44
|
+
user.trial_until.should eql(Date.today + 30.days)
|
45
|
+
end
|
46
|
+
it 'upgrades account within trial' do
|
47
|
+
user = User.new
|
48
|
+
user.plan = :medium
|
49
|
+
user.plan = :spicy
|
50
|
+
user.trial_until.should eql(Date.today + 30.days)
|
51
|
+
end
|
52
|
+
it 'upgrades account outside of trial' do
|
53
|
+
user = User.new
|
54
|
+
user.plan = :medium
|
55
|
+
user.trial_until = Date.today-1.days
|
56
|
+
user.plan = :spicy
|
57
|
+
user.trial_until.should eql(Date.today - 1.days)
|
58
|
+
end
|
59
|
+
it 'downgrades account within trial' do
|
60
|
+
user = User.new
|
61
|
+
user.plan = :spicy
|
62
|
+
user.plan = :medium
|
63
|
+
user.trial_until.should eql(Date.today + 30.days)
|
64
|
+
end
|
65
|
+
it 'downgrades account outside of trial' do
|
66
|
+
user = User.new
|
67
|
+
user.plan = :spicy
|
68
|
+
user.trial_until = Date.today-1.days
|
69
|
+
user.plan = :medium
|
70
|
+
user.trial_until.should eql(Date.today - 1.days)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe ComplexUser do
|
76
|
+
|
77
|
+
it 'should have possible subscription plans' do
|
78
|
+
ComplexUser.subscription_plans.should == {:mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}}
|
79
|
+
end
|
80
|
+
it 'should have a default plan' do
|
81
|
+
ComplexUser.default_subscription_plan.should eql(:medium)
|
82
|
+
end
|
83
|
+
it 'should have billing settings' do
|
84
|
+
ComplexUser.billing_settings.should == {:period => 31.days, :grace_period => 6.days}
|
85
|
+
end
|
86
|
+
it 'should have trial settings' do
|
87
|
+
ComplexUser.trial_settings.should == {:period => 31.days, :reminder=>6.days}
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should hand plan as a hash' do
|
91
|
+
user = ComplexUser.new
|
92
|
+
user.plan = ComplexUser.subscription_plans.values.first
|
93
|
+
user.subscription_key.should eql(ComplexUser.subscription_plans.keys.first)
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'should handle next due correctly' do
|
97
|
+
it 'on free' do
|
98
|
+
user = ComplexUser.new
|
99
|
+
user.plan = :mild
|
100
|
+
user.next_due.should be_false
|
101
|
+
end
|
102
|
+
it 'on past due' do
|
103
|
+
user = ComplexUser.new
|
104
|
+
user.plan = :spicy
|
105
|
+
user.paid_until = Date.today - 6.days
|
106
|
+
user.trial_until = Date.today - 37.days
|
107
|
+
user.next_due.should eql(Date.today)
|
108
|
+
end
|
109
|
+
it 'on paid' do
|
110
|
+
user = ComplexUser.new
|
111
|
+
user.plan = :spicy
|
112
|
+
user.paid_until = Date.today + 6.days
|
113
|
+
user.trial_until = Date.today - 31.days + 6.days
|
114
|
+
user.next_due.should eql(Date.today+6.days)
|
115
|
+
end
|
116
|
+
it 'on upgrade' do
|
117
|
+
user = ComplexUser.new
|
118
|
+
user.plan = :mild
|
119
|
+
user.trial_until = Date.today - 30.days
|
120
|
+
user.paid_until = Date.today - 1.days
|
121
|
+
user.plan = :spicy
|
122
|
+
user.next_due.should eql(Date.today)
|
123
|
+
end
|
124
|
+
it 'on downgrade' do
|
125
|
+
user = ComplexUser.new
|
126
|
+
user.plan = :spicy
|
127
|
+
user.trial_until = Date.today - 30.days
|
128
|
+
user.paid_until = Date.today - 1.days
|
129
|
+
user.plan = :medium
|
130
|
+
user.next_due.should eql(Date.today)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'should handle trial period' do
|
135
|
+
it 'sets trial' do
|
136
|
+
user = ComplexUser.new
|
137
|
+
user.plan = :mild
|
138
|
+
user.trial_until.should eql(Date.today + 31.days)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe 'should handle grace period' do
|
143
|
+
it 'on not past due' do
|
144
|
+
user = ComplexUser.new
|
145
|
+
user.should_receive(:past_due).and_return(false)
|
146
|
+
user.past_grace?.should be_false
|
147
|
+
end
|
148
|
+
0.upto(6) do |x|
|
149
|
+
it "on past due by #{x} days" do
|
150
|
+
user = ComplexUser.new
|
151
|
+
user.should_receive(:past_due).twice.and_return(x.days)
|
152
|
+
user.past_grace?.should eql(x > 5)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'should handle due correctly' do
|
158
|
+
describe 'past due' do
|
159
|
+
it 'should work via past_due method' do
|
160
|
+
user = ComplexUser.new
|
161
|
+
user.plan = :spicy
|
162
|
+
user.paid_until = Date.today - 6.days
|
163
|
+
user.trial_until = Date.today - 37.days
|
164
|
+
user.past_due.should eql(6.days)
|
165
|
+
end
|
166
|
+
it 'should handle trials' do
|
167
|
+
user = ComplexUser.new
|
168
|
+
user.plan = :spicy
|
169
|
+
user.paid_until = nil
|
170
|
+
user.trial_until = Date.today - 1.days
|
171
|
+
user.past_due.should eql(1.days)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
describe 'not overdue' do
|
175
|
+
it 'should work via past_due method' do
|
176
|
+
user = ComplexUser.new
|
177
|
+
user.plan = :spicy
|
178
|
+
user.paid_until = Date.today + 6.days
|
179
|
+
user.trial_until = Date.today - 31.days + 6.days
|
180
|
+
user.past_due.should be_false
|
181
|
+
end
|
182
|
+
it 'should handle end of trial' do
|
183
|
+
user = ComplexUser.new
|
184
|
+
user.plan = :spicy
|
185
|
+
user.paid_until = nil
|
186
|
+
user.trial_until = nil
|
187
|
+
user.past_due.should be_false
|
188
|
+
end
|
189
|
+
it 'should handle trials' do
|
190
|
+
user = ComplexUser.new
|
191
|
+
user.plan = :spicy
|
192
|
+
user.paid_until = nil
|
193
|
+
user.trial_until = Date.today + 1.days
|
194
|
+
user.past_due.should be_false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
describe 'free account' do
|
198
|
+
it 'should work via past_due trial' do
|
199
|
+
user = ComplexUser.new
|
200
|
+
user.plan = :mild
|
201
|
+
user.paid_until = Date.today - 6.days
|
202
|
+
user.trial_until = Date.today - 37.days
|
203
|
+
user.past_due.should be_false
|
204
|
+
end
|
205
|
+
it 'should handle past_due trials' do
|
206
|
+
user = ComplexUser.new
|
207
|
+
user.plan = :mild
|
208
|
+
user.paid_until = nil
|
209
|
+
user.trial_until = Date.today - 1.days
|
210
|
+
user.past_due.should be_false
|
211
|
+
end
|
212
|
+
it 'should work via trial' do
|
213
|
+
user = ComplexUser.new
|
214
|
+
user.plan = :mild
|
215
|
+
user.paid_until = Date.today + 6.days
|
216
|
+
user.trial_until = Date.today - 31.days + 6.days
|
217
|
+
user.past_due.should be_false
|
218
|
+
end
|
219
|
+
it 'should handle trials' do
|
220
|
+
user = ComplexUser.new
|
221
|
+
user.plan = :mild
|
222
|
+
user.paid_until = nil
|
223
|
+
user.trial_until = Date.today + 1.days
|
224
|
+
user.past_due.should be_false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
describe UserWithoutTrial do
|
232
|
+
|
233
|
+
it 'has no trial' do
|
234
|
+
UserWithoutTrial.trial_settings.should == {:period=>0.days, :reminder=>5.days}
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Daniels
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-10 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: "0"
|
54
54
|
version:
|
55
|
-
description:
|
55
|
+
description: Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script
|
56
56
|
email: james@marginleft.com
|
57
57
|
executables: []
|
58
58
|
|
@@ -69,14 +69,23 @@ files:
|
|
69
69
|
- Rakefile
|
70
70
|
- VERSION
|
71
71
|
- lib/redline.rb
|
72
|
-
- lib/redline/
|
72
|
+
- lib/redline/billing/base.rb
|
73
|
+
- lib/redline/customer/base.rb
|
74
|
+
- lib/redline/customer/instance.rb
|
75
|
+
- lib/redline/customer/settings.rb
|
76
|
+
- lib/redline/subscription/base.rb
|
77
|
+
- lib/redline/subscription/instance.rb
|
78
|
+
- lib/redline/subscription/settings.rb
|
73
79
|
- redline.gemspec
|
80
|
+
- spec/billing_spec.rb
|
81
|
+
- spec/customer_spec.rb
|
74
82
|
- spec/db/database.yml
|
75
83
|
- spec/db/models.rb
|
76
84
|
- spec/db/schema.rb
|
77
85
|
- spec/redline_spec.rb
|
78
86
|
- spec/spec.opts
|
79
87
|
- spec/spec_helper.rb
|
88
|
+
- spec/subscription_spec.rb
|
80
89
|
has_rdoc: true
|
81
90
|
homepage: http://github.com/jamesdaniels/redline
|
82
91
|
licenses: []
|
@@ -104,9 +113,12 @@ rubyforge_project:
|
|
104
113
|
rubygems_version: 1.3.5
|
105
114
|
signing_key:
|
106
115
|
specification_version: 3
|
107
|
-
summary:
|
116
|
+
summary: Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script
|
108
117
|
test_files:
|
118
|
+
- spec/billing_spec.rb
|
119
|
+
- spec/customer_spec.rb
|
109
120
|
- spec/db/models.rb
|
110
121
|
- spec/db/schema.rb
|
111
122
|
- spec/redline_spec.rb
|
112
123
|
- spec/spec_helper.rb
|
124
|
+
- spec/subscription_spec.rb
|