the subscribe product page

This commit is contained in:
Rimian Perkins 2019-11-04 16:37:21 +11:00
parent 2b3c52bdd4
commit e14b4dcd96
21 changed files with 201 additions and 128 deletions

View File

@ -8,7 +8,11 @@ module DiscoursePatrons
def index def index
begin begin
plans = ::Stripe::Plan.list(active: true) if params[:product_id].present?
plans = ::Stripe::Plan.list(active: true, product: params[:product_id])
else
plans = ::Stripe::Plan.list(active: true)
end
serialized = plans[:data].map do |plan| serialized = plans[:data].map do |plan|
plan.to_h.slice(:id, :amount, :currency, :interval) plan.to_h.slice(:id, :amount, :currency, :interval)

View File

@ -8,14 +8,38 @@ module DiscoursePatrons
def index def index
begin begin
products = ::Stripe::Product.list(active: true) response = ::Stripe::Product.list(active: true)
# TODO: Serialize. Remove some attributes like metadata products = response[:data].map do |p|
render_json_dump products.data serialize(p)
end
render_json_dump products
rescue ::Stripe::InvalidRequestError => e rescue ::Stripe::InvalidRequestError => e
return render_json_error e.message return render_json_error e.message
end end
end end
def show
begin
product = ::Stripe::Product.retrieve(params[:id])
render_json_dump serialize(product)
rescue ::Stripe::InvalidRequestError => e
return render_json_error e.message
end
end
private
def serialize(product)
{
id: product[:id],
name: product[:name],
description: product[:metadata][:description]
}
end
end end
end end

View File

@ -8,14 +8,14 @@ module DiscoursePatrons
def index def index
begin begin
customers = ::Stripe::Customer.list( products = ::Stripe::Product.list(active: true)
email: current_user.email,
expand: ['data.subscriptions']
)
subscriptions = customers[:data].map do |customer| subscriptions = products[:data].map do |p|
customer[:subscriptions][:data] {
end.flatten(1) id: p[:id],
description: p.dig(:metadata, :description)
}
end
render_json_dump subscriptions render_json_dump subscriptions
@ -50,17 +50,6 @@ module DiscoursePatrons
end end
end end
def destroy
begin
subscription = ::Stripe::Subscription.delete(params[:id])
render_json_dump subscription
rescue ::Stripe::InvalidRequestError => e
return render_json_error e.message
end
end
private private
def plan_group(plan) def plan_group(plan)

View File

@ -25,6 +25,17 @@ module DiscoursePatrons
return render_json_error e.message return render_json_error e.message
end end
end end
def destroy
begin
subscription = ::Stripe::Subscription.delete(params[:id])
render_json_dump subscription
rescue ::Stripe::InvalidRequestError => e
return render_json_error e.message
end
end
end end
end end
end end

View File

@ -34,9 +34,9 @@ export default Ember.Controller.extend({
} }
subscription.save().then(() => { subscription.save().then(() => {
bootbox.alert("ok payment good... some kind of message"); bootbox.alert(I18n.t("discourse_patrons.transactions.payment.success"));
this.transitionToRoute( this.transitionToRoute(
"user.billing", "user.subscriptions",
Discourse.User.current().username.toLowerCase() Discourse.User.current().username.toLowerCase()
); );
}); });

View File

@ -20,8 +20,8 @@ const Plan = Discourse.Model.extend({
}); });
Plan.reopenClass({ Plan.reopenClass({
findAll() { findAll(data) {
return ajax("/patrons/plans", { method: "get" }).then(result => return ajax("/patrons/plans", { method: "get", data }).then(result =>
result.map(plan => Plan.create(plan)) result.map(plan => Plan.create(plan))
); );
} }

View File

@ -7,6 +7,12 @@ Product.reopenClass({
return ajax("/patrons/products", { method: "get" }).then(result => return ajax("/patrons/products", { method: "get" }).then(result =>
result.map(product => Product.create(product)) result.map(product => Product.create(product))
); );
},
find(id) {
return ajax(`/patrons/products/${id}`, { method: "get" }).then(
product => Product.create(product)
);
} }
}); });

