chargify_api_ares 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +7 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +57 -0
  5. data/Guardfile +15 -0
  6. data/README.md +29 -37
  7. data/Rakefile +12 -5
  8. data/chargify_api_ares.gemspec +25 -63
  9. data/examples/coupons.rb +50 -0
  10. data/{samples → examples}/customers.rb +1 -1
  11. data/{samples → examples}/metered_components.rb +5 -2
  12. data/{samples → examples}/products.rb +1 -1
  13. data/{samples → examples}/subscriptions.rb +2 -2
  14. data/{samples → examples}/transactions.rb +2 -3
  15. data/lib/chargify_api_ares.rb +14 -314
  16. data/lib/chargify_api_ares/config.rb +16 -0
  17. data/lib/chargify_api_ares/resources/base.rb +14 -0
  18. data/lib/chargify_api_ares/resources/component.rb +4 -0
  19. data/lib/chargify_api_ares/resources/coupon.rb +19 -0
  20. data/lib/chargify_api_ares/resources/customer.rb +21 -0
  21. data/lib/chargify_api_ares/resources/payment_profile.rb +4 -0
  22. data/lib/chargify_api_ares/resources/product.rb +25 -0
  23. data/lib/chargify_api_ares/resources/product_family.rb +34 -0
  24. data/lib/chargify_api_ares/resources/site.rb +7 -0
  25. data/lib/chargify_api_ares/resources/statement.rb +4 -0
  26. data/lib/chargify_api_ares/resources/subscription.rb +123 -0
  27. data/lib/chargify_api_ares/resources/transaction.rb +17 -0
  28. data/lib/chargify_api_ares/resources/usage.rb +11 -0
  29. data/spec/factories.rb +28 -24
  30. data/spec/remote/remote.example.yml +6 -0
  31. data/spec/remote/remote_spec.rb +347 -452
  32. data/spec/remote/spec_helper.rb +11 -15
  33. data/spec/{base_spec.rb → resources/base_spec.rb} +1 -1
  34. data/spec/resources/coupon_spec.rb +35 -0
  35. data/spec/resources/customer_spec.rb +36 -0
  36. data/spec/resources/product_family_spec.rb +57 -0
  37. data/spec/resources/product_spec.rb +23 -0
  38. data/spec/{subscriptions_component_spec.rb → resources/subscription_component_spec.rb} +2 -2
  39. data/spec/{subscription_spec.rb → resources/subscription_spec.rb} +2 -2
  40. data/spec/resources/usage_spec.rb +44 -0
  41. data/spec/spec_helper.rb +6 -8
  42. data/spec/{mocks → support}/fake_resource.rb +0 -0
  43. metadata +148 -29
  44. data/VERSION +0 -1
  45. data/config/remote.example.yml +0 -7
  46. data/spec/components_spec.rb +0 -48
  47. data/spec/customer_spec.rb +0 -23
  48. data/spec/product_spec.rb +0 -23
