mirror of
https://github.com/discourse/discourse-subscriptions.git
synced 2025-07-08 15:22:46 +00:00
REFACTOR: Simplify controller setup (#24)
The code in the plugin needed a dramatic cleanup. This refactor collapses the Plan/Product/Subscription controllers on the backend into one new controller: `SubscribeController`. This reduces N+1 calls to the back end during the subscription process and simplifies use of the code. I've also removed a bunch of dead code and refactored some logic into methods for easier readability. No feature/functionality changes in this commit; only refactoring. However, refactoring will allow for implementation of better anonymous user handling, so this is largely a foundation to enable making that change.
This commit is contained in:
parent
3428429d77
commit
3a5078ded6
@ -1,23 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
class CustomersController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
def create
|
|
||||||
begin
|
|
||||||
customer = ::Stripe::Customer.create(
|
|
||||||
email: current_user.email,
|
|
||||||
source: params[:source]
|
|
||||||
)
|
|
||||||
|
|
||||||
render_json_dump customer
|
|
||||||
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,27 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
class PlansController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
def index
|
|
||||||
begin
|
|
||||||
if params[:product_id].present?
|
|
||||||
plans = ::Stripe::Price.list(active: true, product: params[:product_id])
|
|
||||||
else
|
|
||||||
plans = ::Stripe::Price.list(active: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
serialized = plans[:data].map do |plan|
|
|
||||||
plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring)
|
|
||||||
end.sort_by { |plan| plan[:amount] }
|
|
||||||
|
|
||||||
render_json_dump serialized
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,63 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
class ProductsController < ::ApplicationController
|
|
||||||
include DiscourseSubscriptions::Stripe
|
|
||||||
|
|
||||||
before_action :set_api_key
|
|
||||||
|
|
||||||
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 do |p|
|
|
||||||
serialize(p)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render_json_dump products
|
|
||||||
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
begin
|
|
||||||
product = ::Stripe::Product.retrieve(params[:id])
|
|
||||||
|
|
||||||
render_json_dump serialize(product)
|
|
||||||
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
render_json_error e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def serialize(product)
|
|
||||||
{
|
|
||||||
id: product[:id],
|
|
||||||
name: product[:name],
|
|
||||||
description: PrettyText.cook(product[:metadata][:description]),
|
|
||||||
subscribed: current_user_products.include?(product[:id])
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_user_products
|
|
||||||
return [] if current_user.nil?
|
|
||||||
|
|
||||||
Customer
|
|
||||||
.select(:product_id)
|
|
||||||
.where(user_id: current_user.id)
|
|
||||||
.map { |c| c.product_id }.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
module DiscourseSubscriptions
|
||||||
class SubscriptionsController < ::ApplicationController
|
class SubscribeController < ::ApplicationController
|
||||||
include DiscourseSubscriptions::Stripe
|
include DiscourseSubscriptions::Stripe
|
||||||
include DiscourseSubscriptions::Group
|
include DiscourseSubscriptions::Group
|
||||||
before_action :set_api_key
|
before_action :set_api_key
|
||||||
@ -9,23 +9,49 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
begin
|
begin
|
||||||
products = ::Stripe::Product.list(active: true)
|
product_ids = Product.all.pluck(:external_id)
|
||||||
|
products = []
|
||||||
|
|
||||||
subscriptions = products[:data].map do |p|
|
if product_ids.present? && is_stripe_configured?
|
||||||
{
|
response = ::Stripe::Product.list({
|
||||||
id: p[:id],
|
ids: product_ids,
|
||||||
description: p.dig(:metadata, :description)
|
active: true
|
||||||
}
|
})
|
||||||
|
|
||||||
|
products = response[:data].map do |p|
|
||||||
|
serialize_product(p)
|
||||||
end
|
end
|
||||||
|
|
||||||
render_json_dump subscriptions
|
end
|
||||||
|
|
||||||
|
render_json_dump products
|
||||||
|
|
||||||
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
|
render_json_error e.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
params.require(:id)
|
||||||
|
begin
|
||||||
|
product = ::Stripe::Product.retrieve(params[:id])
|
||||||
|
plans = ::Stripe::Price.list(active: true, product: params[:id])
|
||||||
|
|
||||||
|
response = {
|
||||||
|
product: serialize_product(product),
|
||||||
|
plans: serialize_plans(plans)
|
||||||
|
}
|
||||||
|
|
||||||
|
render_json_dump response
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
params.require([:source, :plan])
|
||||||
begin
|
begin
|
||||||
|
customer = create_customer(params[:source])
|
||||||
plan = ::Stripe::Price.retrieve(params[:plan])
|
plan = ::Stripe::Price.retrieve(params[:plan])
|
||||||
|
|
||||||
recurring_plan = plan[:type] == 'recurring'
|
recurring_plan = plan[:type] == 'recurring'
|
||||||
@ -34,7 +60,7 @@ module DiscourseSubscriptions
|
|||||||
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days]
|
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days]
|
||||||
|
|
||||||
transaction = ::Stripe::Subscription.create(
|
transaction = ::Stripe::Subscription.create(
|
||||||
customer: params[:customer],
|
customer: customer[:id],
|
||||||
items: [{ price: params[:plan] }],
|
items: [{ price: params[:plan] }],
|
||||||
metadata: metadata_user,
|
metadata: metadata_user,
|
||||||
trial_period_days: trial_days
|
trial_period_days: trial_days
|
||||||
@ -43,11 +69,11 @@ module DiscourseSubscriptions
|
|||||||
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[:status] == 'incomplete'
|
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[:status] == 'incomplete'
|
||||||
else
|
else
|
||||||
invoice_item = ::Stripe::InvoiceItem.create(
|
invoice_item = ::Stripe::InvoiceItem.create(
|
||||||
customer: params[:customer],
|
customer: customer[:id],
|
||||||
price: params[:plan]
|
price: params[:plan]
|
||||||
)
|
)
|
||||||
invoice = ::Stripe::Invoice.create(
|
invoice = ::Stripe::Invoice.create(
|
||||||
customer: params[:customer]
|
customer: customer[:id]
|
||||||
)
|
)
|
||||||
transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id])
|
transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id])
|
||||||
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == 'open'
|
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == 'open'
|
||||||
@ -65,6 +91,7 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def finalize
|
def finalize
|
||||||
|
params.require([:plan, :transaction])
|
||||||
begin
|
begin
|
||||||
price = ::Stripe::Price.retrieve(params[:plan])
|
price = ::Stripe::Price.retrieve(params[:plan])
|
||||||
transaction = retrieve_transaction(params[:transaction])
|
transaction = retrieve_transaction(params[:transaction])
|
||||||
@ -76,24 +103,6 @@ module DiscourseSubscriptions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve_transaction(transaction)
|
|
||||||
begin
|
|
||||||
case transaction
|
|
||||||
when /^sub_/
|
|
||||||
::Stripe::Subscription.retrieve(transaction)
|
|
||||||
when /^in_/
|
|
||||||
::Stripe::Invoice.retrieve(transaction)
|
|
||||||
end
|
|
||||||
rescue ::Stripe::InvalidRequestError => e
|
|
||||||
e.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def retrieve_payment_intent(invoice_id)
|
|
||||||
invoice = ::Stripe::Invoice.retrieve(invoice_id)
|
|
||||||
::Stripe::PaymentIntent.retrieve(invoice[:payment_intent])
|
|
||||||
end
|
|
||||||
|
|
||||||
def finalize_transaction(transaction, plan)
|
def finalize_transaction(transaction, plan)
|
||||||
group = plan_group(plan)
|
group = plan_group(plan)
|
||||||
|
|
||||||
@ -115,6 +124,55 @@ module DiscourseSubscriptions
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def serialize_product(product)
|
||||||
|
{
|
||||||
|
id: product[:id],
|
||||||
|
name: product[:name],
|
||||||
|
description: PrettyText.cook(product[:metadata][:description]),
|
||||||
|
subscribed: current_user_products.include?(product[:id])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_user_products
|
||||||
|
return [] if current_user.nil?
|
||||||
|
|
||||||
|
Customer
|
||||||
|
.select(:product_id)
|
||||||
|
.where(user_id: current_user.id)
|
||||||
|
.map { |c| c.product_id }.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_plans(plans)
|
||||||
|
plans[:data].map do |plan|
|
||||||
|
plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring)
|
||||||
|
end.sort_by { |plan| plan[:amount] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_customer(source)
|
||||||
|
::Stripe::Customer.create(
|
||||||
|
email: current_user.email,
|
||||||
|
source: source
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve_payment_intent(invoice_id)
|
||||||
|
invoice = ::Stripe::Invoice.retrieve(invoice_id)
|
||||||
|
::Stripe::PaymentIntent.retrieve(invoice[:payment_intent])
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve_transaction(transaction)
|
||||||
|
begin
|
||||||
|
case transaction
|
||||||
|
when /^sub_/
|
||||||
|
::Stripe::Subscription.retrieve(transaction)
|
||||||
|
when /^in_/
|
||||||
|
::Stripe::Invoice.retrieve(transaction)
|
||||||
|
end
|
||||||
|
rescue ::Stripe::InvalidRequestError => e
|
||||||
|
e.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def metadata_user
|
def metadata_user
|
||||||
{ user_id: current_user.id, username: current_user.username_lower }
|
{ user_id: current_user.id, username: current_user.username_lower }
|
||||||
end
|
end
|
@ -19,13 +19,7 @@ module DiscourseSubscriptions
|
|||||||
customer_ids.each do |customer_id|
|
customer_ids.each do |customer_id|
|
||||||
# lots of matching because the Stripe API doesn't make it easy to match products => payments except from invoices
|
# lots of matching because the Stripe API doesn't make it easy to match products => payments except from invoices
|
||||||
all_invoices = ::Stripe::Invoice.list(customer: customer_id)
|
all_invoices = ::Stripe::Invoice.list(customer: customer_id)
|
||||||
invoices_with_products = all_invoices[:data].select do |invoice|
|
invoices_with_products = parse_invoices(all_invoices, product_ids)
|
||||||
# i cannot dig it so we must get iffy with it
|
|
||||||
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
|
|
||||||
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_lines[:price][:product]
|
|
||||||
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product]
|
|
||||||
product_ids.include?(invoice_product_id)
|
|
||||||
end
|
|
||||||
invoice_ids = invoices_with_products.map { |invoice| invoice[:id] }
|
invoice_ids = invoices_with_products.map { |invoice| invoice[:id] }
|
||||||
payments = ::Stripe::PaymentIntent.list(customer: customer_id)
|
payments = ::Stripe::PaymentIntent.list(customer: customer_id)
|
||||||
payments_from_invoices = payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) }
|
payments_from_invoices = payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) }
|
||||||
@ -41,6 +35,21 @@ module DiscourseSubscriptions
|
|||||||
render_json_error e.message
|
render_json_error e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_invoices(all_invoices, product_ids)
|
||||||
|
invoices_with_products = all_invoices[:data].select do |invoice|
|
||||||
|
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
|
||||||
|
invoice_product_id = parse_invoice_lines(invoice_lines)
|
||||||
|
product_ids.include?(invoice_product_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_invoice_lines(invoice_lines)
|
||||||
|
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_lines[:price][:product]
|
||||||
|
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import Customer from "discourse/plugins/discourse-subscriptions/discourse/models/customer";
|
|
||||||
import Subscription from "discourse/plugins/discourse-subscriptions/discourse/models/subscription";
|
import Subscription from "discourse/plugins/discourse-subscriptions/discourse/models/subscription";
|
||||||
import Transaction from "discourse/plugins/discourse-subscriptions/discourse/models/transaction";
|
import Transaction from "discourse/plugins/discourse-subscriptions/discourse/models/transaction";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
@ -28,16 +27,12 @@ export default Controller.extend({
|
|||||||
this.set("loading", false);
|
this.set("loading", false);
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
const customer = Customer.create({ source: result.token.id });
|
|
||||||
|
|
||||||
return customer.save().then((c) => {
|
|
||||||
const subscription = Subscription.create({
|
const subscription = Subscription.create({
|
||||||
customer: c.id,
|
source: result.token.id,
|
||||||
plan: plan.get("id"),
|
plan: plan.get("id"),
|
||||||
});
|
});
|
||||||
|
|
||||||
return subscription.save();
|
return subscription.save();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import EmberObject from "@ember/object";
|
|
||||||
|
|
||||||
const Customer = EmberObject.extend({
|
|
||||||
save() {
|
|
||||||
const data = {
|
|
||||||
source: this.source,
|
|
||||||
};
|
|
||||||
|
|
||||||
return ajax("/s/customers", { method: "post", data });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Customer;
|
|
@ -1,19 +0,0 @@
|
|||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import EmberObject from "@ember/object";
|
|
||||||
|
|
||||||
const Group = EmberObject.extend({});
|
|
||||||
|
|
||||||
Group.reopenClass({
|
|
||||||
subscriptionGroup:
|
|
||||||
Discourse.SiteSettings.discourse_patrons_subscription_group,
|
|
||||||
|
|
||||||
find() {
|
|
||||||
return ajax(`/groups/${this.subscriptionGroup}`, { method: "get" }).then(
|
|
||||||
(result) => {
|
|
||||||
return Group.create(result.group);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Group;
|
|
@ -1,6 +1,5 @@
|
|||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
|
|
||||||
const Plan = EmberObject.extend({
|
const Plan = EmberObject.extend({
|
||||||
amountDollars: Ember.computed("unit_amount", {
|
amountDollars: Ember.computed("unit_amount", {
|
||||||
@ -24,12 +23,4 @@ const Plan = EmberObject.extend({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Plan.reopenClass({
|
|
||||||
findAll(data) {
|
|
||||||
return ajax("/s/plans", { method: "get", data }).then((result) =>
|
|
||||||
result.map((plan) => Plan.create(plan))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Plan;
|
export default Plan;
|
||||||
|
@ -5,16 +5,10 @@ const Product = EmberObject.extend({});
|
|||||||
|
|
||||||
Product.reopenClass({
|
Product.reopenClass({
|
||||||
findAll() {
|
findAll() {
|
||||||
return ajax("/s/products", { method: "get" }).then((result) =>
|
return ajax("/s", { method: "get" }).then((result) =>
|
||||||
result.map((product) => Product.create(product))
|
result.map((product) => Product.create(product))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
find(id) {
|
|
||||||
return ajax(`/s/products/${id}`, { method: "get" }).then((product) =>
|
|
||||||
Product.create(product)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Product;
|
export default Product;
|
||||||
|
@ -10,19 +10,17 @@ const Subscription = EmberObject.extend({
|
|||||||
|
|
||||||
save() {
|
save() {
|
||||||
const data = {
|
const data = {
|
||||||
customer: this.customer,
|
source: this.source,
|
||||||
plan: this.plan,
|
plan: this.plan,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ajax("/s/subscriptions", { method: "post", data });
|
return ajax("/s/create", { method: "post", data });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Subscription.reopenClass({
|
Subscription.reopenClass({
|
||||||
findAll() {
|
show(id) {
|
||||||
return ajax("/s/subscriptions", { method: "get" }).then((result) =>
|
return ajax(`/s/${id}`, { method: "get" });
|
||||||
result.map((subscription) => Subscription.create(subscription))
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@ export default {
|
|||||||
plan: plan,
|
plan: plan,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ajax("/s/subscriptions/finalize", { method: "post", data });
|
return ajax("/s/finalize", { method: "post", data });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import Route from "@ember/routing/route";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
|
|
||||||
export default Route.extend({
|
|
||||||
queryParams: {
|
|
||||||
order: {
|
|
||||||
refreshModel: true,
|
|
||||||
},
|
|
||||||
descending: {
|
|
||||||
refreshModel: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
model(params) {
|
|
||||||
return ajax("/patrons/admin", {
|
|
||||||
method: "get",
|
|
||||||
data: {
|
|
||||||
order: params.order,
|
|
||||||
descending: params.descending,
|
|
||||||
},
|
|
||||||
}).then((results) => results);
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,15 +1,19 @@
|
|||||||
import Route from "@ember/routing/route";
|
import Route from "@ember/routing/route";
|
||||||
import Product from "discourse/plugins/discourse-subscriptions/discourse/models/product";
|
import Product from "discourse/plugins/discourse-subscriptions/discourse/models/product";
|
||||||
import Plan from "discourse/plugins/discourse-subscriptions/discourse/models/plan";
|
import Plan from "discourse/plugins/discourse-subscriptions/discourse/models/plan";
|
||||||
import { hash } from "rsvp";
|
import Subscription from "discourse/plugins/discourse-subscriptions/discourse/models/subscription";
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
model(params) {
|
model(params) {
|
||||||
const product_id = params["subscription-id"];
|
const product_id = params["subscription-id"];
|
||||||
|
|
||||||
const product = Product.find(product_id);
|
return Subscription.show(product_id).then((result) => {
|
||||||
const plans = Plan.findAll({ product_id });
|
result.product = Product.create(result.product);
|
||||||
|
result.plans = result.plans.map((plan) => {
|
||||||
|
return Plan.create(plan);
|
||||||
|
});
|
||||||
|
|
||||||
return hash({ plans, product });
|
return result;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -18,14 +18,11 @@ DiscourseSubscriptions::Engine.routes.draw do
|
|||||||
resources :subscriptions, only: [:index, :destroy]
|
resources :subscriptions, only: [:index, :destroy]
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :customers, only: [:create]
|
get '/' => 'subscribe#index'
|
||||||
resources :plans, only: [:index], constraints: SubscriptionsUserConstraint.new
|
get '.json' => 'subscribe#index'
|
||||||
resources :products, only: [:index, :show]
|
get '/:id' => 'subscribe#show'
|
||||||
resources :subscriptions, only: [:create]
|
post '/create' => 'subscribe#create'
|
||||||
|
post '/finalize' => 'subscribe#finalize'
|
||||||
post '/subscriptions/finalize' => 'subscriptions#finalize'
|
|
||||||
|
|
||||||
post '/hooks' => 'hooks#create'
|
post '/hooks' => 'hooks#create'
|
||||||
get '/' => 'subscriptions#index', constraints: SubscriptionsUserConstraint.new
|
|
||||||
get '/:id' => 'subscriptions#index', constraints: SubscriptionsUserConstraint.new
|
|
||||||
end
|
end
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
RSpec.describe CustomersController do
|
|
||||||
describe "create" do
|
|
||||||
describe "authenticated" do
|
|
||||||
let(:user) { Fabricate(:user, email: 'hello.2@example.com') }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "creates a stripe customer" do
|
|
||||||
::Stripe::Customer.expects(:create).with(
|
|
||||||
email: 'hello.2@example.com',
|
|
||||||
source: 'tok_interesting'
|
|
||||||
)
|
|
||||||
|
|
||||||
post "/s/customers.json", params: { source: 'tok_interesting' }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,43 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
RSpec.describe PlansController do
|
|
||||||
let(:user) { Fabricate(:user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "index" do
|
|
||||||
it "lists the active plans" do
|
|
||||||
::Stripe::Price.expects(:list).with(active: true)
|
|
||||||
get "/s/plans.json"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "lists the active plans for a product" do
|
|
||||||
::Stripe::Price.expects(:list).with(active: true, product: 'prod_3765')
|
|
||||||
get "/s/plans.json", params: { product_id: 'prod_3765' }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "orders and serialises the plans" do
|
|
||||||
::Stripe::Price.expects(:list).returns(
|
|
||||||
data: [
|
|
||||||
{ id: 'plan_id123', unit_amount: 1220, currency: 'aud', recurring: { interval: 'year' }, metadata: {} },
|
|
||||||
{ id: 'plan_id234', unit_amount: 1399, currency: 'usd', recurring: { interval: 'year' }, metadata: {} },
|
|
||||||
{ id: 'plan_id678', unit_amount: 1000, currency: 'aud', recurring: { interval: 'week' }, metadata: {} }
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
get "/s/plans.json"
|
|
||||||
|
|
||||||
expect(response.parsed_body).to eq([
|
|
||||||
{ "currency" => "aud", "id" => "plan_id123", "recurring" => { "interval" => "year" }, "unit_amount" => 1220 },
|
|
||||||
{ "currency" => "usd", "id" => "plan_id234", "recurring" => { "interval" => "year" }, "unit_amount" => 1399 },
|
|
||||||
{ "currency" => "aud", "id" => "plan_id678", "recurring" => { "interval" => "week" }, "unit_amount" => 1000 }
|
|
||||||
])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,97 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
RSpec.describe ProductsController do
|
|
||||||
describe "products" do
|
|
||||||
let(:product) do
|
|
||||||
{
|
|
||||||
id: "prodct_23456",
|
|
||||||
name: "Very Special Product",
|
|
||||||
metadata: {
|
|
||||||
description: "Many people listened to my phone call with the Ukrainian President while it was being made"
|
|
||||||
},
|
|
||||||
otherstuff: true,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
let(:product_ids) { ["prodct_23456"] }
|
|
||||||
|
|
||||||
before do
|
|
||||||
Fabricate(:product, external_id: "prodct_23456")
|
|
||||||
SiteSetting.discourse_subscriptions_public_key = "public-key"
|
|
||||||
SiteSetting.discourse_subscriptions_secret_key = "secret-key"
|
|
||||||
end
|
|
||||||
|
|
||||||
context "unauthenticated" do
|
|
||||||
it "gets products" do
|
|
||||||
::Stripe::Product.expects(:list).with(ids: product_ids, active: true).returns(data: [product])
|
|
||||||
|
|
||||||
get "/s/products.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
|
|
||||||
}])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "authenticated" do
|
|
||||||
let(:user) { Fabricate(:user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "index" do
|
|
||||||
it "gets products" do
|
|
||||||
::Stripe::Product.expects(:list).with(ids: product_ids, active: true).returns(data: [product])
|
|
||||||
|
|
||||||
get "/s/products.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
|
|
||||||
}])
|
|
||||||
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/products.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/products.json"
|
|
||||||
data = response.parsed_body
|
|
||||||
expect(data.first["subscribed"]).to eq false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'show' do
|
|
||||||
it 'retrieves the product' do
|
|
||||||
::Stripe::Product.expects(:retrieve).with('prod_walterwhite').returns(product)
|
|
||||||
get "/s/products/prod_walterwhite.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
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
262
spec/requests/subscribe_controller_spec.rb
Normal file
262
spec/requests/subscribe_controller_spec.rb
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
module DiscourseSubscriptions
|
||||||
|
RSpec.describe SubscribeController do
|
||||||
|
let (:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
context "showing products" do
|
||||||
|
let(:product) do
|
||||||
|
{
|
||||||
|
id: "prodct_23456",
|
||||||
|
name: "Very Special Product",
|
||||||
|
metadata: {
|
||||||
|
description: "Many people listened to my phone call with the Ukrainian President while it was being made"
|
||||||
|
},
|
||||||
|
otherstuff: true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:prices) do
|
||||||
|
{
|
||||||
|
data: [
|
||||||
|
{ id: 'plan_id123', unit_amount: 1220, currency: 'aud', recurring: { interval: 'year' }, metadata: {} },
|
||||||
|
{ id: 'plan_id234', unit_amount: 1399, currency: 'usd', recurring: { interval: 'year' }, metadata: {} },
|
||||||
|
{ id: 'plan_id678', unit_amount: 1000, currency: 'aud', recurring: { interval: 'week' }, metadata: {} }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:product_ids) { ["prodct_23456"] }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
Fabricate(:product, external_id: "prodct_23456")
|
||||||
|
SiteSetting.discourse_subscriptions_public_key = "public-key"
|
||||||
|
SiteSetting.discourse_subscriptions_secret_key = "secret-key"
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
|
||||||
|
it "gets products" do
|
||||||
|
::Stripe::Product.expects(:list).with(ids: product_ids, active: true).returns(data: [product])
|
||||||
|
|
||||||
|
get "/s.json"
|
||||||
|
|
||||||
|
expect(response.parsed_body).to eq([{
|
||||||
|
"id" => "prodct_23456",
|
||||||
|
"name" => "Very Special Product",
|
||||||
|
"description" => PrettyText.cook("Many people listened to my phone call with the Ukrainian President while it was being made"),
|
||||||
|
"subscribed" => false
|
||||||
|
}])
|
||||||
|
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 "#show" do
|
||||||
|
it 'retrieves the product' do
|
||||||
|
::Stripe::Product.expects(:retrieve).with('prod_walterwhite').returns(product)
|
||||||
|
::Stripe::Price.expects(:list).with(active: true, product: 'prod_walterwhite').returns(prices)
|
||||||
|
get "/s/prod_walterwhite.json"
|
||||||
|
|
||||||
|
expect(response.parsed_body).to eq({
|
||||||
|
"product" => {
|
||||||
|
"id" => "prodct_23456",
|
||||||
|
"name" => "Very Special Product",
|
||||||
|
"description" => PrettyText.cook("Many people listened to my phone call with the Ukrainian President while it was being made"),
|
||||||
|
"subscribed" => false
|
||||||
|
},
|
||||||
|
"plans" => [
|
||||||
|
{ "currency" => "aud", "id" => "plan_id123", "recurring" => { "interval" => "year" }, "unit_amount" => 1220 },
|
||||||
|
{ "currency" => "usd", "id" => "plan_id234", "recurring" => { "interval" => "year" }, "unit_amount" => 1399 },
|
||||||
|
{ "currency" => "aud", "id" => "plan_id678", "recurring" => { "interval" => "week" }, "unit_amount" => 1000 }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "creating subscriptions" do
|
||||||
|
context "unauthenticated" do
|
||||||
|
it "does not create a subscription" do
|
||||||
|
::Stripe::Customer.expects(:create).never
|
||||||
|
::Stripe::Price.expects(:retrieve).never
|
||||||
|
::Stripe::Subscription.expects(:create).never
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "authenticated" do
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
before do
|
||||||
|
::Stripe::Customer.expects(:create).returns(id: 'cus_1234')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a subscription" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(
|
||||||
|
type: 'recurring',
|
||||||
|
product: 'product_12345',
|
||||||
|
metadata: {
|
||||||
|
group_name: 'awesome',
|
||||||
|
trial_period_days: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
::Stripe::Subscription.expects(:create).with(
|
||||||
|
customer: 'cus_1234',
|
||||||
|
items: [ price: 'plan_1234' ],
|
||||||
|
metadata: { user_id: user.id, username: user.username_lower },
|
||||||
|
trial_period_days: 0
|
||||||
|
).returns(status: 'active', customer: 'cus_1234')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
}.to change { DiscourseSubscriptions::Customer.count }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a one time payment subscription" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(
|
||||||
|
type: 'one_time',
|
||||||
|
product: 'product_12345',
|
||||||
|
metadata: {
|
||||||
|
group_name: 'awesome'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
::Stripe::InvoiceItem.expects(:create)
|
||||||
|
|
||||||
|
::Stripe::Invoice.expects(:create).returns(status: 'open', id: 'in_123')
|
||||||
|
|
||||||
|
::Stripe::Invoice.expects(:finalize_invoice).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123')
|
||||||
|
|
||||||
|
::Stripe::Invoice.expects(:retrieve).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123')
|
||||||
|
|
||||||
|
::Stripe::PaymentIntent.expects(:retrieve).returns(status: 'successful')
|
||||||
|
|
||||||
|
::Stripe::Invoice.expects(:pay).returns(status: 'paid', customer: 'cus_1234')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post '/s/create.json', params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
}.to change { DiscourseSubscriptions::Customer.count }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "creates a customer model" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: {})
|
||||||
|
::Stripe::Subscription.expects(:create).returns(status: 'active', customer: 'cus_1234')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
}.to change { DiscourseSubscriptions::Customer.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#finalize strong customer authenticated transaction" do
|
||||||
|
context "with subscription" do
|
||||||
|
it "finalizes the subscription" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(id: "plan_1234", product: "prod_1234", metadata: {})
|
||||||
|
::Stripe::Subscription.expects(:retrieve).returns(id: "sub_123", customer: 'cus_1234', status: "active")
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/finalize.json", params: { plan: 'plan_1234', transaction: 'sub_1234' }
|
||||||
|
}.to change { DiscourseSubscriptions::Customer.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with one-time payment" do
|
||||||
|
it "finalizes the one-time payment" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(id: "plan_1234", product: "prod_1234", metadata: {})
|
||||||
|
::Stripe::Invoice.expects(:retrieve).returns(id: "in_123", customer: 'cus_1234', status: "paid")
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/finalize.json", params: { plan: 'plan_1234', transaction: 'in_1234' }
|
||||||
|
}.to change { DiscourseSubscriptions::Customer.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "user groups" do
|
||||||
|
let(:group_name) { 'group-123' }
|
||||||
|
let(:group) { Fabricate(:group, name: group_name) }
|
||||||
|
|
||||||
|
context "unauthorized group" do
|
||||||
|
before do
|
||||||
|
::Stripe::Customer.expects(:create).returns(id: 'cus_1234')
|
||||||
|
::Stripe::Subscription.expects(:create).returns(status: 'active')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not add the user to the admins group" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'admins' })
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
expect(user.admin).to eq false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not add the user to other group" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'other' })
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
expect(user.groups).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "plan has group in metadata" do
|
||||||
|
before do
|
||||||
|
::Stripe::Customer.expects(:create).returns(id: 'cus_1234')
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: group_name })
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not add the user to the group when subscription fails" do
|
||||||
|
::Stripe::Subscription.expects(:create).returns(status: 'failed')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
}.not_to change { group.users.count }
|
||||||
|
|
||||||
|
expect(user.groups).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds the user to the group when the subscription is active" do
|
||||||
|
::Stripe::Subscription.expects(:create).returns(status: 'active')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
}.to change { group.users.count }
|
||||||
|
|
||||||
|
expect(user.groups).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds the user to the group when the subscription is trialing" do
|
||||||
|
::Stripe::Subscription.expects(:create).returns(status: 'trialing')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/s/create.json", params: { plan: 'plan_1234', source: 'tok_1234' }
|
||||||
|
}.to change { group.users.count }
|
||||||
|
|
||||||
|
expect(user.groups).not_to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,166 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
module DiscourseSubscriptions
|
|
||||||
RSpec.describe SubscriptionsController do
|
|
||||||
context "not authenticated" do
|
|
||||||
it "does not create a subscription" do
|
|
||||||
::Stripe::Price.expects(:retrieve).never
|
|
||||||
::Stripe::Subscription.expects(:create).never
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "authenticated" do
|
|
||||||
let(:user) { Fabricate(:user) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "create" do
|
|
||||||
it "creates a subscription" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(
|
|
||||||
type: 'recurring',
|
|
||||||
product: 'product_12345',
|
|
||||||
metadata: {
|
|
||||||
group_name: 'awesome',
|
|
||||||
trial_period_days: 0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
::Stripe::Subscription.expects(:create).with(
|
|
||||||
customer: 'cus_1234',
|
|
||||||
items: [ price: 'plan_1234' ],
|
|
||||||
metadata: { user_id: user.id, username: user.username_lower },
|
|
||||||
trial_period_days: 0
|
|
||||||
).returns(status: 'active', customer: 'cus_1234')
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
}.to change { DiscourseSubscriptions::Customer.count }
|
|
||||||
end
|
|
||||||
|
|
||||||
it "creates a one time payment subscription" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(
|
|
||||||
type: 'one_time',
|
|
||||||
product: 'product_12345',
|
|
||||||
metadata: {
|
|
||||||
group_name: 'awesome'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
::Stripe::InvoiceItem.expects(:create)
|
|
||||||
|
|
||||||
::Stripe::Invoice.expects(:create).returns(status: 'open', id: 'in_123')
|
|
||||||
|
|
||||||
::Stripe::Invoice.expects(:finalize_invoice).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123')
|
|
||||||
|
|
||||||
::Stripe::Invoice.expects(:retrieve).returns(id: 'in_123', status: 'open', payment_intent: 'pi_123')
|
|
||||||
|
|
||||||
::Stripe::PaymentIntent.expects(:retrieve).returns(status: 'successful')
|
|
||||||
|
|
||||||
::Stripe::Invoice.expects(:pay).returns(status: 'paid', customer: 'cus_1234')
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post '/s/subscriptions.json', params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
}.to change { DiscourseSubscriptions::Customer.count }
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
it "creates a customer model" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: {})
|
|
||||||
::Stripe::Subscription.expects(:create).returns(status: 'active', customer: 'cus_1234')
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
}.to change { DiscourseSubscriptions::Customer.count }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "strong customer authenticated transaction" do
|
|
||||||
context "with subscription" do
|
|
||||||
it "finalizes the subscription" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(id: "plan_1234", product: "prod_1234", metadata: {})
|
|
||||||
::Stripe::Subscription.expects(:retrieve).returns(id: "sub_123", customer: 'cus_1234', status: "active")
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions/finalize.json", params: { plan: 'plan_1234', transaction: 'sub_1234' }
|
|
||||||
}.to change { DiscourseSubscriptions::Customer.count }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with one-time payment" do
|
|
||||||
it "finalizes the one-time payment" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(id: "plan_1234", product: "prod_1234", metadata: {})
|
|
||||||
::Stripe::Invoice.expects(:retrieve).returns(id: "in_123", customer: 'cus_1234', status: "paid")
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions/finalize.json", params: { plan: 'plan_1234', transaction: 'in_1234' }
|
|
||||||
}.to change { DiscourseSubscriptions::Customer.count }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "user groups" do
|
|
||||||
let(:group_name) { 'group-123' }
|
|
||||||
let(:group) { Fabricate(:group, name: group_name) }
|
|
||||||
|
|
||||||
context "unauthorized group" do
|
|
||||||
before do
|
|
||||||
::Stripe::Subscription.expects(:create).returns(status: 'active')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not add the user to the admins group" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'admins' })
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
expect(user.admin).to eq false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not add the user to other group" do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: 'other' })
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
expect(user.groups).to be_empty
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "plan has group in metadata" do
|
|
||||||
before do
|
|
||||||
::Stripe::Price.expects(:retrieve).returns(type: 'recurring', metadata: { group_name: group_name })
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not add the user to the group when subscription fails" do
|
|
||||||
::Stripe::Subscription.expects(:create).returns(status: 'failed')
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
}.not_to change { group.users.count }
|
|
||||||
|
|
||||||
expect(user.groups).to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it "adds the user to the group when the subscription is active" do
|
|
||||||
::Stripe::Subscription.expects(:create).returns(status: 'active')
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
}.to change { group.users.count }
|
|
||||||
|
|
||||||
expect(user.groups).not_to be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it "adds the user to the group when the subscription is trialing" do
|
|
||||||
::Stripe::Subscription.expects(:create).returns(status: 'trialing')
|
|
||||||
|
|
||||||
expect {
|
|
||||||
post "/s/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
|
|
||||||
}.to change { group.users.count }
|
|
||||||
|
|
||||||
expect(user.groups).not_to be_empty
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
x
Reference in New Issue
Block a user