From 53ef2fd14a2ea96c5f225ff48b864fc24c108b8c Mon Sep 17 00:00:00 2001 From: mattagra Date: Tue, 6 Mar 2018 11:17:43 -0800 Subject: [PATCH] Support updating subscription with multiple plan items --- lib/stripe_mock/request_handlers/customers.rb | 2 +- .../helpers/subscription_helpers.rb | 21 ++--- lib/stripe_mock/request_handlers/invoices.rb | 2 +- .../request_handlers/subscriptions.rb | 87 ++++++++----------- .../subscription_examples.rb | 61 +++++++++++-- 5 files changed, 103 insertions(+), 70 deletions(-) diff --git a/lib/stripe_mock/request_handlers/customers.rb b/lib/stripe_mock/request_handlers/customers.rb index c35930bc2..4928715df 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.merge!(custom_subscription_params(plan, customers[ params[:id] ], params)) + subscription = resolve_subscription_changes(subscription, [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 d63c4f4b5..14df93f23 100644 --- a/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb +++ b/lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb @@ -6,13 +6,22 @@ def get_customer_subscription(customer, sub_id) customer[:subscriptions][:data].find{|sub| sub[:id] == sub_id } end - def custom_subscription_params(plan, cus, options = {}) + 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 = {}) 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 = { plan: plan, customer: cus[:id], current_period_start: start_time, created: created_time } + params = { customer: cus[:id], current_period_start: start_time, created: created_time } + params.merge!({ :plan => (plans.size == 1 ? plans.first : nil) }) params.merge! options.select {|k,v| k =~ /application_fee_percent|quantity|metadata|tax_percent/} # TODO: Implement coupon logic @@ -87,14 +96,6 @@ def total_items_amount(items) items.each { |i| total += (i[:quantity] || 1) * i[:plan][:amount] } total end - - def mock_subscription_items(items = []) - data = [] - items.each do |i| - data << Data.mock_subscription_item(i.merge(plan: plans[i[:plan].to_s])) - end - data - end end end end diff --git a/lib/stripe_mock/request_handlers/invoices.rb b/lib/stripe_mock/request_handlers/invoices.rb index 8f8e242bc..3e4434082 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.merge!(custom_subscription_params(subscription_plan, customer, { trial_end: params[:subscription_trial_end] })) + preview_subscription = resolve_subscription_changes(preview_subscription, [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 28933c1e9..8a4fbd192 100644 --- a/lib/stripe_mock/request_handlers/subscriptions.rb +++ b/lib/stripe_mock/request_handlers/subscriptions.rb @@ -32,24 +32,10 @@ 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] - item = params[:items].values.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 - plan_id = plan_id_from_params(params) - - plan = assert_existence :plan, plan_id, plans[plan_id] - + subscription_plans = get_subscription_plans_from_params(params) customer = assert_existence :customer, $1, customers[$1] if params[:source] @@ -59,10 +45,11 @@ def create_customer_subscription(route, method_url, params, headers) end subscription = Data.mock_subscription({ id: (params[:id] || new_id('su')) }) - subscription.merge!(custom_subscription_params(plan, customer, params)) + subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params) # Ensure customer has card to charge if plan has no trial and is not free - verify_card_present(customer, plan, subscription, params) + # Note: needs updating for subscriptions with multiple plans + verify_card_present(customer, subscription_plans.first, subscription, params) if params[:coupon] coupon_id = params[:coupon] @@ -82,25 +69,23 @@ 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 - plan_id = plan_id_from_params(params) - - plan = plan_id ? assert_existence(:plan, plan_id, plans[plan_id]) : nil + subscription_plans = get_subscription_plans_from_params(params) 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 plan && customer - unless customer[:currency].to_s == plan[:currency].to_s - raise Stripe::InvalidRequestError.new('lol', 'currency', http_status: 400) + 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 end end @@ -117,11 +102,11 @@ def create_subscription(route, method_url, params, headers) end subscription = Data.mock_subscription({ id: (params[:id] || new_id('su')) }) - subscription.merge!(custom_subscription_params(plan, customer, params)) - subscription[:items][:data] = mock_subscription_items(params[:items].values) if params[:items] + subscription = resolve_subscription_changes(subscription, subscription_plans, customer, params) # Ensure customer has card to charge if plan has no trial and is not free - verify_card_present(customer, plan, subscription, params) + # Note: needs updating for subscriptions with multiple plans + verify_card_present(customer, subscription_plans.first, subscription, params) if params[:coupon] coupon_id = params[:coupon] @@ -141,8 +126,6 @@ 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 @@ -176,21 +159,12 @@ def update_subscription(route, method_url, params, headers) customer[:default_source] = new_card[:id] end - # 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 + subscription_plans = get_subscription_plans_from_params(params) - plan = plans[plan_id] + # 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] } + end if params[:coupon] coupon_id = params[:coupon] @@ -207,10 +181,7 @@ def update_subscription(route, method_url, params, headers) raise Stripe::InvalidRequestError.new("No such coupon: #{coupon_id}", 'coupon', http_status: 400) end end - - assert_existence :plan, plan_id, plan - params[:plan] = plan if params[:plan] - verify_card_present(customer, plan, subscription) + verify_card_present(customer, subscription_plans.first, subscription) if subscription[:cancel_at_period_end] subscription[:cancel_at_period_end] = false @@ -218,14 +189,12 @@ def update_subscription(route, method_url, params, headers) end params[:current_period_start] = subscription[:current_period_start] - subscription.merge!(custom_subscription_params(plan, customer, params)) + subscription = resolve_subscription_changes(subscription, subscription_plans, 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 @@ -259,8 +228,20 @@ def cancel_subscription(route, method_url, params, headers) private - def clear_top_level_plan_if_multiple_items(subscription) - subscription[:plan] = nil if subscription[:items][:data].size > 1 + 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] + 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/subscription_examples.rb b/spec/shared_stripe_examples/subscription_examples.rb index e5ba12dcb..6f3dd9d51 100644 --- a/spec/shared_stripe_examples/subscription_examples.rb +++ b/spec/shared_stripe_examples/subscription_examples.rb @@ -504,6 +504,57 @@ def gen_card_tk expect(customer.subscriptions.data.first.customer).to eq(customer.id) end + it "updates a stripe customer's existing subscription with single plan when multiple plans inside of items" do + silver_plan = stripe_helper.create_plan(id: 'silver') + gold_plan = stripe_helper.create_plan(id: 'gold') + addon_plan = stripe_helper.create_plan(id: 'addon_plan') + 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 }] + expect(sub.save).to be_truthy + + expect(sub.object).to eq('subscription') + expect(sub.plan).to be_nil + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq(1) + expect(customer.subscriptions.data.length).to eq(1) + + expect(customer.subscriptions.data.first.id).to eq(sub.id) + expect(customer.subscriptions.data.first.plan).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[1].plan.to_hash).to eq(addon_plan.to_hash) + end + + it "updates a stripe customer's existing subscription with multple plans when multiple plans inside of items" do + silver_plan = stripe_helper.create_plan(id: 'silver') + gold_plan = stripe_helper.create_plan(id: 'gold') + addon1_plan = stripe_helper.create_plan(id: 'addon1') + addon2_plan = stripe_helper.create_plan(id: 'addon2') + 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 }] + expect(sub.save).to be_truthy + + expect(sub.object).to eq('subscription') + expect(sub.plan).to be_nil + + customer = Stripe::Customer.retrieve('test_customer_sub') + expect(customer.subscriptions.data).to_not be_empty + expect(customer.subscriptions.count).to eq(1) + expect(customer.subscriptions.data.length).to eq(1) + + expect(customer.subscriptions.data.first.id).to eq(sub.id) + expect(customer.subscriptions.data.first.plan).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[1].plan.to_hash).to eq(addon2_plan.to_hash) + end + it 'when adds coupon', live: true do plan = stripe_helper.create_plan(id: 'plan_with_coupon2', name: 'One More Test Plan', amount: 777) coupon = stripe_helper.create_coupon @@ -857,13 +908,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('si_1AwFf62eZvKYlo2C9u6Dhf9') - expect(subscription.items.data.first.created).to eq(1504035973) + 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.object).to eq('subscription_item') - 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.amount).to eq(0) + expect(subscription.items.data.first.plan.created).to eq(1466698898) expect(subscription.items.data.first.plan.currency).to eq('usd') - expect(subscription.items.data.first.quantity).to eq(1) + expect(subscription.items.data.first.quantity).to eq(2) end end