11 Commits
Author SHA1 Message Date
Blake Erickson 7fe7be7ef8 add nonce to pricing-table.js 2024-04-24 11:38:56 -06:00
Blake Erickson d6c4ec21ae add pricing table to csp 2024-04-24 11:18:47 -06:00
Blake Erickson c48cbc7071 fix spec still linking to /s 2024-04-24 11:12:48 -06:00
spirobel 66cf05943c DEV: Change add_admin_route from products to plans 2024-04-24 11:11:38 -06:00
Blake Erickson e46693b94e linting 2024-04-24 10:09:53 -06:00
spirobel fcc414dbbe DEV: fix backend tests
After the engine url got moved to /subscriptions, the tests had to be adapted.

The subscribe controller does not return products anymore after moving to the stripe pricing table, so the respective tests were removed.
2024-04-23 16:24:34 -06:00
spirobel 411ac6ec80 DEV: add frontend tests to make sure pricing-table works
We test three conditions:
1. Make sure the customer email is prefilled, if there is a logged in discourse user
2. The pricing table also displays, if there is no logged in discourse user
3. If the pricing table is not configured, the pricing table element does not show up.
2024-04-23 15:32:19 -06:00
spirobel ce988c9512 DEV: continue to use subscribe_controller instead of introducting a new controller 2024-04-23 15:30:28 -06:00
spirobel bf7079928e DEV: fix linter issues 2024-04-23 15:26:52 -06:00
spirobel ff981f76e8 DEV: Remove the old product / pricing table display routes 2024-04-23 15:24:58 -06:00
spirobel 13d23dc3fa FEATURE: Add new stripe based pricing table
This commit adds the new stripe based pricing table.
If the user is logged in, the email field will be prepopulated with the users email.

The pricing table can be configured in the stripe dashboard.

Once the discourse_subscriptions_pricing_table setting is filled with the pricing table embed code from the stripe dashboard,
the pricing table will be displayed on /subscriptions

