chargify_api_ares 0.4.4 → 0.5.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.
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