From 45ece3420021ce8c0e5faa6e8b251a9441fe5537 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 22 Sep 2018 14:03:30 +1000 Subject: [PATCH] Add donations causes --- .../discourse_donations/charges_controller.rb | 3 +- app/jobs/jobs.rb | 1 + .../update_category_donation_statistics.rb | 69 ++++++++++++++ app/services/discourse_donations/stripe.rb | 24 ++++- .../discourse/components/donation-list.js.es6 | 5 +- .../discourse/components/donation-row.js.es6 | 6 +- .../discourse/components/stripe-card.js.es6 | 44 +++++++-- .../donations-category-header-container.hbs | 3 + .../donations_category_settings.hbs | 11 +++ .../connectors/extra-nav-item/donate.hbs | 0 .../controllers/cancel-subscription.js.es6 | 2 +- .../discourse/controllers/donate.js.es6 | 7 +- .../initializers/donations-edits.js.es6 | 23 +++-- .../templates/components/stripe-card.hbs | 9 ++ .../donations-category-header-widget.js.es6 | 90 +++++++++++++++++++ assets/stylesheets/discourse-donations.scss | 67 ++++++++++++++ config/locales/client.en.yml | 18 ++++ config/settings.yml | 24 +++-- plugin.rb | 60 +++++++++++++ 19 files changed, 433 insertions(+), 33 deletions(-) create mode 100644 app/jobs/scheduled/update_category_donation_statistics.rb create mode 100644 assets/javascripts/discourse/connectors/below-site-header/donations-category-header-container.hbs create mode 100644 assets/javascripts/discourse/connectors/category-custom-settings/donations_category_settings.hbs rename assets/javascripts/discourse/{templates => }/connectors/extra-nav-item/donate.hbs (100%) create mode 100644 assets/javascripts/discourse/widgets/donations-category-header-widget.js.es6 diff --git a/app/controllers/discourse_donations/charges_controller.rb b/app/controllers/discourse_donations/charges_controller.rb index 017be3f..b157130 100644 --- a/app/controllers/discourse_donations/charges_controller.rb +++ b/app/controllers/discourse_donations/charges_controller.rb @@ -48,6 +48,7 @@ module DiscourseDonations begin Rails.logger.debug "Creating a Stripe charge for #{user_params[:amount]}" opts = { + cause: user_params[:cause], email: @email, token: user_params[:stripeToken], amount: user_params[:amount] @@ -153,7 +154,7 @@ module DiscourseDonations end def user_params - params.permit(:user_id, :name, :username, :email, :password, :stripeToken, :type, :amount, :create_account) + params.permit(:user_id, :name, :username, :email, :password, :stripeToken, :cause, :type, :amount, :create_account) end def set_user diff --git a/app/jobs/jobs.rb b/app/jobs/jobs.rb index e804950..1a1620c 100644 --- a/app/jobs/jobs.rb +++ b/app/jobs/jobs.rb @@ -1 +1,2 @@ load File.expand_path('../regular/donation_user.rb', __FILE__) +load File.expand_path('../scheduled/update_category_donation_statistics.rb', __FILE__) diff --git a/app/jobs/scheduled/update_category_donation_statistics.rb b/app/jobs/scheduled/update_category_donation_statistics.rb new file mode 100644 index 0000000..c22f613 --- /dev/null +++ b/app/jobs/scheduled/update_category_donation_statistics.rb @@ -0,0 +1,69 @@ +module Jobs + class UpdateCategoryDonationStatistics < ::Jobs::Scheduled + every 1.hour + + def execute(args) + return unless SiteSetting.discourse_donations_cause_category + + ::Stripe.api_key = SiteSetting.discourse_donations_secret_key + totals = {} + backers = {} + categories = [] + + raw_charges = ::Stripe::Charge.list( + expand: ['data.invoice.subscription', 'data.customer'] + ) + raw_charges = raw_charges.is_a?(Object) ? raw_charges['data'] : [] + + raw_charges.each do |c| + cause_base = c['invoice'] && c['invoice']['subscription'] ? c['invoice']['subscription'] : c + category_id = cause_base['metadata']['discourse_cause'].to_i + + backer_base = c['customer'] + backer_user_id = backer_base['metadata']['discourse_user_id'].to_i + backer_email = backer_base['email'] + + if category_id > 0 && Category.exists?(id: category_id) + categories.push(category_id) + + current = totals[category_id] || {} + amount = c['amount'].to_i + date = Time.at(c['created']).to_datetime + + totals[category_id] ||= {} + totals[category_id][:total] ||= 0 + totals[category_id][:month] ||= 0 + + totals[category_id][:total] += amount + + if date.month == Date.today.month + totals[category_id][:month] += amount + end + + backers[category_id] ||= [] + + if backer_user_id > 0 && User.exists?(id: backer_user_id) + backers[category_id].push(backer_user_id) unless backers[category_id].include? backer_user_id + elsif user = User.find_by_email(backer_email) + backers[category_id].push(user.id) unless backers[category_id].include? user.id + end + end + end + + categories.each do |category_id| + category = Category.find(category_id) + + if totals[category_id] + category.custom_fields['donations_total'] = totals[category_id][:total] + category.custom_fields['donations_month'] = totals[category_id][:month] + end + + if backers[category_id] + category.custom_fields['donations_backers'] = backers[category_id] + end + + category.save_custom_fields(true) + end + end + end +end diff --git a/app/services/discourse_donations/stripe.rb b/app/services/discourse_donations/stripe.rb index cf7288e..fec2d2c 100644 --- a/app/services/discourse_donations/stripe.rb +++ b/app/services/discourse_donations/stripe.rb @@ -41,7 +41,11 @@ module DiscourseDonations amount: opts[:amount], description: @description, currency: @currency, - receipt_email: customer.email + receipt_email: customer.email, + metadata: { + discourse_cause: opts[:cause], + discourse_user_id: user.id + } ) @charge @@ -72,7 +76,11 @@ module DiscourseDonations customer: customer.id, items: [{ plan: plan_id - }] + }], + metadata: { + discourse_cause: opts[:cause], + discourse_user_id: user.id + } ) end @@ -186,10 +194,18 @@ module DiscourseDonations end if !customer && opts[:create] - customer = ::Stripe::Customer.create( + customer_opts = { email: opts[:email], source: opts[:source] - ) + } + + if user + customer_opts[:metadata] = { + discourse_user_id: user.id + } + end + + customer = ::Stripe::Customer.create(customer_opts) if user user.custom_fields['stripe_customer_id'] = customer.id diff --git a/assets/javascripts/discourse/components/donation-list.js.es6 b/assets/javascripts/discourse/components/donation-list.js.es6 index 3b9b4da..57ec8f9 100644 --- a/assets/javascripts/discourse/components/donation-list.js.es6 +++ b/assets/javascripts/discourse/components/donation-list.js.es6 @@ -1,8 +1,5 @@ -import { ajax } from 'discourse/lib/ajax'; -import { popupAjaxError } from 'discourse/lib/ajax-error'; - export default Ember.Component.extend({ classNames: 'donation-list', hasSubscriptions: Ember.computed.notEmpty('subscriptions'), hasCharges: Ember.computed.notEmpty('charges') -}) +}); diff --git a/assets/javascripts/discourse/components/donation-row.js.es6 b/assets/javascripts/discourse/components/donation-row.js.es6 index 56533f5..9c00574 100644 --- a/assets/javascripts/discourse/components/donation-row.js.es6 +++ b/assets/javascripts/discourse/components/donation-row.js.es6 @@ -1,7 +1,7 @@ import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import { formatAnchor, formatAmount } from '../lib/donation-utilities'; -import { default as computed, observes, on } from 'ember-addons/ember-computed-decorators'; +import { default as computed } from 'ember-addons/ember-computed-decorators'; import showModal from "discourse/lib/show-modal"; export default Ember.Component.extend({ @@ -60,7 +60,7 @@ export default Ember.Component.extend({ period(anchor, interval) { return I18n.t(`discourse_donations.period.${interval}`, { anchor: formatAnchor(interval, moment.unix(anchor)) - }) + }); }, cancelSubscription() { @@ -93,4 +93,4 @@ export default Ember.Component.extend({ }); } } -}) +}); diff --git a/assets/javascripts/discourse/components/stripe-card.js.es6 b/assets/javascripts/discourse/components/stripe-card.js.es6 index 2708ca1..c123caa 100644 --- a/assets/javascripts/discourse/components/stripe-card.js.es6 +++ b/assets/javascripts/discourse/components/stripe-card.js.es6 @@ -1,7 +1,7 @@ import { ajax } from 'discourse/lib/ajax'; import { formatAnchor, zeroDecimalCurrencies } from '../lib/donation-utilities'; import { default as computed } from 'ember-addons/ember-computed-decorators'; -import { emailValid } from "discourse/lib/utilities"; +import { emailValid as emailValidHelper } from "discourse/lib/utilities"; export default Ember.Component.extend({ result: [], @@ -29,6 +29,34 @@ export default Ember.Component.extend({ }); }, + @computed + causes() { + const categoryEnabled = Discourse.SiteSettings.discourse_donations_cause_category; + + if (categoryEnabled) { + let categoryIds = Discourse.SiteSettings.discourse_donations_causes_categories.split('|'); + + if (categoryIds.length) { + categoryIds = categoryIds.map(Number); + return this.site + .get("categoriesList") + .filter(c => { + return categoryIds.indexOf(c.id) > -1; + }).map(c => { + return { + id: c.id, + name: c.name + }; + }); + } else { + return []; + } + } else { + const causes = Discourse.SiteSettings.discourse_donations_causes; + return causes ? causes.split('|') : []; + } + }, + @computed('types') donationTypes(types) { return types.map((type) => { @@ -97,7 +125,7 @@ export default Ember.Component.extend({ @computed('email') emailValid(email) { - return emailValid(email); + return emailValidHelper(email); }, @computed('email', 'emailValid') @@ -110,9 +138,14 @@ export default Ember.Component.extend({ return currentUser || emailValid; }, - @computed('userReady', 'stripeReady') - formIncomplete(userReady, stripeReady) { - return !userReady || !stripeReady; + @computed('cause') + causeValid(cause) { + return cause || !Discourse.SiteSettings.discourse_donations_cause_required; + }, + + @computed('userReady', 'stripeReady', 'causeValid') + formIncomplete(userReady, stripeReady, causeValid) { + return !userReady || !stripeReady || !causeValid; }, @computed('transactionInProgress', 'formIncomplete') @@ -181,6 +214,7 @@ export default Ember.Component.extend({ let params = { stripeToken: data.token.id, + cause: self.get('cause'), type: self.get('type'), amount, email: self.get('email'), diff --git a/assets/javascripts/discourse/connectors/below-site-header/donations-category-header-container.hbs b/assets/javascripts/discourse/connectors/below-site-header/donations-category-header-container.hbs new file mode 100644 index 0000000..8de7bcd --- /dev/null +++ b/assets/javascripts/discourse/connectors/below-site-header/donations-category-header-container.hbs @@ -0,0 +1,3 @@ +{{#if siteSettings.discourse_donations_cause_category}} + {{mount-widget widget="category-header-widget"}} +{{/if}} diff --git a/assets/javascripts/discourse/connectors/category-custom-settings/donations_category_settings.hbs b/assets/javascripts/discourse/connectors/category-custom-settings/donations_category_settings.hbs new file mode 100644 index 0000000..42048b2 --- /dev/null +++ b/assets/javascripts/discourse/connectors/category-custom-settings/donations_category_settings.hbs @@ -0,0 +1,11 @@ +{{#if siteSettings.discourse_donations_cause_category}} +
+ + {{text-field value=category.custom_fields.donations_github placeholderKey="discourse_donations.cause.github.setting_placeholder"}} +
+ +
+ + {{user-selector usernames=category.custom_fields.donations_maintainers}} +
+{{/if}} diff --git a/assets/javascripts/discourse/templates/connectors/extra-nav-item/donate.hbs b/assets/javascripts/discourse/connectors/extra-nav-item/donate.hbs similarity index 100% rename from assets/javascripts/discourse/templates/connectors/extra-nav-item/donate.hbs rename to assets/javascripts/discourse/connectors/extra-nav-item/donate.hbs diff --git a/assets/javascripts/discourse/controllers/cancel-subscription.js.es6 b/assets/javascripts/discourse/controllers/cancel-subscription.js.es6 index 57a6565..5afc458 100644 --- a/assets/javascripts/discourse/controllers/cancel-subscription.js.es6 +++ b/assets/javascripts/discourse/controllers/cancel-subscription.js.es6 @@ -9,4 +9,4 @@ export default Ember.Controller.extend({ this.send('closeModal'); } } -}) +}); diff --git a/assets/javascripts/discourse/controllers/donate.js.es6 b/assets/javascripts/discourse/controllers/donate.js.es6 index 2073af0..cd4b511 100644 --- a/assets/javascripts/discourse/controllers/donate.js.es6 +++ b/assets/javascripts/discourse/controllers/donate.js.es6 @@ -2,6 +2,7 @@ import { default as computed } from 'ember-addons/ember-computed-decorators'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import { ajax } from 'discourse/lib/ajax'; import { getOwner } from 'discourse-common/lib/get-owner'; +import { emailValid } from "discourse/lib/utilities"; export default Ember.Controller.extend({ loadingDonations: false, @@ -41,8 +42,8 @@ export default Ember.Controller.extend({ Ember.run.later(() => { this.set('hasEmailResult', false); - }, 6000) - }) + }, 6000); + }); }, showLogin() { @@ -50,4 +51,4 @@ export default Ember.Controller.extend({ controller.send('showLogin'); } } -}) +}); diff --git a/assets/javascripts/discourse/initializers/donations-edits.js.es6 b/assets/javascripts/discourse/initializers/donations-edits.js.es6 index b0bceff..928d01c 100644 --- a/assets/javascripts/discourse/initializers/donations-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/donations-edits.js.es6 @@ -2,28 +2,37 @@ import { withPluginApi } from 'discourse/lib/plugin-api'; export default { name: 'donations-edits', - initialize() { + initialize(container) { + const siteSettings = container.lookup('site-settings:main'); + withPluginApi('0.8.12', api => { api.decorateCooked($post => { const $form = $post.find('.stripe-checkout'); if ($form.length) { const $input = $form.find('input'); - const settings = Discourse.SiteSettings; var s = document.createElement('script'); s.src = 'https://checkout.stripe.com/checkout.js'; s.setAttribute('class', 'stripe-button'); - s.setAttribute('data-key', settings.discourse_donations_public_key); + s.setAttribute('data-key', siteSettings.discourse_donations_public_key); s.setAttribute('data-amount', $input.attr('amount')); - s.setAttribute('data-name', settings.discourse_donations_shop_name); + s.setAttribute('data-name', siteSettings.discourse_donations_shop_name); s.setAttribute('data-description', $form.attr('content')); s.setAttribute('data-image', $form.attr('image') || ''); s.setAttribute('data-locale', 'auto'); - s.setAttribute('data-zip-code', settings.discourse_donations_zip_code); - s.setAttribute('data-billing-address', settings.discourse_donations_billing_address); - s.setAttribute('data-currency', settings.discourse_donations_currency); + s.setAttribute('data-zip-code', siteSettings.discourse_donations_zip_code); + s.setAttribute('data-billing-address', siteSettings.discourse_donations_billing_address); + s.setAttribute('data-currency', siteSettings.discourse_donations_currency); $form.append(s); } }); + + if (siteSettings.discourse_donations_cause_category) { + api.decorateWidget('category-header-widget:after', helper => { + helper.widget.appEvents.on('page:changed', () => { + helper.widget.scheduleRerender(); + }); + }); + } }); } }; diff --git a/assets/javascripts/discourse/templates/components/stripe-card.hbs b/assets/javascripts/discourse/templates/components/stripe-card.hbs index e8ff719..870d90d 100644 --- a/assets/javascripts/discourse/templates/components/stripe-card.hbs +++ b/assets/javascripts/discourse/templates/components/stripe-card.hbs @@ -1,4 +1,13 @@
+
+ +
+ {{combo-box content=causes value=cause none='discourse_donations.cause.placeholder'}} +
+
+