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
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|
plan.to_h.slice(:id, :amount, :currency, :interval)

View File

@ -8,14 +8,38 @@ module DiscoursePatrons
def index
begin
products = ::Stripe::Product.list(active: true)
response = ::Stripe::Product.list(active: true)
# TODO: Serialize. Remove some attributes like metadata
render_json_dump products.data
products = response[:data].map do |p|
serialize(p)
end
render_json_dump products
rescue ::Stripe::InvalidRequestError => e
return 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
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

View File

@ -8,14 +8,14 @@ module DiscoursePatrons
def index
begin
customers = ::Stripe::Customer.list(
email: current_user.email,
expand: ['data.subscriptions']
)
products = ::Stripe::Product.list(active: true)
subscriptions = customers[:data].map do |customer|
customer[:subscriptions][:data]
end.flatten(1)
subscriptions = products[:data].map do |p|
{
id: p[:id],
description: p.dig(:metadata, :description)
}
end
render_json_dump subscriptions
@ -50,17 +50,6 @@ module DiscoursePatrons
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
def plan_group(plan)

View File

@ -25,6 +25,17 @@ module DiscoursePatrons
return render_json_error e.message
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

View File

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

View File

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

View File

@ -7,6 +7,12 @@ Product.reopenClass({
return ajax("/patrons/products", { method: "get" }).then(result =>
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 });
},
destroy() {
return ajax(`/patrons/subscriptions/${this.id}`, { method: "delete" }).then(
result => Subscription.create(result)
);
}
});
Subscription.reopenClass({
findAll() {
return ajax("/patrons/subscriptions", { method: "get" }).then(result =>
result.map(subscription => {
subscription.plan = Plan.create(subscription.plan);
return Subscription.create(subscription);
})
result.map(subscription => Subscription.create(subscription))
);
}
});

View File

@ -6,6 +6,12 @@ const UserSubscription = Discourse.Model.extend({
@computed("status")
canceled(status) {
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() {
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 Subscription from "discourse/plugins/discourse-patrons/discourse/models/subscription";
import Product from "discourse/plugins/discourse-patrons/discourse/models/product";
export default Discourse.Route.extend({
model() {
const plans = Plan.findAll().then(results =>
results.map(p => ({ id: p.id, name: p.subscriptionRate }))
);
const subscription = Subscription.create();
return Ember.RSVP.hash({ plans, subscription });
}
return Product.findAll();
},
});

View File

@ -1,34 +1,12 @@
<h3>
{{i18n 'discourse_patrons.subscribe.title'}}
</h3>
<div class="discourse-patrons-section-columns">
<div class="section-column discourse-patrons-confirmation-billing">
<h4>{{model.group.full_name}}</h4>
<p>
{{{model.group.bio_cooked}}}
</p>
<div class="container">
<div class="title-wrapper">
<h1>
{{i18n 'discourse_patrons.subscribe.title'}}
</h1>
</div>
<div class="section-column">
{{combo-box valueAttribute="id" content=model.plans value=model.subscription.plan}}
{{#d-button
action="stripePaymentHandler"
class="btn btn-primary btn-payment btn-discourse-patrons"}}
{{i18n 'discourse_patrons.subscribe.buttons.subscribe'}}
{{/d-button}}
<hr>
<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>
{{outlet}}
</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
namespace :user do
resources :subscriptions, only: [:index]
resources :subscriptions, only: [:index, :destroy]
end
resources :customers, only: [:create]
resources :invoices, only: [:index]
resources :patrons, only: [:index, :create]
resources :plans, only: [:index]
resources :products, only: [:index]
resources :subscriptions, only: [:index, :create, :destroy]
resources :products, only: [:index, :show]
resources :subscriptions, only: [:create]
get '/' => 'patrons#index'
get '/subscribe' => 'patrons#index'
get '/subscribe/:id' => 'patrons#index'
end

View File

@ -81,7 +81,7 @@ module DiscoursePatrons
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!' }))
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

View File

@ -10,6 +10,11 @@ module DiscoursePatrons
get "/patrons/plans.json"
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
::Stripe::Plan.expects(:list).returns(
data: [

View File

@ -4,10 +4,41 @@ require 'rails_helper'
module DiscoursePatrons
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
it "lists the active products" do
::Stripe::Product.expects(:list).with(active: true)
it "gets products" do
::Stripe::Product.expects(:list).with(active: true).returns(data: [product])
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

View File

@ -10,11 +10,6 @@ module DiscoursePatrons
::Stripe::Subscription.expects(:create).never
post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
end
it "does not destroy a subscription" do
::Stripe::Subscription.expects(:delete).never
patch "/patrons/subscriptions/sub_12345.json"
end
end
context "authenticated" do
@ -24,30 +19,6 @@ module DiscoursePatrons
sign_in(user)
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
it "creates a subscription" do
::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' }
end
it "creates a customer" do
it "creates a customer model" do
::Stripe::Plan.expects(:retrieve).returns(metadata: {})
::Stripe::Subscription.expects(:create).returns(status: 'active')
@ -66,15 +37,6 @@ module DiscoursePatrons
post "/patrons/subscriptions.json", params: { plan: 'plan_1234', customer: 'cus_1234' }
}.to change { DiscoursePatrons::Customer.count }
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
describe "user groups" do
@ -135,13 +97,6 @@ module DiscoursePatrons
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

View File

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