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.
* 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.
* 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.
Visit `/patrons`
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.
## 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);
const settings = Discourse.SiteSettings;
const amounts = Discourse.SiteSettings.discourse_patrons_amounts.split("|");
const amounts = settings.discourse_patrons_amounts.split("|");
this.setProperties({
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";
const Plan = Discourse.Model.extend({
destroy() {}
});
const Plan = Discourse.Model.extend({});
Plan.reopenClass({
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))
);
}

View File

@ -1,5 +1,6 @@
export default function() {
this.route("patrons", function() {
this.route("subscribe");
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({
model() {
return Plan.find();
return AdminPlan.find();
},
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_secret_key: "Stripe Secret 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_zip_code: "Show Zip Code"
discourse_patrons_billing_address: "Collect billing address"
@ -15,18 +16,24 @@ en:
js:
discourse_patrons:
title: Discourse Patrons
nav_item: Payment
heading:
payment: Make a Payment
success: Thank you!
payment:
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
navigation:
subscribe: Subscribe
subscribe:
title: Subscribe
buttons:
subscribe: Subscribe
one_time:
heading:
payment: Make a Payment
success: Thank you!
payment:
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:
name: Full name
email: Email

View File

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

View File

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

View File

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

View File

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

View File

@ -3,22 +3,20 @@
require 'rails_helper'
module DiscoursePatrons
module Admin
RSpec.describe SubscriptionsController do
RSpec.describe Admin::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
expect(DiscoursePatrons::Admin::SubscriptionsController < ::Admin::AdminController).to eq(true)
end
it 'is a subclass of AdminController' do
expect(DiscoursePatrons::Admin::SubscriptionsController < ::Admin::AdminController).to eq(true)
end
it "gets the empty subscriptions" do
::Stripe::Subscription.expects(:list)
get "/patrons/admin/subscriptions.json"
expect(response.status).to eq(204)
end
it "gets the empty subscriptions" do
::Stripe::Subscription.expects(:list)
get "/patrons/admin/subscriptions.json"
expect(response.status).to eq(204)
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");
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;
this.get("/patrons", () => response({ email: "hello@example.com" }));
this.get("/groups/:plan", id => {
return response({ full_name: "Saboo", bio_cooked: "This is the plan" });
});
}