@@ -0,0 +1,16 @@
1
+ module Chargify
2
+ class << self
3
+ attr_accessor :subdomain, :api_key, :site, :format, :timeout
4
+
5
+ def configure
6
+ yield self
7
+
8
+ Base.user = api_key
9
+ Base.password = 'X'
10
+ Base.timeout = timeout unless (timeout.blank?)
11
+
12
+ self.site ||= "https://#{subdomain}.chargify.com"
13
+ Base.site = site
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ module Chargify
2
+ class Base < ActiveResource::Base
3
+ self.format = :xml
4
+
5
+ def self.element_name
6
+ name.split(/::/).last.underscore
7
+ end
8
+
9
+ def to_xml(options = {})
10
+ options.merge!(:dasherize => false)
11
+ super
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module Chargify
2
+ class Component < Base
3
+ end
4
+ end
@@ -0,0 +1,19 @@
1
+ module Chargify
2
+ class Coupon < Base
3
+ def self.find_all_by_product_family_id(product_family_id)
4
+ Coupon.find(:all, :params => { :product_family_id => product_family_id })
5
+ end
6
+
7
+ def self.find_by_product_family_id_and_code(product_family_id, code)
8
+ Coupon.new get(:lookup, :product_family_id => product_family_id, :code => code)
9
+ end
10
+
11
+ def usage
12
+ get :usage
13
+ end
14
+
15
+ def archive
16
+ self.destroy
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Chargify
2
+ class Customer < Base
3
+ def self.find_by_reference(reference)
4
+ Customer.new get(:lookup, :reference => reference)
5
+ end
6
+
7
+ class Subscription < Base
8
+ self.prefix = "/customers/:customer_id/"
9
+ end
10
+
11
+ def subscriptions(params = {})
12
+ params.merge!({:customer_id => self.id})
13
+ Subscription.find(:all, :params => params)
14
+ end
15
+
16
+ def payment_profiles(params = {})
17
+ params.merge!({:customer_id => self.id})
18
+ PaymentProfile.find(:all, :params => params)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ module Chargify
2
+ class PaymentProfile < Base
3
+ end
4
+ end
@@ -0,0 +1,25 @@
1
+ module Chargify
2
+ class Product < Base
3
+ def self.find_by_handle(handle)
4
+ Product.new get(:lookup, :handle => handle)
5
+ end
6
+
7
+ protected
8
+
9
+ # Products are created in the scope of a ProductFamily, i.e. /product_families/nnn/products
10
+ #
11
+ # This alters the collection path such that it uses the product_family_id that is set on the
12
+ # attributes.
13
+ def create
14
+ pfid = begin
15
+ self.product_family_id
16
+ rescue NoMethodError
17
+ 0
18
+ end
19
+ connection.post("/product_families/#{pfid}/products.#{self.class.format.extension}", encode, self.class.headers).tap do |response|
20
+ self.id = id_from_response(response)
21
+ load_attributes_from_response(response)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ module Chargify
2
+ class ProductFamily < Base
3
+ def self.find_by_handle(handle, attributes = {})
4
+ ProductFamily.find(:one, :from => :lookup, :params => { :handle => handle })
5
+ end
6
+
7
+ class Product < Base
8
+ self.prefix = "/product_families/:product_family_id/"
9
+ end
10
+
11
+ class Component < Base
12
+ self.prefix = "/product_families/:product_family_id/"
13
+ end
14
+
15
+ class Coupon < Base
16
+ self.prefix = "/product_families/:product_family_id/"
17
+ end
18
+
19
+ def products(params = {})
20
+ params.merge!(:product_family_id => self.id)
21
+ ::Chargify::ProductFamily::Product.find(:all, :params => params)
22
+ end
23
+
24
+ def components(params = {})
25
+ params.merge!({:product_family_id => self.id})
26
+ ::Chargify::ProductFamily::Component.find(:all, :params => params)
27
+ end
28
+
29
+ def coupons(params = {})
30
+ params.merge!(:product_family_id => self.id)
31
+ ::Chargify::ProductFamily::Coupon.find(:all, :params => params)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ module Chargify
2
+ class Site < Base
3
+ def self.clear_data!(params = {})
4
+ post(:clear_data, params)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module Chargify
2
+ class Statement < Base
3
+ end
4
+ end
@@ -0,0 +1,123 @@
1
+ module Chargify
2
+ class Subscription < Base
3
+ def self.find_by_customer_reference(reference)
4
+ customer = Customer.find_by_reference(reference)
5
+ find(:first, :params => {:customer_id => customer.id})
6
+ end
7
+
8
+ # Strip off nested attributes of associations before saving, or type-mismatch errors will occur
9
+ def save
10
+ self.attributes.delete('customer')
11
+ self.attributes.delete('product')
12
+ self.attributes.delete('credit_card')
13
+ super
14
+ end
15
+
16
+ def cancel
17
+ destroy
18
+ end
19
+
20
+ def component(id)
21
+ Component.find(id, :params => {:subscription_id => self.id})
22
+ end
23
+
24
+ def components(params = {})
25
+ params.merge!({:subscription_id => self.id})
26
+ Component.find(:all, :params => params)
27
+ end
28
+
29
+ def payment_profile
30
+ self.respond_to?('credit_card') ? credit_card : nil
31
+ end
32
+
33
+ # Perform a one-time charge on an existing subscription.
34
+ # For more information, please see the one-time charge API docs available
35
+ # at: http://support.chargify.com/faqs/api/api-charges
36
+ def charge(attrs = {})
37
+ post :charges, {}, attrs.to_xml(:root => :charge)
38
+ end
39
+
40
+ def credit(attrs = {})
41
+ post :credits, {}, attrs.to_xml(:root => :credit)
42
+ end
43
+
44
+ def refund(attrs = {})
45
+ post :refunds, {}, attrs.to_xml(:root => :refund)
46
+ end
47
+
48
+ def reactivate(params = {})
49
+ put :reactivate, params
50
+ end
51
+
52
+ def reset_balance
53
+ put :reset_balance
54
+ end
55
+
56
+ def migrate(attrs = {})
57
+ post :migrations, :migration => attrs
58
+ end
59
+
60
+ def statement(id)
61
+ statement = Chargify::Statement.find(id)
62
+ raise ActiveResource::ResourceNotFound.new(nil) if (statement.subscription_id != self.id)
63
+ statement
64
+ end
65
+
66
+ def statements(params = {})
67
+ params.merge!(:subscription_id => self.id)
68
+ Statement.find(:all, :params => params)
69
+ end
70
+
71
+ def transactions(params = {})
72
+ params.merge!(:subscription_id => self.id)
73
+ Transaction.find(:all, :params => params)
74
+ end
75
+
76
+ def adjustment(attrs = {})
77
+ post :adjustments, {}, attrs.to_xml(:root => :adjustment)
78
+ end
79
+
80
+ def add_coupon(code)
81
+ post :add_coupon, :code => code
82
+ end
83
+
84
+ def remove_coupon(code=nil)
85
+ if code.nil?
86
+ delete :remove_coupon
87
+ else
88
+ delete :remove_coupon, :code => code
89
+ end
90
+ end
91
+
92
+ class Component < Base
93
+ self.prefix = "/subscriptions/:subscription_id/"
94
+
95
+ # All Subscription Components are considered already existing records, but the id isn't used
96
+ def id
97
+ self.component_id
98
+ end
99
+ end
100
+
101
+ class Statement < Base
102
+ self.prefix = "/subscriptions/:subscription_id/"
103
+ end
104
+
105
+ class Transaction < Base
106
+ self.prefix = "/subscriptions/:subscription_id/"
107
+
108
+ def full_refund(attrs = {})
109
+ return false if self.transaction_type != 'payment'
110
+
111
+ attrs.merge!(:amount_in_cents => self.amount_in_cents)
112
+ self.refund(attrs)
113
+ end
114
+
115
+ def refund(attrs = {})
116
+ return false if self.transaction_type != 'payment'
117
+
118
+ attrs.merge!(:payment_id => self.id)
119
+ Subscription.find(self.prefix_options[:subscription_id]).refund(attrs)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,17 @@
1
+ module Chargify
2
+ class Transaction < Base
3
+ def full_refund(attrs = {})
4
+ return false if self.transaction_type != 'payment'
5
+
6
+ attrs.merge!(:amount_in_cents => self.amount_in_cents)
7
+ self.refund(attrs)
8
+ end
9
+
10
+ def refund(attrs = {})
11
+ return false if self.transaction_type != 'payment'
12
+
13
+ attrs.merge!(:payment_id => self.id)
14
+ Subscription.find(self.subscription_id).refund(attrs)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Chargify
2
+ class Usage < Base
3
+ def subscription_id=(i)
4
+ self.prefix_options[:subscription_id] = i
5
+ end
6
+
7
+ def component_id=(i)
8
+ self.prefix_options[:component_id] = i
9
+ end
10
+ end
11
+ end
data/spec/factories.rb CHANGED
@@ -2,46 +2,50 @@ FactoryGirl.define do
2
2
  sequence :email do |n|