View File

@ -15,22 +15,13 @@ const Subscription = Discourse.Model.extend({
}; };
return ajax("/patrons/subscriptions", { method: "post", data }); return ajax("/patrons/subscriptions", { method: "post", data });
},
destroy() {
return ajax(`/patrons/subscriptions/${this.id}`, { method: "delete" }).then(
result => Subscription.create(result)
);
} }
}); });
Subscription.reopenClass({ Subscription.reopenClass({
findAll() { findAll() {
return ajax("/patrons/subscriptions", { method: "get" }).then(result => return ajax("/patrons/subscriptions", { method: "get" }).then(result =>
result.map(subscription => { result.map(subscription => Subscription.create(subscription))
subscription.plan = Plan.create(subscription.plan);
return Subscription.create(subscription);
})
); );
} }
}); });

View File

@ -6,6 +6,12 @@ const UserSubscription = Discourse.Model.extend({
@computed("status") @computed("status")
canceled(status) { canceled(status) {
return status === "canceled"; return status === "canceled";
},
destroy() {
return ajax(`/patrons/user/subscriptions/${this.id}`, { method: "delete" }).then(
result => UserSubscription.create(result)
);
} }
}); });

View File

@ -1,5 +1,7 @@
export default function() { export default function() {
this.route("patrons", function() { this.route("patrons", function() {
this.route("subscribe"); this.route("subscribe", function() {
this.route("show", { path: "/:subscription-id" });
});
}); });
} }

View File

@ -0,0 +1,16 @@
import Product from "discourse/plugins/discourse-patrons/discourse/models/product";
import Plan from "discourse/plugins/discourse-patrons/discourse/models/plan";
import Subscription from "discourse/plugins/discourse-patrons/discourse/models/subscription";
export default Discourse.Route.extend({
model(params) {
const product_id = params["subscription-id"];
const product = Product.find(product_id);
const subscription = Subscription.create();
const plans = Plan.findAll({ product_id: product_id }).then(results =>
results.map(p => ({ id: p.id, name: p.subscriptionRate }))
);
return Ember.RSVP.hash({ plans, product, subscription });
},
});

View File

@ -1,14 +1,7 @@
import Plan from "discourse/plugins/discourse-patrons/discourse/models/plan"; import Product from "discourse/plugins/discourse-patrons/discourse/models/product";
import Subscription from "discourse/plugins/discourse-patrons/discourse/models/subscription";
export default Discourse.Route.extend({ export default Discourse.Route.extend({
model() { model() {
const plans = Plan.findAll().then(results => return Product.findAll();
results.map(p => ({ id: p.id, name: p.subscriptionRate })) },
);
const subscription = Subscription.create();
return Ember.RSVP.hash({ plans, subscription });
}
}); });

View File

@ -1,34 +1,12 @@
<h3> <div class="container">
{{i18n 'discourse_patrons.subscribe.title'}} <div class="title-wrapper">
</h3> <h1>
{{i18n 'discourse_patrons.subscribe.title'}}
<div class="discourse-patrons-section-columns"> </h1>
<div class="section-column discourse-patrons-confirmation-billing">
<h4>{{model.group.full_name}}</h4>
<p>
{{{model.group.bio_cooked}}}
</p>
</div> </div>
<div class="section-column">
{{combo-box valueAttribute="id" content=model.plans value=model.subscription.plan}}
{{#d-button <hr>
action="stripePaymentHandler"
class="btn btn-primary btn-payment btn-discourse-patrons"}}
{{i18n 'discourse_patrons.subscribe.buttons.subscribe'}}
{{/d-button}}
<hr> {{outlet}}
<h4>{{i18n 'discourse_patrons.subscribe.card.title'}}</h4>
{{subscribe-card cardElement=cardElement}}
<div id="discourse-patrons-subscribe-customer">
<h4>{{i18n 'discourse_patrons.subscribe.customer.title'}}</h4>
<div class="discourse-patrons-subscribe-customer-empty">
{{i18n 'discourse_patrons.subscribe.customer.empty'}}
</div>
</div>
</div>
</div> </div>

