Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support subscription items #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
3 changes: 1 addition & 2 deletions lib/stripe_mock/data/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ def initialize(data, options = {})
@limit = [[options[:limit] || 10, 100].min, 1].max # restrict @limit to 1..100
@starting_after = options[:starting_after]
if @data.first.is_a?(Hash) && @data.first[:created]
@data.sort_by! { |x| x[:created] }
@data.reverse!
@data.sort! { |x, y| y[:created] <=> x[:created] }
elsif @data.first.respond_to?(:created)
@data.sort_by { |x| x.created }
@data.reverse!
Expand Down
2 changes: 1 addition & 1 deletion lib/stripe_mock/request_handlers/customers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def new_customer(route, method_url, params, headers)
end

subscription = Data.mock_subscription({ id: new_id('su') })
subscription = resolve_subscription_changes(subscription, [plan], customers[ params[:id] ], params)
subscription.merge!(custom_subscription_params(plan, customers[ params[:id] ], params))
add_subscription_to_customer(customers[ params[:id] ], subscription)
subscriptions[subscription[:id]] = subscription
elsif params[:trial_end]
Expand Down
31 changes: 20 additions & 11 deletions lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,13 @@ def get_customer_subscription(customer, sub_id)
customer[:subscriptions][:data].find{|sub| sub[:id] == sub_id }
end

def resolve_subscription_changes(subscription, plans, customer, options = {})
subscription.merge!(custom_subscription_params(plans, customer, options))
subscription[:items][:data] = plans.map { |plan| Data.mock_subscription_item({ plan: plan }) }
subscription
end

def custom_subscription_params(plans, cus, options = {})
def custom_subscription_params(plan, cus, options = {})
verify_trial_end(options[:trial_end]) if options[:trial_end]

plan = plans.first if plans.size == 1

now = Time.now.utc.to_i
created_time = options[:created] || now
start_time = options[:current_period_start] || now
params = { customer: cus[:id], current_period_start: start_time, created: created_time }
params.merge!({ :plan => (plans.size == 1 ? plans.first : nil) })
params = { plan: plan, customer: cus[:id], current_period_start: start_time, created: created_time }
params.merge! options.select {|k,v| k =~ /application_fee_percent|quantity|metadata|tax_percent/}
# TODO: Implement coupon logic

Expand All @@ -33,6 +24,24 @@ def custom_subscription_params(plans, cus, options = {})
params.merge!({status: 'trialing', current_period_end: end_time, trial_start: start_time, trial_end: end_time})
end

items = options[:items] || []
items = items.values if items.respond_to?(:values)
if items.any?
items_data = []
items.each do |item|
plan = assert_existence(:plan, item[:plan], plans[item[:plan]])
quantity = item[:quantity] || 1
items_data.push(Data.mock_subscription_item(plan: plan, quantity: quantity, created: Time.now.utc.to_i))
end
params[:items] = Data.mock_list_object(items_data)
if items_data.size == 1
params.merge!(:plan => items_data[0][:plan], :quantity => items_data[0][:quantity])
elsif items_data.size > 1
params.delete(:plan)
params.delete(:quantity)
end
end

params
end

Expand Down
2 changes: 1 addition & 1 deletion lib/stripe_mock/request_handlers/invoices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def upcoming_invoice(route, method_url, params, headers)
invoice_date = Time.now.to_i
subscription_plan = assert_existence :plan, subscription_plan_id, plans[subscription_plan_id.to_s]
preview_subscription = Data.mock_subscription
preview_subscription = resolve_subscription_changes(preview_subscription, [subscription_plan], customer, { trial_end: params[:subscription_trial_end] })
preview_subscription.merge!(custom_subscription_params(subscription_plan, customer, { trial_end: params[:subscription_trial_end] }))
preview_subscription[:id] = subscription[:id]
preview_subscription[:quantity] = subscription_quantity
subscription_proration_date = params[:subscription_proration_date] || Time.now
Expand Down
93 changes: 60 additions & 33 deletions lib/stripe_mock/request_handlers/subscriptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,26 @@ def retrieve_customer_subscriptions(route, method_url, params, headers)
customer[:subscriptions]
end

def plan_id_from_params(params)
if params[:plan]
params[:plan].to_s
elsif params[:items]
items = params[:items]
items = items.values if items.respond_to?(:values)
item = items.find { |item| item[:plan] }
if item
item[:plan].to_s
end
end
end