3
3
  "customer#{n}@example.com"
4
4
  end
5
-
5
+
6
+ sequence :product_name do |n|
7
+ "Product #{n}"
8
+ end
9
+
6
10
  sequence :customer_id do |n|
7
11
  n
8
12
  end
9
-
10
- factory :customer, :class => Chargify::Customer do |c|
11
- c.first_name { Faker::Name.first_name }
12
- c.last_name { Faker::Name.last_name }
13
- c.email { FactoryGirl.generate(:email) }
14
- c.organization { Faker::Company.name }
15
- c.created_at { 2.days.ago }
16
- c.updated_at { 1.hour.ago }
13
+
14
+ sequence :subscription_id do |n|
15
+ n
17
16
  end
18
-
19
-
17
+
20
18
  sequence :product_id do |n|
21
19
  n
22
20
  end
23
21
 
24
- sequence :product_name do |n|
25
- "Product #{n}"
22
+ factory :customer, :class => Chargify::Customer do |f|
23
+ f.first_name { Faker::Name.first_name }
24
+ f.last_name { Faker::Name.last_name }
25
+ f.email { FactoryGirl.generate(:email) }
26
+ f.organization { Faker::Company.name }
27
+ f.created_at { 2.days.ago }
28
+ f.updated_at { 1.hour.ago }
26
29
  end
27
30
 
28
- factory :product, :class => Chargify::Product do |p|
29
- p.name { FactoryGirl.generate(:product_name) }
31
+ factory :product, :class => Chargify::Product do |f|
32
+ f.name { FactoryGirl.generate(:product_name) }
30
33
  end
31
34
 
32
- sequence :subscription_id do |n|
33
- n
35
+ factory :product_family, :class => Chargify::ProductFamily do |f|
36
+ f.name { Faker::Name.name }
37
+ f.handle 'mining'
34
38
  end
35
39
 
36
- factory :subscription, :class => Chargify::Subscription do |s|
37
- s.balance_in_cents 500
38
- s.current_period_ends_at 3.days.from_now
40
+ factory :subscription, :class => Chargify::Subscription do |f|
41
+ f.balance_in_cents 500
42
+ f.current_period_ends_at 3.days.from_now
39
43
  end
40
44
 
41
- factory :subscription_with_extra_attrs, :parent => :subscription do |swea|
42
- swea.customer Chargify::Customer.new
43
- swea.product Chargify::Product.new
44
- swea.credit_card "CREDIT CARD"
45
+ factory :subscription_with_extra_attrs, :parent => :subscription do |f|
46
+ f.customer Chargify::Customer.new
47
+ f.product Chargify::Product.new
48
+ f.credit_card "CREDIT CARD"
45
49
  end
46
50
 
47
51
  factory :component, :class => Chargify::Component do |f|
@@ -0,0 +1,6 @@
1
+ # Copy this file to spec/remote/remote.yml and make adjustments as necessary.
2
+ #
3
+ # Note: Remote tests will only work when configured to run on a test site that uses the Bogus gateway.
4
+ # Warning: All data in the site specified will be cleared and replaced with test data.
5
+ api_key: your_api_key
6
+ site: your_site
@@ -1,514 +1,409 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- if run_remote_tests?
4
- describe "Remote" do
5
- before(:all) do
6
- clear_site_data
7
- setup_plans
8
- setup_customer
9
- end
10
-
11
- describe "creating a new subscription to a product with a trial" do
12
- context "when providing valid attributes for the customer and the payment profile" do
13
- before(:each) do
14
- @subscription = create_once(:subscription) do
15
- Chargify::Subscription.create(
16
- :product_handle => @@basic_plan.handle,
17
- :customer_attributes => valid_customer_attributes,
18
- :payment_profile_attributes => good_payment_profile_attributes
19
- )
20
- end
21
- end
22
-
23
- it "successfully creates the subscription" do
24
- @subscription.should be_a(Chargify::Subscription)
25
- end
26
-
27
- it "sets the current_period_started_at attribute to now" do
28
- @subscription.current_period_started_at.utc.should be_close(now.utc, approximately)
29
- end
30
-
31
- it "sets the current_period_ends_at attribute to 1 month from now" do
32
- @subscription.current_period_ends_at.utc.should be_close(one_month_from_now.utc, approximately)
33
- end
34
-
35
- it "is in the trialing state" do
36
- @subscription.state.should == 'trialing'
37
- end
3
+ describe "Remote" do
4
+
5
+ let(:acme_projects) { Chargify::ProductFamily.create(:name => "Acme Projects") }
6
+
7
+ let(:basic_plan) do
8
+ Chargify::Product.create(
9
+ :product_family_id => acme_projects.id,
10
+ :name => "Basic Plan",
11
+ :handle => "basic",
12
+ :price_in_cents => 1000,
13
+ :interval => 1,
14
+ :interval_unit => 'month',
15
+ :trial_interval => 1,
16
+ :trial_interval_unit => 'month',
17
+ :trial_price_in_cents => 0)
18
+ end
19
+
20
+ let(:pro_plan) do
21
+ Chargify::Product.create(
22
+ :product_family_id => acme_projects.id,
23
+ :name => "Pro Plan",
24
+ :handle => "pro",
25
+ :price_in_cents => 5000,
26
+ :interval => 1,
27
+ :interval_unit => 'month')
28
+ end
29
+
30
+ let(:johnadoe) do
31
+ Chargify::Customer.create(
32
+ :first_name => "John",
33
+ :last_name => "Doe",
34
+ :email => "john.doe@example.com",
35
+ :reference => "johndoe")
36
+ end
37
+
38
+ let(:johnadoes_credit_card) { Chargify::PaymentProfile.create(good_payment_profile_attributes.merge(:customer_id => johnadoe.id)) }
39
+
40
+ before(:all) do
41
+ # Make sure the test site data is set up correctly
42
+ clear_site_data; acme_projects; basic_plan; pro_plan; johnadoe; johnadoes_credit_card
43
+ end
44
+
45
+ describe "creating a new subscription to a product with a trial" do
46
+ context "when providing valid attributes for the customer and the payment profile" do
47
+ before(:all) do
48
+ @subscription = Chargify::Subscription.create(
49
+ :product_handle => basic_plan.handle,
50
+ :customer_attributes => {
51
+ :first_name => "Rick",
52
+ :last_name => "James",
53
+ :email => "rick@example.com",
54
+ :reference => "rickjames"
55
+ },
56
+ :payment_profile_attributes => good_payment_profile_attributes)
38
57
  end
