diff --git a/lib/stripe_mock/data/list.rb b/lib/stripe_mock/data/list.rb index 4214c0e08..e5effdbfd 100644 --- a/lib/stripe_mock/data/list.rb +++ b/lib/stripe_mock/data/list.rb @@ -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! diff --git a/lib/stripe_mock/request_handlers/customers.rb b/lib/stripe_mock/request_handlers/customers.rb index 4928715df..c35930bc2 100644 --- a/lib/stripe_mock/request_handlers/customers.rb +++ b/lib/stripe_mock/request_handlers/customers.rb @@ -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] diff --git a/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb b/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb index 14df93f23..f6ec36bd2 100644 --- a/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +++ b/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb @@ -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 @@ -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 diff --git a/lib/stripe_mock/request_handlers/invoices.rb b/lib/stripe_mock/request_handlers/invoices.rb index 3e4434082..8f8e242bc 100644 --- a/lib/stripe_mock/request_handlers/invoices.rb +++ b/lib/stripe_mock/request_handlers/invoices.rb @@ -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 diff --git a/lib/stripe_mock/request_handlers/subscriptions.rb b/lib/stripe_mock/request_handlers/subscriptions.rb index 8a4fbd192..8324b3fc0 100644 --- a/lib/stripe_mock/request_handlers/subscriptions.rb +++ b/lib/stripe_mock/request_handlers/subscriptions.rb @@ -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] @@ -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] @@ -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 @@ -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] @@ -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 @@ -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] @@ -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 @@ -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={}) diff --git a/spec/shared_stripe_examples/charge_examples.rb b/spec/shared_stripe_examples/charge_examples.rb index f437ba1ff..bd1357a50 100644 --- a/spec/shared_stripe_examples/charge_examples.rb +++ b/spec/shared_stripe_examples/charge_examples.rb @@ -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 diff --git a/spec/shared_stripe_examples/refund_examples.rb b/spec/shared_stripe_examples/refund_examples.rb index 99ae9dc1a..8bc14dc78 100644 --- a/spec/shared_stripe_examples/refund_examples.rb +++ b/spec/shared_stripe_examples/refund_examples.rb @@ -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 diff --git a/spec/shared_stripe_examples/subscription_examples.rb b/spec/shared_stripe_examples/subscription_examples.rb index 6f3dd9d51..c9140b79b 100644 --- a/spec/shared_stripe_examples/subscription_examples.rb +++ b/spec/shared_stripe_examples/subscription_examples.rb @@ -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') @@ -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" ) @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/spec/shared_stripe_examples/webhook_event_examples.rb b/spec/shared_stripe_examples/webhook_event_examples.rb index 6685dba00..900c5b501 100644 --- a/spec/shared_stripe_examples/webhook_event_examples.rb +++ b/spec/shared_stripe_examples/webhook_event_examples.rb @@ -199,14 +199,14 @@ events = Stripe::Event.all(limit: 3) expect(events.count).to eq(3) - expect(events.map &:id).to include(invoice_item_created_event.id, invoice_created_event.id, coupon_created_event.id) - expect(events.map &:type).to include('invoiceitem.created', 'invoice.created', 'coupon.created') + expect(events.map &:id).to include(customer_created_event.id, plan_created_event.id, coupon_created_event.id) + expect(events.map &:type).to include('customer.created', 'plan.created', 'coupon.created') end end - - describe 'Subscription events' do - it "Checks for billing items in customer.subscription.created" do + + describe 'Subscription events' do + it "Checks for billing items in customer.subscription.created" do subscription_created_event = StripeMock.mock_webhook_event('customer.subscription.created') expect(subscription_created_event).to be_a(Stripe::Event) expect(subscription_created_event.id).to_not be_nil @@ -216,7 +216,7 @@ expect(subscription_created_event.data.object.items.data.first.id).to eq('si_00000000000000') end - it "Checks for billing items in customer.subscription.deleted" do + it "Checks for billing items in customer.subscription.deleted" do subscription_deleted_event = StripeMock.mock_webhook_event('customer.subscription.deleted') expect(subscription_deleted_event).to be_a(Stripe::Event) expect(subscription_deleted_event.id).to_not be_nil @@ -225,8 +225,8 @@ expect(subscription_deleted_event.data.object.items.data.first).to respond_to(:plan) expect(subscription_deleted_event.data.object.items.data.first.id).to eq('si_00000000000000') end - - it "Checks for billing items in customer.subscription.updated" do + + it "Checks for billing items in customer.subscription.updated" do subscription_updated_event = StripeMock.mock_webhook_event('customer.subscription.updated') expect(subscription_updated_event).to be_a(Stripe::Event) expect(subscription_updated_event.id).to_not be_nil @@ -235,8 +235,8 @@ expect(subscription_updated_event.data.object.items.data.first).to respond_to(:plan) expect(subscription_updated_event.data.object.items.data.first.id).to eq('si_00000000000000') end - - it "Checks for billing items in customer.subscription.trial_will_end" do + + it "Checks for billing items in customer.subscription.trial_will_end" do subscription_trial_will_end_event = StripeMock.mock_webhook_event('customer.subscription.trial_will_end') expect(subscription_trial_will_end_event).to be_a(Stripe::Event) expect(subscription_trial_will_end_event.id).to_not be_nil @@ -247,7 +247,7 @@ end end - describe 'Invoices events' do + describe 'Invoices events' do it "Checks for billing items in invoice.payment_succeeded" do invoice_payment_succeeded = StripeMock.mock_webhook_event('invoice.payment_succeeded') expect(invoice_payment_succeeded).to be_a(Stripe::Event)