basic subscribe page

This commit is contained in:
Rimian Perkins 2019-10-10 13:52:55 +11:00
parent 91045583ad
commit bb31deae89
23 changed files with 182 additions and 40 deletions

View File

@ -11,13 +11,10 @@ This is a newer version of https://github.com/rimian/discourse-donations.
* Be sure your site is enforcing https. * Be sure your site is enforcing https.
* Follow the install instructions here: https://meta.discourse.org/t/install-a-plugin/19157 * Follow the install instructions here: https://meta.discourse.org/t/install-a-plugin/19157
* Add your Stripe public and private keys in settings and set the currency to your local value. * Add your Stripe public and private keys in settings and set the currency to your local value.
* Enable the plugin and wait for people to donate money.
## Usage ## Creating Subscription Plans
Enable the plugin and enter your Stripe API keys in the settings. You can also configure amounts and the default currency. When users subscribe to your Discourse application, they are added to a user group. You can create new user groups or use existing ones. Of course, you should be careful what permissions you apply to the user group.
Visit `/patrons`
## Testing ## Testing

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module DiscoursePatrons
class PlansController < ::ApplicationController
include DiscoursePatrons::Stripe
before_action :set_api_key
def index
plans = ::Stripe::Plan.list
render json: plans.data
end
end
end

View File