def create_customer_subscription(route, method_url, params, headers)
route =~ method_url

subscription_plans = get_subscription_plans_from_params(params)
plan_id = plan_id_from_params(params)

plan = assert_existence :plan, plan_id, plans[plan_id]

customer = assert_existence :customer, $1, customers[$1]

if params[:source]
Expand All @@ -45,11 +61,10 @@ def create_customer_subscription(route, method_url, params, headers)
end

subscription = Data.mock_subscription({ id: (params[:id] || new_id('su')) })
subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params)
subscription.merge!(custom_subscription_params(plan, customer, params))

# Ensure customer has card to charge if plan has no trial and is not free
# Note: needs updating for subscriptions with multiple plans
verify_card_present(customer, subscription_plans.first, subscription, params)
verify_card_present(customer, plan, subscription, params)

if params[:coupon]
coupon_id = params[:coupon]
Expand All @@ -69,23 +84,25 @@ def create_customer_subscription(route, method_url, params, headers)
subscriptions[subscription[:id]] = subscription
add_subscription_to_customer(customer, subscription)

clear_top_level_plan_if_multiple_items(subscription)

subscriptions[subscription[:id]]
end

def create_subscription(route, method_url, params, headers)
route =~ method_url

subscription_plans = get_subscription_plans_from_params(params)
plan_id = plan_id_from_params(params)

plan = plan_id ? assert_existence(:plan, plan_id, plans[plan_id]) : nil

customer = params[:customer]
customer_id = customer.is_a?(Stripe::Customer) ? customer[:id] : customer.to_s
customer = assert_existence :customer, customer_id, customers[customer_id]

if subscription_plans && customer
subscription_plans.each do |plan|
unless customer[:currency].to_s == plan[:currency].to_s
raise Stripe::InvalidRequestError.new('lol', 'currency', http_status: 400)
end
if plan && customer
unless customer[:currency].to_s == plan[:currency].to_s
raise Stripe::InvalidRequestError.new('lol', 'currency', http_status: 400)
end
end

Expand All @@ -102,11 +119,10 @@ def create_subscription(route, method_url, params, headers)
end

subscription = Data.mock_subscription({ id: (params[:id] || new_id('su')) })
subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params)
subscription.merge!(custom_subscription_params(plan, customer, params))

# Ensure customer has card to charge if plan has no trial and is not free
# Note: needs updating for subscriptions with multiple plans
verify_card_present(customer, subscription_plans.first, subscription, params)
verify_card_present(customer, plan, subscription, params)

if params[:coupon]
coupon_id = params[:coupon]
Expand All @@ -126,6 +142,8 @@ def create_subscription(route, method_url, params, headers)
subscriptions[subscription[:id]] = subscription
add_subscription_to_customer(customer, subscription)

clear_top_level_plan_if_multiple_items(subscription)

subscriptions[subscription[:id]]
end

Expand Down Expand Up @@ -159,13 +177,22 @@ def update_subscription(route, method_url, params, headers)
customer[:default_source] = new_card[:id]
end

subscription_plans = get_subscription_plans_from_params(params)

# subscription plans are not being updated but load them for the response
if subscription_plans.empty?
subscription_plans = subscription[:items][:data].map { |item| item[:plan] }
# expand the plan for addition to the customer object
plan_id = plan_id_from_params(params)

unless plan_id
plan_id = if subscription[:plan]
subscription[:plan][:id]
elsif subscription[:items]
row = subscription[:items][:data].find { |item| item[:plan] }
if row
row[:plan][:id]
end
end
end

plan = plans[plan_id]

if params[:coupon]
coupon_id = params[:coupon]

Expand All @@ -181,20 +208,29 @@ def update_subscription(route, method_url, params, headers)
raise Stripe::InvalidRequestError.new("No such coupon: #{coupon_id}", 'coupon', http_status: 400)
end
end
verify_card_present(customer, subscription_plans.first, subscription)

assert_existence :plan, plan_id, plan
params[:plan] = plan if params[:plan]
verify_card_present(customer, plan, subscription)

if subscription[:cancel_at_period_end]
subscription[:cancel_at_period_end] = false
subscription[:canceled_at] = nil
end