39
- end
40
-
41
- describe "creating a new subscription to a product without a trial" do
42
- context "when providing an existing customer reference and valid payment profile attributes" do
43
- before(:each) do
44
- @subscription = create_once(:subscription) do
45
- Chargify::Subscription.create(
46
- :product_handle => @@pro_plan.handle,
47
- :customer_reference => @@johnadoe.reference,
48
- :payment_profile_attributes => good_payment_profile_attributes
49
- )
50
- end
51
- end
52
-
53
- it "successfully creates the subscription" do
54
- @subscription.should be_a(Chargify::Subscription)
55
- end
56
-
57
- it "sets the current_period_started_at attribute to now" do
58
- @subscription.current_period_started_at.utc.should be_close(now.utc, approximately)
59
- end
60
-
61
- it "sets the current_period_ends_at attribute to 1 month from now" do
62
- @subscription.current_period_ends_at.utc.should be_close(one_month_from_now.utc, approximately)
63
- end
64
-
65
- it "is in the active state" do
66
- @subscription.state.should == 'active'
67
- end
68
-
69
- it "belongs to the existing customer" do
70
- @subscription.customer.should == @@johnadoe
71
- end
58
+
59
+ it "sets the current_period_started_at attribute to now" do
60
+ @subscription.current_period_started_at.utc.should be_within(60).of(now.utc)
72
61
  end
73
62
 
74
- context "when providing an existing customer reference and an existing payment profile" do
75
- before(:each) do
76
- @subscription = create_once(:subscription) do
77
- Chargify::Subscription.create(
78
- :product_handle => @@pro_plan.handle,
79
- :customer_reference => @@johnadoe.reference,
80
- :payment_profile_id => @@johnadoes_credit_card.id.to_s
81
- )
82
- end
83
- end
84
-
85
- it "successfully creates the subscription" do
86
- @subscription.should be_a(Chargify::Subscription)
87
- end
88
-
89
- it "is in the active state" do
90
- @subscription.state.should == 'active'
91
- end
92
-
93
- it "belongs to the existing customer" do
94
- @subscription.customer.should == @@johnadoe
95
- end
96
-
97
- it "uses the provided credit card" do
98
- expected_card = Chargify::PaymentProfile.find(@@johnadoes_credit_card.id)
99
- @subscription.payment_profile.id.should == @@johnadoes_credit_card.id
100
- @subscription.payment_profile.attributes.should == expected_card.attributes
101
- end
63
+ it "sets the current_period_ends_at attribute to 1 month from now" do
64
+ @subscription.current_period_ends_at.utc.should be_within(60).of(one_month_from_now.utc)
102
65
  end
103
66
 
104
- context "when providing valid attributes for the customer and attributes for a credit card that cannot be stored" do
105
- before(:each) do
106
- @customer_attributes = valid_customer_attributes.dup
107
- @subscription = create_once(:subscription) do
108
- Chargify::Subscription.create(
109
- :product_handle => @@basic_plan.handle,
110
- :customer_attributes => @customer_attributes,
111
- :payment_profile_attributes => unstorable_payment_profile_attributes
112
- )
113
- end
114
- end
115
-
116
- it "does not create the subscription" do
117
- @subscription.should_not be_valid
118
- end
119
-
120
- it "does not create the customer" do
121
- lambda {
122
- Chargify::Customer.find_by_reference(@customer_attributes[:reference])
123
- }.should raise_error(ActiveResource::ResourceNotFound)
124
- end
67
+ it "is in the trialing state" do
68
+ @subscription.state.should == 'trialing'
125
69
  end
126
-
127
70
  end
128
-
129
- describe "importing a subscription to a product with a trial and a next billing date 10 days from now" do
130
- context "when giving valid attributes for the customer and the payment profile" do
131
- before(:each) do
132
- @subscription = create_once(:subscription) do
133
- Chargify::Subscription.create(
134
- :product_handle => @@basic_plan.handle,
135
- :customer_attributes => valid_customer_attributes,
136
- :payment_profile_attributes => pretokenized_card_attributes,
137
- :next_billing_at => ten_days_from_now.utc
138
- )
139
- end
140
- end
141
-
142
- it "successfully creates the subscription" do
143
- @subscription.should be_a(Chargify::Subscription)
144
- end
145
-
146
- it "sets the current_period_started_at attribute to now" do
147
- @subscription.current_period_started_at.utc.should be_close(now.utc, approximately)
148
- end
149
-
150
- it "sets the current_period_ends_at attribute to 1 month from now" do
151
- @subscription.current_period_ends_at.utc.should be_close(ten_days_from_now.utc, approximately)
152
- end
153
-
154
- it "is in the active state" do
155
- @subscription.state.should == 'active'
156
- end
71
+ end
72
+
73
+ describe "creating a new subscription to a product without a trial" do
74
+ context "when providing an existing customer reference and valid payment profile attributes" do
75
+ before(:all) do
76
+ @subscription = Chargify::Subscription.create(
77
+ :product_handle => pro_plan.handle,
78
+ :customer_reference => johnadoe.reference,
79
+ :payment_profile_attributes => good_payment_profile_attributes)
157
80
  end
