From d55d149d7de8e3f790311d22afcd9f9e7baacbc8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 21 Jun 2018 19:00:43 +1000 Subject: [PATCH] Add recurring donations --- .../discourse_donations/charges_controller.rb | 43 ++++++++---- app/services/discourse_donations/stripe.rb | 70 ++++++++++++++++--- .../discourse/components/stripe-card.js.es6 | 34 +++++++++ .../templates/components/stripe-card.hbs | 12 +++- config/locales/client.en.yml | 14 +++- config/settings.yml | 9 +++ plugin.rb | 2 +- 7 files changed, 158 insertions(+), 26 deletions(-) diff --git a/app/controllers/discourse_donations/charges_controller.rb b/app/controllers/discourse_donations/charges_controller.rb index 7563474..c712d19 100644 --- a/app/controllers/discourse_donations/charges_controller.rb +++ b/app/controllers/discourse_donations/charges_controller.rb @@ -1,10 +1,11 @@ require_dependency 'discourse' module DiscourseDonations - class ChargesController < ApplicationController + class ChargesController < ::ApplicationController skip_before_action :verify_authenticity_token, only: [:create] skip_before_action :check_xhr + before_action :set_user_and_email, only: [:create] def create Rails.logger.info user_params.inspect @@ -12,7 +13,7 @@ module DiscourseDonations output = { 'messages' => [], 'rewards' => [] } if create_account - if !email.present? || !user_params[:username].present? + if !@email.present? || !user_params[:username].present? output['messages'] << I18n.t('login.missing_user_field') end if user_params[:password] && user_params[:password].length > User.max_password_length @@ -32,15 +33,19 @@ module DiscourseDonations begin Rails.logger.debug "Creating a Stripe charge for #{user_params[:amount]}" - charge_params = [user_params[:stripeToken], user_params[:amount]] + opts = { + email: @email, + token: user_params[:stripeToken], + amount: user_params[:amount] + } - if user - charge_params.unshift(user, user.email) + if user_params[:type] === 'once' + charge = payment.charge(@user, opts) else - charge_params.unshift(nil, email) + opts[:type] = user_params[:type] + charge = payment.subscribe(@user, opts) end - charge = payment.charge(*charge_params) rescue ::Stripe::CardError => e err = e.json_body[:error] @@ -58,7 +63,7 @@ module DiscourseDonations output['rewards'] << { type: :group, name: group_name } if group_name output['rewards'] << { type: :badge, name: badge_name } if badge_name - if create_account && email.present? + if create_account && @email.present? args = user_params.to_h.slice(:email, :username, :password, :name).merge(rewards: output['rewards']) Jobs.enqueue(:donation_user, args) end @@ -97,19 +102,27 @@ module DiscourseDonations end def user_params - params.permit(:user_id, :name, :username, :email, :password, :stripeToken, :amount, :create_account) + params.permit(:user_id, :name, :username, :email, :password, :stripeToken, :type, :amount, :create_account) end def email - user_params[:email] || user.try(:email) end - def user - if user_params[:user_id] - User.find(user_params[:user_id]) - else - current_user + def set_user_and_email + user = current_user + + if user_params[:user_id].present? + user = User.find(user_params[:user_id]) end + + if user_params[:email].present? + email = user_params[:email] + else + email = user.try(:email) + end + + @user = user + @email = email end end end diff --git a/app/services/discourse_donations/stripe.rb b/app/services/discourse_donations/stripe.rb index 936fcb7..893d4a2 100644 --- a/app/services/discourse_donations/stripe.rb +++ b/app/services/discourse_donations/stripe.rb @@ -1,3 +1,5 @@ +RECURRING_DONATION_PRODUCT_ID = 'discourse_donation_recurring' + module DiscourseDonations class Stripe attr_reader :charge, :currency, :description @@ -10,32 +12,47 @@ module DiscourseDonations def checkoutCharge(user = nil, email, token, amount) customer = customer(user, email, token) + charge = ::Stripe::Charge.create( customer: customer.id, amount: amount, description: @description, currency: @currency ) + charge end - def charge(user = nil, email, token, amount) - customer = customer(user, email, token) + def charge(user = nil, opts) + customer = customer(user, opts[:email], opts[:token]) + @charge = ::Stripe::Charge.create( customer: customer.id, - amount: amount, - description: description, - currency: currency + amount: opts[:amount], + description: @description, + currency: @currency ) + @charge end - def subscribe(user = nil, email, opts) - customer = customer(user, email, opts[:stripeToken]) + def subscribe(user = nil, opts) + customer = customer(user, opts[:email], opts[:token]) + + plans = ::Stripe::Plan.list + type = opts[:type] + plan_id = create_plan_id(type) + + unless plans.data && plans.data.any? { |p| p['id'] === plan_id } + result = create_plan(type, opts[:amount]) + plan_id = result['id'] + end + @subscription = ::Stripe::Subscription.create( customer: customer.id, - plan: opts[:plan] + items: [{ plan: plan_id }] ) + @subscription end @@ -47,10 +64,12 @@ module DiscourseDonations email: email, source: source ) + if user user.custom_fields['stripe_customer_id'] = customer.id user.save_custom_fields(true) end + customer end end @@ -58,5 +77,40 @@ module DiscourseDonations def successful? @charge[:paid] end + + def create_plan(type, amount) + id = create_plan_id(type) + nickname = id.gsub(/_/, ' ').titleize + + products = ::Stripe::Product.list(type: 'service') + + if products['data'] && products['data'].any? { |p| p['id'] === RECURRING_DONATION_PRODUCT_ID } + product = RECURRING_DONATION_PRODUCT_ID + else + result = create_product + product = result['id'] + end + + ::Stripe::Plan.create( + id: id, + nickname: nickname, + interval: type.tr('ly', ''), + currency: @currency, + product: product, + amount: amount.to_i + ) + end + + def create_product + ::Stripe::Product.create( + id: RECURRING_DONATION_PRODUCT_ID, + name: "Discourse Donation Recurring", + type: 'service' + ) + end + + def create_plan_id(type) + "discourse_donation_recurring_#{type}" + end end end diff --git a/assets/javascripts/discourse/components/stripe-card.js.es6 b/assets/javascripts/discourse/components/stripe-card.js.es6 index ee83b52..add4db7 100644 --- a/assets/javascripts/discourse/components/stripe-card.js.es6 +++ b/assets/javascripts/discourse/components/stripe-card.js.es6 @@ -16,6 +16,39 @@ export default Ember.Component.extend({ this.set('settings', getRegister(this).lookup('site-settings:main')); this.set('create_accounts', this.get('anon') && this.get('settings').discourse_donations_enable_create_accounts); this.set('stripe', Stripe(this.get('settings').discourse_donations_public_key)); + + const types = Discourse.SiteSettings.discourse_donations_types.split('|') || []; + this.set('types', types); + this.set('type', types[0]); + }, + + @computed('types') + donationTypes(types) { + return types.map((type) => { + return { + id: type, + name: I18n.t(`discourse_donations.types.${type}`) + } + }) + }, + + @computed('type') + period(type) { + let anchor; + + if (type === 'weekly') { + anchor = moment().format('dddd'); + } + + if (type === 'monthly') { + anchor = moment().format('Do'); + } + + if (type === 'yearly') { + anchor = moment().format('MMMM D'); + } + + return I18n.t(`discourse_donations.period.${type}`, { anchor }); }, @computed @@ -91,6 +124,7 @@ export default Ember.Component.extend({ const amount = transactionFeeEnabled ? this.get('totalAmount') : this.get('amount'); let params = { stripeToken: data.token.id, + type: self.get('type'), amount: amount * 100, email: self.get('email'), username: self.get('username'), diff --git a/assets/javascripts/discourse/templates/components/stripe-card.hbs b/assets/javascripts/discourse/templates/components/stripe-card.hbs index d3fd676..7b3fc3b 100644 --- a/assets/javascripts/discourse/templates/components/stripe-card.hbs +++ b/assets/javascripts/discourse/templates/components/stripe-card.hbs @@ -1,5 +1,14 @@
+
+ +
+ {{combo-box content=donationTypes value=type}} +
+
+