if params[:quantity] && subscription[:items][:data].size > 1
raise Stripe::InvalidRequestError.new('Cannot update using quantity parameter when multiple plans exist on the subscription. Updates must be made to individual items instead.', nil, http_status: 400)
end

params[:current_period_start] = subscription[:current_period_start]
subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params)
subscription.merge!(custom_subscription_params(plan, customer, params))

# delete the old subscription, replace with the new subscription
customer[:subscriptions][:data].reject! { |sub| sub[:id] == subscription[:id] }
customer[:subscriptions][:data] << subscription

clear_top_level_plan_if_multiple_items(subscription)

subscription
end

Expand Down Expand Up @@ -228,20 +264,11 @@ def cancel_subscription(route, method_url, params, headers)

private

def get_subscription_plans_from_params(params)
plan_ids = if params[:plan]
[params[:plan].to_s]
elsif params[:items]
items = params[:items]
items = items.values if items.respond_to?(:values)
items.map { |item| item[:plan].to_s if item[:plan] }
else
[]
end
plan_ids.each do |plan_id|
assert_existence :plan, plan_id, plans[plan_id]
def clear_top_level_plan_if_multiple_items(subscription)
if subscription[:items][:data].size > 1
subscription[:plan] = nil
subscription[:quantity] = nil
end
plan_ids.map { |plan_id| plans[plan_id] }
end

def verify_card_present(customer, plan, subscription, params={})
Expand Down
2 changes: 1 addition & 1 deletion spec/shared_stripe_examples/charge_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@
end

it "stores all charges in memory" do
expect(Stripe::Charge.all.data.map(&:id).reverse).to eq([@charge.id, @charge2.id])
expect(Stripe::Charge.all.data.map(&:id)).to eq([@charge.id, @charge2.id])
end

it "defaults count to 10 charges" do
Expand Down
2 changes: 1 addition & 1 deletion spec/shared_stripe_examples/refund_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@
end

it "stores all charges in memory" do
expect(Stripe::Refund.all.data.map(&:id)).to eq([@refund2.id, @refund.id])
expect(Stripe::Refund.all.data.map(&:id)).to eq([@refund.id, @refund2.id])
end

it "defaults count to 10 charges" do
Expand Down
39 changes: 31 additions & 8 deletions spec/shared_stripe_examples/subscription_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def gen_card_tk
expect(customer.subscriptions.data).to be_empty
expect(customer.subscriptions.count).to eq(0)

sub = Stripe::Subscription.create({ items: [{ plan: 'silver' }],
sub = Stripe::Subscription.create({ items: [{ plan: 'silver', :quantity => 4 }],
customer: customer.id, metadata: { foo: "bar", example: "yes" } })

expect(sub.object).to eq('subscription')
Expand All @@ -31,6 +31,7 @@ def gen_card_tk

expect(customer.subscriptions.data.first.id).to eq(sub.id)
expect(customer.subscriptions.data.first.plan.to_hash).to eq(plan.to_hash)
expect(customer.subscriptions.data.first.quantity).to eq(4)
expect(customer.subscriptions.data.first.customer).to eq(customer.id)
expect(customer.subscriptions.data.first.metadata.foo).to eq( "bar" )
expect(customer.subscriptions.data.first.metadata.example).to eq( "yes" )
Expand Down Expand Up @@ -511,11 +512,12 @@ def gen_card_tk
customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk, plan: silver_plan.id)

sub = Stripe::Subscription.retrieve(customer.subscriptions.data.first.id)
sub.items = [{ plan: gold_plan.id, quantity: 2 }, { plan: addon_plan.id, quantity: 2 }]
sub.items = [{ plan: gold_plan.id, quantity: 4 }, { plan: addon_plan.id, quantity: 5 }]
expect(sub.save).to be_truthy

expect(sub.object).to eq('subscription')
expect(sub.plan).to be_nil
expect(sub.quantity).to be_nil

customer = Stripe::Customer.retrieve('test_customer_sub')
expect(customer.subscriptions.data).to_not be_empty
Expand All @@ -524,9 +526,12 @@ def gen_card_tk

expect(customer.subscriptions.data.first.id).to eq(sub.id)
expect(customer.subscriptions.data.first.plan).to be_nil
expect(customer.subscriptions.data.first.quantity).to be_nil
expect(customer.subscriptions.data.first.customer).to eq(customer.id)
expect(customer.subscriptions.data.first.items.data[0].plan.to_hash).to eq(gold_plan.to_hash)
expect(customer.subscriptions.data.first.items.data[0].quantity).to eq(4)
expect(customer.subscriptions.data.first.items.data[1].plan.to_hash).to eq(addon_plan.to_hash)
expect(customer.subscriptions.data.first.items.data[1].quantity).to eq(5)
end

it "updates a stripe customer's existing subscription with multple plans when multiple plans inside of items" do
Expand All @@ -537,11 +542,12 @@ def gen_card_tk
customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk)
sub = Stripe::Subscription.create(customer: customer.id, items: [{ plan: silver_plan.id }, { plan: addon1_plan.id }])