View File

@ -0,0 +1,17 @@
{{#each model as |product|}}
<div>
<h2>{{product.name}}</h2>
<p>
{{product.description}}
</p>
<div class="pull-right">
{{#link-to "patrons.subscribe.show" product.id class="btn btn-primary"}}
{{i18n 'discourse_patrons.subscribe.title'}}
{{/link-to}}
</div>
</div>
{{/each}}

View File

@ -0,0 +1,32 @@
<div class="discourse-patrons-section-columns">
<div class="section-column discourse-patrons-confirmation-billing">
<h2>
{{model.product.name}}
</h2>
<p>
{{model.product.description}}
</p>
</div>
<div class="section-column">
{{combo-box valueAttribute="id" content=model.plans value=model.product.plan}}
{{#d-button
action="stripePaymentHandler"
class="btn btn-primary btn-payment btn-discourse-patrons"}}
{{i18n 'discourse_patrons.subscribe.buttons.subscribe'}}
{{/d-button}}
<hr>
<h4>{{i18n 'discourse_patrons.subscribe.card.title'}}</h4>
{{subscribe-card cardElement=cardElement}}
{{!-- <div id="discourse-patrons-subscribe-customer">
<h4>{{i18n 'discourse_patrons.subscribe.customer.title'}}</h4>
<div class="discourse-patrons-subscribe-customer-empty">
{{i18n 'discourse_patrons.subscribe.customer.empty'}}
</div>
</div> --}}
</div>
</div>

View File

@ -13,16 +13,17 @@ DiscoursePatrons::Engine.routes.draw do
end end
namespace :user do namespace :user do
resources :subscriptions, only: [:index] resources :subscriptions, only: [:index, :destroy]
end end
resources :customers, only: [:create] resources :customers, only: [:create]
resources :invoices, only: [:index] resources :invoices, only: [:index]
resources :patrons, only: [:index, :create] resources :patrons, only: [:index, :create]
resources :plans, only: [:index] resources :plans, only: [:index]
resources :products, only: [:index] resources :products, only: [:index, :show]
resources :subscriptions, only: [:index, :create, :destroy] resources :subscriptions, only: [:create]
get '/' => 'patrons#index' get '/' => 'patrons#index'
get '/subscribe' => 'patrons#index' get '/subscribe' => 'patrons#index'
get '/subscribe/:id' => 'patrons#index'
end end

View File

@ -81,7 +81,7 @@ module DiscoursePatrons
it 'has a description' do it 'has a description' do
::Stripe::Product.expects(:create).with(has_entry(metadata: { description: 'Oi, I think he just said bless be all the bignoses!' })) ::Stripe::Product.expects(:create).with(has_entry(metadata: { description: 'Oi, I think he just said bless be all the bignoses!' }))
post "/patrons/admin/products.json", params: { metadata: { description: 'Oi, I think he just said bless be all the bignoses!' }} post "/patrons/admin/products.json", params: { metadata: { description: 'Oi, I think he just said bless be all the bignoses!' } }
end end
end end

View File

@ -10,6 +10,11 @@ module DiscoursePatrons
get "/patrons/plans.json" get "/patrons/plans.json"
end end
it "lists the active plans for a product" do
::Stripe::Plan.expects(:list).with(active: true, product: 'prod_3765')
get "/patrons/plans.json", params: { product_id: 'prod_3765' }
end
it "orders and serialises the plans" do it "orders and serialises the plans" do
::Stripe::Plan.expects(:list).returns( ::Stripe::Plan.expects(:list).returns(
data: [ data: [

View File

@ -4,10 +4,41 @@ require 'rails_helper'
module DiscoursePatrons module DiscoursePatrons
RSpec.describe ProductsController do RSpec.describe ProductsController 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
describe "index" do describe "index" do
it "lists the active products" do it "gets products" do
::Stripe::Product.expects(:list).with(active: true) ::Stripe::Product.expects(:list).with(active: true).returns(data: [product])
get "/patrons/products.json" get "/patrons/products.json"
expect(JSON.parse(response.body)).to eq([{
"id" => "prodct_23456",
"name" => "Very Special Product",
"description" => "Many people listened to my phone call with the Ukrainian President while it was being made"
}])
end
end
describe 'show' do
it 'retrieves the product' do
::Stripe::Product.expects(:retrieve).with('prod_walterwhite').returns(product)
get "/patrons/products/prod_walterwhite.json"
expect(JSON.parse(response.body)).to eq(
"id" => "prodct_23456",
"name" => "Very Special Product",
"description" => "Many people listened to my phone call with the Ukrainian President while it was being made"
)
end end
end end
end end

View File

@ -10,11 +10,6 @@ module DiscoursePatrons
::Stripe::Subscription.expects(:create).never ::Stripe::Subscription.expects(:create).never
post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' } post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
end end
it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never
patch "/patrons/subscriptions/sub_12345.json"
end
end end
context "authenticated" do context "authenticated" do
@ -24,30 +19,6 @@ module DiscoursePatrons
sign_in(user) sign_in(user)
end end
describe "index" do
let(:customers) do
{
data: [{
id: "cus_23456",
subscriptions: {
data: [{ id: "sub_1234" }, { id: "sub_4567" }]
},
}]
}
end
it "gets subscriptions" do
::Stripe::Customer.expects(:list).with(
email: user.email,
expand: ['data.subscriptions']
).returns(customers)
get "/patrons/subscriptions.json"
expect(JSON.parse(response.body)).to eq([{ "id" => "sub_1234" }, { "id" => "sub_4567" }])
end
end
describe "create" do describe "create" do
it "creates a subscription" do it "creates a subscription" do
::Stripe::Plan.expects(:retrieve).returns(metadata: { group_name: 'awesome' }) ::Stripe::Plan.expects(:retrieve).returns(metadata: { group_name: 'awesome' })
@ -58,7 +29,7 @@ module DiscoursePatrons
post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' } post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
end end
it "creates a customer" do it "creates a customer model" do
::Stripe::Plan.expects(:retrieve).returns(metadata: {}) ::Stripe::Plan.expects(:retrieve).returns(metadata: {})
::Stripe::Subscription.expects(:create).returns(status: 'active') ::Stripe::Subscription.expects(:create).returns(status: 'active')
@ -66,15 +37,6 @@ module DiscoursePatrons
post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' } post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
}.to change { DiscoursePatrons::Customer.count } }.to change { DiscoursePatrons::Customer.count }
end end
it "does not create a customer id one existeth" do
::Stripe::Plan.expects(:retrieve).returns(metadata: {})
::Stripe::Subscription.expects(:create).returns(status: 'active')
DiscoursePatrons::Customer.create(user_id: user.id, customer_id: 'cus_1234')
DiscoursePatrons::Customer.expects(:create).never
post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
end
end end
describe "user groups" do describe "user groups" do
@ -135,13 +97,6 @@ module DiscoursePatrons
end end
end end
end end
describe "delete" do
it "deletes a subscription" do
::Stripe::Subscription.expects(:delete).with('sub_12345')
delete "/patrons/subscriptions/sub_12345.json"
end
end
end end
end end
end end

View File

@ -13,6 +13,11 @@ module DiscoursePatrons
::Stripe::Customer.expects(:list).never ::Stripe::Customer.expects(:list).never
get "/patrons/user/subscriptions.json" get "/patrons/user/subscriptions.json"
end end
it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never
patch "/patrons/user/subscriptions/sub_12345.json"
end
end end
context "authenticated" do context "authenticated" do
@ -45,6 +50,13 @@ module DiscoursePatrons
expect(JSON.parse(response.body)).to eq([{ "id" => "sub_1234" }, { "id" => "sub_4567" }]) expect(JSON.parse(response.body)).to eq([{ "id" => "sub_1234" }, { "id" => "sub_4567" }])
end end
end end
describe "delete" do
it "deletes a subscription" do
::Stripe::Subscription.expects(:delete).with('sub_12345')
delete "/patrons/user/subscriptions/sub_12345.json"
end
end
end end
end end
end end