- <%= hidden_field_tag 'plan_id', @plan.id %>
+ <%= hidden_field_tag 'price_id', @price.id %>
<%= submit_tag _('Subscribe'), id: 'js-stripe-submit', disabled: true, data: { disable_with: 'Processing...' } %>
<%= link_to _('Cancel'), pro_plans_path, class: 'settings__cancel-button' %>
diff --git a/app/views/alaveteli_pro/subscriptions/_subscription.html.erb b/app/views/alaveteli_pro/subscriptions/_subscription.html.erb
index d46ad91a98..c021268225 100644
--- a/app/views/alaveteli_pro/subscriptions/_subscription.html.erb
+++ b/app/views/alaveteli_pro/subscriptions/_subscription.html.erb
@@ -1,12 +1,12 @@
- <%= subscription.plan.product.name %>
+ <%= subscription.price.product.name %>
- <%= format_currency(subscription.plan.amount_with_tax) %>
- <%= billing_frequency(subscription.plan) %>
+ <%= format_currency(subscription.price.unit_amount_with_tax) %>
+ <%= billing_frequency(subscription.price) %>
<% if subscription.discounted? %>
<%= _('{{discounted_amount}} with discount ' \
diff --git a/config/general.yml-example b/config/general.yml-example
index 630a11fbe2..63e49ecf1a 100644
--- a/config/general.yml-example
+++ b/config/general.yml-example
@@ -1202,6 +1202,21 @@ STRIPE_SECRET_KEY: ''
# ---
STRIPE_NAMESPACE: ''
+# List of Stripe Subscription Prices IDs which if a user signs up to will grant
+# them access to Alaveteli Pro. Rendered on the Pro pricing pages in the ordered
+# defined here.
+#
+# STRIPE_PRICE_IDS - Array Stripe Price IDs
+#
+# Examples:
+#
+# STRIPE_PRICE_IDS:
+# - pro
+# - pro-annual-billing
+#
+# ---
+STRIPE_PRICE_IDS: []
+
# Stripe.com webhook secret. Only required for Alaveteli Pro.
#
# STRIPE_WEBHOOK_SECRET: - String (default: '')
diff --git a/lib/configuration.rb b/lib/configuration.rb
index 00f3aef75f..2629833fc5 100644
--- a/lib/configuration.rb
+++ b/lib/configuration.rb
@@ -109,6 +109,7 @@ module AlaveteliConfiguration
SMTP_MAILER_PORT: 25,
SMTP_MAILER_USER_NAME: '',
STRIPE_NAMESPACE: '',
+ STRIPE_PRICE_IDS: [],
STRIPE_PUBLISHABLE_KEY: '',
STRIPE_SECRET_KEY: '',
STRIPE_TAX_RATE: '0.20',
diff --git a/spec/controllers/alaveteli_pro/plans_controller_spec.rb b/spec/controllers/alaveteli_pro/plans_controller_spec.rb
index 0a12f51164..e25b38e1bf 100644
--- a/spec/controllers/alaveteli_pro/plans_controller_spec.rb
+++ b/spec/controllers/alaveteli_pro/plans_controller_spec.rb
@@ -7,15 +7,20 @@
let(:stripe_helper) { StripeMock.create_test_helper }
let(:product) { stripe_helper.create_product }
- let!(:pro_plan) do
- stripe_helper.create_plan(
- id: 'pro', product: product.id, amount: 1000
+ before do
+ allow(AlaveteliConfiguration).to receive(:stripe_price_ids).
+ and_return(['pro'])
+ end
+
+ let!(:pro_price) do
+ stripe_helper.create_price(
+ id: 'pro', product: product.id, unit_amount: 1000
)
end
- let!(:alaveteli_pro_plan) do
- stripe_helper.create_plan(
- id: 'alaveteli-pro', product: product.id, amount: 1000
+ let!(:alaveteli_pro_price) do
+ stripe_helper.create_price(
+ id: 'alaveteli-pro', product: product.id, unit_amount: 1000
)
end
@@ -41,7 +46,7 @@
end
it 'uses the default plan for pricing info' do
- expect(assigns(:plans)).to eq([pro_plan])
+ expect(assigns(:prices)).to eq([pro_price])
end
end
@@ -68,13 +73,13 @@
sign_in user
end
- context 'with a valid plan' do
+ context 'with a valid price' do
before do
get :show, params: { id: 'pro' }
end
- it 'finds the specified plan' do
- expect(assigns(:plan)).to eq(pro_plan)
+ it 'finds the specified price' do
+ expect(assigns(:price)).to eq(pro_price)
end
it 'renders the plan page' do
@@ -94,7 +99,7 @@
end
it 'finds the specified plan' do
- expect(assigns(:plan)).to eq(alaveteli_pro_plan)
+ expect(assigns(:price)).to eq(alaveteli_pro_price)
end
it 'renders the plan page' do
@@ -113,7 +118,9 @@
Stripe::Customer.create(email: user.email,
source: stripe_helper.generate_card_token)
- Stripe::Subscription.create(customer: customer, plan: 'pro')
+ Stripe::Subscription.create(
+ customer: customer, items: [{ price: 'pro' }]
+ )
user.create_pro_account(stripe_customer_id: customer.id)
user.add_role(:pro)
get :show, params: { id: 'pro' }
@@ -135,8 +142,9 @@
Stripe::Customer.create(email: user.email,
source: stripe_helper.generate_card_token)
- subscription =
- Stripe::Subscription.create(customer: customer, plan: 'pro')
+ subscription = Stripe::Subscription.create(
+ customer: customer, items: [{ price: 'pro' }]
+ )
Stripe::Subscription.cancel(subscription.id)
user.create_pro_account(stripe_customer_id: customer.id)
@@ -152,7 +160,7 @@
end
end
- context 'with an invalid plan' do
+ context 'with an invalid price' do
it 'returns ActiveRecord::RecordNotFound' do
expect {
get :show, params: { id: 'invalid-123' }
diff --git a/spec/controllers/alaveteli_pro/subscriptions_controller_spec.rb b/spec/controllers/alaveteli_pro/subscriptions_controller_spec.rb
index 47cd0c38a7..7a4a7de766 100644
--- a/spec/controllers/alaveteli_pro/subscriptions_controller_spec.rb
+++ b/spec/controllers/alaveteli_pro/subscriptions_controller_spec.rb
@@ -8,9 +8,9 @@
let(:product) { stripe_helper.create_product }
- let!(:plan) do
- stripe_helper.create_plan(
- id: 'pro', product: product.id, amount: 1000
+ let!(:price) do
+ stripe_helper.create_price(
+ id: 'pro', product: product.id, unit_amount: 1000
)
end
@@ -72,14 +72,14 @@
to eq(user.email)
end
- it 'subscribes the user to the plan' do
- expect(assigns(:subscription).plan.id).to eq(plan.id)
+ it 'subscribes the user to the price' do
+ expect(assigns(:subscription).price.id).to eq(price.id)
expect(assigns(:pro_account).stripe_customer_id).
to eq(assigns(:subscription).customer)
end
- it 'sets subscription plan amount and tax percentage' do
- expect(assigns(:subscription).plan.amount).to eq 1000
+ it 'sets subscription price unit amount and tax percentage' do
+ expect(assigns(:subscription).price.unit_amount).to eq 1000
expect(assigns(:subscription).tax_percent).to eql 25.0
end
@@ -109,14 +109,14 @@
sign_in user
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
# reset user so authenticated_user reloads
controller.instance_variable_set(:@user, nil)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -136,7 +136,7 @@
before do
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -148,7 +148,7 @@
before do
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => 'coupon_code'
}
end
@@ -161,9 +161,9 @@
end
context 'with Stripe namespace and coupon code' do
- let!(:plan) do
- stripe_helper.create_plan(
- id: 'alaveteli-pro', product: product.id, amount: 1000
+ let!(:price) do
+ stripe_helper.create_price(
+ id: 'alaveteli-pro', product: product.id, unit_amount: 1000
)
end
@@ -179,7 +179,7 @@
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => 'coupon_code'
}
end
@@ -201,7 +201,7 @@
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -235,8 +235,7 @@
# we can't create a subscription in the incomplete status so we have
# to need a lot of stubs.
subscription = Stripe::Subscription.create(
- customer: customer,
- plan: 'pro'
+ customer: customer, items: [{ price: 'pro' }]
)
subs = double(:subscription_collection).as_null_object
@@ -249,7 +248,7 @@
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro'
+ 'price_id' => 'pro'
}
end
end
@@ -259,7 +258,7 @@
StripeMock.prepare_card_error(:card_declined, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -283,7 +282,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -308,7 +307,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -333,7 +332,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -358,7 +357,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -383,7 +382,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => ''
}
end
@@ -408,7 +407,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => 'INVALID'
}
end
@@ -433,7 +432,7 @@
StripeMock.prepare_error(error, :create_subscription)
post :create, params: {
'stripe_token' => token,
- 'plan_id' => 'pro',
+ 'price_id' => 'pro',
'coupon_code' => 'EXPIRED'
}
end
@@ -453,12 +452,12 @@
end
context 'when invalid params are submitted' do
- it 'redirects to the plan page if there is a plan' do
- post :create, params: { plan_id: 'pro' }
+ it 'redirects to the plan page if there is a price' do
+ post :create, params: { price_id: 'pro' }
expect(response).to redirect_to(plan_path('pro'))
end
- it 'redirects to the pricing page if there is no plan' do
+ it 'redirects to the pricing page if there is no price' do
post :create
expect(response).to redirect_to(pro_plans_path)
end
@@ -481,10 +480,20 @@
context 'with a signed-in user' do
let(:token) { stripe_helper.generate_card_token }
- let(:customer) { Stripe::Customer.create(source: token, plan: 'pro') }
+ let(:customer) do
+ Stripe::Customer.create(source: token)
+ end
+
+ let(:subscription) do
+ Stripe::Subscription.create(
+ customer: customer, items: [{ price: price.id }]
+ )
+ end
+
let(:pro_account) do
FactoryBot.create(:pro_account, stripe_customer_id: customer.id)
end
+
let(:user) { pro_account.user }
before do
@@ -569,13 +578,13 @@
context 'subscription invoice open' do
before do
- plan = double(id: 'pro', to_param: 'pro')
+ price = double(id: 'pro', to_param: 'pro')
subscription = double(
:subscription,
require_authorisation?: false,
invoice_open?: true,
- plan: plan
+ price: price
)
allow(pro_account.subscriptions).to receive(:retrieve).with('1').
@@ -741,16 +750,21 @@
let(:user) { FactoryBot.create(:pro_user) }
let!(:customer) do
- stripe_helper.create_plan(id: 'test', product: product.id)
- customer = Stripe::Customer.create({
+ price = stripe_helper.create_price(id: 'test', product: product.id)
+ customer = Stripe::Customer.create(
email: user.email,
- source: stripe_helper.generate_card_token,
- plan: 'test'
- })
+ source: stripe_helper.generate_card_token
+ )
user.pro_account.update!(stripe_customer_id: customer.id)
customer
end
+ let!(:subscription) do
+ Stripe::Subscription.create(
+ customer: customer, items: [{ price: price.id }]
+ )
+ end
+
before do
sign_in user
end
@@ -769,8 +783,7 @@
it 'assigns subscriptions' do
get :index
expect(assigns[:subscriptions].count).to eq(1)
- expect(assigns[:subscriptions].first.id).
- to eq(customer.subscriptions.first.id)
+ expect(assigns[:subscriptions].first.id).to eq(subscription.id)
end
end
end
@@ -807,7 +820,7 @@
context 'with a signed-in user' do
let(:user) { FactoryBot.create(:pro_user) }
- let(:plan) { stripe_helper.create_plan(id: 'test', product: product.id) }
+ let(:price) { stripe_helper.create_price(id: 'test', product: product.id) }
let(:customer) do
customer = Stripe::Customer.create({
@@ -819,7 +832,9 @@
end
let(:subscription) do
- Stripe::Subscription.create(customer: customer, plan: plan.id)
+ Stripe::Subscription.create(
+ customer: customer, items: [{ price: price.id }]
+ )
end
before do
@@ -851,7 +866,9 @@
email: 'test@example.org',
source: stripe_helper.generate_card_token
})
- Stripe::Subscription.create(customer: customer, plan: plan.id)
+ Stripe::Subscription.create(
+ customer: customer, items: [{ price: price.id }]
+ )
end
before do
@@ -979,7 +996,7 @@
end
context 'when invalid params are submitted' do
- it 'redirects to the plan page if there is a plan' do
+ it 'redirects to the plan page if there is a price' do
delete :destroy, params: { id: 'unknown' }
expect(response).to redirect_to(subscriptions_path)
end
diff --git a/spec/helpers/alaveteli_pro/plan_helper_spec.rb b/spec/helpers/alaveteli_pro/plan_helper_spec.rb
deleted file mode 100644
index bdcab955b1..0000000000
--- a/spec/helpers/alaveteli_pro/plan_helper_spec.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-require 'spec_helper'
-
-describe AlaveteliPro::PlanHelper do
- let(:plan) { double('Plan') }
-
- describe '#billing_frequency' do
- it 'returns "Billed: Daily" for daily plan' do
- allow(plan).to receive(:interval).and_return('day')
- allow(plan).to receive(:interval_count).and_return(1)
- expect(helper.billing_frequency(plan)).to eq('Billed: Daily')
- end
-
- it 'returns "Billed: Weekly" for weekly plan' do
- allow(plan).to receive(:interval).and_return('week')
- allow(plan).to receive(:interval_count).and_return(1)
- expect(helper.billing_frequency(plan)).to eq('Billed: Weekly')
- end
-
- it 'returns "Billed: Monthly" for monthly plan' do
- allow(plan).to receive(:interval).and_return('month')
- allow(plan).to receive(:interval_count).and_return(1)
- expect(helper.billing_frequency(plan)).to eq('Billed: Monthly')
- end
-
- it 'returns "Billed: Annually" for yearly plan' do
- allow(plan).to receive(:interval).and_return('year')
- allow(plan).to receive(:interval_count).and_return(1)
- expect(helper.billing_frequency(plan)).to eq('Billed: Annually')
- end
-
- it 'returns custom message for other intervals' do
- allow(plan).to receive(:interval).and_return('quarter')
- allow(plan).to receive(:interval_count).and_return(1)
- expect(helper.billing_frequency(plan)).to eq('Billed: every quarter')
- end
- end
-
- describe '#billing_interval' do
- it 'returns singular interval for interval_count of 1' do
- allow(plan).to receive(:interval).and_return('month')
- allow(plan).to receive(:interval_count).and_return(1)
- expect(helper.billing_interval(plan)).to eq('per user, per month')
- end
-
- it 'returns plural interval for interval_count greater than 1' do
- allow(plan).to receive(:interval).and_return('month')
- allow(plan).to receive(:interval_count).and_return(3)
- expect(helper.billing_interval(plan)).to eq('per user, every 3 months')
- end
- end
-end
diff --git a/spec/helpers/alaveteli_pro/price_helper_spec.rb b/spec/helpers/alaveteli_pro/price_helper_spec.rb
new file mode 100644
index 0000000000..e459231cc2
--- /dev/null
+++ b/spec/helpers/alaveteli_pro/price_helper_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe AlaveteliPro::PriceHelper do
+ let(:price) { double('Stripe::Price') }
+
+ describe '#billing_frequency' do
+ it 'returns "Billed: Daily" for daily price' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'day', 'interval_count' => 1)
+ expect(helper.billing_frequency(price)).to eq('Billed: Daily')
+ end
+
+ it 'returns "Billed: Weekly" for weekly price' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'week', 'interval_count' => 1)
+ expect(helper.billing_frequency(price)).to eq('Billed: Weekly')
+ end
+
+ it 'returns "Billed: Monthly" for monthly price' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'month', 'interval_count' => 1)
+ expect(helper.billing_frequency(price)).to eq('Billed: Monthly')
+ end
+
+ it 'returns "Billed: Annually" for yearly price' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'year', 'interval_count' => 1)
+ expect(helper.billing_frequency(price)).to eq('Billed: Annually')
+ end
+
+ it 'returns custom message for other intervals' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'quarter', 'interval_count' => 1)
+ expect(helper.billing_frequency(price)).to eq('Billed: every quarter')
+ end
+ end
+
+ describe '#billing_interval' do
+ it 'returns singular interval for interval_count of 1' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'month', 'interval_count' => 1)
+ expect(helper.billing_interval(price)).to eq('per user, per month')
+ end
+
+ it 'returns plural interval for interval_count greater than 1' do
+ allow(price).to receive(:recurring).
+ and_return('interval' => 'month', 'interval_count' => 3)
+ expect(helper.billing_interval(price)).to eq('per user, every 3 months')
+ end
+ end
+end
diff --git a/spec/models/alaveteli_pro/plan_spec.rb b/spec/models/alaveteli_pro/plan_spec.rb
deleted file mode 100644
index a061ae27d1..0000000000
--- a/spec/models/alaveteli_pro/plan_spec.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe AlaveteliPro::Plan do
- let(:plan) { double(:plan, amount: 833) }
- subject { described_class.new(plan) }
-
- describe '.list' do
- before { described_class.instance_variable_set(:@list, nil) }
-
- it 'returns an array with one pro plan' do
- pro_plan = double('pro_plan')
- allow(described_class).to receive(:retrieve).with('pro').
- and_return(pro_plan)
-
- expect(described_class.list).to eq([pro_plan])
- end
-
- it 'memoizes the result' do
- expect(described_class).to receive(:retrieve).once.
- and_return(double('pro_plan'))
- 2.times { described_class.list }
- end
- end
-
- describe '.retrieve' do
- it 'retrieves a plan from Stripe' do
- stripe_plan = double('stripe_plan')
- allow(Stripe::Plan).to receive(:retrieve).with('test_pro').
- and_return(stripe_plan)
- allow(described_class).to receive(:add_stripe_namespace).with('pro').
- and_return('test_pro')
-
- plan = described_class.retrieve('pro')
- expect(plan).to be_an_instance_of(described_class)
- end
-
- it 'returns nil if Stripe::InvalidRequestError is raised' do
- allow(Stripe::Plan).to receive(:retrieve).
- and_raise(Stripe::InvalidRequestError.new('', ''))
-
- expect(described_class.retrieve('invalid')).to be_nil
- end
- end
-
- describe '#to_param' do
- it 'removes the stripe namespace from the id' do
- plan = described_class.new(double('stripe_plan', id: 'test_pro'))
- allow(plan).to receive(:remove_stripe_namespace).with('test_pro').
- and_return('pro')
-
- expect(plan.to_param).to eq('pro')
- end
- end
-
- describe '#product' do
- let(:stripe_plan) { double('stripe_plan', product: 'prod_123') }
- let(:plan) { described_class.new(stripe_plan) }
-
- it 'retrieves the product from Stripe' do
- product = double('product')
- allow(Stripe::Product).to receive(:retrieve).with('prod_123').
- and_return(product)
-
- expect(plan.product).to eq(product)
- end
-
- it 'memoizes the result' do
- expect(Stripe::Product).to receive(:retrieve).once.
- and_return(double('product'))
- 2.times { plan.product }
- end
-
- it 'returns nil if there is no product_id' do
- allow(stripe_plan).to receive(:product).and_return(nil)
-
- expect(plan.product).to be_nil
- end
- end
-
- describe '#amount_with_tax' do
- context 'with the default tax rate' do
- it 'adds 20% tax to the plan amount' do
- expect(subject.amount_with_tax).to eq(1000)
- end
- end
-
- context 'with a custom tax rate' do
- before do
- allow(AlaveteliConfiguration).
- to receive(:stripe_tax_rate).and_return('0.25')
- end
-
- it 'adds 25% tax to the plan amount' do
- expect(subject.amount_with_tax).to eq(1041)
- end
- end
- end
-
- it 'delegates to the stripe plan' do
- expect(subject.amount).to eq(833)
- end
-
- describe '#tax_percent' do
- it 'returns the tax rate as a percentage' do
- allow(AlaveteliConfiguration).to receive(:stripe_tax_rate).
- and_return('0.20')
- expect(subject.tax_percent).to eq(20.0)
- end
- end
-end
diff --git a/spec/models/alaveteli_pro/price_spec.rb b/spec/models/alaveteli_pro/price_spec.rb
new file mode 100644
index 0000000000..444001d8d5
--- /dev/null
+++ b/spec/models/alaveteli_pro/price_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+RSpec.describe AlaveteliPro::Price do
+ let(:price) { double(:price, unit_amount: 833) }
+ subject { described_class.new(price) }
+
+ describe '.list' do
+ before { described_class.instance_variable_set(:@list, nil) }
+
+ let(:price_ids) { %w[price_1 price_2 price_3] }
+ let(:price_1) { double('AlaveteliPro::Price') }
+ let(:price_2) { double('AlaveteliPro::Price') }
+
+ before do
+ allow(AlaveteliConfiguration).to receive(:stripe_price_ids).
+ and_return(price_ids)
+ allow(described_class).to receive(:retrieve).with('price_1').
+ and_return(price_1)
+ allow(described_class).to receive(:retrieve).with('price_2').
+ and_return(price_2)
+ allow(described_class).to receive(:retrieve).with('price_3').
+ and_return(nil)
+ end
+
+ it 'returns a list of retrieved prices' do
+ expect(described_class.list).to eq([price_1, price_2])
+ end
+
+ it 'memoizes the result' do
+ expect(described_class).to receive(:retrieve).once.with('price_1')
+ expect(described_class).to receive(:retrieve).once.with('price_2')
+ expect(described_class).to receive(:retrieve).once.with('price_3')
+
+ 2.times { described_class.list }
+ end
+ end
+
+ describe '.retrieve' do
+ it 'retrieves a price from Stripe' do
+ stripe_price = double('stripe_price')
+ allow(Stripe::Price).to receive(:retrieve).with('test_pro').
+ and_return(stripe_price)
+ allow(described_class).to receive(:add_stripe_namespace).with('pro').
+ and_return('test_pro')
+
+ price = described_class.retrieve('pro')
+ expect(price).to be_an_instance_of(described_class)
+ end
+
+ it 'returns nil if Stripe::InvalidRequestError is raised' do
+ allow(Stripe::Price).to receive(:retrieve).
+ and_raise(Stripe::InvalidRequestError.new('', ''))
+
+ expect(described_class.retrieve('invalid')).to be_nil
+ end
+ end
+
+ describe '#to_param' do
+ it 'removes the stripe namespace from the id' do
+ price = described_class.new(double('stripe_price', id: 'test_pro'))
+ allow(price).to receive(:remove_stripe_namespace).with('test_pro').
+ and_return('pro')
+
+ expect(price.to_param).to eq('pro')
+ end
+ end
+
+ describe '#product' do
+ let(:stripe_price) { double('stripe_price', product: 'prod_123') }
+ let(:price) { described_class.new(stripe_price) }
+
+ it 'retrieves the product from Stripe' do
+ product = double('product')
+ allow(Stripe::Product).to receive(:retrieve).with('prod_123').
+ and_return(product)
+
+ expect(price.product).to eq(product)
+ end
+
+ it 'memoizes the result' do
+ expect(Stripe::Product).to receive(:retrieve).once.
+ and_return(double('product'))
+ 2.times { price.product }
+ end
+
+ it 'returns nil if there is no product_id' do
+ allow(stripe_price).to receive(:product).and_return(nil)
+
+ expect(price.product).to be_nil
+ end
+ end
+
+ describe '#unit_amount_with_tax' do
+ context 'with the default tax rate' do
+ it 'adds 20% tax to the price unit_amount' do
+ expect(subject.unit_amount_with_tax).to eq(1000)
+ end
+ end
+
+ context 'with a custom tax rate' do
+ before do
+ allow(AlaveteliConfiguration).
+ to receive(:stripe_tax_rate).and_return('0.25')
+ end
+
+ it 'adds 25% tax to the price unit_amount' do
+ expect(subject.unit_amount_with_tax).to eq(1041)
+ end
+ end
+ end
+
+ it 'delegates to the stripe price' do
+ expect(subject.unit_amount).to eq(833)
+ end
+
+ describe '#tax_percent' do
+ it 'returns the tax rate as a percentage' do
+ allow(AlaveteliConfiguration).to receive(:stripe_tax_rate).
+ and_return('0.20')
+ expect(subject.tax_percent).to eq(20.0)
+ end
+ end
+end
diff --git a/spec/views/alaveteli_pro/plans/index.html.erb_spec.rb b/spec/views/alaveteli_pro/plans/index.html.erb_spec.rb
index 3892685f00..ccff9152fc 100644
--- a/spec/views/alaveteli_pro/plans/index.html.erb_spec.rb
+++ b/spec/views/alaveteli_pro/plans/index.html.erb_spec.rb
@@ -7,19 +7,19 @@
let(:stripe_helper) { StripeMock.create_test_helper }
let(:product) { stripe_helper.create_product }
- let(:plan) { AlaveteliPro::Plan.new(stripe_plan) }
+ let(:price) { AlaveteliPro::Price.new(stripe_price) }
let(:cents_price) { 880 }
- let(:stripe_plan) do
- stripe_helper.create_plan(
- id: 'pro', product: product.id, amount: cents_price
+ let(:stripe_price) do
+ stripe_helper.create_price(
+ id: 'pro', product: product.id, unit_amount: cents_price
)
end
before do
allow(AlaveteliConfiguration).to receive(:iso_currency_code).
and_return('GBP')
- assign :plans, [plan]
+ assign :prices, [price]
assign :pro_site_name, 'AlaveteliPro'
end