sub.items = [{ plan: gold_plan.id, quantity: 2 }, { plan: addon2_plan.id, quantity: 2 }]
sub.items = [{ plan: gold_plan.id, quantity: 1 }, { plan: addon2_plan.id, quantity: 5 }]
expect(sub.save).to be_truthy

expect(sub.object).to eq('subscription')
expect(sub.plan).to be_nil
expect(sub.quantity).to be_nil

customer = Stripe::Customer.retrieve('test_customer_sub')
expect(customer.subscriptions.data).to_not be_empty
Expand All @@ -550,9 +556,12 @@ def gen_card_tk

expect(customer.subscriptions.data.first.id).to eq(sub.id)
expect(customer.subscriptions.data.first.plan).to be_nil
expect(customer.subscriptions.data.first.quantity).to be_nil
expect(customer.subscriptions.data.first.customer).to eq(customer.id)
expect(customer.subscriptions.data.first.items.data[0].plan.to_hash).to eq(gold_plan.to_hash)
expect(customer.subscriptions.data.first.items.data[0].quantity).to eq(1)
expect(customer.subscriptions.data.first.items.data[1].plan.to_hash).to eq(addon2_plan.to_hash)
expect(customer.subscriptions.data.first.items.data[1].quantity).to eq(5)
end

it 'when adds coupon', live: true do
Expand Down Expand Up @@ -633,6 +642,20 @@ def gen_card_tk
expect(customer.subscriptions.data.first.plan.to_hash).to eq(free.to_hash)
end

it "throws an error when updating quantity and subscription has multiple plans" do
gold_plan = stripe_helper.create_plan(id: 'gold')
addon_plan = stripe_helper.create_plan(id: 'addon')
customer = Stripe::Customer.create(id: 'test_customer_sub', source: gen_card_tk)
sub = Stripe::Subscription.create(customer: customer.id, items: [{ plan: gold_plan.id }, { plan: addon_plan.id }])

sub.quantity = 5
expect { sub.save }.to raise_error {|e|
expect(e).to be_a Stripe::InvalidRequestError
expect(e.http_status).to eq(400)
expect(e.message).to eq('Cannot update using quantity parameter when multiple plans exist on the subscription. Updates must be made to individual items instead.')
}
end

[nil, 0].each do |trial_period_days|
it "throws an error when updating a customer with no card, and plan trail_period_days = #{trial_period_days}", live: true do
begin
Expand Down Expand Up @@ -908,13 +931,13 @@ def gen_card_tk
expect(subscription.items.object).to eq('list')
expect(subscription.items.data.class).to eq(Array)
expect(subscription.items.data.count).to eq(1)
expect(subscription.items.data.first.id).to eq('test_txn_default')
expect(subscription.items.data.first.created).to eq(1504716183)
expect(subscription.items.data.first.id).to eq('si_1AwFf62eZvKYlo2C9u6Dhf9')
expect(subscription.items.data.first.created).to eq(1504035973)
expect(subscription.items.data.first.object).to eq('subscription_item')
expect(subscription.items.data.first.plan.amount).to eq(0)
expect(subscription.items.data.first.plan.created).to eq(1466698898)
expect(subscription.items.data.first.plan.amount).to eq(999)
expect(subscription.items.data.first.plan.created).to eq(1504035972)
expect(subscription.items.data.first.plan.currency).to eq('usd')
expect(subscription.items.data.first.quantity).to eq(2)
expect(subscription.items.data.first.quantity).to eq(1)
end
end

Expand Down
Loading