mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:45:25 +08:00
## Summary Correctly enforce profile visibility restrictions in the subscriptions contributors endpoint and user serializers. This prevents the exposure of sensitive profile fields—including bio, location, and website—to anonymous viewers when public profiles are disabled via site settings. The fix also hardens the user and user card serializers to omit profile details for unauthorized scopes as a defense-in-depth measure. ## Source - Patch Triage: https://patch.discourse.org/patch-triage/1286 Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
924 lines
31 KiB
Ruby
Vendored
924 lines
31 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe DiscourseSubscriptions::SubscribeController do
|
|
let(:user) { Fabricate(:user) }
|
|
let(:campaign_user) { Fabricate(:user) }
|
|
|
|
before do
|
|
SiteSetting.discourse_subscriptions_enabled = true
|
|
SiteSetting.discourse_subscriptions_secret_key = "secret-key"
|
|
end
|
|
|
|
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"
|
|
end
|
|
|
|
describe "#index" do
|
|
let(:customer) do
|
|
Fabricate(:customer, product_id: product[:id], user_id: user.id, customer_id: "x")
|
|
end
|
|
|
|
it "gets products" do
|
|
::Stripe::Product
|
|
.expects(:list)
|
|
.with({ ids: product_ids, active: true }, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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(:subscription, external_id: "sub_12345", customer_id: customer.id, status: nil)
|
|
|
|
::Stripe::Product
|
|
.expects(:list)
|
|
.with({ ids: product_ids, active: true }, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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 }, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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")
|
|
user.user_stat.update!(post_count: 1)
|
|
campaign_user.user_stat.update!(post_count: 1)
|
|
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 = ""
|
|
|
|
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
|
|
Fabricate(:product, external_id: "prod_walterwhite")
|
|
|
|
::Stripe::Product
|
|
.expects(:retrieve)
|
|
.with("prod_walterwhite", DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(product)
|
|
::Stripe::Price
|
|
.expects(:list)
|
|
.with(
|
|
{ active: true, product: "prod_walterwhite" },
|
|
DiscourseSubscriptions::Stripe.request_opts,
|
|
)
|
|
.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
|
|
|
|
it "returns 404 for a product outside the allowlist" do
|
|
::Stripe::Product.expects(:retrieve).never
|
|
::Stripe::Price.expects(:list).never
|
|
|
|
get "/s/prod_hidden.json"
|
|
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#contributors" do
|
|
fab!(:contributor, :user)
|
|
fab!(:hidden_contributor, :user)
|
|
|
|
before do
|
|
SiteSetting.discourse_subscriptions_campaign_show_contributors = true
|
|
SiteSetting.discourse_subscriptions_campaign_product = ""
|
|
SiteSetting.hide_user_profiles_from_public = false
|
|
SiteSetting.allow_users_to_hide_profile = true
|
|
contributor.user_stat.update!(post_count: 1)
|
|
hidden_contributor.user_stat.update!(post_count: 1)
|
|
hidden_contributor.user_option.update!(hide_profile: true)
|
|
Fabricate(:customer, product_id: "prodct_23456", user_id: contributor.id, customer_id: "x")
|
|
Fabricate(
|
|
:customer,
|
|
product_id: "prod_campaign",
|
|
user_id: hidden_contributor.id,
|
|
customer_id: "y",
|
|
)
|
|
end
|
|
|
|
it "enforces profile visibility for anonymous contributors" do
|
|
get "/s/contributors.json"
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
visible_contributor_ids = response.parsed_body.map { |serialized_user| serialized_user["id"] }
|
|
|
|
SiteSetting.hide_user_profiles_from_public = true
|
|
|
|
get "/s/contributors.json"
|
|
|
|
expect(visible_contributor_ids).to contain_exactly(contributor.id)
|
|
expect(response).to have_http_status(:forbidden)
|
|
expect(response.parsed_body).to include("error_type" => "invalid_access")
|
|
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
|
|
fab!(:published_product) { Fabricate(:product, external_id: "product_12345") }
|
|
|
|
before { sign_in(user) }
|
|
|
|
describe "#create" do
|
|
before do
|
|
::Stripe::Customer
|
|
.stubs(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "cus_1234")
|
|
end
|
|
|
|
it "creates a subscription without automatic_tax param" do
|
|
SiteSetting.discourse_subscriptions_enable_automatic_tax = false
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
trial_period_days: 0,
|
|
},
|
|
)
|
|
|
|
expected_subscription_params = {
|
|
customer: "cus_1234",
|
|
items: [{ price: "plan_1234" }],
|
|
metadata: {
|
|
user_id: user.id,
|
|
username: user.username_lower,
|
|
},
|
|
trial_period_days: 0,
|
|
promotion_code: nil,
|
|
}
|
|
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with do |params, opts|
|
|
params == expected_subscription_params && !params.key?(:automatic_tax) &&
|
|
opts == DiscourseSubscriptions::Stripe.request_opts
|
|
end
|
|
.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 subscription with automatic tax" do
|
|
SiteSetting.discourse_subscriptions_enable_automatic_tax = true
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
trial_period_days: 0,
|
|
},
|
|
)
|
|
|
|
expected_subscription_params = {
|
|
customer: "cus_1234",
|
|
items: [{ price: "plan_1234" }],
|
|
metadata: {
|
|
user_id: user.id,
|
|
username: user.username_lower,
|
|
},
|
|
trial_period_days: 0,
|
|
promotion_code: nil,
|
|
automatic_tax: {
|
|
enabled: true,
|
|
},
|
|
}
|
|
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with(expected_subscription_params, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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 "rejects a plan outside the allowlist" do
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with("price_hidden", DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "prod_hidden",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
trial_period_days: 0,
|
|
},
|
|
)
|
|
::Stripe::Customer.expects(:create).never
|
|
::Stripe::Subscription.expects(:create).never
|
|
|
|
expect {
|
|
post "/s/create.json", params: { plan: "price_hidden", source: "tok_1234" }
|
|
}.not_to change { DiscourseSubscriptions::Customer.count }
|
|
|
|
expect(response.status).to eq(404)
|
|
end
|
|
|
|
it "returns 422 on a one time payment subscription error" do
|
|
# It's possible that the invoice item doesn't get attached
|
|
# to the invoice. This means the invoice is paid, but for $0.00 with
|
|
# a pending invoice item.
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "one_time",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
},
|
|
)
|
|
|
|
::Stripe::InvoiceItem.expects(:create).with(
|
|
anything,
|
|
DiscourseSubscriptions::Stripe.request_opts,
|
|
)
|
|
|
|
::Stripe::Invoice
|
|
.expects(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "open", id: "in_123")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:finalize_invoice)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "paid", payment_intent: "pi_123")
|
|
|
|
expect {
|
|
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
|
|
}.not_to change { DiscourseSubscriptions::Customer.count }
|
|
|
|
expect(response.status).to eq 422
|
|
end
|
|
|
|
it "creates a one time payment subscription without automatic tax" do
|
|
SiteSetting.discourse_subscriptions_enable_automatic_tax = false
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "one_time",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
},
|
|
)
|
|
|
|
::Stripe::InvoiceItem.expects(:create).with(
|
|
anything,
|
|
DiscourseSubscriptions::Stripe.request_opts,
|
|
)
|
|
|
|
expected_one_time_params = { customer: "cus_1234" }
|
|
|
|
::Stripe::Invoice
|
|
.expects(:create)
|
|
.with do |params, opts|
|
|
params == expected_one_time_params && !params.key?(:automatic_tax) &&
|
|
opts == DiscourseSubscriptions::Stripe.request_opts
|
|
end
|
|
.returns(status: "open", id: "in_123")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:finalize_invoice)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "open", payment_intent: "pi_123")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "open", payment_intent: "pi_123")
|
|
|
|
::Stripe::PaymentIntent
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "successful")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:pay)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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 one time payment subscription" do
|
|
SiteSetting.discourse_subscriptions_enable_automatic_tax = true
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "one_time",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
},
|
|
)
|
|
|
|
::Stripe::InvoiceItem.expects(:create).with(
|
|
anything,
|
|
DiscourseSubscriptions::Stripe.request_opts,
|
|
)
|
|
|
|
expected_one_time_params = { customer: "cus_1234", automatic_tax: { enabled: true } }
|
|
|
|
::Stripe::Invoice
|
|
.expects(:create)
|
|
.with(expected_one_time_params, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "open", id: "in_123")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:finalize_invoice)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "open", payment_intent: "pi_123")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "open", payment_intent: "pi_123")
|
|
|
|
::Stripe::PaymentIntent
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "successful")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:pay)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(type: "recurring", product: "product_12345", metadata: {})
|
|
.twice
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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",
|
|
DiscourseSubscriptions::Stripe.request_opts,
|
|
)
|
|
|
|
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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(type: "recurring", product: "product_12345", metadata: {})
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
trial_period_days: 0,
|
|
},
|
|
)
|
|
|
|
::Stripe::PromotionCode
|
|
.expects(:list)
|
|
.with({ code: "invalid" }, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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" }, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(data: [{ id: "promo123", coupon: { id: "c123" } }])
|
|
end
|
|
|
|
it "applies promo code to recurring subscription" do
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
trial_period_days: 0,
|
|
},
|
|
)
|
|
|
|
expected_subscription_params = {
|
|
customer: "cus_1234",
|
|
items: [price: "plan_1234"],
|
|
metadata: {
|
|
user_id: user.id,
|
|
username: user.username_lower,
|
|
},
|
|
trial_period_days: 0,
|
|
promotion_code: "promo123",
|
|
}
|
|
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with(expected_subscription_params, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "one_time",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: "awesome",
|
|
},
|
|
)
|
|
|
|
::Stripe::Invoice
|
|
.expects(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "open", id: "in_123")
|
|
|
|
::Stripe::InvoiceItem.expects(:create).with(
|
|
{
|
|
customer: "cus_1234",
|
|
price: "plan_1234",
|
|
discounts: [{ coupon: "c123" }],
|
|
invoice: "in_123",
|
|
},
|
|
DiscourseSubscriptions::Stripe.request_opts,
|
|
)
|
|
|
|
::Stripe::Invoice
|
|
.expects(:finalize_invoice)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "open", payment_intent: "pi_123")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", status: "open", payment_intent: "pi_123")
|
|
|
|
::Stripe::PaymentIntent
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "successful")
|
|
|
|
::Stripe::Invoice
|
|
.expects(:pay)
|
|
.with(anything, anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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
|
|
server_session["pending_subscription"] = {
|
|
transaction_id: "sub_1234",
|
|
plan_id: "plan_1234",
|
|
}
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "plan_1234", product: "product_12345", metadata: {})
|
|
::Stripe::Subscription
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "sub_123", customer: "cus_1234", status: "active")
|
|
|
|
expect { post "/s/finalize.json" }.to change { DiscourseSubscriptions::Customer.count }
|
|
end
|
|
end
|
|
|
|
context "with one-time payment" do
|
|
it "finalizes the one-time payment" do
|
|
server_session["pending_subscription"] = {
|
|
transaction_id: "in_1234",
|
|
plan_id: "plan_1234",
|
|
}
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "plan_1234", product: "product_12345", metadata: {})
|
|
::Stripe::Invoice
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "in_123", customer: "cus_1234", status: "paid")
|
|
|
|
expect { post "/s/finalize.json" }.to change { DiscourseSubscriptions::Customer.count }
|
|
end
|
|
end
|
|
|
|
it "returns 403 with no pending server session" do
|
|
post "/s/finalize.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "prevents replay by rejecting a second finalize" do
|
|
server_session["pending_subscription"] = {
|
|
transaction_id: "sub_123",
|
|
plan_id: "plan_1234",
|
|
}
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "plan_1234", product: "product_12345", metadata: {})
|
|
::Stripe::Subscription
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "sub_123", customer: "cus_1234", status: "active")
|
|
|
|
post "/s/finalize.json"
|
|
expect(response.status).to eq(200)
|
|
|
|
post "/s/finalize.json"
|
|
expect(response.status).to eq(403)
|
|
end
|
|
|
|
it "rejects a pending plan outside the allowlist" do
|
|
server_session["pending_subscription"] = {
|
|
transaction_id: "sub_123",
|
|
plan_id: "price_hidden",
|
|
}
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with("price_hidden", DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "price_hidden", product: "prod_hidden", metadata: {})
|
|
::Stripe::Subscription.expects(:retrieve).never
|
|
|
|
expect { post "/s/finalize.json" }.not_to change {
|
|
DiscourseSubscriptions::Customer.count
|
|
}
|
|
|
|
expect(response.status).to eq(404)
|
|
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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "cus_1234")
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(status: "active")
|
|
end
|
|
|
|
it "does not add the user to the admins group" do
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(id: "cus_1234")
|
|
::Stripe::Price
|
|
.expects(:retrieve)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.returns(
|
|
type: "recurring",
|
|
product: "product_12345",
|
|
metadata: {
|
|
group_name: group_name,
|
|
},
|
|
)
|
|
end
|
|
|
|
it "does not add the user to the group when subscription fails" do
|
|
::Stripe::Subscription
|
|
.expects(:create)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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)
|
|
.with(anything, DiscourseSubscriptions::Stripe.request_opts)
|
|
.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
|