# frozen_string_literal: true require "rails_helper" RSpec.describe DiscourseSubscriptions::SubscribeController do let(:user) { Fabricate(:user) } let(:campaign_user) { Fabricate(:user) } before { SiteSetting.discourse_subscriptions_enabled = true } context "when showing products" do let(:product) do { id: "prodct_23456", name: "Very Special Product", metadata: { description: "Many people listened to my phone call with the Ukrainian President while it was being made", repurchaseable: false, }, otherstuff: true, } end let(:prices) do { data: [ { id: "plan_id123", unit_amount: 1220, currency: "aud", recurring: { interval: "year", }, metadata: { }, }, { id: "plan_id234", unit_amount: 1399, currency: "usd", recurring: { interval: "year", }, metadata: { }, }, { id: "plan_id678", unit_amount: 1000, currency: "aud", recurring: { interval: "week", }, metadata: { }, }, ], } end let(:product_ids) { ["prodct_23456"] } before do sign_in(user) Fabricate(:product, external_id: "prodct_23456") SiteSetting.discourse_subscriptions_public_key = "public-key" SiteSetting.discourse_subscriptions_secret_key = "secret-key" end describe "#index" do it "gets products" do ::Stripe::Product .expects(:list) .with({ ids: product_ids, active: true }) .returns(data: [product]) get "/s.json" expect(response.parsed_body).to eq( [ { "id" => "prodct_23456", "name" => "Very Special Product", "description" => PrettyText.cook( "Many people listened to my phone call with the Ukrainian President while it was being made", ), "subscribed" => false, "repurchaseable" => false, }, ], ) end it "is subscribed" do Fabricate(:customer, product_id: product[:id], user_id: user.id, customer_id: "x") ::Stripe::Product .expects(:list) .with({ ids: product_ids, active: true }) .returns(data: [product]) get "/s.json" data = response.parsed_body expect(data.first["subscribed"]).to eq true end it "is not subscribed" do ::DiscourseSubscriptions::Customer.delete_all ::Stripe::Product .expects(:list) .with({ ids: product_ids, active: true }) .returns(data: [product]) get "/s.json" data = response.parsed_body expect(data.first["subscribed"]).to eq false end end describe "#get_contributors" do before do Fabricate(:product, external_id: "prod_campaign") Fabricate(:customer, product_id: "prodct_23456", user_id: user.id, customer_id: "x") Fabricate( :customer, product_id: "prod_campaign", user_id: campaign_user.id, customer_id: "y", ) end context "when not showing contributors" do it "returns nothing if not set to show contributors" do SiteSetting.discourse_subscriptions_campaign_show_contributors = false get "/s/contributors.json" data = response.parsed_body expect(data).to be_empty end end context "when showing contributors" do before { SiteSetting.discourse_subscriptions_campaign_show_contributors = true } it "filters users by campaign product if set" do SiteSetting.discourse_subscriptions_campaign_product = "prod_campaign" get "/s/contributors.json" data = response.parsed_body expect(data.first["id"]).to eq campaign_user.id expect(data.length).to eq 1 end it "shows all purchases if campaign product not set" do SiteSetting.discourse_subscriptions_campaign_product = nil get "/s/contributors.json" data = response.parsed_body expect(data.length).to eq 2 end end end describe "#show" do it "retrieves the product" do ::Stripe::Product.expects(:retrieve).with("prod_walterwhite").returns(product) ::Stripe::Price .expects(:list) .with(active: true, product: "prod_walterwhite") .returns(prices) get "/s/prod_walterwhite.json" expect(response.parsed_body).to eq( { "product" => { "id" => "prodct_23456", "name" => "Very Special Product", "description" => PrettyText.cook( "Many people listened to my phone call with the Ukrainian President while it was being made", ), "subscribed" => false, "repurchaseable" => false, }, "plans" => [ { "currency" => "aud", "id" => "plan_id123", "recurring" => { "interval" => "year", }, "unit_amount" => 1220, }, { "currency" => "usd", "id" => "plan_id234", "recurring" => { "interval" => "year", }, "unit_amount" => 1399, }, { "currency" => "aud", "id" => "plan_id678", "recurring" => { "interval" => "week", }, "unit_amount" => 1000, }, ], }, ) end end end context "when creating subscriptions" do context "when unauthenticated" do it "does not create a subscription" do ::Stripe::Customer.expects(:create).never ::Stripe::Price.expects(:retrieve).never ::Stripe::Subscription.expects(:create).never post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } end end context "when authenticated" do before { sign_in(user) } describe "#create" do before { ::Stripe::Customer.expects(:create).returns(id: "cus_1234") } it "creates a subscription" do ::Stripe::Price.expects(:retrieve).returns( type: "recurring", product: "product_12345", metadata: { group_name: "awesome", trial_period_days: 0, }, ) ::Stripe::Subscription .expects(:create) .with( customer: "cus_1234", items: [price: "plan_1234"], metadata: { user_id: user.id, username: user.username_lower, }, trial_period_days: 0, promotion_code: nil, ) .returns(status: "active", customer: "cus_1234") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { DiscourseSubscriptions::Customer.count } end it "creates a one time payment subscription" do ::Stripe::Price.expects(:retrieve).returns( type: "one_time", product: "product_12345", metadata: { group_name: "awesome", }, ) ::Stripe::InvoiceItem.expects(:create) ::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123") ::Stripe::Invoice.expects(:finalize_invoice).returns( id: "in_123", status: "open", payment_intent: "pi_123", ) ::Stripe::Invoice.expects(:retrieve).returns( id: "in_123", status: "open", payment_intent: "pi_123", ) ::Stripe::PaymentIntent.expects(:retrieve).returns(status: "successful") ::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { DiscourseSubscriptions::Customer.count } end it "creates a customer model" do ::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {}).twice ::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { DiscourseSubscriptions::Customer.count } ::Stripe::Customer.expects(:retrieve).with("cus_1234") expect { post "/s/create.json", params: { plan: "plan_5678", source: "tok_5678" } }.not_to change { DiscourseSubscriptions::Customer.count } end context "with customer name & address" do it "creates a customer & subscription when a customer address is provided" do ::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {}) ::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234", cardholder_name: "A. Customer", cardholder_address: { line1: "123 Main Street", city: "Anywhere", state: "VT", country: "US", postal_code: "12345", }, } }.to change { DiscourseSubscriptions::Customer.count } end end context "with promo code" do context "with invalid code" do it "prevents use of invalid coupon codes" do ::Stripe::Price.expects(:retrieve).returns( type: "recurring", product: "product_12345", metadata: { group_name: "awesome", trial_period_days: 0, }, ) ::Stripe::PromotionCode.expects(:list).with({ code: "invalid" }).returns(data: []) post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234", promo: "invalid", } data = response.parsed_body expect(data["errors"]).not_to be_blank end end context "with valid code" do before do ::Stripe::PromotionCode .expects(:list) .with({ code: "123" }) .returns(data: [{ id: "promo123", coupon: { id: "c123" } }]) end it "applies promo code to recurring subscription" do ::Stripe::Price.expects(:retrieve).returns( type: "recurring", product: "product_12345", metadata: { group_name: "awesome", trial_period_days: 0, }, ) ::Stripe::Subscription .expects(:create) .with( customer: "cus_1234", items: [price: "plan_1234"], metadata: { user_id: user.id, username: user.username_lower, }, trial_period_days: 0, promotion_code: "promo123", ) .returns(status: "active", customer: "cus_1234") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234", promo: "123", } }.to change { DiscourseSubscriptions::Customer.count } end it "applies promo code to one time purchase" do ::Stripe::Price.expects(:retrieve).returns( type: "one_time", product: "product_12345", metadata: { group_name: "awesome", }, ) ::Stripe::InvoiceItem.expects(:create).with( customer: "cus_1234", price: "plan_1234", discounts: [{ coupon: "c123" }], ) ::Stripe::Invoice.expects(:create).returns(status: "open", id: "in_123") ::Stripe::Invoice.expects(:finalize_invoice).returns( id: "in_123", status: "open", payment_intent: "pi_123", ) ::Stripe::Invoice.expects(:retrieve).returns( id: "in_123", status: "open", payment_intent: "pi_123", ) ::Stripe::PaymentIntent.expects(:retrieve).returns(status: "successful") ::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234", promo: "123", } }.to change { DiscourseSubscriptions::Customer.count } end end end end describe "#finalize strong customer authenticated transaction" do context "with subscription" do it "finalizes the subscription" do ::Stripe::Price.expects(:retrieve).returns( id: "plan_1234", product: "prod_1234", metadata: { }, ) ::Stripe::Subscription.expects(:retrieve).returns( id: "sub_123", customer: "cus_1234", status: "active", ) expect { post "/s/finalize.json", params: { plan: "plan_1234", transaction: "sub_1234" } }.to change { DiscourseSubscriptions::Customer.count } end end context "with one-time payment" do it "finalizes the one-time payment" do ::Stripe::Price.expects(:retrieve).returns( id: "plan_1234", product: "prod_1234", metadata: { }, ) ::Stripe::Invoice.expects(:retrieve).returns( id: "in_123", customer: "cus_1234", status: "paid", ) expect { post "/s/finalize.json", params: { plan: "plan_1234", transaction: "in_1234" } }.to change { DiscourseSubscriptions::Customer.count } end end end describe "user groups" do let(:group_name) { "group-123" } let(:group) { Fabricate(:group, name: group_name) } context "with unauthorized group" do before do ::Stripe::Customer.expects(:create).returns(id: "cus_1234") ::Stripe::Subscription.expects(:create).returns(status: "active") end it "does not add the user to the admins group" do ::Stripe::Price.expects(:retrieve).returns( type: "recurring", metadata: { group_name: "admins", }, ) post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } expect(user.admin).to eq false end it "does not add the user to other group" do ::Stripe::Price.expects(:retrieve).returns( type: "recurring", metadata: { group_name: "other", }, ) post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } expect(user.groups).to be_empty end end context "when plan has group in metadata" do before do ::Stripe::Customer.expects(:create).returns(id: "cus_1234") ::Stripe::Price.expects(:retrieve).returns( type: "recurring", metadata: { group_name: group_name, }, ) end it "does not add the user to the group when subscription fails" do ::Stripe::Subscription.expects(:create).returns(status: "failed") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.not_to change { group.users.count } expect(user.groups).to be_empty end it "adds the user to the group when the subscription is active" do ::Stripe::Subscription.expects(:create).returns(status: "active") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { group.users.count } expect(user.groups).not_to be_empty end it "adds the user to the group when the subscription is trialing" do ::Stripe::Subscription.expects(:create).returns(status: "trialing") expect { post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" } }.to change { group.users.count } expect(user.groups).not_to be_empty end end end end end end