158
- end
159
81
 
160
- describe "creating failed subscriptions" do
161
- context "due to providing payment profile attribtues for a card that will be declined" do
162
- before(:each) do
163
- @customer_attributes = valid_customer_attributes.dup
164
- @subscription = create_once(:subscription) do
165
- Chargify::Subscription.create(
166
- :product_handle => @@pro_plan.handle,
167
- :customer_attributes => @customer_attributes,
168
- :payment_profile_attributes => declined_payment_profile_attributes
169
- )
170
- end
171
- end
172
-
173
- it "does not create the subscription" do
174
- @subscription.should_not be_valid
175
- end
176
-
177
- it "does not create the customer" do
178
- lambda {
179
- Chargify::Customer.find_by_reference(@customer_attributes[:reference])
180
- }.should raise_error(ActiveResource::ResourceNotFound)
181
- end
82
+ it "sets the current_period_started_at attribute to now" do
83
+ @subscription.current_period_started_at.utc.should be_within(60).of(now.utc)
182
84
  end
183
- end
184
-
185
- describe "cancelling a subscription" do
186
- before(:each) do
187
- @subscription = create_once(:subscription) do
188
- Chargify::Subscription.create(
189
- :product_handle => @@pro_plan.handle,
190
- :customer_reference => @@johnadoe.reference,
191
- :payment_profile_attributes => good_payment_profile_attributes
192
- )
193
- end
194
- @subscription.cancel
85
+
86
+ it "sets the current_period_ends_at attribute to 1 month from now" do
87
+ @subscription.current_period_ends_at.utc.should be_within(60).of(one_month_from_now.utc)
195
88
  end
196
-
197
- it "is in the canceled state" do
198
- Chargify::Subscription.find(@subscription.id).state.should == 'canceled'
89
+
90
+ it "is in the active state" do
91
+ @subscription.state.should == 'active'
92
+ end
93
+
94
+ it "belongs to the existing customer" do
95
+ @subscription.customer.should == johnadoe
199
96
  end
200
97
  end
201
-
202
- describe "reactivating a subscriptions" do
203
- before(:each) do
204
- @subscription = create_once(:subscription) do
205
- Chargify::Subscription.create(
206
- :product_handle => @@pro_plan.handle,
207
- :customer_reference => @@johnadoe.reference,
208
- :payment_profile_attributes => good_payment_profile_attributes
209
- )
210
- end
211
- @subscription.cancel
212
- @subscription.reload.state.should == 'canceled'
213
- @subscription.reactivate
98
+
99
+ context "when providing an existing customer reference and an existing payment profile" do
100
+ before(:all) do
101
+ @subscription = Chargify::Subscription.create(
102
+ :product_handle => pro_plan.handle,
103
+ :customer_reference => johnadoe.reference,
104
+ :payment_profile_id => johnadoes_credit_card.id.to_s)
214
105
  end
215
-
216
- it "puts it in the active state" do
217
- @subscription.reload.state.should == 'active'
106
+
107
+ it "is in the active state" do
108
+ @subscription.state.should == 'active'
218
109
  end
219
- end
220
-
221
- describe "adding a one time charge" do
222
- before(:each) do
223
- @subscription = create_once(:subscription) do
224
- Chargify::Subscription.create(
225
- :product_handle => @@pro_plan.handle,
226
- :customer_reference => @@johnadoe.reference,
227
- :payment_profile_attributes => good_payment_profile_attributes
228
- )
229
- end
110
+
111
+ it "belongs to the existing customer" do
112
+ @subscription.customer.should == johnadoe
230
113
  end
231
- it "creates a charge and payment" do
232
- lambda{
233
- @subscription.charge(:amount => 7, :memo => 'One Time Charge')
234
- }.should change{@subscription.reload.transactions.size}.by(2)
235
- @subscription.transactions.first.amount_in_cents.should == 700
114
+
115
+ it "uses the provided credit card" do
116
+ expected_card = Chargify::PaymentProfile.find(johnadoes_credit_card.id)
117
+ @subscription.payment_profile.id.should == johnadoes_credit_card.id
118
+ @subscription.payment_profile.attributes.should == expected_card.attributes
236
119
  end
237
120
  end
238
-
239
- describe "adding a credit" do
240
- before(:each) do
241
- @subscription = create_once(:subscription) do
242
- Chargify::Subscription.create(
243
- :product_handle => @@pro_plan.handle,
244
- :customer_reference => @@johnadoe.reference,
245
- :payment_profile_attributes => good_payment_profile_attributes
246
- )
247
- end
121
+
122
+ context "when providing valid attributes for the customer and attributes for a credit card that cannot be stored" do
123
+ before(:all) do
124
+ @subscription = Chargify::Subscription.create(
125
+ :product_handle => basic_plan.handle,
126
+ :customer_attributes => {
127
+ :first_name => "Ziggy",
128
+ :last_name => "Marley",
129
+ :email => "ziggy@example.com",
130
+ :reference => "ziggy"
131
+ },
132
+ :payment_profile_attributes => unstorable_payment_profile_attributes)
248
133
  end
249
-
250
- it "creates a credit" do
251
- lambda{
252
- @subscription.credit(:amount => 7, :memo => 'credit')
253
- }.should change{@subscription.reload.transactions.size}.by(1)
254
- @subscription.transactions.first.amount_in_cents.should == -700
134
+
135
+ it "does not create the subscription" do
136
+ @subscription.should_not be_valid
255
137
  end
256
- end
257
-
258
- describe "adding a refund" do
259
- before(:each) do
260
- @subscription = create_once(:subscription) do
261
- Chargify::Subscription.create(
262
- :product_handle => @@pro_plan.handle,
263
- :customer_reference => @@johnadoe.reference,
264
- :payment_profile_attributes => good_payment_profile_attributes
265
- )
266
- end
267
138
 
