Skip to content
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