@ -10,7 +10,7 @@ export default Ember.Component.extend({
this._super(...arguments); this._super(...arguments);
const settings = Discourse.SiteSettings; const settings = Discourse.SiteSettings;
const amounts = Discourse.SiteSettings.discourse_patrons_amounts.split("|"); const amounts = settings.discourse_patrons_amounts.split("|");
this.setProperties({ this.setProperties({
confirmation: false, confirmation: false,

View File

@ -0,0 +1,11 @@
export default Ember.Controller.extend({
actions: {
stripePaymentHandler(/* data */) {
// console.log('stripePaymentHandler', data);
},
paymentSuccessHandler(/* paymentIntentId */) {
// console.log('paymentSuccessHandler');
}
}
});

View File

@ -0,0 +1,15 @@
import { ajax } from "discourse/lib/ajax";
const AdminPlan = Discourse.Model.extend({
destroy() {}
});
AdminPlan.reopenClass({
find() {
return ajax("/patrons/admin/plans", { method: "get" }).then(result =>
result.plans.map(plan => AdminPlan.create(plan))
);
}
});
export default AdminPlan;

View File

@ -0,0 +1,18 @@
import { ajax } from "discourse/lib/ajax";
const Group = Discourse.Model.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;

View File

@ -1,12 +1,10 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
const Plan = Discourse.Model.extend({ const Plan = Discourse.Model.extend({});
destroy() {}
});
Plan.reopenClass({ Plan.reopenClass({
find() { find() {
return ajax("/patrons/admin/plans", { method: "get" }).then(result => return ajax("/patrons/plans", { method: "get" }).then(result =>
result.plans.map(plan => Plan.create(plan)) result.plans.map(plan => Plan.create(plan))
); );
} }

View File

@ -1,5 +1,6 @@
export default function() { export default function() {
this.route("patrons", function() { this.route("patrons", function() {
this.route("subscribe");
this.route("show", { path: ":pid" }); this.route("show", { path: ":pid" });
}); });
} }

View File

@ -1,8 +1,8 @@
import Plan from "discourse/plugins/discourse-patrons/discourse/models/plan"; import AdminPlan from "discourse/plugins/discourse-patrons/discourse/models/admin-plan";
export default Discourse.Route.extend({ export default Discourse.Route.extend({
model() { model() {
return Plan.find(); return AdminPlan.find();
}, },
actions: { actions: {

View File

@ -0,0 +1,11 @@
import Group from "discourse/plugins/discourse-patrons/discourse/models/group";
import Plan from "discourse/plugins/discourse-patrons/discourse/models/plan";
export default Discourse.Route.extend({
model() {
const group = Group.find();
const plans = Plan.find().then(results => results.map(p => p.id));
return Ember.RSVP.hash({ group, plans });
}
});

View File

@ -0,0 +1,4 @@
{{#link-to 'patrons.subscribe' class='discourse-patrons-subscribe'}}
{{i18n 'discourse_patrons.navigation.subscribe'}}
{{/link-to}}

View File

@ -0,0 +1,20 @@
<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>
<div class="section-column">
{{combo-box valueAttribute="id" content=model.plans value=model.plan}}
{{#d-button class="btn btn-primary btn-payment btn-discourse-patrons"}}
{{i18n 'discourse_patrons.subscribe.buttons.subscribe'}}
{{/d-button}}
</div>
</div>

View File

@ -3,6 +3,7 @@ en:
discourse_patrons_enabled: "Enable the Discourse Patrons plugin." discourse_patrons_enabled: "Enable the Discourse Patrons plugin."
discourse_patrons_secret_key: "Stripe Secret Key" discourse_patrons_secret_key: "Stripe Secret Key"
discourse_patrons_public_key: "Stripe Public Key" discourse_patrons_public_key: "Stripe Public Key"
discourse_patrons_subscription_group: The name of the group the user is added to when successfully subscribed
discourse_patrons_currency: "Currency Code" discourse_patrons_currency: "Currency Code"
discourse_patrons_zip_code: "Show Zip Code" discourse_patrons_zip_code: "Show Zip Code"
discourse_patrons_billing_address: "Collect billing address" discourse_patrons_billing_address: "Collect billing address"
@ -15,18 +16,24 @@ en:
js: js:
discourse_patrons: discourse_patrons:
title: Discourse Patrons title: Discourse Patrons
nav_item: Payment navigation:
heading: subscribe: Subscribe
payment: Make a Payment subscribe:
success: Thank you! title: Subscribe
payment: buttons:
optional: Optional subscribe: Subscribe
receipt_info: A receipt is sent to this email address one_time:
your_information: Your information heading:
payment_information: Payment information payment: Make a Payment
payment_confirmation: Confirm information success: Thank you!
amount: Amount payment:
payment_intent_id: Payment ID optional: Optional
receipt_info: A receipt is sent to this email address
your_information: Your information
payment_information: Payment information
payment_confirmation: Confirm information
amount: Amount
payment_intent_id: Payment ID
billing: billing:
name: Full name name: Full name
email: Email email: Email

View File

@ -11,8 +11,9 @@ DiscoursePatrons::Engine.routes.draw do
resources :subscriptions, only: [:index] resources :subscriptions, only: [:index]
end end
resources :plans, only: [:index]
resources :patrons, only: [:index, :create]
get '/' => 'patrons#index' get '/' => 'patrons#index'
get '/:pid' => 'patrons#show' get '/:pid' => 'patrons#show'
resources :patrons, only: [:index, :create]
end end

View File

@ -7,6 +7,9 @@ plugins:
discourse_patrons_secret_key: discourse_patrons_secret_key:
default: '' default: ''
client: false client: false
discourse_patrons_subscription_group:
default: ''
client: true
discourse_patrons_payment_page: discourse_patrons_payment_page:
client: true client: true
default: '' default: ''

View File

@ -42,6 +42,7 @@ after_initialize do
"../app/controllers/admin/plans_controller", "../app/controllers/admin/plans_controller",
"../app/controllers/admin/subscriptions_controller", "../app/controllers/admin/subscriptions_controller",
"../app/controllers/patrons_controller", "../app/controllers/patrons_controller",
"../app/controllers/plans_controller",
"../app/models/payment", "../app/models/payment",
"../app/serializers/payment_serializer", "../app/serializers/payment_serializer",
].each { |path| require File.expand_path(path, __FILE__) } ].each { |path| require File.expand_path(path, __FILE__) }

View File

@ -53,7 +53,7 @@ module DiscoursePatrons
before { sign_in(admin) } before { sign_in(admin) }
describe "index" do describe "index" do
it "is ok" do it "lists the plans" do
::Stripe::Plan.expects(:list) ::Stripe::Plan.expects(:list)
get "/patrons/admin/plans.json" get "/patrons/admin/plans.json"
end end

View File

@ -3,22 +3,20 @@
require 'rails_helper' require 'rails_helper'
module DiscoursePatrons module DiscoursePatrons
module Admin RSpec.describe Admin::SubscriptionsController do
RSpec.describe SubscriptionsController do
let(:admin) { Fabricate(:admin) } let(:admin) { Fabricate(:admin) }
before { sign_in(admin) } before { sign_in(admin) }
it 'is a subclass of AdminController' do it 'is a subclass of AdminController' do
expect(DiscoursePatrons::Admin::SubscriptionsController < ::Admin::AdminController).to eq(true) expect(DiscoursePatrons::Admin::SubscriptionsController < ::Admin::AdminController).to eq(true)
end end
it "gets the empty subscriptions" do it "gets the empty subscriptions" do
::Stripe::Subscription.expects(:list) ::Stripe::Subscription.expects(:list)
get "/patrons/admin/subscriptions.json" get "/patrons/admin/subscriptions.json"
expect(response.status).to eq(204) expect(response.status).to eq(204)
end
end end
end end
end end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
require 'rails_helper'
module DiscoursePatrons
RSpec.describe PlansController do
describe "index" do
it "lists the plans" do
::Stripe::Plan.expects(:list)
get "/patrons/plans.json"
end
end
end
end

View File

@ -11,7 +11,7 @@ acceptance("Discourse Patrons", {
} }
}); });
QUnit.test("viewing", async assert => { QUnit.test("viewing the one-off payment page", async assert => {
await visit("/patrons"); await visit("/patrons");
assert.ok($(".donations-page-payment").length, "has payment form class"); assert.ok($(".donations-page-payment").length, "has payment form class");

View File

@ -0,0 +1,12 @@
import { acceptance } from "helpers/qunit-helpers";
acceptance("Discourse Patrons");
QUnit.test("plugin outlets", async assert => {
await visit("/");
assert.ok(
$("#navigation-bar .discourse-patrons-subscribe").length,
"has a subscribe button"
);
});

View File

@ -0,0 +1,13 @@
import { acceptance } from "helpers/qunit-helpers";
acceptance("Discourse Patrons", {
settings: {
discourse_patrons_subscription_group: "plan-id"
}
});
QUnit.test("subscribing", async assert => {
await visit("/patrons/subscribe");
assert.ok($("h3").length, "has a heading");
});

View File

@ -2,4 +2,8 @@ export default function(helpers) {
const { response } = helpers; const { response } = helpers;
this.get("/patrons", () => response({ email: "hello@example.com" })); this.get("/patrons", () => response({ email: "hello@example.com" }));
this.get("/groups/:plan", id => {
return response({ full_name: "Saboo", bio_cooked: "This is the plan" });
});
} }