268
- @subscription.charge(:amount => 7, :memo => 'One Time Charge')
269
- @subscription.reload
139
+ it "does not create the customer" do
140
+ lambda {
141
+ Chargify::Customer.find_by_reference("ziggy")
142
+ }.should raise_error(ActiveResource::ResourceNotFound)
270
143
  end
144
+ end
271
145
 
272
- it "creates a refund" do
273
- lambda{
274
- @subscription.refund :payment_id => @subscription.transactions[0].id, :amount => 7,
275
- :memo => 'Refunding One Time Charge'
276
- }.should change{@subscription.reload.transactions.size}.by(1)
277
- @subscription.transactions.first.amount_in_cents.should == 700
278
- @subscription.transactions.first.transaction_type.should == 'refund'
146
+ end
147
+
148
+ describe "importing a subscription to a product with a trial and a next billing date 10 days from now" do
149
+ context "when giving valid attributes for the customer and the payment profile" do
150
+ before(:all) do
151
+ @subscription = Chargify::Subscription.create(
152
+ :product_handle => basic_plan.handle,
153
+ :customer_attributes => {
154
+ :first_name => "John",
155
+ :last_name => "Denver",
156
+ :email => "john.denver@example.com",
157
+ :reference => "johndenver"
158
+ },
159
+ :payment_profile_attributes => pretokenized_card_attributes,
160
+ :next_billing_at => ten_days_from_now.utc)
161
+ @subscription.should be_a(Chargify::Subscription)
279
162
  end
280
163
 
281
- context "via subscription payment (Chargify::Subscription::Transaction)" do
282
- before :each do
283
- @payment = @subscription.transactions.first
284
- end
285
-
286
- it "creates a refund" do
287
- lambda{
288
- @payment.refund :amount => 7, :memo => 'Refunding One Time Charge'
289
- }.should change{@subscription.reload.transactions.size}.by(1)
290
- @subscription.transactions.first.amount_in_cents.should == 700
291
- @subscription.transactions.first.transaction_type.should == 'refund'
292
- end
293
-
294
- it "creates a full refund" do
295
- lambda{
296
- @payment.full_refund :memo => 'Refunding One Time Charge'
297
- }.should change{@subscription.reload.transactions.size}.by(1)
298
- @subscription.transactions.first.amount_in_cents.should == 700
299
- @subscription.transactions.first.transaction_type.should == 'refund'
300
- end
164
+ it "sets the current_period_started_at attribute to now" do
165
+ @subscription.current_period_started_at.utc.should be_within(60).of(now.utc)
301
166
  end
302
167
 
303
- context "via site payment (Chargify::Transaction)" do
304
- before :each do
305
- @site_payment = Chargify::Transaction.find(:first)
306
- end
307
-
308
- it "creates a refund" do
309
- lambda{
310
- @site_payment.refund :amount => 7, :memo => 'Refunding One Time Charge'
311
- }.should change{@subscription.reload.transactions.size}.by(1)
312
- @subscription.transactions.first.amount_in_cents.should == 700
313
- @subscription.transactions.first.transaction_type.should == 'refund'
314
- end
315
-
316
- it "creates a full refund" do
317
- lambda{
318
- @site_payment.full_refund :memo => 'Refunding One Time Charge'
319
- }.should change{@subscription.reload.transactions.size}.by(1)
320
- @subscription.transactions.first.amount_in_cents.should == 700
321
- @subscription.transactions.first.transaction_type.should == 'refund'
322
- end
168
+ it "sets the current_period_ends_at attribute to 1 month from now" do
169
+ @subscription.current_period_ends_at.utc.should be_within(60).of(ten_days_from_now.utc)
323
170
  end
324
- end
325
-
326
- def already_cleared_site_data?
327
- @@already_cleared_site_data ||= nil
328
- @@already_cleared_site_data == true
329
- end
330
-
331
- def cleared_site_data!
332
- @@already_cleared_site_data = true
333
- end
334
-
335
- def clear_site_data
336
- return if already_cleared_site_data?
337
- begin
338
- Chargify::Site.clear_data!
339
- cleared_site_data!
340
- rescue ActiveResource::ForbiddenAccess
341
- raise StandardError.new("Remote specs may only be run against a site in test mode")
171
+
172
+ it "is in the active state" do
173
+ @subscription.state.should == 'active'
342
174
  end
343
175
  end
176
+ end
344
177
 
345
- # Create Basic and Pro products in the Acme Projects family
346
- def setup_plans
347
- begin
348
- @@acme_projects ||= Chargify::ProductFamily.find_by_handle('acme-projects')
349
- rescue ActiveResource::ResourceNotFound
350
- @@acme_projects = Chargify::ProductFamily.new(
351
- :name => "Acme Projects"
352
- )
353
- result = @@acme_projects.save
354
- result.should be_true
355
- end
356
-
357
- begin
358
- @@basic_plan ||= Chargify::Product.find_by_handle('basic')
359
- rescue ActiveResource::ResourceNotFound
360
- @@basic_plan = Chargify::Product.new(
361
- :product_family_id => @@acme_projects.id,
362
- :name => "Basic Plan",
363
- :handle => "basic",
364
- :price_in_cents => 1000,
365
- :interval => 1,
366
- :interval_unit => 'month',
367
- :trial_interval => 1,
368
- :trial_interval_unit => 'month',
369
- :trial_price_in_cents => 0
370
- )
371
- result = @@basic_plan.save
372
- result.should be_true
178
+ describe "creating failed subscriptions" do
179
+ context "due to providing payment profile attribtues for a card that will be declined" do
180
+ before(:all) do
181
+ @subscription = Chargify::Subscription.create(
182
+ :product_handle => pro_plan.handle,
183
+ :customer_attributes => {
184
+ :first_name => "Frank",
185
+ :last_name => "Sinatra",
186
+ :email => "frank.sinatra@example.com",
187
+ :reference => "franksinatra"
188
+ },
189
+ :payment_profile_attributes => declined_payment_profile_attributes)
373
190
  end