For more details see https://stripe.com/docs/payments/checkout/pricing-table
2024-04-23 15:18:31 -06:00
30 changed files with 295 additions and 604 deletions
@@ -11,20 +11,7 @@ module DiscourseSubscriptions
requires_login except: %i[index contributors show]
def index
begin
product_ids = Product.all.pluck(:external_id)
products = []
if product_ids.present? && is_stripe_configured?
response = ::Stripe::Product.list({ ids: product_ids, active: true })
products = response[:data].map { |p| serialize_product(p) }
end
render_json_dump products
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
head 200
end
def contributors
@@ -58,12 +58,14 @@ export default Component.extend({
}
if (this.currentUser && this.showContributors) {
return ajax("/s/contributors", { method: "get" }).then((result) => {
this.setProperties({
contributors: result,
loading: false,
});
});
return ajax("/subscriptions/contributors", { method: "get" }).then(
(result) => {
this.setProperties({
contributors: result,
loading: false,
});
}
);
}
},
@@ -1,10 +0,0 @@
import Controller from "@ember/controller";
import User from "discourse/models/user";
import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({
@discourseComputed()
isLoggedIn() {
return User.current();
},
});
@@ -1,195 +0,0 @@
/* global Stripe */
import Controller from "@ember/controller";
import { not } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
import Subscription from "discourse/plugins/discourse-subscriptions/discourse/models/subscription";
import Transaction from "discourse/plugins/discourse-subscriptions/discourse/models/transaction";
export default Controller.extend({
dialog: service(),
router: service(),
selectedPlan: null,
promoCode: null,
cardholderName: null,
cardholderAddress: {
line1: null,
city: null,
state: null,
country: null,
postalCode: null,
},
isAnonymous: not("currentUser"),
isCountryUS: false,
isCountryCA: false,
init() {
this._super(...arguments);
this.set(
"stripe",
Stripe(this.siteSettings.discourse_subscriptions_public_key)
);
const elements = this.get("stripe").elements();
this.set("cardElement", elements.create("card", { hidePostalCode: true }));
this.set("isCountryUS", this.cardholderAddress.country === "US");
this.set("isCountryCA", this.cardholderAddress.country === "CA");
},
alert(path) {
this.dialog.alert(I18n.t(`discourse_subscriptions.${path}`));
},
@discourseComputed("model.product.repurchaseable", "model.product.subscribed")
canPurchase(repurchaseable, subscribed) {
if (!repurchaseable && subscribed) {
return false;
}
return true;
},
createSubscription(plan) {
return this.stripe
.createToken(this.get("cardElement"), {
name: this.cardholderName, // Recommended by Stripe
address_line1: this.cardholderAddress.line1 ?? "",
address_city: this.cardholderAddress.city ?? "",
address_state: this.cardholderAddress.state ?? "",
address_zip: this.cardholderAddress.postalCode ?? "",
address_country: this.cardholderAddress.country, // Recommended by Stripe
})
.then((result) => {
if (result.error) {
this.set("loading", false);
return result;
} else {
const subscription = Subscription.create({
source: result.token.id,
plan: plan.get("id"),
promo: this.promoCode,
cardholderName: this.cardholderName,
cardholderAddress: this.cardholderAddress,
});
return subscription.save();
}
});
},
handleAuthentication(plan, transaction) {
return this.stripe
.confirmCardPayment(transaction.payment_intent.client_secret)
.then((result) => {
if (
result.paymentIntent &&
result.paymentIntent.status === "succeeded"
) {
return result;
} else {
this.set("loading", false);
this.dialog.alert(result.error.message || result.error);
return result;
}
});
},
_advanceSuccessfulTransaction(plan) {
this.alert("plans.success");
this.set("loading", false);
this.router.transitionTo(
plan.type === "recurring"
? "user.billing.subscriptions"
: "user.billing.payments",
this.currentUser.username.toLowerCase()
);
},
actions: {
changeCountry(country) {
this.set("cardholderAddress.country", country);
this.set("isCountryUS", country === "US");
this.set("isCountryCA", country === "CA");
},
changeState(stateOrProvince) {
this.set("cardholderAddress.state", stateOrProvince);
},
stripePaymentHandler() {
this.set("loading", true);
const plan = this.get("model.plans")
.filterBy("id", this.selectedPlan)
.get("firstObject");
const cardholderAddress = this.cardholderAddress;
const cardholderName = this.cardholderName;
if (!plan) {
this.alert("plans.validate.payment_options.required");
this.set("loading", false);
return;
}
if (!cardholderName) {
this.alert("subscribe.invalid_cardholder_name");
this.set("loading", false);
return;
}
if (!cardholderAddress.country) {
this.alert("subscribe.invalid_cardholder_country");
this.set("loading", false);
return;
}
if (cardholderAddress.country === "US" && !cardholderAddress.state) {
this.alert("subscribe.invalid_cardholder_state");
this.set("loading", false);
return;
}
if (cardholderAddress.country === "CA" && !cardholderAddress.state) {
this.alert("subscribe.invalid_cardholder_province");
this.set("loading", false);
return;
}
let transaction = this.createSubscription(plan);
transaction
.then((result) => {
if (result.error) {
this.dialog.alert(result.error.message || result.error);
} else if (
result.status === "incomplete" ||
result.status === "open"
) {
const transactionId = result.id;
const planId = this.selectedPlan;
this.handleAuthentication(plan, result).then(
(authenticationResult) => {
if (authenticationResult && !authenticationResult.error) {
return Transaction.finalize(transactionId, planId).then(
() => {
this._advanceSuccessfulTransaction(plan);
}
);
}
}
);
} else {
this._advanceSuccessfulTransaction(plan);
}
})
.catch((result) => {
this.dialog.alert(
result.jqXHR.responseJSON.errors[0] || result.errorThrown
);
this.set("loading", false);
});
},
},
});
@@ -1,3 +0,0 @@
import Controller from "@ember/controller";
export default Controller.extend({});
@@ -0,0 +1,37 @@
import Controller from "@ember/controller";
import { computed } from "@ember/object";
import { htmlSafe } from "@ember/template";
import I18n from "I18n";
export default Controller.extend({
init() {
// Perform any initialization logic here
this._super(...arguments);
// Additional custom initialization code
if (this.currentUser) {
this.currentUser
.checkEmail()
.then(() => this.set("email", this.currentUser.email));
}
},
pricingTable: computed("email", function () {
try {
const pricing_table_info = JSON.parse(
this.siteSettings.discourse_subscriptions_pricing_table
);
if (this.currentUser) {
return htmlSafe(`<stripe-pricing-table
pricing-table-id="${pricing_table_info.pricingTableId}"
publishable-key="${pricing_table_info.publishableKey}"
customer-email="${this.email}"></stripe-pricing-table>`);
} else {
return htmlSafe(`<stripe-pricing-table
pricing-table-id="${pricing_table_info.pricingTableId}"
publishable-key="${pricing_table_info.publishableKey}"
></stripe-pricing-table>`);
}
} catch (error) {
return I18n.t("discourse_subscriptions.subscribe.no_products");
}
}),
});
@@ -12,7 +12,7 @@ export default {
api.addNavigationBarItem({
name: "subscribe",
displayName: I18n.t("discourse_subscriptions.navigation.subscribe"),
href: "/s",
href: "/subscriptions",
});
}
@@ -23,6 +23,36 @@ export default {
href: `/u/${user.username}/billing/subscriptions`,
content: "Billing",
});
if (user.admin) {
api.modifyClassStatic("model:site-setting", {
pluginId: "discourse-subscriptions",
update(key, value, opts = {}) {
if (key === "discourse_subscriptions_pricing_table") {
const inputString = value;
// Extract pricing-table-id
const pricingTableIdRegex = /pricing-table-id="([^"]+)"/;
const pricingTableIdMatch =
inputString.match(pricingTableIdRegex);
const pricingTableId = pricingTableIdMatch
? pricingTableIdMatch[1]
: null;
// Extract publishable-key
const publishableKeyRegex = /publishable-key="([^"]+)"/;
const publishableKeyMatch =
inputString.match(publishableKeyRegex);
const publishableKey = publishableKeyMatch
? publishableKeyMatch[1]
: null;
if (pricingTableId && publishableKey) {
value = JSON.stringify({ pricingTableId, publishableKey });
}
}
this._super(key, value, opts);
},
});
}
}
});
},
@@ -40,12 +40,14 @@ const AdminProduct = EmberObject.extend({
AdminProduct.reopenClass({
findAll() {
return ajax("/s/admin/products", { method: "get" }).then((result) => {
if (result === null) {
return { unconfigured: true };
return ajax("/subscriptions/admin/products", { method: "get" }).then(
(result) => {
if (result === null) {
return { unconfigured: true };
}
return result.map((product) => AdminProduct.create(product));
}
return result.map((product) => AdminProduct.create(product));
});
);
},
find(id) {
@@ -1,5 +1,3 @@
export default function () {
this.route("subscribe", { path: "/s" }, function () {
this.route("show", { path: "/:subscription-id" });
});
this.route("subscriptions");
}
@@ -1,11 +0,0 @@
<div class="container">
<div class="title-wrapper">
<h1>
{{i18n "discourse_subscriptions.subscribe.title"}}
</h1>
</div>
<hr />
{{outlet}}
</div>
@@ -1,5 +0,0 @@
{{#unless isLoggedIn}}
<LoginRequired />
{{/unless}}
<ProductList @products={{model}} @isLoggedIn={{isLoggedIn}} />
@@ -1,126 +0,0 @@
<div class="discourse-subscriptions-section-columns">
<div class="section-column discourse-subscriptions-confirmation-billing">
<h2>
{{model.product.name}}
</h2>
<hr />
<p>
{{html-safe model.product.description}}
</p>
</div>
<div class="section-column">
{{#if canPurchase}}
<h2>
{{i18n "discourse_subscriptions.subscribe.card.title"}}
</h2>
<hr />
<PaymentOptions @plans={{model.plans}} @selectedPlan={{selectedPlan}} />
<hr />
<SubscribeCard @cardElement={{cardElement}} />
{{#if loading}}
{{loading-spinner}}
{{else if isAnonymous}}
<LoginRequired />
{{else}}
<Input
@type="text"
name="cardholder_name"
placeholder={{i18n
"discourse_subscriptions.subscribe.cardholder_name"
}}
@value={{cardholderName}}
class="subscribe-name"
/>
<div class="address-fields">
<SubscribeCountrySelect
@value={{cardholderAddress.country}}
@onChange={{action "changeCountry"}}
/>
<Input
@type="text"
name="cardholder_postal_code"
placeholder={{i18n
"discourse_subscriptions.subscribe.cardholder_address.postal_code"
}}
@value={{cardholderAddress.postalCode}}
class="subscribe-address-postal-code"
/>
</div>
<Input
@type="text"
name="cardholder_line1"
placeholder={{i18n
"discourse_subscriptions.subscribe.cardholder_address.line1"
}}
@value={{cardholderAddress.line1}}
class="subscribe-address-line1"
/>
<div class="address-fields">
<Input
@type="text"
name="cardholder_city"
placeholder={{i18n
"discourse_subscriptions.subscribe.cardholder_address.city"
}}
@value={{cardholderAddress.city}}
class="subscribe-address-city"
/>
{{#if isCountryUS}}
<SubscribeUsStateSelect
@value={{cardholderAddress.state}}
@onChange={{action "changeState"}}
/>
{{else if isCountryCA}}
<SubscribeCaProvinceSelect
@value={{cardholderAddress.state}}
@onChange={{action "changeState"}}
/>
{{else}}
<Input
@type="text"
name="cardholder_state"
placeholder={{i18n
"discourse_subscriptions.subscribe.cardholder_address.state"
}}
@value={{cardholderAddress.state}}
class="subscribe-address-state"
/>
{{/if}}
</div>
<Input
@type="text"
name="promo_code"
placeholder={{i18n "discourse_subscriptions.subscribe.promo_code"}}
@value={{promoCode}}
class="subscribe-promo-code"
/>
<DButton
@disabled={{loading}}
@action={{action "stripePaymentHandler"}}
class="btn btn-primary btn-payment"
@label="discourse_subscriptions.plans.payment_button"
/>
{{/if}}
{{else}}
<h2>{{i18n "discourse_subscriptions.subscribe.already_purchased"}}</h2>
<LinkTo
@route="user.billing.subscriptions"
@model={{currentUser.username}}
class="btn btn-primary"
>
{{i18n "discourse_subscriptions.subscribe.go_to_billing"}}
</LinkTo>
{{/if}}
</div>
</div>
@@ -0,0 +1,3 @@
<div class="container">
{{pricingTable}}
</div>
+1
View File
@@ -7,6 +7,7 @@ en:
site_settings:
discourse_subscriptions_enabled: Enable the Discourse Subscriptions plugin.
discourse_subscriptions_extra_nav_subscribe: Show the subscribe button in the primary navigation
discourse_subscriptions_pricing_table: Copy and paste the stripe pricing table embed code here
discourse_subscriptions_public_key: Stripe Publishable Key
discourse_subscriptions_secret_key: Stripe Secret Key
discourse_subscriptions_webhook_secret: Stripe Webhook Secret
+4
View File
@@ -4,6 +4,10 @@ discourse_subscriptions:
discourse_subscriptions_extra_nav_subscribe:
default: false
client: true
discourse_subscriptions_pricing_table:
default: ''
client: true
refresh: true
discourse_subscriptions_public_key:
default: ''
client: true
+15 -4
View File
@@ -21,10 +21,19 @@ register_svg_icon "far-credit-card" if respond_to?(:register_svg_icon)
register_html_builder("server:before-head-close") do |controller|
"<script src='https://js.stripe.com/v3/' nonce='#{controller.helpers.csp_nonce_placeholder}'></script>"
end
register_html_builder("server:before-head-close") do |controller|
"<script async src='https://js.stripe.com/v3/pricing-table.js' nonce='#{controller.helpers.csp_nonce_placeholder}'></script>"
end
extend_content_security_policy(script_src: %w[https://js.stripe.com/v3/ https://hooks.stripe.com])
extend_content_security_policy(
script_src: %w[
https://js.stripe.com/v3/
https://hooks.stripe.com
https://js.stripe.com/v3/pricing-table.js
],
)
add_admin_route "discourse_subscriptions.admin_navigation", "discourse-subscriptions.products"
add_admin_route "discourse_subscriptions.admin_navigation", "discourse-subscriptions.plans"
Discourse::Application.routes.append do
get "/admin/plugins/discourse-subscriptions" => "admin/plugins#index",
@@ -65,14 +74,16 @@ require_relative "app/controllers/concerns/group"
after_initialize do
::Stripe.api_version = "2024-04-10"
Discourse::Application.routes.append do
mount ::DiscourseSubscriptions::Engine, at: "subscriptions"
end
::Stripe.set_app_info(
"Discourse Subscriptions",
version: "2.8.2",
url: "https://github.com/discourse/discourse-subscriptions",
)
Discourse::Application.routes.append { mount ::DiscourseSubscriptions::Engine, at: "s" }
add_to_serializer(:site, :show_campaign_banner) do
begin
enabled = SiteSetting.discourse_subscriptions_enabled
@@ -12,7 +12,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
context "when unauthenticated" do
it "does nothing" do
::Stripe::PromotionCode.expects(:list).never
get "/s/admin/coupons.json"
get "/subscriptions/admin/coupons.json"
expect(response.status).to eq(404)
end
end
@@ -29,7 +29,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
.with({ limit: 100 })
.returns({ data: [{ id: "promo_123", coupon: { valid: true } }] })
get "/s/admin/coupons.json"
get "/subscriptions/admin/coupons.json"
expect(response.status).to eq(200)
expect(response.parsed_body[0]["id"]).to eq("promo_123")
end
@@ -40,7 +40,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
.with({ limit: 100 })
.returns({ data: [{ id: "promo_123", coupon: { valid: false } }] })
get "/s/admin/coupons.json"
get "/subscriptions/admin/coupons.json"
expect(response.status).to eq(200)
expect(response.parsed_body).to be_blank
end
@@ -53,7 +53,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
{ code: "p123", coupon: { amount_off: 2000 } },
)
post "/s/admin/coupons.json",
post "/subscriptions/admin/coupons.json",
params: {
promo: "p123",
discount_type: "amount",
@@ -71,7 +71,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
{ code: "p123", coupon: { percent_off: 20 } },
)
post "/s/admin/coupons.json",
post "/subscriptions/admin/coupons.json",
params: {
promo: "p123",
discount_type: "percent",
+55 -21
View File
@@ -13,11 +13,11 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "index" do
it "does not get the plans" do
::Stripe::Price.expects(:list).never
get "/s/admin/plans.json"
get "/subscriptions/admin/plans.json"
end
it "not ok" do
get "/s/admin/plans.json"
get "/subscriptions/admin/plans.json"
expect(response.status).to eq 404
end
end
@@ -25,11 +25,21 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "create" do
it "does not create a plan" do
::Stripe::Price.expects(:create).never
post "/s/admin/plans.json", params: { name: "Rick Astley", amount: 1, interval: "week" }
post "/subscriptions/admin/plans.json",
params: {
name: "Rick Astley",
amount: 1,
interval: "week",
}
end
it "is not ok" do
post "/s/admin/plans.json", params: { name: "Rick Astley", amount: 1, interval: "week" }
post "/subscriptions/admin/plans.json",
params: {
name: "Rick Astley",
amount: 1,
interval: "week",
}
expect(response.status).to eq 404
end
end
@@ -37,11 +47,11 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "show" do
it "does not show the plan" do
::Stripe::Price.expects(:retrieve).never
get "/s/admin/plans/plan_12345.json"
get "/subscriptions/admin/plans/plan_12345.json"
end
it "is not ok" do
get "/s/admin/plans/plan_12345.json"
get "/subscriptions/admin/plans/plan_12345.json"
expect(response.status).to eq 404
end
end
@@ -49,7 +59,7 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "update" do
it "does not update a plan" do
::Stripe::Price.expects(:update).never
delete "/s/admin/plans/plan_12345.json"
delete "/subscriptions/admin/plans/plan_12345.json"
end
end
end
@@ -62,19 +72,19 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "index" do
it "lists the plans" do
::Stripe::Price.expects(:list).with(nil)
get "/s/admin/plans.json"
get "/subscriptions/admin/plans.json"
end
it "lists the plans for the product" do
::Stripe::Price.expects(:list).with({ product: "prod_id123" })
get "/s/admin/plans.json", params: { product_id: "prod_id123" }
get "/subscriptions/admin/plans.json", params: { product_id: "prod_id123" }
end
end
describe "show" do
it "shows a plan" do
::Stripe::Price.expects(:retrieve).with("plan_12345").returns(currency: "aud")
get "/s/admin/plans/plan_12345.json"
get "/subscriptions/admin/plans/plan_12345.json"
expect(response.status).to eq 200
end
@@ -83,7 +93,7 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
.expects(:retrieve)
.with("plan_12345")
.returns(currency: "aud", recurring: { interval: "year" })
get "/s/admin/plans/plan_12345.json"
get "/subscriptions/admin/plans/plan_12345.json"
plan = response.parsed_body
expect(plan["currency"]).to eq "AUD"
@@ -94,17 +104,29 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "create" do
it "creates a plan with a nickname" do
::Stripe::Price.expects(:create).with(has_entry(:nickname, "Veg"))
post "/s/admin/plans.json", params: { nickname: "Veg", metadata: { group_name: "" } }
post "/subscriptions/admin/plans.json",
params: {
nickname: "Veg",
metadata: {
group_name: "",
},
}
end
it "creates a plan with a currency" do
::Stripe::Price.expects(:create).with(has_entry(:currency, "AUD"))
post "/s/admin/plans.json", params: { currency: "AUD", metadata: { group_name: "" } }
post "/subscriptions/admin/plans.json",
params: {
currency: "AUD",
metadata: {
group_name: "",
},
}
end
it "creates a plan with an interval" do
::Stripe::Price.expects(:create).with(has_entry(recurring: { interval: "week" }))
post "/s/admin/plans.json",
post "/subscriptions/admin/plans.json",
params: {
type: "recurring",
interval: "week",
@@ -116,17 +138,23 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
it "creates a plan as a one-time purchase" do
::Stripe::Price.expects(:create).with(Not(has_key(:recurring)))
post "/s/admin/plans.json", params: { metadata: { group_name: "" } }
post "/subscriptions/admin/plans.json", params: { metadata: { group_name: "" } }
end
it "creates a plan with an amount" do
::Stripe::Price.expects(:create).with(has_entry(:unit_amount, "102"))
post "/s/admin/plans.json", params: { amount: "102", metadata: { group_name: "" } }
post "/subscriptions/admin/plans.json",
params: {
amount: "102",
metadata: {
group_name: "",
},
}
end
it "creates a plan with a product" do
::Stripe::Price.expects(:create).with(has_entry(product: "prod_walterwhite"))
post "/s/admin/plans.json",
post "/subscriptions/admin/plans.json",
params: {
product: "prod_walterwhite",
metadata: {
@@ -137,7 +165,13 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
it "creates a plan with an active status" do
::Stripe::Price.expects(:create).with(has_entry(:active, "false"))
post "/s/admin/plans.json", params: { active: "false", metadata: { group_name: "" } }
post "/subscriptions/admin/plans.json",
params: {
active: "false",
metadata: {
group_name: "",
},
}
end
# TODO: Need to fix the metadata tests
@@ -145,19 +179,19 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
#it 'has metadata' do
# ::Stripe::Price.expects(:create).with(has_entry(:group_name, "discourse-user-group-name"))
# post "/s/admin/plans.json", params: { amount: "100", metadata: { group_name: 'discourse-user-group-name' } }
# post "/subscriptions/admin/plans.json", params: { amount: "100", metadata: { group_name: 'discourse-user-group-name' } }
#end
#it "creates a plan with a trial period" do
# ::Stripe::Price.expects(:create).with(has_entry(trial_period_days: '14'))
# post "/s/admin/plans.json", params: { trial_period_days: '14' }
# post "/subscriptions/admin/plans.json", params: { trial_period_days: '14' }
#end
end
describe "update" do
it "updates a plan" do
::Stripe::Price.expects(:update)
patch "/s/admin/plans/plan_12345.json",
patch "/subscriptions/admin/plans/plan_12345.json",
params: {
trial_period_days: "14",
metadata: {
+15 -15
View File
@@ -12,31 +12,31 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
context "when unauthenticated" do
it "does not list the products" do
::Stripe::Product.expects(:list).never
get "/s/admin/products.json"
get "/subscriptions/admin/products.json"
expect(response.status).to eq(404)
end
it "does not create the product" do
::Stripe::Product.expects(:create).never
post "/s/admin/products.json"
post "/subscriptions/admin/products.json"
expect(response.status).to eq(404)
end
it "does not show the product" do
::Stripe::Product.expects(:retrieve).never
get "/s/admin/products/prod_qwerty123.json"
get "/subscriptions/admin/products/prod_qwerty123.json"
expect(response.status).to eq(404)
end
it "does not update the product" do
::Stripe::Product.expects(:update).never
put "/s/admin/products/prod_qwerty123.json"
put "/subscriptions/admin/products/prod_qwerty123.json"
expect(response.status).to eq(404)
end
it "does not delete the product" do
::Stripe::Product.expects(:delete).never
delete "/s/admin/products/u2.json"
delete "/subscriptions/admin/products/u2.json"
expect(response.status).to eq(404)
end
end
@@ -50,7 +50,7 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
it "gets the empty products" do
SiteSetting.discourse_subscriptions_public_key = "public-key"
SiteSetting.discourse_subscriptions_secret_key = "secret-key"
get "/s/admin/products.json"
get "/subscriptions/admin/products.json"
expect(response.parsed_body).to be_empty
end
end
@@ -58,24 +58,24 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
describe "create" do
it "is of product type service" do
::Stripe::Product.expects(:create).with(has_entry(:type, "service"))
post "/s/admin/products.json", params: {}
post "/subscriptions/admin/products.json", params: {}
end
it "has a name" do
::Stripe::Product.expects(:create).with(has_entry(:name, "Jesse Pinkman"))
post "/s/admin/products.json", params: { name: "Jesse Pinkman" }
post "/subscriptions/admin/products.json", params: { name: "Jesse Pinkman" }
end
it "has an active attribute" do
::Stripe::Product.expects(:create).with(has_entry(active: "false"))
post "/s/admin/products.json", params: { active: "false" }
post "/subscriptions/admin/products.json", params: { active: "false" }
end
it "has a statement descriptor" do
::Stripe::Product.expects(:create).with(
has_entry(statement_descriptor: "Blessed are the cheesemakers"),
)
post "/s/admin/products.json",
post "/subscriptions/admin/products.json",
params: {
statement_descriptor: "Blessed are the cheesemakers",
}
@@ -83,7 +83,7 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
it "has no statement descriptor if empty" do
::Stripe::Product.expects(:create).with(has_key(:statement_descriptor)).never
post "/s/admin/products.json", params: { statement_descriptor: "" }
post "/subscriptions/admin/products.json", params: { statement_descriptor: "" }
end
it "has metadata" do
@@ -96,7 +96,7 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
),
)
post "/s/admin/products.json",
post "/subscriptions/admin/products.json",
params: {
metadata: {
description: "Oi, I think he just said bless be all the bignoses!",
@@ -109,21 +109,21 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
describe "show" do
it "retrieves the product" do
::Stripe::Product.expects(:retrieve).with("prod_walterwhite")
get "/s/admin/products/prod_walterwhite.json"
get "/subscriptions/admin/products/prod_walterwhite.json"
end
end
describe "update" do
it "updates the product" do
::Stripe::Product.expects(:update)
patch "/s/admin/products/prod_walterwhite.json", params: {}
patch "/subscriptions/admin/products/prod_walterwhite.json", params: {}
end
end
describe "delete" do
it "deletes the product" do
::Stripe::Product.expects(:delete).with("prod_walterwhite")
delete "/s/admin/products/prod_walterwhite.json"
delete "/subscriptions/admin/products/prod_walterwhite.json"
end
end
end
@@ -24,13 +24,13 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
context "when unauthenticated" do
it "does nothing" do
::Stripe::Subscription.expects(:list).never
get "/s/admin/subscriptions.json"
get "/subscriptions/admin/subscriptions.json"
expect(response.status).to eq(404)
end
it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never
patch "/s/admin/subscriptions/sub_12345.json"
patch "/subscriptions/admin/subscriptions/sub_12345.json"
end
end
@@ -50,7 +50,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
.expects(:list)
.with(expand: ["data.plan.product"], limit: 10, starting_after: nil)
.returns(has_more: false, data: [{ id: "sub_12345" }, { id: "sub_nope" }])
get "/s/admin/subscriptions.json"
get "/subscriptions/admin/subscriptions.json"
subscriptions = response.parsed_body["data"][0]["id"]
expect(response.status).to eq(200)
@@ -62,7 +62,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
.expects(:list)
.with(expand: ["data.plan.product"], limit: 10, starting_after: "sub_nope")
.returns(has_more: false, data: [{ id: "sub_77777" }, { id: "sub_yepnoep" }])
get "/s/admin/subscriptions.json", params: { last_record: "sub_nope" }
get "/subscriptions/admin/subscriptions.json", params: { last_record: "sub_nope" }
subscriptions = response.parsed_body["data"][0]["id"]
expect(response.status).to eq(200)
@@ -81,7 +81,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
.with("sub_12345")
.returns(plan: { product: "pr_34578" }, customer: "c_123")
expect { delete "/s/admin/subscriptions/sub_12345.json" }.to change {
expect { delete "/subscriptions/admin/subscriptions/sub_12345.json" }.to change {
DiscourseSubscriptions::Customer.count
}.by(-1)
end
@@ -100,7 +100,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
customer: "c_123",
)
expect { delete "/s/admin/subscriptions/sub_12345.json" }.to change {
expect { delete "/subscriptions/admin/subscriptions/sub_12345.json" }.to change {
user.groups.count
}.by(-1)
end
@@ -119,7 +119,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
customer: "c_123",
)
expect { delete "/s/admin/subscriptions/sub_12345.json" }.not_to change {
expect { delete "/subscriptions/admin/subscriptions/sub_12345.json" }.not_to change {
user.groups.count
}
end
@@ -136,7 +136,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
::Stripe::Invoice.expects(:retrieve).with("in_123").returns(payment_intent: "pi_123")
::Stripe::Refund.expects(:create).with({ payment_intent: "pi_123" })
delete "/s/admin/subscriptions/sub_12345.json", params: { refund: true }
delete "/subscriptions/admin/subscriptions/sub_12345.json", params: { refund: true }
end
end
end
+1 -1
View File
@@ -15,7 +15,7 @@ RSpec.describe DiscourseSubscriptions::AdminController do
end
it "is ok" do
get "/s/admin.json"
get "/subscriptions/admin.json"
expect(response.status).to eq(200)
end
end
+9 -7
View File
@@ -17,7 +17,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
.with("we-want-a-shrubbery", "stripe-webhook-signature", "zascharoo")
.returns(type: "something")
post "/s/hooks.json", params: payload, headers: headers
post "/subscriptions/hooks.json", params: payload, headers: headers
expect(response.status).to eq 200
end
@@ -51,7 +51,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
end
it "is successfull" do
post "/s/hooks.json"
post "/subscriptions/hooks.json"
expect(response.status).to eq 200
end
@@ -60,7 +60,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
event_data[:object][:status] = "incomplete"
event_data[:previous_attributes] = { status: "incomplete" }
expect { post "/s/hooks.json" }.not_to change { user.groups.count }
expect { post "/subscriptions/hooks.json" }.not_to change { user.groups.count }
expect(response.status).to eq 200
end
@@ -69,7 +69,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
event_data[:object][:status] = "incomplete"
event_data[:previous_attributes] = { status: "something-else" }
expect { post "/s/hooks.json" }.not_to change { user.groups.count }
expect { post "/subscriptions/hooks.json" }.not_to change { user.groups.count }
expect(response.status).to eq 200
end
@@ -78,7 +78,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
event_data[:object][:status] = "complete"
event_data[:previous_attributes] = { status: "incomplete" }
expect { post "/s/hooks.json" }.to change { user.groups.count }.by(1)
expect { post "/subscriptions/hooks.json" }.to change { user.groups.count }.by(1)
expect(response.status).to eq 200
end
@@ -95,13 +95,15 @@ RSpec.describe DiscourseSubscriptions::HooksController do
end
it "deletes the customer" do
expect { post "/s/hooks.json" }.to change { DiscourseSubscriptions::Customer.count }.by(-1)
expect { post "/subscriptions/hooks.json" }.to change {
DiscourseSubscriptions::Customer.count
}.by(-1)
expect(response.status).to eq 200
end
it "removes the user from the group" do
expect { post "/s/hooks.json" }.to change { user.groups.count }.by(-1)
expect { post "/subscriptions/hooks.json" }.to change { user.groups.count }.by(-1)
expect(response.status).to eq 200
end
+28 -70
View File
@@ -68,56 +68,6 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
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")
@@ -132,7 +82,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
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"
get "/subscriptions/contributors.json"
data = response.parsed_body
expect(data).to be_empty
@@ -145,7 +95,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
it "filters users by campaign product if set" do
SiteSetting.discourse_subscriptions_campaign_product = "prod_campaign"
get "/s/contributors.json"
get "/subscriptions/contributors.json"
data = response.parsed_body
expect(data.first["id"]).to eq campaign_user.id
@@ -155,7 +105,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
it "shows all purchases if campaign product not set" do
SiteSetting.discourse_subscriptions_campaign_product = nil
get "/s/contributors.json"
get "/subscriptions/contributors.json"
data = response.parsed_body
expect(data.length).to eq 2
@@ -170,7 +120,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
.expects(:list)
.with(active: true, product: "prod_walterwhite")
.returns(prices)
get "/s/prod_walterwhite.json"
get "/subscriptions/prod_walterwhite.json"
expect(response.parsed_body).to eq(
{
@@ -222,7 +172,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController 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" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
end
end
@@ -257,7 +207,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
.returns(status: "active", customer: "cus_1234")
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
}.to change { DiscourseSubscriptions::Customer.count }
end
@@ -291,7 +241,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234")
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
}.to change { DiscourseSubscriptions::Customer.count }
end
@@ -300,13 +250,13 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234")
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/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" }
post "/subscriptions/create.json", params: { plan: "plan_5678", source: "tok_5678" }
}.not_to change { DiscourseSubscriptions::Customer.count }
end
@@ -315,7 +265,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {})
::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234")
expect {
post "/s/create.json",
post "/subscriptions/create.json",
params: {
plan: "plan_1234",
source: "tok_1234",
@@ -346,7 +296,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::PromotionCode.expects(:list).with({ code: "invalid" }).returns(data: [])
post "/s/create.json",
post "/subscriptions/create.json",
params: {
plan: "plan_1234",
source: "tok_1234",
@@ -391,7 +341,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
.returns(status: "active", customer: "cus_1234")
expect {
post "/s/create.json",
post "/subscriptions/create.json",
params: {
plan: "plan_1234",
source: "tok_1234",
@@ -434,7 +384,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234")
expect {
post "/s/create.json",
post "/subscriptions/create.json",
params: {
plan: "plan_1234",
source: "tok_1234",
@@ -462,7 +412,11 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
)
expect {
post "/s/finalize.json", params: { plan: "plan_1234", transaction: "sub_1234" }
post "/subscriptions/finalize.json",
params: {
plan: "plan_1234",
transaction: "sub_1234",
}
}.to change { DiscourseSubscriptions::Customer.count }
end
end
@@ -482,7 +436,11 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
)
expect {
post "/s/finalize.json", params: { plan: "plan_1234", transaction: "in_1234" }
post "/subscriptions/finalize.json",
params: {
plan: "plan_1234",
transaction: "in_1234",
}
}.to change { DiscourseSubscriptions::Customer.count }
end
end
@@ -505,7 +463,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
group_name: "admins",
},
)
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
expect(user.admin).to eq false
end
@@ -516,7 +474,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
group_name: "other",
},
)
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
expect(user.groups).to be_empty
end
end
@@ -536,7 +494,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "failed")
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
}.not_to change { group.users.count }
expect(user.groups).to be_empty
@@ -546,7 +504,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "active")
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
}.to change { group.users.count }
expect(user.groups).not_to be_empty
@@ -556,7 +514,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "trialing")
expect {
post "/s/create.json", params: { plan: "plan_1234", source: "tok_1234" }
post "/subscriptions/create.json", params: { plan: "plan_1234", source: "tok_1234" }
}.to change { group.users.count }
expect(user.groups).not_to be_empty
@@ -12,7 +12,7 @@ RSpec.describe DiscourseSubscriptions::User::PaymentsController do
context "when not authenticated" do
it "does not get the payment intents" do
::Stripe::PaymentIntent.expects(:list).never
get "/s/user/payments.json"
get "/subscriptions/user/payments.json"
expect(response.status).to eq(403)
end
end
@@ -52,7 +52,7 @@ RSpec.describe DiscourseSubscriptions::User::PaymentsController do
],
)
get "/s/user/payments.json"
get "/subscriptions/user/payments.json"
parsed_body = response.parsed_body
invoice = parsed_body[0]["invoice"]
@@ -14,18 +14,21 @@ RSpec.describe DiscourseSubscriptions::User::SubscriptionsController do
context "when not authenticated" do
it "does not get the subscriptions" do
::Stripe::Customer.expects(:list).never
get "/s/user/subscriptions.json"
get "/subscriptions/user/subscriptions.json"
end
it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never
patch "/s/user/subscriptions/sub_12345.json"
patch "/subscriptions/user/subscriptions/sub_12345.json"
end
it "doesn't update payment method for subscription" do
::Stripe::Subscription.expects(:update).never
::Stripe::PaymentMethod.expects(:attach).never
put "/s/user/subscriptions/sub_12345.json", params: { payment_method: "pm_abc123abc" }
put "/subscriptions/user/subscriptions/sub_12345.json",
params: {
payment_method: "pm_abc123abc",
}
end
end
@@ -74,7 +77,7 @@ RSpec.describe DiscourseSubscriptions::User::SubscriptionsController do
.with(email: user.email, expand: ["data.subscriptions"])
.returns(customers)
get "/s/user/subscriptions.json"
get "/subscriptions/user/subscriptions.json"
subscription = response.parsed_body.first
@@ -100,7 +103,10 @@ RSpec.describe DiscourseSubscriptions::User::SubscriptionsController do
it "updates the payment method for subscription" do
::Stripe::Subscription.expects(:update).once
::Stripe::PaymentMethod.expects(:attach).once
put "/s/user/subscriptions/sub_1234.json", params: { payment_method: "pm_abc123abc" }
put "/subscriptions/user/subscriptions/sub_1234.json",
params: {
payment_method: "pm_abc123abc",
}
end
end
end
+1 -1
View File
@@ -29,7 +29,7 @@ describe "Subscription products", type: :system do
end
it "shows the login modal" do
visit("/s")
visit("/subscriptions")
find("button.login-required.subscriptions").click
@@ -1,18 +0,0 @@
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
import { acceptance, count } from "discourse/tests/helpers/qunit-helpers";
import { stubStripe } from "discourse/plugins/discourse-subscriptions/helpers/stripe";
acceptance("Discourse Subscriptions", function (needs) {
needs.user();
needs.hooks.beforeEach(function () {
stubStripe();
});
test("viewing product page", async function (assert) {
await visit("/s");
assert.ok(count(".product-list") > 0, "has product page");
assert.ok(count(".product:first-child a") > 0, "has a link");
});
});
@@ -0,0 +1,50 @@
import { visit } from "@ember/test-helpers";
import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Discourse Subscriptions", function (needs) {
needs.user();
needs.settings({
discourse_subscriptions_pricing_table: JSON.stringify({
pricingTableId: "pricingTableId",
publishableKey: "publishableKey",
}),
});
test("pricing table element includes email", async function (assert) {
await visit("/subscriptions");
assert.equal(
document.querySelector("stripe-pricing-table").outerHTML,
`<stripe-pricing-table pricing-table-id="pricingTableId" publishable-key="publishableKey" customer-email="[email protected]"></stripe-pricing-table>`
);
});
});
acceptance("Discourse Subscriptions", function (needs) {
needs.settings({
discourse_subscriptions_pricing_table: JSON.stringify({
pricingTableId: "pricingTableId",
publishableKey: "publishableKey",
}),
});
test("pricing table works for people without account", async function (assert) {
await visit("/subscriptions");
assert.equal(
document.querySelector("stripe-pricing-table").outerHTML,
`<stripe-pricing-table pricing-table-id="pricingTableId" publishable-key="publishableKey"></stripe-pricing-table>`
);
});
});
acceptance("Discourse Subscriptions", function (needs) {
needs.user();
test("pricing table element does not show up if not configured", async function (assert) {
await visit("/subscriptions");
assert.equal(document.querySelector("stripe-pricing-table"), null);
});
});
@@ -1,66 +0,0 @@
import { click, visit } from "@ember/test-helpers";
import { test } from "qunit";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
import { acceptance, count } from "discourse/tests/helpers/qunit-helpers";
import I18n from "discourse-i18n";
import { stubStripe } from "discourse/plugins/discourse-subscriptions/helpers/stripe";
function singleProductPretender() {
pretender.get("/s", () => {
const products = [
{
id: "prod_23o8I7tU4g56",
name: "Awesome Product",
description:
"Subscribe to our awesome product. For only $230.10 per month, you can get access. This is a test site. No real credit card transactions.",
},
];
return response(products);
});
}
acceptance("Discourse Subscriptions", function (needs) {
needs.user();
needs.hooks.beforeEach(function () {
stubStripe();
});
test("subscribing", async function (assert) {
await visit("/s");
await click(".product:first-child a");
assert.ok(
count(".discourse-subscriptions-section-columns") > 0,
"has the sections for billing"
);
assert.ok(
count(".subscribe-buttons button") > 0,
"has buttons for subscribe"
);
});
test("skips products list on sites with one product", async function (assert) {
singleProductPretender();
await visit("/s");
assert.dom(".subscribe-buttons button").exists({ count: 1 });
assert.dom("input.subscribe-promo-code").exists();
assert.dom("button.btn-payment").exists();
});
// In YAML `NO:` is a boolean, so we need quotes around `"NO":`.
test("Norway is translated correctly", async function (assert) {
assert.equal(
I18n.t("discourse_subscriptions.subscribe.countries.NO"),
"Norway"
);
assert.equal(
I18n.t("discourse_subscriptions.subscribe.countries.NG"),
"Nigeria"
);
});
});
@@ -1,7 +1,7 @@
export default function (helpers) {
const { response } = helpers;
this.get("/s", () => {
this.get("/subscriptions", () => {
const products = [
{
id: "prod_23o8I7tU4g56",
@@ -20,7 +20,7 @@ export default function (helpers) {
return response(products);
});
this.get("/s/:id", () => {
this.get("/subscriptions/:id", () => {
const product = {
id: "prod_23o8I7tU4g56",
name: "Awesome Product",