diff --git a/README.md b/README.md index 315b4a0..8d021b0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb new file mode 100644 index 0000000..bb3bc7a --- /dev/null +++ b/app/controllers/plans_controller.rb @@ -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 diff --git a/assets/javascripts/discourse/components/donation-form.js.es6 b/assets/javascripts/discourse/components/donation-form.js.es6 index 82232b8..6b182a3 100644 --- a/assets/javascripts/discourse/components/donation-form.js.es6 +++ b/assets/javascripts/discourse/components/donation-form.js.es6 @@ -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, diff --git a/assets/javascripts/discourse/controllers/patrons-subscribe.js.es6 b/assets/javascripts/discourse/controllers/patrons-subscribe.js.es6 new file mode 100644 index 0000000..8e16cae --- /dev/null +++ b/assets/javascripts/discourse/controllers/patrons-subscribe.js.es6 @@ -0,0 +1,11 @@ +export default Ember.Controller.extend({ + actions: { + stripePaymentHandler(/* data */) { + // console.log('stripePaymentHandler', data); + }, + + paymentSuccessHandler(/* paymentIntentId */) { + // console.log('paymentSuccessHandler'); + } + } +}); diff --git a/assets/javascripts/discourse/models/admin-plan.js.es6 b/assets/javascripts/discourse/models/admin-plan.js.es6 new file mode 100644 index 0000000..53c4a83 --- /dev/null +++ b/assets/javascripts/discourse/models/admin-plan.js.es6 @@ -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; diff --git a/assets/javascripts/discourse/models/group.js.es6 b/assets/javascripts/discourse/models/group.js.es6 new file mode 100644 index 0000000..b230e16 --- /dev/null +++ b/assets/javascripts/discourse/models/group.js.es6 @@ -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; diff --git a/assets/javascripts/discourse/models/plan.js.es6 b/assets/javascripts/discourse/models/plan.js.es6 index 46c1ca4..a9fe0fa 100644 --- a/assets/javascripts/discourse/models/plan.js.es6 +++ b/assets/javascripts/discourse/models/plan.js.es6 @@ -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)) ); } diff --git a/assets/javascripts/discourse/patrons-route-map.js.es6 b/assets/javascripts/discourse/patrons-route-map.js.es6 index 2c0777c..c8fa513 100644 --- a/assets/javascripts/discourse/patrons-route-map.js.es6 +++ b/assets/javascripts/discourse/patrons-route-map.js.es6 @@ -1,5 +1,6 @@ export default function() { this.route("patrons", function() { + this.route("subscribe"); this.route("show", { path: ":pid" }); }); } diff --git a/assets/javascripts/discourse/routes/admin-plugins-discourse-patrons-plans-index.js.es6 b/assets/javascripts/discourse/routes/admin-plugins-discourse-patrons-plans-index.js.es6 index ac02092..8863e99 100644 --- a/assets/javascripts/discourse/routes/admin-plugins-discourse-patrons-plans-index.js.es6 +++ b/assets/javascripts/discourse/routes/admin-plugins-discourse-patrons-plans-index.js.es6 @@ -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: { diff --git a/assets/javascripts/discourse/routes/patrons-subscribe.js.es6 b/assets/javascripts/discourse/routes/patrons-subscribe.js.es6 new file mode 100644 index 0000000..6d0204d --- /dev/null +++ b/assets/javascripts/discourse/routes/patrons-subscribe.js.es6 @@ -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 }); + } +}); diff --git a/assets/javascripts/discourse/templates/connectors/extra-nav-item/subscribe.hbs b/assets/javascripts/discourse/templates/connectors/extra-nav-item/subscribe.hbs new file mode 100644 index 0000000..9cca7e6 --- /dev/null +++ b/assets/javascripts/discourse/templates/connectors/extra-nav-item/subscribe.hbs @@ -0,0 +1,4 @@ + +{{#link-to 'patrons.subscribe' class='discourse-patrons-subscribe'}} + {{i18n 'discourse_patrons.navigation.subscribe'}} +{{/link-to}} diff --git a/assets/javascripts/discourse/templates/patrons/subscribe.hbs b/assets/javascripts/discourse/templates/patrons/subscribe.hbs new file mode 100644 index 0000000..940fe65 --- /dev/null +++ b/assets/javascripts/discourse/templates/patrons/subscribe.hbs @@ -0,0 +1,20 @@ + +

+ {{i18n 'discourse_patrons.subscribe.title'}} +

+ +
+
+

{{model.group.full_name}}

+

+ {{{model.group.bio_cooked}}} +

+
+
+ {{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}} +
+
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 6717294..78c563c 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -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 diff --git a/config/routes.rb b/config/routes.rb index c2b1f90..9b23cbe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/config/settings.yml b/config/settings.yml index b74281a..a00b351 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -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: '' diff --git a/plugin.rb b/plugin.rb index 9d08bb8..8e9ab8a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -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__) } diff --git a/spec/requests/admin/plans_controller_spec.rb b/spec/requests/admin/plans_controller_spec.rb index 69041e3..2d59109 100644 --- a/spec/requests/admin/plans_controller_spec.rb +++ b/spec/requests/admin/plans_controller_spec.rb @@ -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 diff --git a/spec/requests/admin/subscriptions_controller_spec.rb b/spec/requests/admin/subscriptions_controller_spec.rb index 640d4eb..a650ddb 100644 --- a/spec/requests/admin/subscriptions_controller_spec.rb +++ b/spec/requests/admin/subscriptions_controller_spec.rb @@ -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 diff --git a/spec/requests/plans_controller_spec.rb b/spec/requests/plans_controller_spec.rb new file mode 100644 index 0000000..070a309 --- /dev/null +++ b/spec/requests/plans_controller_spec.rb @@ -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 diff --git a/test/javascripts/acceptance/payments-test.js.es6 b/test/javascripts/acceptance/payments-test.js.es6 index cae338b..50aab4e 100644 --- a/test/javascripts/acceptance/payments-test.js.es6 +++ b/test/javascripts/acceptance/payments-test.js.es6 @@ -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"); diff --git a/test/javascripts/acceptance/plugin-outlets-test.js.es6 b/test/javascripts/acceptance/plugin-outlets-test.js.es6 new file mode 100644 index 0000000..9bf814a --- /dev/null +++ b/test/javascripts/acceptance/plugin-outlets-test.js.es6 @@ -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" + ); +}); diff --git a/test/javascripts/acceptance/subscribe-test.js.es6 b/test/javascripts/acceptance/subscribe-test.js.es6 new file mode 100644 index 0000000..026b5bc --- /dev/null +++ b/test/javascripts/acceptance/subscribe-test.js.es6 @@ -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"); +}); diff --git a/test/javascripts/helpers/discourse-patrons-pretender.js.es6 b/test/javascripts/helpers/discourse-patrons-pretender.js.es6 index 4339bcc..7e90b9b 100644 --- a/test/javascripts/helpers/discourse-patrons-pretender.js.es6 +++ b/test/javascripts/helpers/discourse-patrons-pretender.js.es6 @@ -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" }); + }); }