374
191
 
375
- begin
376
- @@pro_plan ||= Chargify::Product.find_by_handle('pro')
377
- rescue ActiveResource::ResourceNotFound
378
- @@pro_plan = Chargify::Product.new(
379
- :product_family_id => @@acme_projects.id,
380
- :name => "Pro Plan",
381
- :handle => "pro",
382
- :price_in_cents => 5000,
383
- :interval => 1,
384
- :interval_unit => 'month'
385
- )
386
- result = @@pro_plan.save
387
- result.should be_true
192
+ it "does not create the subscription" do
193
+ @subscription.should_not be_valid
388
194
  end
389
- end
390
195
 
391
- def setup_customer
392
- # Create a customer
393
- begin
394
- @@johnadoe ||= Chargify::Customer.find_by_reference('a')
395
- rescue ActiveResource::ResourceNotFound
396
- @@johnadoe = Chargify::Customer.new(valid_customer_attributes)
397
- result = @@johnadoe.save
398
- result.should be_true
399
-
400
- @@johnadoes_credit_card = Chargify::PaymentProfile.new(
401
- good_payment_profile_attributes.merge(:customer_id => @@johnadoe.id)
402
- )
403
- result = @@johnadoes_credit_card.save
404
- result.should be_true
196
+ it "does not create the customer" do
197
+ lambda {
198
+ Chargify::Customer.find_by_reference("franksinatra")
199
+ }.should raise_error(ActiveResource::ResourceNotFound)
405
200
  end
406
201
  end
407
-
408
- def now
409
- Time.now
410
- end
411
-
412
- def one_month_from_now
413
- Time.now + 1.month
202
+ end
203
+
204
+ describe "cancelling a subscription" do
205
+ before(:all) do
206
+ @subscription = Chargify::Subscription.create(
207
+ :product_handle => pro_plan.handle,
208
+ :customer_reference => johnadoe.reference,
209
+ :payment_profile_attributes => good_payment_profile_attributes)
414
210
  end
415
-
416
- def ten_days_from_now
417
- 10.days.from_now
211
+
212
+ it "is in the canceled state" do
213
+ @subscription.cancel
214
+ Chargify::Subscription.find(@subscription.id).state.should == 'canceled'
418
215
  end
419
-
420
- # Gives a reasonable range for time comparisons
421
- def approximately
422
- @approximately ||= 5.minutes
216
+ end
217
+
218
+ describe "reactivating a subscriptions" do
219
+ before(:all) do
220
+ @subscription = Chargify::Subscription.create(
221
+ :product_handle => pro_plan.handle,
222
+ :customer_reference => johnadoe.reference,
223
+ :payment_profile_attributes => good_payment_profile_attributes)
423
224
  end
424
-
425
- def valid_customer_attributes
426
- initial = next_customer_initial
427
- {
428
- :first_name => "John #{initial.upcase}",
429
- :last_name => "Doe",
430
- :email => "john.#{initial}.doe@example.com",
431
- :reference => initial
432
- }
225
+
226
+ it "puts it in the active state" do
227
+ @subscription.cancel
228
+ @subscription.reload.state.should == 'canceled'
229
+ @subscription.reactivate
230
+ @subscription.reload.state.should == 'active'
433
231
  end
434
-
435
- def good_payment_profile_attributes
436
- {
437
- :full_number => '1',
438
- :expiration_month => '12',
439
- :expiration_year => Time.now.year + 1
440
- }
232
+ end
233
+
234
+ describe "adding a one time charge" do
235
+ before(:all) do
236
+ @subscription = Chargify::Subscription.create(
237
+ :product_handle => pro_plan.handle,
238
+ :customer_reference => johnadoe.reference,
239
+ :payment_profile_attributes => good_payment_profile_attributes)
441
240
  end
442
241
 
443
- def declined_payment_profile_attributes
444
- {
445
- :full_number => '2',
446
- :expiration_month => '12',
447
- :expiration_year => Time.now.year + 1
448
- }
242
+ it "creates a charge and payment" do
243
+ lambda{
244
+ @subscription.charge(:amount => 7, :memo => 'One Time Charge')
245
+ }.should change{@subscription.reload.transactions.size}.by(2)
246
+ most_recent_transaction(@subscription).amount_in_cents.should == 700
449
247
  end
248
+ end
450
249
 
451
- def unstorable_payment_profile_attributes
452
- {
453
- :full_number => '3',
454
- :expiration_month => '12',
455
- :expiration_year => Time.now.year + 1
456
- }
250
+ describe "adding a credit" do
251
+ before(:all) do
252
+ @subscription = Chargify::Subscription.create(
253
+ :product_handle => pro_plan.handle,
254
+ :customer_reference => johnadoe.reference,
255
+ :payment_profile_attributes => good_payment_profile_attributes)
457
256
  end
458
257
 
459
- def expired_payment_profile_attributes
460
- {
461
- :full_number => '1',
462
- :expiration_month => '12',
463
- :expiration_year => Time.now.year - 1
464
- }
258
+ it "creates a credit" do
259
+ lambda{
260
+ @subscription.credit(:amount => 7, :memo => 'credit')
261
+ }.should change{@subscription.reload.transactions.size}.by(1)
262
+ most_recent_transaction(@subscription).amount_in_cents.should == -700
465
263
  end
466
-
467
- def pretokenized_card_attributes
468
- {
469
- :vault_token => '1',
470
- :current_vault => 'bogus',
471
- :expiration_month => '12',
472
- :expiration_year => Time.now.year + 1,
473
- :last_four => '1234',
474
- :card_type => 'visa'
475
- }
264
+ end
265
+
266
+ describe "adding a refund" do
267
+ before(:all) do
268
+ @subscription = Chargify::Subscription.create(
269
+ :product_handle => pro_plan.handle,
270
+ :customer_reference => johnadoe.reference,
271
+ :payment_profile_attributes => good_payment_profile_attributes)
476
272
  end
