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] requires_login except: %i[index contributors show]
def index def index
begin head 200
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
end end
def contributors def contributors
@@ -58,12 +58,14 @@ export default Component.extend({
} }
if (this.currentUser && this.showContributors) { if (this.currentUser && this.showContributors) {
return ajax("/s/contributors", { method: "get" }).then((result) => { return ajax("/subscriptions/contributors", { method: "get" }).then(
this.setProperties({ (result) => {
contributors: result, this.setProperties({
loading: false, 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({ api.addNavigationBarItem({
name: "subscribe", name: "subscribe",
displayName: I18n.t("discourse_subscriptions.navigation.subscribe"), displayName: I18n.t("discourse_subscriptions.navigation.subscribe"),
href: "/s", href: "/subscriptions",
}); });
} }
@@ -23,6 +23,36 @@ export default {
href: `/u/${user.username}/billing/subscriptions`, href: `/u/${user.username}/billing/subscriptions`,
content: "Billing", 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({ AdminProduct.reopenClass({
findAll() { findAll() {
return ajax("/s/admin/products", { method: "get" }).then((result) => { return ajax("/subscriptions/admin/products", { method: "get" }).then(
if (result === null) { (result) => {
return { unconfigured: true }; if (result === null) {
return { unconfigured: true };
}
return result.map((product) => AdminProduct.create(product));
} }
return result.map((product) => AdminProduct.create(product)); );
});
}, },
find(id) { find(id) {
@@ -1,5 +1,3 @@
export default function () { export default function () {
this.route("subscribe", { path: "/s" }, function () { this.route("subscriptions");
this.route("show", { path: "/:subscription-id" });
});
} }
@@ -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: site_settings:
discourse_subscriptions_enabled: Enable the Discourse Subscriptions plugin. discourse_subscriptions_enabled: Enable the Discourse Subscriptions plugin.
discourse_subscriptions_extra_nav_subscribe: Show the subscribe button in the primary navigation 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_public_key: Stripe Publishable Key
discourse_subscriptions_secret_key: Stripe Secret Key discourse_subscriptions_secret_key: Stripe Secret Key
discourse_subscriptions_webhook_secret: Stripe Webhook Secret discourse_subscriptions_webhook_secret: Stripe Webhook Secret
+4
View File
@@ -4,6 +4,10 @@ discourse_subscriptions:
discourse_subscriptions_extra_nav_subscribe: discourse_subscriptions_extra_nav_subscribe:
default: false default: false
client: true client: true
discourse_subscriptions_pricing_table:
default: ''
client: true
refresh: true
discourse_subscriptions_public_key: discourse_subscriptions_public_key:
default: '' default: ''
client: true 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| register_html_builder("server:before-head-close") do |controller|
"<script src='https://js.stripe.com/v3/' nonce='#{controller.helpers.csp_nonce_placeholder}'></script>" "<script src='https://js.stripe.com/v3/' nonce='#{controller.helpers.csp_nonce_placeholder}'></script>"
end 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 Discourse::Application.routes.append do
get "/admin/plugins/discourse-subscriptions" => "admin/plugins#index", get "/admin/plugins/discourse-subscriptions" => "admin/plugins#index",
@@ -65,14 +74,16 @@ require_relative "app/controllers/concerns/group"
after_initialize do after_initialize do
::Stripe.api_version = "2024-04-10" ::Stripe.api_version = "2024-04-10"
Discourse::Application.routes.append do
mount ::DiscourseSubscriptions::Engine, at: "subscriptions"
end
::Stripe.set_app_info( ::Stripe.set_app_info(
"Discourse Subscriptions", "Discourse Subscriptions",
version: "2.8.2", version: "2.8.2",
url: "https://github.com/discourse/discourse-subscriptions", url: "https://github.com/discourse/discourse-subscriptions",
) )
Discourse::Application.routes.append { mount ::DiscourseSubscriptions::Engine, at: "s" }
add_to_serializer(:site, :show_campaign_banner) do add_to_serializer(:site, :show_campaign_banner) do
begin begin
enabled = SiteSetting.discourse_subscriptions_enabled enabled = SiteSetting.discourse_subscriptions_enabled
@@ -12,7 +12,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
context "when unauthenticated" do context "when unauthenticated" do
it "does nothing" do it "does nothing" do
::Stripe::PromotionCode.expects(:list).never ::Stripe::PromotionCode.expects(:list).never
get "/s/admin/coupons.json" get "/subscriptions/admin/coupons.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
end end
@@ -29,7 +29,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
.with({ limit: 100 }) .with({ limit: 100 })
.returns({ data: [{ id: "promo_123", coupon: { valid: true } }] }) .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.status).to eq(200)
expect(response.parsed_body[0]["id"]).to eq("promo_123") expect(response.parsed_body[0]["id"]).to eq("promo_123")
end end
@@ -40,7 +40,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
.with({ limit: 100 }) .with({ limit: 100 })
.returns({ data: [{ id: "promo_123", coupon: { valid: false } }] }) .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.status).to eq(200)
expect(response.parsed_body).to be_blank expect(response.parsed_body).to be_blank
end end
@@ -53,7 +53,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
{ code: "p123", coupon: { amount_off: 2000 } }, { code: "p123", coupon: { amount_off: 2000 } },
) )
post "/s/admin/coupons.json", post "/subscriptions/admin/coupons.json",
params: { params: {
promo: "p123", promo: "p123",
discount_type: "amount", discount_type: "amount",
@@ -71,7 +71,7 @@ RSpec.describe DiscourseSubscriptions::Admin::CouponsController do
{ code: "p123", coupon: { percent_off: 20 } }, { code: "p123", coupon: { percent_off: 20 } },
) )
post "/s/admin/coupons.json", post "/subscriptions/admin/coupons.json",
params: { params: {
promo: "p123", promo: "p123",
discount_type: "percent", discount_type: "percent",
+55 -21
View File
@@ -13,11 +13,11 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "index" do describe "index" do
it "does not get the plans" do it "does not get the plans" do
::Stripe::Price.expects(:list).never ::Stripe::Price.expects(:list).never
get "/s/admin/plans.json" get "/subscriptions/admin/plans.json"
end end
it "not ok" do it "not ok" do
get "/s/admin/plans.json" get "/subscriptions/admin/plans.json"
expect(response.status).to eq 404 expect(response.status).to eq 404
end end
end end
@@ -25,11 +25,21 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "create" do describe "create" do
it "does not create a plan" do it "does not create a plan" do
::Stripe::Price.expects(:create).never ::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 end
it "is not ok" do 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 expect(response.status).to eq 404
end end
end end
@@ -37,11 +47,11 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "show" do describe "show" do
it "does not show the plan" do it "does not show the plan" do
::Stripe::Price.expects(:retrieve).never ::Stripe::Price.expects(:retrieve).never
get "/s/admin/plans/plan_12345.json" get "/subscriptions/admin/plans/plan_12345.json"
end end
it "is not ok" do 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 expect(response.status).to eq 404
end end
end end
@@ -49,7 +59,7 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "update" do describe "update" do
it "does not update a plan" do it "does not update a plan" do
::Stripe::Price.expects(:update).never ::Stripe::Price.expects(:update).never
delete "/s/admin/plans/plan_12345.json" delete "/subscriptions/admin/plans/plan_12345.json"
end end
end end
end end
@@ -62,19 +72,19 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "index" do describe "index" do
it "lists the plans" do it "lists the plans" do
::Stripe::Price.expects(:list).with(nil) ::Stripe::Price.expects(:list).with(nil)
get "/s/admin/plans.json" get "/subscriptions/admin/plans.json"
end end
it "lists the plans for the product" do it "lists the plans for the product" do
::Stripe::Price.expects(:list).with({ product: "prod_id123" }) ::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
end end
describe "show" do describe "show" do
it "shows a plan" do it "shows a plan" do
::Stripe::Price.expects(:retrieve).with("plan_12345").returns(currency: "aud") ::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 expect(response.status).to eq 200
end end
@@ -83,7 +93,7 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
.expects(:retrieve) .expects(:retrieve)
.with("plan_12345") .with("plan_12345")
.returns(currency: "aud", recurring: { interval: "year" }) .returns(currency: "aud", recurring: { interval: "year" })
get "/s/admin/plans/plan_12345.json" get "/subscriptions/admin/plans/plan_12345.json"
plan = response.parsed_body plan = response.parsed_body
expect(plan["currency"]).to eq "AUD" expect(plan["currency"]).to eq "AUD"
@@ -94,17 +104,29 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
describe "create" do describe "create" do
it "creates a plan with a nickname" do it "creates a plan with a nickname" do
::Stripe::Price.expects(:create).with(has_entry(:nickname, "Veg")) ::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 end
it "creates a plan with a currency" do it "creates a plan with a currency" do
::Stripe::Price.expects(:create).with(has_entry(:currency, "AUD")) ::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 end
it "creates a plan with an interval" do it "creates a plan with an interval" do
::Stripe::Price.expects(:create).with(has_entry(recurring: { interval: "week" })) ::Stripe::Price.expects(:create).with(has_entry(recurring: { interval: "week" }))
post "/s/admin/plans.json", post "/subscriptions/admin/plans.json",
params: { params: {
type: "recurring", type: "recurring",
interval: "week", interval: "week",
@@ -116,17 +138,23 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
it "creates a plan as a one-time purchase" do it "creates a plan as a one-time purchase" do
::Stripe::Price.expects(:create).with(Not(has_key(:recurring))) ::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 end
it "creates a plan with an amount" do it "creates a plan with an amount" do
::Stripe::Price.expects(:create).with(has_entry(:unit_amount, "102")) ::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 end
it "creates a plan with a product" do it "creates a plan with a product" do
::Stripe::Price.expects(:create).with(has_entry(product: "prod_walterwhite")) ::Stripe::Price.expects(:create).with(has_entry(product: "prod_walterwhite"))
post "/s/admin/plans.json", post "/subscriptions/admin/plans.json",
params: { params: {
product: "prod_walterwhite", product: "prod_walterwhite",
metadata: { metadata: {
@@ -137,7 +165,13 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
it "creates a plan with an active status" do it "creates a plan with an active status" do
::Stripe::Price.expects(:create).with(has_entry(:active, "false")) ::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 end
# TODO: Need to fix the metadata tests # TODO: Need to fix the metadata tests
@@ -145,19 +179,19 @@ RSpec.describe DiscourseSubscriptions::Admin::PlansController do
#it 'has metadata' do #it 'has metadata' do
# ::Stripe::Price.expects(:create).with(has_entry(:group_name, "discourse-user-group-name")) # ::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 #end
#it "creates a plan with a trial period" do #it "creates a plan with a trial period" do
# ::Stripe::Price.expects(:create).with(has_entry(trial_period_days: '14')) # ::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
end end
describe "update" do describe "update" do
it "updates a plan" do it "updates a plan" do
::Stripe::Price.expects(:update) ::Stripe::Price.expects(:update)
patch "/s/admin/plans/plan_12345.json", patch "/subscriptions/admin/plans/plan_12345.json",
params: { params: {
trial_period_days: "14", trial_period_days: "14",
metadata: { metadata: {
+15 -15
View File
@@ -12,31 +12,31 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
context "when unauthenticated" do context "when unauthenticated" do
it "does not list the products" do it "does not list the products" do
::Stripe::Product.expects(:list).never ::Stripe::Product.expects(:list).never
get "/s/admin/products.json" get "/subscriptions/admin/products.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "does not create the product" do it "does not create the product" do
::Stripe::Product.expects(:create).never ::Stripe::Product.expects(:create).never
post "/s/admin/products.json" post "/subscriptions/admin/products.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "does not show the product" do it "does not show the product" do
::Stripe::Product.expects(:retrieve).never ::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) expect(response.status).to eq(404)
end end
it "does not update the product" do it "does not update the product" do
::Stripe::Product.expects(:update).never ::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) expect(response.status).to eq(404)
end end
it "does not delete the product" do it "does not delete the product" do
::Stripe::Product.expects(:delete).never ::Stripe::Product.expects(:delete).never
delete "/s/admin/products/u2.json" delete "/subscriptions/admin/products/u2.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
end end
@@ -50,7 +50,7 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
it "gets the empty products" do it "gets the empty products" do
SiteSetting.discourse_subscriptions_public_key = "public-key" SiteSetting.discourse_subscriptions_public_key = "public-key"
SiteSetting.discourse_subscriptions_secret_key = "secret-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 expect(response.parsed_body).to be_empty
end end
end end
@@ -58,24 +58,24 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
describe "create" do describe "create" do
it "is of product type service" do it "is of product type service" do
::Stripe::Product.expects(:create).with(has_entry(:type, "service")) ::Stripe::Product.expects(:create).with(has_entry(:type, "service"))
post "/s/admin/products.json", params: {} post "/subscriptions/admin/products.json", params: {}
end end
it "has a name" do it "has a name" do
::Stripe::Product.expects(:create).with(has_entry(:name, "Jesse Pinkman")) ::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 end
it "has an active attribute" do it "has an active attribute" do
::Stripe::Product.expects(:create).with(has_entry(active: "false")) ::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 end
it "has a statement descriptor" do it "has a statement descriptor" do
::Stripe::Product.expects(:create).with( ::Stripe::Product.expects(:create).with(
has_entry(statement_descriptor: "Blessed are the cheesemakers"), has_entry(statement_descriptor: "Blessed are the cheesemakers"),
) )
post "/s/admin/products.json", post "/subscriptions/admin/products.json",
params: { params: {
statement_descriptor: "Blessed are the cheesemakers", statement_descriptor: "Blessed are the cheesemakers",
} }
@@ -83,7 +83,7 @@ RSpec.describe DiscourseSubscriptions::Admin::ProductsController do
it "has no statement descriptor if empty" do it "has no statement descriptor if empty" do
::Stripe::Product.expects(:create).with(has_key(:statement_descriptor)).never ::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 end
it "has metadata" do 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: { params: {
metadata: { metadata: {
description: "Oi, I think he just said bless be all the bignoses!", 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 describe "show" do
it "retrieves the product" do it "retrieves the product" do
::Stripe::Product.expects(:retrieve).with("prod_walterwhite") ::Stripe::Product.expects(:retrieve).with("prod_walterwhite")
get "/s/admin/products/prod_walterwhite.json" get "/subscriptions/admin/products/prod_walterwhite.json"
end end
end end
describe "update" do describe "update" do
it "updates the product" do it "updates the product" do
::Stripe::Product.expects(:update) ::Stripe::Product.expects(:update)
patch "/s/admin/products/prod_walterwhite.json", params: {} patch "/subscriptions/admin/products/prod_walterwhite.json", params: {}
end end
end end
describe "delete" do describe "delete" do
it "deletes the product" do it "deletes the product" do
::Stripe::Product.expects(:delete).with("prod_walterwhite") ::Stripe::Product.expects(:delete).with("prod_walterwhite")
delete "/s/admin/products/prod_walterwhite.json" delete "/subscriptions/admin/products/prod_walterwhite.json"
end end
end end
end end
@@ -24,13 +24,13 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
context "when unauthenticated" do context "when unauthenticated" do
it "does nothing" do it "does nothing" do
::Stripe::Subscription.expects(:list).never ::Stripe::Subscription.expects(:list).never
get "/s/admin/subscriptions.json" get "/subscriptions/admin/subscriptions.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "does not destroy a subscription" do it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never ::Stripe::Subscription.expects(:delete).never
patch "/s/admin/subscriptions/sub_12345.json" patch "/subscriptions/admin/subscriptions/sub_12345.json"
end end
end end
@@ -50,7 +50,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
.expects(:list) .expects(:list)
.with(expand: ["data.plan.product"], limit: 10, starting_after: nil) .with(expand: ["data.plan.product"], limit: 10, starting_after: nil)
.returns(has_more: false, data: [{ id: "sub_12345" }, { id: "sub_nope" }]) .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"] subscriptions = response.parsed_body["data"][0]["id"]
expect(response.status).to eq(200) expect(response.status).to eq(200)
@@ -62,7 +62,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
.expects(:list) .expects(:list)
.with(expand: ["data.plan.product"], limit: 10, starting_after: "sub_nope") .with(expand: ["data.plan.product"], limit: 10, starting_after: "sub_nope")
.returns(has_more: false, data: [{ id: "sub_77777" }, { id: "sub_yepnoep" }]) .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"] subscriptions = response.parsed_body["data"][0]["id"]
expect(response.status).to eq(200) expect(response.status).to eq(200)
@@ -81,7 +81,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
.with("sub_12345") .with("sub_12345")
.returns(plan: { product: "pr_34578" }, customer: "c_123") .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 DiscourseSubscriptions::Customer.count
}.by(-1) }.by(-1)
end end
@@ -100,7 +100,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
customer: "c_123", 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 user.groups.count
}.by(-1) }.by(-1)
end end
@@ -119,7 +119,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
customer: "c_123", 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 user.groups.count
} }
end end
@@ -136,7 +136,7 @@ RSpec.describe DiscourseSubscriptions::Admin::SubscriptionsController do
::Stripe::Invoice.expects(:retrieve).with("in_123").returns(payment_intent: "pi_123") ::Stripe::Invoice.expects(:retrieve).with("in_123").returns(payment_intent: "pi_123")
::Stripe::Refund.expects(:create).with({ 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 end
end end
+1 -1
View File
@@ -15,7 +15,7 @@ RSpec.describe DiscourseSubscriptions::AdminController do
end end
it "is ok" do it "is ok" do
get "/s/admin.json" get "/subscriptions/admin.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
end end
+9 -7
View File
@@ -17,7 +17,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
.with("we-want-a-shrubbery", "stripe-webhook-signature", "zascharoo") .with("we-want-a-shrubbery", "stripe-webhook-signature", "zascharoo")
.returns(type: "something") .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 expect(response.status).to eq 200
end end
@@ -51,7 +51,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
end end
it "is successfull" do it "is successfull" do
post "/s/hooks.json" post "/subscriptions/hooks.json"
expect(response.status).to eq 200 expect(response.status).to eq 200
end end
@@ -60,7 +60,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
event_data[:object][:status] = "incomplete" event_data[:object][:status] = "incomplete"
event_data[:previous_attributes] = { 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 expect(response.status).to eq 200
end end
@@ -69,7 +69,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
event_data[:object][:status] = "incomplete" event_data[:object][:status] = "incomplete"
event_data[:previous_attributes] = { status: "something-else" } 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 expect(response.status).to eq 200
end end
@@ -78,7 +78,7 @@ RSpec.describe DiscourseSubscriptions::HooksController do
event_data[:object][:status] = "complete" event_data[:object][:status] = "complete"
event_data[:previous_attributes] = { status: "incomplete" } 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 expect(response.status).to eq 200
end end
@@ -95,13 +95,15 @@ RSpec.describe DiscourseSubscriptions::HooksController do
end end
it "deletes the customer" do 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 expect(response.status).to eq 200
end end
it "removes the user from the group" do 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 expect(response.status).to eq 200
end end
+28 -70
View File
@@ -68,56 +68,6 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
SiteSetting.discourse_subscriptions_secret_key = "secret-key" SiteSetting.discourse_subscriptions_secret_key = "secret-key"
end 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 describe "#get_contributors" do
before do before do
Fabricate(:product, external_id: "prod_campaign") Fabricate(:product, external_id: "prod_campaign")
@@ -132,7 +82,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
context "when not showing contributors" do context "when not showing contributors" do
it "returns nothing if not set to show contributors" do it "returns nothing if not set to show contributors" do
SiteSetting.discourse_subscriptions_campaign_show_contributors = false SiteSetting.discourse_subscriptions_campaign_show_contributors = false
get "/s/contributors.json" get "/subscriptions/contributors.json"
data = response.parsed_body data = response.parsed_body
expect(data).to be_empty expect(data).to be_empty
@@ -145,7 +95,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
it "filters users by campaign product if set" do it "filters users by campaign product if set" do
SiteSetting.discourse_subscriptions_campaign_product = "prod_campaign" SiteSetting.discourse_subscriptions_campaign_product = "prod_campaign"
get "/s/contributors.json" get "/subscriptions/contributors.json"
data = response.parsed_body data = response.parsed_body
expect(data.first["id"]).to eq campaign_user.id 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 it "shows all purchases if campaign product not set" do
SiteSetting.discourse_subscriptions_campaign_product = nil SiteSetting.discourse_subscriptions_campaign_product = nil
get "/s/contributors.json" get "/subscriptions/contributors.json"
data = response.parsed_body data = response.parsed_body
expect(data.length).to eq 2 expect(data.length).to eq 2
@@ -170,7 +120,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
.expects(:list) .expects(:list)
.with(active: true, product: "prod_walterwhite") .with(active: true, product: "prod_walterwhite")
.returns(prices) .returns(prices)
get "/s/prod_walterwhite.json" get "/subscriptions/prod_walterwhite.json"
expect(response.parsed_body).to eq( expect(response.parsed_body).to eq(
{ {
@@ -222,7 +172,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Customer.expects(:create).never ::Stripe::Customer.expects(:create).never
::Stripe::Price.expects(:retrieve).never ::Stripe::Price.expects(:retrieve).never
::Stripe::Subscription.expects(:create).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
end end
@@ -257,7 +207,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
.returns(status: "active", customer: "cus_1234") .returns(status: "active", customer: "cus_1234")
expect { 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 } }.to change { DiscourseSubscriptions::Customer.count }
end end
@@ -291,7 +241,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234") ::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234")
expect { 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 } }.to change { DiscourseSubscriptions::Customer.count }
end end
@@ -300,13 +250,13 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234") ::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234")
expect { 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 } }.to change { DiscourseSubscriptions::Customer.count }
::Stripe::Customer.expects(:retrieve).with("cus_1234") ::Stripe::Customer.expects(:retrieve).with("cus_1234")
expect { 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 } }.not_to change { DiscourseSubscriptions::Customer.count }
end end
@@ -315,7 +265,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {}) ::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {})
::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234") ::Stripe::Subscription.expects(:create).returns(status: "active", customer: "cus_1234")
expect { expect {
post "/s/create.json", post "/subscriptions/create.json",
params: { params: {
plan: "plan_1234", plan: "plan_1234",
source: "tok_1234", source: "tok_1234",
@@ -346,7 +296,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::PromotionCode.expects(:list).with({ code: "invalid" }).returns(data: []) ::Stripe::PromotionCode.expects(:list).with({ code: "invalid" }).returns(data: [])
post "/s/create.json", post "/subscriptions/create.json",
params: { params: {
plan: "plan_1234", plan: "plan_1234",
source: "tok_1234", source: "tok_1234",
@@ -391,7 +341,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
.returns(status: "active", customer: "cus_1234") .returns(status: "active", customer: "cus_1234")
expect { expect {
post "/s/create.json", post "/subscriptions/create.json",
params: { params: {
plan: "plan_1234", plan: "plan_1234",
source: "tok_1234", source: "tok_1234",
@@ -434,7 +384,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234") ::Stripe::Invoice.expects(:pay).returns(status: "paid", customer: "cus_1234")
expect { expect {
post "/s/create.json", post "/subscriptions/create.json",
params: { params: {
plan: "plan_1234", plan: "plan_1234",
source: "tok_1234", source: "tok_1234",
@@ -462,7 +412,11 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
) )
expect { 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 } }.to change { DiscourseSubscriptions::Customer.count }
end end
end end
@@ -482,7 +436,11 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
) )
expect { 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 } }.to change { DiscourseSubscriptions::Customer.count }
end end
end end
@@ -505,7 +463,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
group_name: "admins", 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 expect(user.admin).to eq false
end end
@@ -516,7 +474,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
group_name: "other", 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 expect(user.groups).to be_empty
end end
end end
@@ -536,7 +494,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "failed") ::Stripe::Subscription.expects(:create).returns(status: "failed")
expect { 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 } }.not_to change { group.users.count }
expect(user.groups).to be_empty expect(user.groups).to be_empty
@@ -546,7 +504,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "active") ::Stripe::Subscription.expects(:create).returns(status: "active")
expect { 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 } }.to change { group.users.count }
expect(user.groups).not_to be_empty expect(user.groups).not_to be_empty
@@ -556,7 +514,7 @@ RSpec.describe DiscourseSubscriptions::SubscribeController do
::Stripe::Subscription.expects(:create).returns(status: "trialing") ::Stripe::Subscription.expects(:create).returns(status: "trialing")
expect { 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 } }.to change { group.users.count }
expect(user.groups).not_to be_empty expect(user.groups).not_to be_empty
@@ -12,7 +12,7 @@ RSpec.describe DiscourseSubscriptions::User::PaymentsController do
context "when not authenticated" do context "when not authenticated" do
it "does not get the payment intents" do it "does not get the payment intents" do
::Stripe::PaymentIntent.expects(:list).never ::Stripe::PaymentIntent.expects(:list).never
get "/s/user/payments.json" get "/subscriptions/user/payments.json"
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
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 parsed_body = response.parsed_body
invoice = parsed_body[0]["invoice"] invoice = parsed_body[0]["invoice"]
@@ -14,18 +14,21 @@ RSpec.describe DiscourseSubscriptions::User::SubscriptionsController do
context "when not authenticated" do context "when not authenticated" do
it "does not get the subscriptions" do it "does not get the subscriptions" do
::Stripe::Customer.expects(:list).never ::Stripe::Customer.expects(:list).never
get "/s/user/subscriptions.json" get "/subscriptions/user/subscriptions.json"
end end
it "does not destroy a subscription" do it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never ::Stripe::Subscription.expects(:delete).never
patch "/s/user/subscriptions/sub_12345.json" patch "/subscriptions/user/subscriptions/sub_12345.json"
end end
it "doesn't update payment method for subscription" do it "doesn't update payment method for subscription" do
::Stripe::Subscription.expects(:update).never ::Stripe::Subscription.expects(:update).never
::Stripe::PaymentMethod.expects(:attach).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
end end
@@ -74,7 +77,7 @@ RSpec.describe DiscourseSubscriptions::User::SubscriptionsController do
.with(email: user.email, expand: ["data.subscriptions"]) .with(email: user.email, expand: ["data.subscriptions"])
.returns(customers) .returns(customers)
get "/s/user/subscriptions.json" get "/subscriptions/user/subscriptions.json"
subscription = response.parsed_body.first subscription = response.parsed_body.first
@@ -100,7 +103,10 @@ RSpec.describe DiscourseSubscriptions::User::SubscriptionsController do
it "updates the payment method for subscription" do it "updates the payment method for subscription" do
::Stripe::Subscription.expects(:update).once ::Stripe::Subscription.expects(:update).once
::Stripe::PaymentMethod.expects(:attach).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 end
end end
+1 -1
View File
@@ -29,7 +29,7 @@ describe "Subscription products", type: :system do
end end
it "shows the login modal" do it "shows the login modal" do
visit("/s") visit("/subscriptions")
find("button.login-required.subscriptions").click 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) { export default function (helpers) {
const { response } = helpers; const { response } = helpers;
this.get("/s", () => { this.get("/subscriptions", () => {
const products = [ const products = [
{ {
id: "prod_23o8I7tU4g56", id: "prod_23o8I7tU4g56",
@@ -20,7 +20,7 @@ export default function (helpers) {
return response(products); return response(products);
}); });
this.get("/s/:id", () => { this.get("/subscriptions/:id", () => {
const product = { const product = {
id: "prod_23o8I7tU4g56", id: "prod_23o8I7tU4g56",
name: "Awesome Product", name: "Awesome Product",