477
-
478
- # Don't create more than 26 customers until we add more initials :)
479
- def next_customer_initial
480
- @@customer_initial_index ||= 0
481
- initial = customer_initials[@@customer_initial_index]
482
- @@customer_initial_index += 1
483
- initial
273
+
274
+ before(:each) do
275
+ @subscription.charge(:amount => 7, :memo => 'One Time Charge')
276
+ @subscription.reload
277
+ @payment = most_recent_transaction(@subscription)
278
+ @payment.transaction_type.should == 'payment'
484
279
  end
485
-
486
- # An array of intials
487
- def customer_initials
488
- @customer_initials ||= ('a'..'z').to_a
280
+
281
+ context "via Chargify::Subscription#refund" do
282
+ it "creates a refund" do
283
+ lambda{
284
+ @subscription.refund :payment_id => @payment.id, :amount => 7,
285
+ :memo => 'Refunding One Time Charge'
286
+ }.should change{@subscription.reload.transactions.size}.by(1)
287
+
288
+ tx = most_recent_transaction(@subscription)
289
+
290
+ tx.amount_in_cents.should == 700
291
+ tx.transaction_type.should == 'refund'
292
+ end
489
293
  end
490
294
 
491
- # Allows us to create remote resources once per spec hierarchy
492
- def create_once(type, &block)
493
- hierarchy = example_group_hierarchy.collect(&:object_id).to_s
295
+ context "via Chargify::Transaction#refund" do
296
+ it "creates a refund" do
297
+ lambda{
298
+ @payment.refund :amount => 7, :memo => 'Refunding One Time Charge'
299
+ }.should change{@subscription.reload.transactions.size}.by(1)
300
+ tx = most_recent_transaction(@subscription)
301
+
302
+ tx.amount_in_cents.should == 700
303
+ tx.transaction_type.should == 'refund'
304
+ end
305
+
306
+ it "creates a full refund" do
307
+ lambda{
308
+ @payment.full_refund :memo => 'Refunding One Time Charge'
309
+ }.should change{@subscription.reload.transactions.size}.by(1)
310
+ tx = most_recent_transaction(@subscription)
494
311
 
495
- unless resource(type, hierarchy)
496
- register_resource(type, hierarchy, block.call)
312
+ tx.amount_in_cents.should == 700
313
+ tx.transaction_type.should == 'refund'
497
314
  end
498
- resource(type, hierarchy)
499
315
  end
500
-
501
- def resource(type, hierarchy)
502
- @@resources ||= {}
503
- @@resources[type] ||= {}
504
- @@resources[type][hierarchy]
316
+ end
317
+
318
+ def clear_site_data
319
+ begin
320
+ Chargify::Site.clear_data!
321
+ Chargify::Product.find(:all).count.should == 0
322
+ Chargify::Customer.find(:all).count.should == 0
323
+ Chargify::ProductFamily.find(:all).count.should == 0
324
+ rescue ActiveResource::ForbiddenAccess
325
+ raise StandardError.new("Remote specs may only be run against a site in test mode")
505
326
  end
506
-
507
- def register_resource(type, hierarchy, resource)
508
- @@resources ||= {}
509
- @@resources[type] ||= {}
510
- @@resources[type][hierarchy] = resource
327
+ end
328
+
329
+ def now
330
+ Time.now
331
+ end
332
+
333
+ def one_month_from_now
334
+ Time.now + 1.month
335
+ end
336
+
337
+ def ten_days_from_now
338
+ 10.days.from_now
339
+ end
340
+
341
+ def good_payment_profile_attributes
342
+ {
343
+ :full_number => '1',
344
+ :expiration_month => '12',
345
+ :expiration_year => Time.now.year + 1
346
+ }
347
+ end
348
+
349
+ def declined_payment_profile_attributes
350
+ {
351
+ :full_number => '2',
352
+ :expiration_month => '12',
353
+ :expiration_year => Time.now.year + 1
354
+ }
355
+ end
356
+
357
+ def unstorable_payment_profile_attributes
358
+ {
359
+ :full_number => '3',
360
+ :expiration_month => '12',
361
+ :expiration_year => Time.now.year + 1
362
+ }
363
+ end
364
+
365
+ def expired_payment_profile_attributes
366
+ {
367
+ :full_number => '1',
368
+ :expiration_month => '12',
369
+ :expiration_year => Time.now.year - 1
370
+ }
371
+ end
372
+
373
+ def pretokenized_card_attributes
374
+ {
375
+ :vault_token => '1',
376
+ :current_vault => 'bogus',
377
+ :expiration_month => '12',
378
+ :expiration_year => Time.now.year + 1,
379
+ :last_four => '1234',
380
+ :card_type => 'visa'
381
+ }
382
+ end
383
+
384
+ # Allows us to create remote resources once per spec hierarchy
385
+ def create_once(type, &block)
386
+ hierarchy = example_group_hierarchy.collect(&:object_id).to_s
387
+
388
+ unless resource(type, hierarchy)
389
+ register_resource(type, hierarchy, block.call)
511
390
  end
391
+ resource(type, hierarchy)
392
+ end
393
+
394
+ def resource(type, hierarchy)
395
+ @resources ||= {}
396
+ @resources[type] ||= {}
397
+ @resources[type][hierarchy]
398
+ end
399
+
400
+ def register_resource(type, hierarchy, resource)
401
+ @resources ||= {}
402
+ @resources[type] ||= {}
403
+ @resources[type][hierarchy] = resource
404
+ end
512
405
 
406
+ def most_recent_transaction(scope)
407
+ scope.transactions.sort_by(&:id).last
513
408
  end
514
409
  end