Add new plugin files.
This gets the plugin running again without name collisions. A solid starting point
This commit is contained in:
parent
89af44ff78
commit
78914e0511
59
README.md
59
README.md
|
@ -1,59 +0,0 @@
|
|||
# Discourse Donations
|
||||
|
||||
[![Build Status](https://travis-ci.org/rimian/discourse-donations.svg?branch=master)](https://travis-ci.org/rimian/discourse-donations)
|
||||
|
||||
Accept donations from visitors to your [Discourse](https://www.discourse.org/) application. Integrates with [Stripe](https://stripe.com).
|
||||
|
||||
## Installation
|
||||
|
||||
* 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
|
||||
|
||||
1. Click **Donate**:
|
||||
![Menu Link](doc/menulink.png)
|
||||
|
||||
1. Enter card details:
|
||||
![Enter card details](doc/pre-payment.png)
|
||||
|
||||
1. Click **Make Payment**:
|
||||
![Enter card details](doc/post-payment.png)
|
||||
|
||||
## Creating new user accounts
|
||||
|
||||
**This is an experimental feature.** A user can create a new account if they makes a successful donation. Enable this in settings. When a user is not logged in, they will be asked to enter details for a new user account. This feature doesn't support mandatory custom user fields yet.
|
||||
|
||||
## Testing
|
||||
|
||||
These commands should run:
|
||||
|
||||
* ```yarn prettier --list-different 'assets/**/*.scss' '**/*.es6'```
|
||||
|
||||
## Tested Credit Card Numbers
|
||||
|
||||
These numbers can be used in test mode to simulate a transaction. For more information see the [Stripe docs](https://stripe.com/docs/testing).
|
||||
|
||||
Card numbers in **bold** have been tested.
|
||||
|
||||
* **4000 0000 0000 0077** Charge succeeds and funds will be added directly to your available balance (bypassing your pending balance).
|
||||
* **4000 0000 0000 0093** Charge succeeds and domestic pricing is used (other test cards use international pricing). This card is only significant in countries with split pricing.
|
||||
* **4000 0000 0000 0010** The address_line1_check and address_zip_check verifications fail. If your account is blocking payments that fail postal code validation, the charge is declined.
|
||||
* **4000 0000 0000 0028** Charge succeeds but the address_line1_check verification fails.
|
||||
* **4000 0000 0000 0036** The address_zip_check verification fails. If your account is blocking payments that fail postal code validation, the charge is declined.
|
||||
* **4000 0000 0000 0044** Charge succeeds but the address_zip_check and address_line1_check verifications are both unavailable.
|
||||
* **4000 0000 0000 0101** If a CVC number is provided, the cvc_check fails. If your account is blocking payments that fail CVC code validation, the charge is declined.
|
||||
* **4000 0000 0000 0341** Attaching this card to a Customer object succeeds, but attempts to charge the customer fail.
|
||||
* **4000 0000 0000 9235** Charge succeeds with a risk_level of elevated and placed into review.
|
||||
* **4000 0000 0000 0002** Charge is declined with a card_declined code.
|
||||
* **4100 0000 0000 0019** Charge is declined with a card_declined code and a fraudulent reason.
|
||||
* **4000 0000 0000 0127** Charge is declined with an incorrect_cvc code.
|
||||
* **4000 0000 0000 0069** Charge is declined with an expired_card code.
|
||||
* **4000 0000 0000 0119** Charge is declined with a processing_error code.
|
||||
* **4242 4242 4242 4241** Charge is declined with an incorrect_number code as the card number fails the Luhn check.
|
||||
|
||||
## Warranty
|
||||
|
||||
This software comes with no warranty of any kind.
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
load File.expand_path('../discourse_donations/charges_controller.rb', __FILE__)
|
||||
load File.expand_path('../discourse_donations/checkout_controller.rb', __FILE__)
|
|
@ -1,190 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseDonations
|
||||
class ChargesController < ::ApplicationController
|
||||
skip_before_action :verify_authenticity_token, only: [:create]
|
||||
|
||||
before_action :ensure_logged_in, only: [:cancel_subscription]
|
||||
before_action :set_user, only: [:index, :create]
|
||||
before_action :set_email, only: [:index, :create, :cancel_subscription]
|
||||
|
||||
def index
|
||||
result = {}
|
||||
|
||||
if current_user
|
||||
stripe = DiscourseDonations::Stripe.new(secret_key, stripe_options)
|
||||
|
||||
list_result = stripe.list(current_user, email: current_user.email)
|
||||
|
||||
result = list_result if list_result.present?
|
||||
end
|
||||
|
||||
render json: success_json.merge(result)
|
||||
end
|
||||
|
||||
def create
|
||||
Rails.logger.info user_params.inspect
|
||||
|
||||
output = { 'messages' => [], 'rewards' => [] }
|
||||
|
||||
if create_account
|
||||
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
|
||||
output['messages'] << I18n.t('login.password_too_long')
|
||||
end
|
||||
if user_params[:username] && ::User.reserved_username?(user_params[:username])
|
||||
output['messages'] << I18n.t('login.reserved_username')
|
||||
end
|
||||
end
|
||||
|
||||
if output['messages'].present?
|
||||
render(json: output.merge(success: false)) && (return)
|
||||
end
|
||||
|
||||
Rails.logger.debug "Creating a Stripe payment"
|
||||
stripe = DiscourseDonations::Stripe.new(secret_key, stripe_options)
|
||||
result = {}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
if user_params[:type] === 'once'
|
||||
result[:charge] = stripe.charge(@user, opts)
|
||||
else
|
||||
opts[:type] = user_params[:type]
|
||||
|
||||
subscription = stripe.subscribe(@user, opts)
|
||||
|
||||
if subscription && subscription['id']
|
||||
invoices = stripe.invoices_for_subscription(@user,
|
||||
email: opts[:email],
|
||||
subscription_id: subscription['id']
|
||||
)
|
||||
end
|
||||
|
||||
result[:subscription] = {}
|
||||
result[:subscription][:subscription] = subscription if subscription
|
||||
result[:subscription][:invoices] = invoices if invoices
|
||||
end
|
||||
|
||||
rescue ::Stripe::CardError => e
|
||||
err = e.json_body[:error]
|
||||
|
||||
output['messages'] << "There was an error (#{err[:type]})."
|
||||
output['messages'] << "Error code: #{err[:code]}" if err[:code]
|
||||
output['messages'] << "Decline code: #{err[:decline_code]}" if err[:decline_code]
|
||||
output['messages'] << "Message: #{err[:message]}" if err[:message]
|
||||
|
||||
render(json: output) && (return)
|
||||
end
|
||||
|
||||
if (result[:charge] && result[:charge]['paid'] == true) ||
|
||||
(result[:subscription] && result[:subscription][:subscription] &&
|
||||
result[:subscription][:subscription]['status'] === 'active')
|
||||
|
||||
output['messages'] << I18n.t('donations.payment.success')
|
||||
|
||||
if (result[:charge] && result[:charge]['receipt_number']) ||
|
||||
(result[:subscription] && result[:subscription][:invoices].first['receipt_number'])
|
||||
output['messages'] << " #{I18n.t('donations.payment.receipt_sent', email: @email)}"
|
||||
end
|
||||
|
||||
output['charge'] = result[:charge] if result[:charge]
|
||||
output['subscription'] = result[:subscription] if result[:subscription]
|
||||
|
||||
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?
|
||||
args = user_params.to_h.slice(:email, :username, :password, :name).merge(rewards: output['rewards'])
|
||||
Jobs.enqueue(:donation_user, args)
|
||||
end
|
||||
|
||||
if SiteSetting.discourse_donations_cause_category
|
||||
Jobs.enqueue(:update_category_donation_statistics)
|
||||
end
|
||||
end
|
||||
|
||||
render json: output
|
||||
end
|
||||
|
||||
def cancel_subscription
|
||||
params.require(:subscription_id)
|
||||
|
||||
stripe = DiscourseDonations::Stripe.new(secret_key, stripe_options)
|
||||
|
||||
result = stripe.cancel_subscription(params[:subscription_id])
|
||||
|
||||
if result[:success]
|
||||
render json: success_json.merge(subscription: result[:subscription])
|
||||
else
|
||||
render json: failed_json.merge(message: result[:message])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_account
|
||||
user_params[:create_account] == 'true' && SiteSetting.discourse_donations_enable_create_accounts
|
||||
end
|
||||
|
||||
def reward?(payment)
|
||||
payment.present? && payment.successful?
|
||||
end
|
||||
|
||||
def group_name
|
||||
SiteSetting.discourse_donations_reward_group_name
|
||||
end
|
||||
|
||||
def badge_name
|
||||
SiteSetting.discourse_donations_reward_badge_name
|
||||
end
|
||||
|
||||
def secret_key
|
||||
SiteSetting.discourse_donations_secret_key
|
||||
end
|
||||
|
||||
def stripe_options
|
||||
{
|
||||
description: SiteSetting.discourse_donations_description,
|
||||
currency: SiteSetting.discourse_donations_currency
|
||||
}
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.permit(:user_id, :name, :username, :email, :password, :stripeToken, :cause, :type, :amount, :create_account)
|
||||
end
|
||||
|
||||
def set_user
|
||||
user = current_user
|
||||
|
||||
if user_params[:user_id].present?
|
||||
if record = User.find_by(user_params[:user_id])
|
||||
user = record
|
||||
end
|
||||
end
|
||||
|
||||
@user = user
|
||||
end
|
||||
|
||||
def set_email
|
||||
email = nil
|
||||
|
||||
if user_params[:email].present?
|
||||
email = user_params[:email]
|
||||
elsif @user
|
||||
email = @user.try(:email)
|
||||
end
|
||||
|
||||
@email = email
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,89 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_dependency 'discourse'
|
||||
|
||||
module DiscourseDonations
|
||||
class CheckoutController < ApplicationController
|
||||
|
||||
skip_before_action :verify_authenticity_token, only: [:create]
|
||||
|
||||
def create
|
||||
Rails.logger.debug params.inspect
|
||||
Rails.logger.debug user_params.inspect
|
||||
|
||||
output = { 'messages' => [], 'rewards' => [] }
|
||||
payment = DiscourseDonations::Stripe.new(secret_key, stripe_options)
|
||||
user = current_user || nil
|
||||
|
||||
begin
|
||||
charge = payment.checkoutCharge(user, user_params[:stripeEmail], user_params[:stripeToken], user_params[:amount])
|
||||
rescue ::Stripe::CardError => e
|
||||
err = e.json_body[:error]
|
||||
|
||||
output['messages'] << "There was an error (#{err[:type]})."
|
||||
output['messages'] << "Error code: #{err[:code]}" if err[:code]
|
||||
output['messages'] << "Decline code: #{err[:decline_code]}" if err[:decline_code]
|
||||
output['messages'] << "Message: #{err[:message]}" if err[:message]
|
||||
|
||||
render(json: output) && (return)
|
||||
end
|
||||
|
||||
if charge['paid']
|
||||
output['messages'] << I18n.l(Time.now(), format: :long) + ': ' + I18n.t('donations.payment.success')
|
||||
output['rewards'] << { type: :group, name: group_name } if group_name
|
||||
output['rewards'] << { type: :badge, name: badge_name } if badge_name
|
||||
end
|
||||
|
||||
render json: output
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reward?(payment)
|
||||
payment.present? && payment.successful?
|
||||
end
|
||||
|
||||
def group_name
|
||||
SiteSetting.discourse_donations_reward_group_name
|
||||
end
|
||||
|
||||
def badge_name
|
||||
SiteSetting.discourse_donations_reward_badge_name
|
||||
end
|
||||
|
||||
def secret_key
|
||||
SiteSetting.discourse_donations_secret_key
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.permit(:amount,
|
||||
:email,
|
||||
:stripeToken,
|
||||
:stripeTokenType,
|
||||
:stripeEmail,
|
||||
:stripeCustomerId,
|
||||
:stripeBillingName,
|
||||
:stripeBillingAddressLine1,
|
||||
:stripeBillingAddressZip,
|
||||
:stripeBillingAddressState,
|
||||
:stripeBillingAddressCity,
|
||||
:stripeBillingAddressCountry,
|
||||
:stripeBillingAddressCountryCode,
|
||||
:stripeShippingName,
|
||||
:stripeShippingAddressLine1,
|
||||
:stripeShippingAddressZip,
|
||||
:stripeShippingAddressState,
|
||||
:stripeShippingAddressCity,
|
||||
:stripeShippingAddressCountry,
|
||||
:stripeShippingAddressCountryCode
|
||||
)
|
||||
end
|
||||
|
||||
def stripe_options
|
||||
{
|
||||
description: SiteSetting.discourse_donations_description,
|
||||
currency: SiteSetting.discourse_donations_currency
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
load File.expand_path('../regular/donation_user.rb', __FILE__)
|
||||
load File.expand_path('../scheduled/update_category_donation_statistics.rb', __FILE__)
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class DonationUser < ::Jobs::Base
|
||||
def execute(args)
|
||||
user = User.create!(args.slice(:username, :password, :name, :email))
|
||||
return unless user.persisted?
|
||||
Jobs.enqueue(
|
||||
:critical_user_email,
|
||||
type: :signup, user_id: user.id, email_token: user.email_tokens.first.token
|
||||
)
|
||||
rewards = DiscourseDonations::Rewards.new(user)
|
||||
args[:rewards].to_a.each do |reward|
|
||||
rewards.grant_badge(reward[:name]) if reward[:type] == 'badge'
|
||||
rewards.add_to_group(reward[:name]) if reward[:type] == 'group'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class UpdateCategoryDonationStatistics < ::Jobs::Scheduled
|
||||
every 1.day
|
||||
|
||||
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
|
|
@ -1,32 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseDonations
|
||||
class Rewards
|
||||
attr_reader :user
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def add_to_group(name)
|
||||
grp = ::Group.find_by_name(name)
|
||||
return if grp.nil?
|
||||
log_group_add(grp)
|
||||
grp.add(user)
|
||||
end
|
||||
|
||||
def grant_badge(name)
|
||||
return unless SiteSetting.enable_badges
|
||||
badge = ::Badge.find_by_name(name)
|
||||
return if badge.nil?
|
||||
BadgeGranter.grant(badge, user)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_group_add(grp)
|
||||
system_user = User.find(-1)
|
||||
GroupActionLogger.new(system_user, grp).log_add_user_to_group(user)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,273 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseDonations
|
||||
class Stripe
|
||||
attr_reader :charge, :currency, :description
|
||||
|
||||
def initialize(secret_key, opts)
|
||||
::Stripe.api_key = secret_key
|
||||
@description = opts[:description]
|
||||
@currency = opts[:currency]
|
||||
end
|
||||
|
||||
def checkoutCharge(user = nil, email, token, amount)
|
||||
customer = customer(user,
|
||||
email: email,
|
||||
source: token,
|
||||
create: true
|
||||
)
|
||||
|
||||
return if !customer
|
||||
|
||||
charge = ::Stripe::Charge.create(
|
||||
customer: customer.id,
|
||||
amount: amount,
|
||||
description: @description,
|
||||
currency: @currency
|
||||
)
|
||||
|
||||
charge
|
||||
end
|
||||
|
||||
def charge(user = nil, opts)
|
||||
customer = customer(user,
|
||||
email: opts[:email],
|
||||
source: opts[:token],
|
||||
create: true
|
||||
)
|
||||
|
||||
return if !customer
|
||||
|
||||
metadata = {
|
||||
discourse_cause: opts[:cause]
|
||||
}
|
||||
|
||||
if (user)
|
||||
metadata[:discourse_user_id] = user.id
|
||||
end
|
||||
|
||||
@charge = ::Stripe::Charge.create(
|
||||
customer: customer.id,
|
||||
amount: opts[:amount],
|
||||
description: @description,
|
||||
currency: @currency,
|
||||
receipt_email: customer.email,
|
||||
metadata: metadata
|
||||
)
|
||||
|
||||
@charge
|
||||
end
|
||||
|
||||
def subscribe(user = nil, opts)
|
||||
customer = customer(user,
|
||||
email: opts[:email],
|
||||
source: opts[:token],
|
||||
create: true
|
||||
)
|
||||
|
||||
return if !customer
|
||||
|
||||
type = opts[:type]
|
||||
amount = opts[:amount]
|
||||
|
||||
plans = ::Stripe::Plan.list
|
||||
plan_id = create_plan_id(type, amount)
|
||||
|
||||
unless plans.data && plans.data.any? { |p| p['id'] === plan_id }
|
||||
result = create_plan(type, amount)
|
||||
|
||||
plan_id = result['id']
|
||||
end
|
||||
|
||||
::Stripe::Subscription.create(
|
||||
customer: customer.id,
|
||||
items: [{
|
||||
plan: plan_id
|
||||
}],
|
||||
metadata: {
|
||||
discourse_cause: opts[:cause],
|
||||
discourse_user_id: user.id
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def list(user, opts = {})
|
||||
customer = customer(user, opts)
|
||||
|
||||
return if !customer
|
||||
|
||||
result = { customer: customer }
|
||||
|
||||
raw_invoices = ::Stripe::Invoice.list(customer: customer.id)
|
||||
raw_invoices = raw_invoices.is_a?(Object) ? raw_invoices['data'] : []
|
||||
|
||||
raw_charges = ::Stripe::Charge.list(customer: customer.id)
|
||||
raw_charges = raw_charges.is_a?(Object) ? raw_charges['data'] : []
|
||||
|
||||
if raw_invoices.any?
|
||||
raw_subscriptions = ::Stripe::Subscription.list(customer: customer.id, status: 'all')
|
||||
raw_subscriptions = raw_subscriptions.is_a?(Object) ? raw_subscriptions['data'] : []
|
||||
|
||||
if raw_subscriptions.any?
|
||||
subscriptions = []
|
||||
|
||||
raw_subscriptions.each do |subscription|
|
||||
invoices = raw_invoices.select do |invoice|
|
||||
invoice['subscription'] === subscription['id']
|
||||
end
|
||||
|
||||
subscriptions.push(
|
||||
subscription: subscription,
|
||||
invoices: invoices
|
||||
)
|
||||
end
|
||||
|
||||
result[:subscriptions] = subscriptions
|
||||
end
|
||||
|
||||
## filter out any charges related to subscriptions
|
||||
raw_invoice_ids = raw_invoices.map { |i| i['id'] }
|
||||
raw_charges = raw_charges.select { |c| raw_invoice_ids.exclude?(c['invoice']) }
|
||||
end
|
||||
|
||||
if raw_charges.any?
|
||||
result[:charges] = raw_charges
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def invoices_for_subscription(user, opts)
|
||||
customer = customer(user,
|
||||
email: opts[:email]
|
||||
)
|
||||
|
||||
invoices = []
|
||||
|
||||
if customer
|
||||
result = ::Stripe::Invoice.list(
|
||||
customer: customer.id,
|
||||
subscription: opts[:subscription_id]
|
||||
)
|
||||
|
||||
invoices = result['data'] if result['data']
|
||||
end
|
||||
|
||||
invoices
|
||||
end
|
||||
|
||||
def cancel_subscription(subscription_id)
|
||||
if subscription = ::Stripe::Subscription.retrieve(subscription_id)
|
||||
result = subscription.delete
|
||||
|
||||
if result['status'] === 'canceled'
|
||||
{ success: true, subscription: subscription }
|
||||
else
|
||||
{ success: false, message: I18n.t('donations.subscription.error.not_cancelled') }
|
||||
end
|
||||
else
|
||||
{ success: false, message: I18n.t('donations.subscription.error.not_found') }
|
||||
end
|
||||
end
|
||||
|
||||
def customer(user, opts = {})
|
||||
customer = nil
|
||||
|
||||
if user && user.stripe_customer_id
|
||||
begin
|
||||
customer = ::Stripe::Customer.retrieve(user.stripe_customer_id)
|
||||
rescue ::Stripe::StripeError => e
|
||||
user.custom_fields['stripe_customer_id'] = nil
|
||||
user.save_custom_fields(true)
|
||||
customer = nil
|
||||
end
|
||||
end
|
||||
|
||||
if !customer && opts[:email]
|
||||
begin
|
||||
customers = ::Stripe::Customer.list(email: opts[:email])
|
||||
|
||||
if customers && customers['data']
|
||||
customer = customers['data'].first if customers['data'].any?
|
||||
end
|
||||
|
||||
if customer && user
|
||||
user.custom_fields['stripe_customer_id'] = customer.id
|
||||
user.save_custom_fields(true)
|
||||
end
|
||||
rescue ::Stripe::StripeError => e
|
||||
customer = nil
|
||||
end
|
||||
end
|
||||
|
||||
if !customer && opts[: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
|
||||
user.save_custom_fields(true)
|
||||
end
|
||||
end
|
||||
|
||||
customer
|
||||
end
|
||||
|
||||
def successful?
|
||||
@charge[:paid]
|
||||
end
|
||||
|
||||
def create_plan(type, amount)
|
||||
id = create_plan_id(type, amount)
|
||||
nickname = id.gsub(/_/, ' ').titleize
|
||||
|
||||
products = ::Stripe::Product.list(type: 'service')
|
||||
|
||||
if products['data'] && products['data'].any? { |p| p['id'] === product_id }
|
||||
product = product_id
|
||||
else
|
||||
result = create_product
|
||||
product = result['id']
|
||||
end
|
||||
|
||||
::Stripe::Plan.create(
|
||||
id: id,
|
||||
nickname: nickname,
|
||||
interval: type,
|
||||
currency: @currency,
|
||||
product: product,
|
||||
amount: amount.to_i
|
||||
)
|
||||
end
|
||||
|
||||
def create_product
|
||||
::Stripe::Product.create(
|
||||
id: product_id,
|
||||
name: product_name,
|
||||
type: 'service'
|
||||
)
|
||||
end
|
||||
|
||||
def product_id
|
||||
@product_id ||= "#{SiteSetting.title}_recurring_donation".freeze
|
||||
end
|
||||
|
||||
def product_name
|
||||
@product_name ||= I18n.t('donations.recurring', site_title: SiteSetting.title)
|
||||
end
|
||||
|
||||
def create_plan_id(type, amount)
|
||||
"discourse_donation_recurring_#{type}_#{amount}".freeze
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
load File.expand_path('../discourse_donations/rewards.rb', __FILE__)
|
||||
load File.expand_path('../discourse_donations/stripe.rb', __FILE__)
|
|
@ -1 +0,0 @@
|
|||
export default Ember.Component.extend({});
|
|
@ -1,5 +0,0 @@
|
|||
export default Ember.Component.extend({
|
||||
classNames: "donation-list",
|
||||
hasSubscriptions: Ember.computed.notEmpty("subscriptions"),
|
||||
hasCharges: Ember.computed.notEmpty("charges")
|
||||
});
|
|
@ -1,99 +0,0 @@
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { formatAnchor, formatAmount } from "../lib/donation-utilities";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNameBindings: [":donation-row", "canceled", "updating"],
|
||||
includePrefix: Ember.computed.or("invoice", "charge"),
|
||||
canceled: Ember.computed.equal("subscription.status", "canceled"),
|
||||
|
||||
@computed("subscription", "invoice", "charge", "customer")
|
||||
data(subscription, invoice, charge, customer) {
|
||||
if (subscription) {
|
||||
return $.extend({}, subscription.plan, {
|
||||
anchor: subscription.billing_cycle_anchor
|
||||
});
|
||||
} else if (invoice) {
|
||||
let receiptSent = false;
|
||||
|
||||
if (invoice.receipt_number && customer.email) {
|
||||
receiptSent = true;
|
||||
}
|
||||
|
||||
return $.extend({}, invoice.lines.data[0], {
|
||||
anchor: invoice.date,
|
||||
invoiceLink: invoice.invoice_pdf,
|
||||
receiptSent
|
||||
});
|
||||
} else if (charge) {
|
||||
let receiptSent = false;
|
||||
|
||||
if (charge.receipt_number && charge.receipt_email) {
|
||||
receiptSent = true;
|
||||
}
|
||||
|
||||
return $.extend({}, charge, {
|
||||
anchor: charge.created,
|
||||
receiptSent
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@computed("data.currency")
|
||||
currency(currency) {
|
||||
return currency ? currency.toUpperCase() : null;
|
||||
},
|
||||
|
||||
@computed("data.amount", "currency")
|
||||
amount(amount, currency) {
|
||||
return formatAmount(amount, currency);
|
||||
},
|
||||
|
||||
@computed("data.interval")
|
||||
interval(interval) {
|
||||
return interval || "once";
|
||||
},
|
||||
|
||||
@computed("data.anchor", "interval")
|
||||
period(anchor, interval) {
|
||||
return I18n.t(`discourse_donations.period.${interval}`, {
|
||||
anchor: formatAnchor(interval, moment.unix(anchor))
|
||||
});
|
||||
},
|
||||
|
||||
cancelSubscription() {
|
||||
const subscriptionId = this.get("subscription.id");
|
||||
this.set("updating", true);
|
||||
|
||||
ajax("/donate/charges/cancel-subscription", {
|
||||
data: {
|
||||
subscription_id: subscriptionId
|
||||
},
|
||||
method: "put"
|
||||
})
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
this.set("subscription", result.subscription);
|
||||
}
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
this.set("updating", false);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
cancelSubscription() {
|
||||
showModal("cancel-subscription", {
|
||||
model: {
|
||||
currency: this.get("currency"),
|
||||
amount: this.get("amount"),
|
||||
period: this.get("period"),
|
||||
confirm: () => this.cancelSubscription()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,286 +0,0 @@
|
|||
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 as emailValidHelper } from "discourse/lib/utilities";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
result: [],
|
||||
stripe: null,
|
||||
transactionInProgress: null,
|
||||
settings: null,
|
||||
showTransactionFeeDescription: false,
|
||||
includeTransactionFee: true,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
const user = this.get("currentUser");
|
||||
const settings = Discourse.SiteSettings;
|
||||
|
||||
this.setProperties({
|
||||
create_accounts:
|
||||
!user && settings.discourse_donations_enable_create_accounts,
|
||||
stripe: Stripe(settings.discourse_donations_public_key),
|
||||
color: jQuery("body").css("color")
|
||||
});
|
||||
|
||||
const types = settings.discourse_donations_types.split("|") || [];
|
||||
const amounts = this.get("donateAmounts");
|
||||
|
||||
this.setProperties({
|
||||
types,
|
||||
type: types[0],
|
||||
amount: amounts[0].value
|
||||
});
|
||||
},
|
||||
|
||||
@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 => {
|
||||
return {
|
||||
id: type,
|
||||
name: I18n.t(`discourse_donations.types.${type}`)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@computed("type")
|
||||
period(type) {
|
||||
return I18n.t(`discourse_donations.period.${type}`, {
|
||||
anchor: formatAnchor(type)
|
||||
});
|
||||
},
|
||||
|
||||
@computed
|
||||
donateAmounts() {
|
||||
const setting = Discourse.SiteSettings.discourse_donations_amounts.split(
|
||||
"|"
|
||||
);
|
||||
if (setting.length) {
|
||||
return setting.map(amount => {
|
||||
return {
|
||||
value: parseInt(amount, 10),
|
||||
name: `${amount}.00`
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
@computed("stripe")
|
||||
card(stripe) {
|
||||
const color = this.get("color");
|
||||
const hidePostalCode = !Discourse.SiteSettings.discourse_donations_zip_code;
|
||||
const elements = stripe.elements();
|
||||
|
||||
const style = {
|
||||
base: {
|
||||
color,
|
||||
iconColor: color,
|
||||
"::placeholder": { color }
|
||||
}
|
||||
};
|
||||
|
||||
const card = elements.create("card", { style, hidePostalCode });
|
||||
|
||||
card.addEventListener("change", event => {
|
||||
if (event.error) {
|
||||
this.set("stripeError", event.error.message);
|
||||
} else {
|
||||
this.set("stripeError", "");
|
||||
}
|
||||
|
||||
if (event.elementType === "card" && event.complete) {
|
||||
this.set("stripeReady", true);
|
||||
}
|
||||
});
|
||||
|
||||
return card;
|
||||
},
|
||||
|
||||
@computed("amount")
|
||||
transactionFee(amount) {
|
||||
const fixed =
|
||||
Discourse.SiteSettings.discourse_donations_transaction_fee_fixed;
|
||||
const percent =
|
||||
Discourse.SiteSettings.discourse_donations_transaction_fee_percent;
|
||||
const fee = (amount + fixed) / (1 - percent) - amount;
|
||||
return Math.round(fee * 100) / 100;
|
||||
},
|
||||
|
||||
@computed("amount", "transactionFee", "includeTransactionFee")
|
||||
totalAmount(amount, fee, include) {
|
||||
if (include) return amount + fee;
|
||||
return amount;
|
||||
},
|
||||
|
||||
@computed("email")
|
||||
emailValid(email) {
|
||||
return emailValidHelper(email);
|
||||
},
|
||||
|
||||
@computed("email", "emailValid")
|
||||
showEmailError(email, emailValid) {
|
||||
return email && email.length > 3 && !emailValid;
|
||||
},
|
||||
|
||||
@computed("currentUser", "emailValid")
|
||||
userReady(currentUser, emailValid) {
|
||||
return currentUser || emailValid;
|
||||
},
|
||||
|
||||
@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")
|
||||
disableSubmit(transactionInProgress, formIncomplete) {
|
||||
return transactionInProgress || formIncomplete;
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
this.get("card").mount("#card-element");
|
||||
jQuery(document).on("click", Ember.run.bind(this, this.documentClick));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
jQuery(document).off("click", Ember.run.bind(this, this.documentClick));
|
||||
},
|
||||
|
||||
documentClick(e) {
|
||||
let $element = jQuery(".transaction-fee-description");
|
||||
let $target = jQuery(e.target);
|
||||
if ($target.closest($element).length < 1 && this._state !== "destroying") {
|
||||
this.set("showTransactionFeeDescription", false);
|
||||
}
|
||||
},
|
||||
|
||||
setSuccess() {
|
||||
this.set("paymentSuccess", true);
|
||||
},
|
||||
|
||||
endTranscation() {
|
||||
this.set("transactionInProgress", false);
|
||||
},
|
||||
|
||||
concatMessages(messages) {
|
||||
this.set("result", this.get("result").concat(messages));
|
||||
},
|
||||
|
||||
actions: {
|
||||
toggleTransactionFeeDescription() {
|
||||
this.toggleProperty("showTransactionFeeDescription");
|
||||
},
|
||||
|
||||
submitStripeCard() {
|
||||
let self = this;
|
||||
this.set("transactionInProgress", true);
|
||||
|
||||
this.get("stripe")
|
||||
.createToken(this.get("card"))
|
||||
.then(data => {
|
||||
self.set("result", []);
|
||||
|
||||
if (data.error) {
|
||||
this.setProperties({
|
||||
stripeError: data.error.message,
|
||||
stripeReady: false
|
||||
});
|
||||
self.endTranscation();
|
||||
} else {
|
||||
const settings = Discourse.SiteSettings;
|
||||
|
||||
const transactionFeeEnabled =
|
||||
settings.discourse_donations_enable_transaction_fee;
|
||||
let amount = transactionFeeEnabled
|
||||
? this.get("totalAmount")
|
||||
: this.get("amount");
|
||||
|
||||
if (
|
||||
zeroDecimalCurrencies.indexOf(
|
||||
settings.discourse_donations_currency
|
||||
) === -1
|
||||
) {
|
||||
amount = amount * 100;
|
||||
}
|
||||
|
||||
let params = {
|
||||
stripeToken: data.token.id,
|
||||
cause: self.get("cause"),
|
||||
type: self.get("type"),
|
||||
amount,
|
||||
email: self.get("email"),
|
||||
username: self.get("username"),
|
||||
create_account: self.get("create_accounts")
|
||||
};
|
||||
|
||||
if (!self.get("paymentSuccess")) {
|
||||
ajax("/donate/charges", {
|
||||
data: params,
|
||||
method: "post"
|
||||
}).then(result => {
|
||||
if (result.subscription) {
|
||||
let subscription = $.extend({}, result.subscription, {
|
||||
new: true
|
||||
});
|
||||
this.get("subscriptions").unshiftObject(subscription);
|
||||
}
|
||||
|
||||
if (result.charge) {
|
||||
let charge = $.extend({}, result.charge, {
|
||||
new: true
|
||||
});
|
||||
this.get("charges").unshiftObject(charge);
|
||||
}
|
||||
|
||||
self.concatMessages(result.messages);
|
||||
|
||||
self.endTranscation();
|
||||
self.onCompleteTransation();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
{{#if siteSettings.discourse_donations_cause_category}}
|
||||
{{mount-widget widget="category-header-widget" args=(hash currentPath=currentPath)}}
|
||||
{{/if}}
|
|
@ -1,36 +0,0 @@
|
|||
{{#if siteSettings.discourse_donations_cause_category}}
|
||||
<section class='field'>
|
||||
{{input type="checkbox" checked=category.custom_fields.donations_show_amounts}}
|
||||
<span>{{i18n 'discourse_donations.cause.amounts.setting_label'}}</span>
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'discourse_donations.cause.github.setting_label'}}</label>
|
||||
{{text-field value=category.custom_fields.donations_github placeholderKey="discourse_donations.cause.github.setting_placeholder"}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'discourse_donations.cause.meta.setting_label'}}</label>
|
||||
{{text-field value=category.custom_fields.donations_meta placeholderKey="discourse_donations.cause.meta.setting_placeholder"}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'discourse_donations.cause.maintainers.label'}}</label>
|
||||
{{user-selector usernames=category.custom_fields.donations_maintainers}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'discourse_donations.cause.maintainers.setting_label'}}</label>
|
||||
{{input value=category.custom_fields.donations_maintainers_label}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'discourse_donations.cause.release_latest.label'}}</label>
|
||||
{{input value=category.custom_fields.donations_release_latest}}
|
||||
</section>
|
||||
|
||||
<section class='field'>
|
||||
<label>{{i18n 'discourse_donations.cause.release_oldest.label'}}</label>
|
||||
{{input value=category.custom_fields.donations_release_oldest}}
|
||||
</section>
|
||||
{{/if}}
|
|
@ -1,5 +0,0 @@
|
|||
{{#if siteSettings.discourse_donations_enabled}}
|
||||
<a href="/donate">
|
||||
{{i18n 'discourse_donations.nav_item'}}
|
||||
</a>
|
||||
{{/if}}
|
|
@ -1,12 +0,0 @@
|
|||
export default Ember.Controller.extend({
|
||||
actions: {
|
||||
confirm() {
|
||||
this.get("model.confirm")();
|
||||
this.send("closeModal");
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.send("closeModal");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,61 +0,0 @@
|
|||
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,
|
||||
loadDonationsDisabled: Ember.computed.not("emailVaild"),
|
||||
|
||||
@computed("charges.[]", "subscriptions.[]")
|
||||
hasDonations(charges, subscriptions) {
|
||||
return (
|
||||
(charges && charges.length > 0) ||
|
||||
(subscriptions && subscriptions.length > 0)
|
||||
);
|
||||
},
|
||||
|
||||
@computed("email")
|
||||
emailVaild(email) {
|
||||
return emailValid(email);
|
||||
},
|
||||
|
||||
actions: {
|
||||
stripeTransationCompleteCtr() {},
|
||||
|
||||
loadDonations() {
|
||||
let email = this.get("email");
|
||||
|
||||
this.set("loadingDonations", true);
|
||||
|
||||
ajax("/donate/charges", {
|
||||
data: { email },
|
||||
type: "GET"
|
||||
})
|
||||
.then(result => {
|
||||
this.setProperties({
|
||||
charges: Ember.A(result.charges),
|
||||
subscriptions: Ember.A(result.subscriptions),
|
||||
customer: result.customer
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
this.setProperties({
|
||||
loadingDonations: false,
|
||||
hasEmailResult: true
|
||||
});
|
||||
|
||||
Ember.run.later(() => {
|
||||
this.set("hasEmailResult", false);
|
||||
}, 6000);
|
||||
});
|
||||
},
|
||||
|
||||
showLogin() {
|
||||
const controller = getOwner(this).lookup("route:application");
|
||||
controller.send("showLogin");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
export default function() {
|
||||
const { disabled_plugins = [] } = this.site;
|
||||
|
||||
if (disabled_plugins.indexOf("discourse-patrons") !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.route("patrons", function() {
|
||||
this.route("show", { path: ":payment_id" });
|
||||
});
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export default function() {
|
||||
this.route("donate");
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
export default {
|
||||
name: "donations-edits",
|
||||
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");
|
||||
var s = document.createElement("script");
|
||||
s.src = "https://checkout.stripe.com/checkout.js";
|
||||
s.setAttribute("class", "stripe-button");
|
||||
s.setAttribute(
|
||||
"data-key",
|
||||
siteSettings.discourse_donations_public_key
|
||||
);
|
||||
s.setAttribute("data-amount", $input.attr("amount"));
|
||||
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",
|
||||
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);
|
||||
}
|
||||
},
|
||||
{ id: "discourse-donations" }
|
||||
);
|
||||
|
||||
if (siteSettings.discourse_donations_cause_category) {
|
||||
api.decorateWidget("category-header-widget:after", helper => {
|
||||
helper.widget.appEvents.on("page:changed", () => {
|
||||
helper.widget.scheduleRerender();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
const formatAnchor = function(type = null, time = moment()) {
|
||||
let format;
|
||||
|
||||
switch (type) {
|
||||
case "once":
|
||||
format = "Do MMMM YYYY";
|
||||
break;
|
||||
case "week":
|
||||
format = "dddd";
|
||||
break;
|
||||
case "month":
|
||||
format = "Do";
|
||||
break;
|
||||
case "year":
|
||||
format = "MMMM D";
|
||||
break;
|
||||
default:
|
||||
format = "dddd";
|
||||
}
|
||||
|
||||
return moment(time).format(format);
|
||||
};
|
||||
|
||||
const zeroDecimalCurrencies = [
|
||||
"MGA",
|
||||
"BIF",
|
||||
"CLP",
|
||||
"PYG",
|
||||
"DFJ",
|
||||
"RWF",
|
||||
"GNF",
|
||||
"UGX",
|
||||
"JPY",
|
||||
"VND",
|
||||
"VUV",
|
||||
"XAF",
|
||||
"KMF",
|
||||
"KRW",
|
||||
"XOF",
|
||||
"XPF"
|
||||
];
|
||||
|
||||
const formatAmount = function(amount, currency) {
|
||||
let zeroDecimal = zeroDecimalCurrencies.indexOf(currency) > -1;
|
||||
return zeroDecimal ? amount : (amount / 100).toFixed(2);
|
||||
};
|
||||
|
||||
export { formatAnchor, formatAmount, zeroDecimalCurrencies };
|
|
@ -1,40 +0,0 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
redirect() {
|
||||
if (!Discourse.SiteSettings.discourse_donations_enabled) {
|
||||
DiscourseURL.routeTo("/");
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
let charges = [];
|
||||
let subscriptions = [];
|
||||
let customer = {};
|
||||
|
||||
controller.set("loadingDonations", true);
|
||||
|
||||
ajax("/donate/charges")
|
||||
.then(result => {
|
||||
if (result) {
|
||||
charges = result.charges;
|
||||
subscriptions = result.subscriptions;
|
||||
customer = result.customer;
|
||||
}
|
||||
|
||||
controller.setProperties({
|
||||
charges: Ember.A(charges),
|
||||
subscriptions: Ember.A(subscriptions),
|
||||
customer
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
controller.set("loadingDonations", false);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
<form id="payment-form" class="form-horizontal">
|
||||
<div class="donations-page-payment">
|
||||
{{stripe-card
|
||||
charges=charges
|
||||
subscriptions=subscriptions
|
||||
onCompleteTransation=onCompleteTransation
|
||||
}}
|
||||
</div>
|
||||
</form>
|
|
@ -1,28 +0,0 @@
|
|||
{{#if hasSubscriptions}}
|
||||
<div class="subscription-list">
|
||||
<div class="underline">{{i18n 'discourse_donations.donations.subscriptions'}}</div>
|
||||
<ul>
|
||||
{{#each subscriptions as |s|}}
|
||||
<li>{{donation-row subscription=s.subscription customer=customer new=s.new}}</li>
|
||||
{{#if s.invoices}}
|
||||
<ul>
|
||||
{{#each s.invoices as |invoice|}}
|
||||
<li>{{donation-row invoice=invoice customer=customer new=s.new}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if hasCharges}}
|
||||
<div class="charge-list">
|
||||
<div class='underline'>{{i18n 'discourse_donations.donations.charges'}}</div>
|
||||
<ul>
|
||||
{{#each charges as |charge|}}
|
||||
<li>{{donation-row charge=charge customer=customer new=charge.new}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -1,41 +0,0 @@
|
|||
{{#if includePrefix}}
|
||||
<span>{{i18n 'discourse_donations.invoice_prefix'}}</span>
|
||||
{{/if}}
|
||||
|
||||
<span class="donation-row-currency">{{currency}}</span>
|
||||
|
||||
<span class="donation-row-amount">{{amount}}</span>
|
||||
|
||||
<span class="donation-row-period">{{period}}</span>
|
||||
|
||||
{{#if invoice}}
|
||||
<a href='{{data.invoiceLink}}' target='_blank'>({{i18n 'discourse_donations.invoice'}})</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if currentUser}}
|
||||
{{#if subscription}}
|
||||
<span class="donation-row-subscription">
|
||||
{{#if updating}}
|
||||
{{loading-spinner size='small'}}
|
||||
{{else}}
|
||||
{{#unless canceled}}
|
||||
<a {{action 'cancelSubscription'}}>
|
||||
{{i18n 'cancel'}}
|
||||
</a>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if receiptSent}}
|
||||
<span>–</span>
|
||||
<span>{{i18n 'discourse_donations.receipt' email=customer.email}}</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if new}}
|
||||
<span class="new-flag">
|
||||
{{d-icon 'circle'}}
|
||||
<span>{{i18n 'new_item'}}</span>
|
||||
</span>
|
||||
{{/if}}
|
|
@ -1,118 +0,0 @@
|
|||
<div class="control-group">
|
||||
<label class="control-label">
|
||||
{{i18n 'discourse_donations.type'}}
|
||||
</label>
|
||||
<div class="controls controls-dropdown">
|
||||
{{combo-box content=donationTypes value=type}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="card-element">
|
||||
{{i18n 'discourse_donations.amount'}}
|
||||
{{siteSettings.discourse_donations_currency}}
|
||||
</label>
|
||||
<div class="controls controls-dropdown">
|
||||
{{combo-box valueAttribute="value" content=donateAmounts value=amount}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if siteSettings.discourse_donations_enable_transaction_fee}}
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
{{input type="checkbox" checked=includeTransactionFee}}
|
||||
<span>{{i18n 'discourse_donations.transaction_fee.label' transactionFee=transactionFee currency=siteSettings.discourse_donations_currency}}</span>
|
||||
<div class='transaction-fee-description' {{action 'toggleTransactionFeeDescription'}}>
|
||||
{{d-icon 'info-circle'}}
|
||||
{{#if showTransactionFeeDescription}}
|
||||
<div class="transaction-fee-description-modal">
|
||||
{{i18n 'discourse_donations.transaction_fee.description'}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class='control-label'>
|
||||
{{i18n 'discourse_donations.transaction_fee.total'}}
|
||||
</label>
|
||||
<div class="controls">
|
||||
{{siteSettings.discourse_donations_currency}}
|
||||
{{totalAmount}}
|
||||
{{period}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="control-group" style="width: 550px;">
|
||||
<label class="control-label" for="card-element">{{i18n 'discourse_donations.card'}}</label>
|
||||
<div class="controls">
|
||||
<div id="card-element"></div>
|
||||
{{#if stripeError}}
|
||||
<div class="stripe-error">{{stripeError}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#unless currentUser}}
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="card-element">{{i18n 'user.email.title'}}</label>
|
||||
<div class="controls">
|
||||
{{text-field value=email}}
|
||||
{{#if showEmailError}}
|
||||
<div class="error">{{i18n 'user.email.invalid'}}</div>
|
||||
{{else}}
|
||||
<div class="instructions">{{i18n 'discourse_donations.email_instructions'}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if create_accounts}}
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="card-element">{{i18n 'user.username.title'}}</label>
|
||||
<div class="controls">
|
||||
{{text-field value=username}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="card-element">{{i18n 'user.name.title'}}</label>
|
||||
<div class="controls">
|
||||
{{text-field value=name}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="card-element">{{i18n 'user.password.title'}}</label>
|
||||
<div class="controls">
|
||||
{{input type="password" value=password}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
<div class="control-group save-button">
|
||||
<div class="controls">
|
||||
|
||||
{{#d-button action="submitStripeCard" class="btn btn-primary btn-payment"}}
|
||||
{{#if create_accounts}}
|
||||
{{i18n 'discourse_donations.submit_with_create_account'}}
|
||||
{{else}}
|
||||
{{i18n 'discourse_donations.submit'}}
|
||||
{{/if}}
|
||||
{{/d-button}}
|
||||
|
||||
{{#if transactionInProgress}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{/if}}
|
||||
|
||||
{{#each result as |message|}}
|
||||
<p>{{{message}}}</p>
|
||||
{{/each}}
|
||||
|
||||
{{#if success}}
|
||||
<p>{{i18n 'discourse_donations.messages.success'}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,40 +0,0 @@
|
|||
<h3>{{i18n 'discourse_donations.title' site_name=siteSettings.title}}</h3>
|
||||
|
||||
<div class="donations-page-description">
|
||||
{{cook-text siteSettings.discourse_donations_page_description}}
|
||||
</div>
|
||||
|
||||
<div class="donations-page-payment">
|
||||
{{donation-form
|
||||
charges=charges
|
||||
subscriptions=subscriptions
|
||||
onCompleteTransation=(action "stripeTransationCompleteCtr")
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div class="donations-page-donations">
|
||||
<h3>{{i18n 'discourse_donations.donations.title'}}</h3>
|
||||
{{#if loadingDonations}}
|
||||
<span>{{i18n 'discourse_donations.donations.loading'}}</span>
|
||||
{{loading-spinner size='small'}}
|
||||
{{else}}
|
||||
{{#if currentUser}}
|
||||
{{#if hasDonations}}
|
||||
{{donation-list charges=charges subscriptions=subscriptions customer=customer}}
|
||||
{{else}}
|
||||
{{i18n 'discourse_donations.donations.none'}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if hasDonations}}
|
||||
{{donation-list charges=charges subscriptions=subscriptions customer=customer}}
|
||||
{{else}}
|
||||
{{#if hasEmailResult}}
|
||||
{{i18n 'discourse_donations.donations.none_email' email=email}}
|
||||
{{else}}
|
||||
{{input value=email placeholder=(i18n 'email')}}
|
||||
{{d-button action='loadDonations' label='discourse_donations.donations.load' disabled=loadDonationsDisabled}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
{{#d-modal-body title='discourse_donations.subscription.cancel.title'}}
|
||||
{{i18n 'discourse_donations.subscription.cancel.description' site=siteSettings.title
|
||||
currency=model.currency
|
||||
amount=model.amount
|
||||
period=model.period}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button action='confirm' label='yes_value' class='btn-primary'}}
|
||||
{{d-button action='cancel' label='no_value'}}
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
index
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
show
|
|
@ -1,196 +0,0 @@
|
|||
import { createWidget } from "discourse/widgets/widget";
|
||||
import { h } from "virtual-dom";
|
||||
import { avatarFor } from "discourse/widgets/post";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
|
||||
function donationDisplay(amount, type) {
|
||||
return h(`div.donations-${type}`, [
|
||||
h("span", I18n.t(`discourse_donations.cause.category.${type}`)),
|
||||
h("span", `$${(amount / 100).toFixed(2)}`)
|
||||
]);
|
||||
}
|
||||
|
||||
createWidget("category-header-widget", {
|
||||
tagName: "span",
|
||||
|
||||
html(args) {
|
||||
const controller = this.register.lookup("controller:navigation/category");
|
||||
const category = controller.get("category");
|
||||
|
||||
if (
|
||||
args.currentPath.toLowerCase().indexOf("category") > -1 &&
|
||||
category &&
|
||||
category.donations_cause
|
||||
) {
|
||||
$("body").addClass("donations-category");
|
||||
|
||||
let contents = [
|
||||
h("div.donations-category-contents", [
|
||||
h("h1", category.name),
|
||||
h("div.category-title-description", h("p", category.description_text))
|
||||
])
|
||||
];
|
||||
|
||||
let metadata = [];
|
||||
|
||||
if (category.donations_total !== undefined) {
|
||||
metadata.push(donationDisplay(category.donations_total || 0, "total"));
|
||||
|
||||
if (Discourse.SiteSettings.discourse_donations_cause_month) {
|
||||
metadata.push(
|
||||
donationDisplay(category.donations_month || 0, "month")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (category.donations_github) {
|
||||
metadata.push(
|
||||
h(
|
||||
"div.donations-github",
|
||||
this.attach("link", {
|
||||
icon: "github",
|
||||
label: "discourse_donations.cause.github.label",
|
||||
href: category.donations_github
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (category.donations_meta) {
|
||||
metadata.push(
|
||||
h(
|
||||
"div.donations-meta",
|
||||
this.attach("link", {
|
||||
href: category.donations_meta,
|
||||
contents: () => {
|
||||
return [
|
||||
h("img.meta-icon", {
|
||||
attributes: {
|
||||
src:
|
||||
"https://discourse-meta.s3.dualstack.us-west-1.amazonaws.com/original/3X/b/1/b19ba793155a785bbd9707bc0cabbd3a987fa126.png?v=6"
|
||||
}
|
||||
}),
|
||||
h("span", I18n.t("discourse_donations.cause.meta.label"))
|
||||
];
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (category.donations_release_oldest) {
|
||||
let releaseArray = category.donations_release_oldest.split("/");
|
||||
let label = releaseArray[releaseArray.length - 1];
|
||||
metadata.push(
|
||||
h("div.donations-release-oldest", [
|
||||
h("span", ">="),
|
||||
this.attach("link", {
|
||||
href: category.donations_release_oldest,
|
||||
icon: "tag",
|
||||
rawLabel: label,
|
||||
omitSpan: true,
|
||||
attributes: {
|
||||
target: "_blank"
|
||||
}
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (category.donations_release_latest) {
|
||||
let releaseArray = category.donations_release_latest.split("/");
|
||||
let label = releaseArray[releaseArray.length - 1];
|
||||
metadata.push(
|
||||
h("div.donations-release-latest", [
|
||||
h("span", "<="),
|
||||
this.attach("link", {
|
||||
href: category.donations_release_latest,
|
||||
icon: "tag",
|
||||
rawLabel: label,
|
||||
omitSpan: true,
|
||||
attributes: {
|
||||
target: "_blank"
|
||||
}
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (metadata.length) {
|
||||
contents.push(h("div.donations-category-metadata", metadata));
|
||||
}
|
||||
|
||||
let users = [];
|
||||
|
||||
if (category.donations_backers.length) {
|
||||
users.push(
|
||||
h("div.donations-backers", [
|
||||
h(
|
||||
"div.donations-backers-title",
|
||||
I18n.t("discourse_donations.cause.backers.label")
|
||||
),
|
||||
category.donations_backers.map(user => {
|
||||
if (user) {
|
||||
return avatarFor("medium", {
|
||||
template: user.avatar_template,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
url: userPath(user.username),
|
||||
className: "backer-avatar"
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (category.donations_maintainers.length) {
|
||||
let maintainersLabel =
|
||||
category.donations_maintainers_label ||
|
||||
I18n.t("discourse_donations.cause.maintainers.label");
|
||||
|
||||
users.push(
|
||||
h("div.donations-maintainers", [
|
||||
h("div.donations-maintainers-title", maintainersLabel),
|
||||
category.donations_maintainers.map(user => {
|
||||
if (user) {
|
||||
return avatarFor("medium", {
|
||||
template: user.avatar_template,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
url: userPath(user.username),
|
||||
className: "maintainer-avatar"
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (users.length) {
|
||||
contents.push(h("div.donations-category-users", users));
|
||||
}
|
||||
|
||||
return h(
|
||||
"div.donations-category-header",
|
||||
{
|
||||
attributes: {
|
||||
style:
|
||||
"background-color: #" +
|
||||
category.color +
|
||||
"; color: #" +
|
||||
category.text_color +
|
||||
";"
|
||||
}
|
||||
},
|
||||
contents
|
||||
);
|
||||
} else {
|
||||
$("body").removeClass("donations-category");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,104 +0,0 @@
|
|||
function validationErrors(tagInfo, content, siteSettings) {
|
||||
let errors = [];
|
||||
if (!siteSettings.discourse_donations_public_key) {
|
||||
errors.push("missing key (site setting)");
|
||||
}
|
||||
if (!siteSettings.discourse_donations_currency) {
|
||||
errors.push("missing currency (site setting)");
|
||||
}
|
||||
if (!siteSettings.discourse_donations_shop_name) {
|
||||
errors.push("missing name (site setting)");
|
||||
}
|
||||
if (!siteSettings.discourse_donations_zip_code) {
|
||||
errors.push("missing zip code toggle (site setting)");
|
||||
}
|
||||
if (!siteSettings.discourse_donations_billing_address) {
|
||||
errors.push("missing billing address toggle (site setting)");
|
||||
}
|
||||
if (!tagInfo.attrs["amount"]) {
|
||||
errors.push("missing amount");
|
||||
}
|
||||
if (!content) {
|
||||
errors.push("missing description");
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function replaceWithStripeOrError(siteSettings) {
|
||||
return function(state, tagInfo, content) {
|
||||
let errors = validationErrors(tagInfo, content, siteSettings);
|
||||
if (errors.length) {
|
||||
displayErrors(state, errors);
|
||||
} else {
|
||||
insertCheckout(state, tagInfo, content);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
function displayErrors(state, errors) {
|
||||
let token = state.push("div-open", "div", 1);
|
||||
token.attrs = [["class", "stripe-errors"]];
|
||||
token = state.push("html_inline", "", 0);
|
||||
token.content = "Stripe checkout can't be rendered: " + errors.join(", ");
|
||||
state.push("div-close", "div", -1);
|
||||
}
|
||||
|
||||
function insertCheckout(state, tagInfo, content) {
|
||||
let token = state.push("stripe-checkout-form-open", "form", 1);
|
||||
token.attrs = [
|
||||
["method", "POST"],
|
||||
["action", "/checkout"],
|
||||
["content", content],
|
||||
["image", tagInfo.attrs["image"]],
|
||||
["class", "stripe-checkout"]
|
||||
];
|
||||
|
||||
token = state.push("stripe-checkout-form-amount", "input", 0);
|
||||
token.attrs = [
|
||||
["type", "hidden"],
|
||||
["name", "amount"],
|
||||
["value", tagInfo.attrs["amount"]]
|
||||
];
|
||||
|
||||
state.push("stripe-checkout-form-close", "form", -1);
|
||||
}
|
||||
|
||||
function setupMarkdownIt(helper, siteSettings) {
|
||||
helper.registerPlugin(md => {
|
||||
md.inline.bbcode.ruler.push("stripe-checkout", {
|
||||
tag: "stripe",
|
||||
replace: replaceWithStripeOrError(siteSettings)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function setup(helper) {
|
||||
helper.registerOptions((opts, siteSettings) => {
|
||||
helper.whiteList([
|
||||
"div[class]",
|
||||
"form[method]",
|
||||
"form[action]",
|
||||
"form[class]",
|
||||
"form[content]",
|
||||
"form[image]",
|
||||
"input[type]",
|
||||
"input[name]",
|
||||
"input[value]",
|
||||
"script[class]",
|
||||
"script[src]",
|
||||
"script[data-key]",
|
||||
"script[data-amount]",
|
||||
"script[data-name]",
|
||||
"script[data-description]",
|
||||
"script[data-image]",
|
||||
"script[data-zip-code]",
|
||||
"script[data-billing-address]",
|
||||
"script[data-currency]",
|
||||
"script[data-locale]"
|
||||
]);
|
||||
if (helper.markdownIt) {
|
||||
setupMarkdownIt(helper, siteSettings);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
div.stripe-errors {
|
||||
border: 1px solid #c33;
|
||||
border-radius: 5px;
|
||||
color: #600;
|
||||
background-color: #fdd;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.donations-page-description {
|
||||
max-width: 700px;
|
||||
font-size: 1.1em;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.donations-page-payment {
|
||||
padding: 30px 0;
|
||||
|
||||
#payment-form {
|
||||
.control-label {
|
||||
margin: 0 6.5px;
|
||||
}
|
||||
|
||||
.select-kit ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.error,
|
||||
.stripe-error {
|
||||
margin-top: 5px;
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.transaction-fee-description {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.transaction-fee-description-modal {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: 20px;
|
||||
background-color: $secondary;
|
||||
border: 1px solid $primary-low;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.4);
|
||||
width: 400px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.body-page .donations-page-donations {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.donation-list {
|
||||
.subscription-list,
|
||||
.charge-list {
|
||||
margin-bottom: 10px;
|
||||
|
||||
> ul {
|
||||
margin: 10px 0;
|
||||
list-style: none;
|
||||
|
||||
.spinner {
|
||||
height: 5px;
|
||||
width: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.underline {
|
||||
border-bottom: 1px solid $primary-medium;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.donation-row {
|
||||
span {
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
&.canceled {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
&.updating {
|
||||
color: $primary-low;
|
||||
}
|
||||
|
||||
.new-flag {
|
||||
color: $tertiary;
|
||||
margin-left: 5px;
|
||||
|
||||
.fa {
|
||||
line-height: 16px;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.donations-category-header {
|
||||
padding-top: 60px;
|
||||
text-align: center;
|
||||
|
||||
.donations-category-contents {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 40px 10px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
margin-right: 0.25em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-transform: capitalize;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.category-title-description {
|
||||
font-size: 1.2rem;
|
||||
padding: 10px 0;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.donations-category-metadata {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
font-size: 1.2rem;
|
||||
|
||||
.donations-total span:first-of-type,
|
||||
.donations-month span:first-of-type {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.donations-github a,
|
||||
.donations-meta a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.donations-meta {
|
||||
.widget-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
img {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.donations-release-latest,
|
||||
.donations-release-oldest {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span:first-of-type {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.donations-category-users {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
font-size: 1.2rem;
|
||||
padding-bottom: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.donations-backers,
|
||||
.donations-maintainers {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.backer-avatar,
|
||||
.maintainer-avatar {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.donations-backers-title,
|
||||
.donations-maintainers-title {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.donations-category {
|
||||
#main-outlet {
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
.donations-category-header .donations-category-metadata {
|
||||
flex-flow: wrap;
|
||||
padding: 0 10px;
|
||||
|
||||
div {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
en:
|
||||
js:
|
||||
discourse_donations:
|
||||
title: Spenden
|
||||
nav_item: Spenden
|
||||
amount: Betrag
|
||||
card: Kreditkarte oder Bankkarte
|
||||
submit: Spende bezahlen
|
||||
messages:
|
||||
success: Thank you for your donation!
|
|
@ -1,96 +0,0 @@
|
|||
en:
|
||||
site_settings:
|
||||
discourse_donations_enabled: "Enable the Discourse Donations plugin."
|
||||
discourse_donations_enable_create_accounts: "EXPERIMENTAL: Enable anonymous users to create accounts after successful payment"
|
||||
discourse_donations_secret_key: "Stripe Secret Key"
|
||||
discourse_donations_public_key: "Stripe Public Key"
|
||||
discourse_donations_shop_name: "Shop Name shown in Stripe Checkout form"
|
||||
discourse_donations_description: "Description shown in Stripe Checkout form"
|
||||
discourse_donations_currency: "Currency Code"
|
||||
discourse_donations_zip_code: "Show Zip Code"
|
||||
discourse_donations_billing_address: "Collect billing address"
|
||||
discourse_donations_reward_badge_name: "Grant this badge to user when a payment is successful"
|
||||
discourse_donations_reward_group_name: "Add the user to this group when a payment is successful"
|
||||
discourse_donations_page_description: "Text to be added to /donate page. Markdown is supported."
|
||||
discourse_donations_enable_transaction_fee: "Give the user the option of including the Stripe transaction fee in their donation."
|
||||
discourse_donations_transaction_fee_fixed: "Fixed part of Stripe transaction fee (changes per region). See <a href='https://stripe.com/pricing'>Stripe's pricing for your region</a> and <a href='https://support.stripe.com/questions/can-i-charge-my-stripe-fees-to-my-customers'>Stripe's explaination of passing fees onto customers</a>."
|
||||
discourse_donations_transaction_fee_percent: "Percent part of Stripe transaction fee (changes per region). See <a href='https://stripe.com/pricing'>Stripe's pricing for your region</a> and <a href='https://support.stripe.com/questions/can-i-charge-my-stripe-fees-to-my-customers'>Stripe's explaination of passing fees onto customers</a>."
|
||||
discourse_donations_amounts: "Donation amounts available to user. First listed will be the default."
|
||||
discourse_donations_custom_amount: "Allow custom donation amount"
|
||||
discourse_donations_types: "Donation types. First listed will be the default."
|
||||
discourse_donations_causes: "Custom causes a user can donate to."
|
||||
discourse_donations_causes_categories: "Categories of cause a user can donate do."
|
||||
discourse_donations_cause_category: "Category causes enabled."
|
||||
discourse_donations_cause_required: "Require user to select a cause when donating."
|
||||
discourse_donations_cause_month: "Show monthly total of donations to each cause."
|
||||
errors:
|
||||
discourse_donations_amount_must_be_number: "Amounts must be numbers"
|
||||
|
||||
js:
|
||||
discourse_donations:
|
||||
nav_item: Donate
|
||||
title: "Make a Donation"
|
||||
amount: Amount
|
||||
card: Card
|
||||
submit: Donate
|
||||
submit_with_create_account: Make Payment and Create Account
|
||||
invoice: "invoice"
|
||||
invoice_prefix: "You gave"
|
||||
receipt: "Receipt sent to {{email}}."
|
||||
cause:
|
||||
label: "Cause"
|
||||
placeholder: "Select a cause"
|
||||
category:
|
||||
total: "Total"
|
||||
month: "Month"
|
||||
backers:
|
||||
label: "Backers"
|
||||
github:
|
||||
label: "Repository"
|
||||
setting_label: "Github"
|
||||
setting_placeholder: "repoistory url"
|
||||
meta:
|
||||
label: "Discussion"
|
||||
setting_label: "Meta"
|
||||
setting_placeholder: "topic url"
|
||||
maintainers:
|
||||
label: "Maintainers"
|
||||
setting_label: "Maintainers label"
|
||||
amounts:
|
||||
setting_label: "Show donation amounts"
|
||||
release_latest:
|
||||
label: "Latest Release Supported"
|
||||
release_oldest:
|
||||
label: "Oldest Release Supported"
|
||||
subscription:
|
||||
cancel:
|
||||
title: "Cancel Recurring Donation"
|
||||
description: >
|
||||
Are you sure you want to cancel your recurring donation to {{site}}
|
||||
of {{currency}} {{amount}} {{period}}?
|
||||
email_instructions: "Required to send you a receipt. Not used for marketing."
|
||||
transaction_fee:
|
||||
label: "Include transaction fee of {{currency}} {{transactionFee}}"
|
||||
description: "When you make a donation we get charged a transaction fee. If you would like to help us out with this fee, check this box and it will be included in your donation."
|
||||
total: "Total"
|
||||
messages:
|
||||
success: Thank you for your donation!
|
||||
type: "Type"
|
||||
types:
|
||||
once: "Once"
|
||||
week: "Weekly"
|
||||
month: "Monthly"
|
||||
year: "Yearly"
|
||||
period:
|
||||
once: "on {{anchor}}"
|
||||
week: "every week on {{anchor}}"
|
||||
month: "on the {{anchor}} of every month"
|
||||
year: "every year on {{anchor}}"
|
||||
donations:
|
||||
title: "Your Donations"
|
||||
load: "Load Donations"
|
||||
loading: "Loading donations"
|
||||
charges: "Once Off"
|
||||
subscriptions: "Recurring"
|
||||
none: "You haven't made a donation yet."
|
||||
none_email: "There are no donations for {{email}}."
|
|
@ -1,15 +0,0 @@
|
|||
fi:
|
||||
site_settings:
|
||||
discourse_donations_enabled: Ota käyttöön lahjoituslisäosa.
|
||||
discourse_donations_secret_key: Stripen Secret Key -salausavain
|
||||
discourse_donations_public_key: Stripen Public Key -tunnistusavain
|
||||
discourse_donations_currency: Valuuttakoodi
|
||||
js:
|
||||
discourse_donations:
|
||||
title: Lahjoita
|
||||
nav_item: Lahjoita
|
||||
amount: Määrä
|
||||
card: Credit- vai debit-kortti
|
||||
submit: Maksa
|
||||
messages:
|
||||
success: Kiitos lahjoituksestasi!
|
|
@ -1,19 +0,0 @@
|
|||
it:
|
||||
site_settings:
|
||||
discourse_donations_enabled: Abilita il plugin per le donazioni.
|
||||
discourse_donations_enable_create_accounts: "SPERIMENTALE: Permetti agli utenti anonimi la creazione di un account dopo un pagamento effettuato con successo"
|
||||
discourse_donations_secret_key: Stripe Secret Key
|
||||
discourse_donations_public_key: Stripe Public Key
|
||||
discourse_donations_currency: Codice Valuta
|
||||
discourse_donations_reward_badge_name: Assegna questo distintivo all'utente quando un pagamento viene effettuato con successo
|
||||
discourse_donations_reward_group_name: Aggiungi l'utente a questo gruppo quando un pagamento viene effettuato con successo
|
||||
js:
|
||||
discourse_donations:
|
||||
title: Donazione
|
||||
nav_item: Donazione
|
||||
amount: Importo
|
||||
card: Carta di credito o debito
|
||||
submit: Effettua il pagamento
|
||||
submit_with_create_account: Effettua il Pagamento e Crea un Account
|
||||
messages:
|
||||
success: Grazie per la tua donazione!
|
|
@ -1,11 +0,0 @@
|
|||
en:
|
||||
donations:
|
||||
recurring: "%{site_title} Recurring Donation"
|
||||
payment:
|
||||
success: 'Thank you, your donation has been successful.'
|
||||
receipt_sent: 'A receipt has been sent to %{email}.'
|
||||
invoice_sent: 'An invoice has been sent to %{email}.'
|
||||
subscription:
|
||||
error:
|
||||
not_found: "Subscription not found."
|
||||
not_cancelled: "Subscription not cancelled."
|
|
@ -1,4 +0,0 @@
|
|||
it:
|
||||
donations:
|
||||
payment:
|
||||
success: 'Grazie. La tua donazione è stata effettuata con successo'
|
|
@ -1,10 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
DiscourseDonations::Engine.routes.draw do
|
||||
get '/' => 'charges#index'
|
||||
|
||||
resources :charges, only: [:index, :create]
|
||||
put '/charges/cancel-subscription' => 'charges#cancel_subscription'
|
||||
|
||||
resources :checkout, only: [:create]
|
||||
DiscoursePatrons::Engine.routes.draw do
|
||||
get '/' => 'patrons#index'
|
||||
get '/:id' => 'patrons#show'
|
||||
end
|
||||
|
|
|
@ -1,78 +1,21 @@
|
|||
plugins:
|
||||
discourse_donations_enabled:
|
||||
discourse_patrons_enabled:
|
||||
default: false
|
||||
client: true
|
||||
discourse_donations_secret_key:
|
||||
discourse_patrons_secret_key:
|
||||
default: ''
|
||||
client: false
|
||||
discourse_donations_public_key:
|
||||
discourse_patrons_public_key:
|
||||
default: ''
|
||||
client: true
|
||||
discourse_donations_enable_create_accounts:
|
||||
discourse_patrons_currency:
|
||||
client: true
|
||||
default: false
|
||||
discourse_donations_description:
|
||||
client: true
|
||||
default: ''
|
||||
discourse_donations_shop_name:
|
||||
client: true
|
||||
default: ''
|
||||
discourse_donations_currency:
|
||||
client: true
|
||||
default: 'USD'
|
||||
discourse_donations_zip_code:
|
||||
default: false
|
||||
client: true
|
||||
discourse_donations_billing_address:
|
||||
default: true
|
||||
client: true
|
||||
discourse_donations_reward_badge_name:
|
||||
client: false
|
||||
default: 'Donation'
|
||||
discourse_donations_reward_group_name:
|
||||
client: false
|
||||
default: 'Donation'
|
||||
discourse_donations_page_description:
|
||||
client: true
|
||||
default: ''
|
||||
discourse_donations_enable_transaction_fee:
|
||||
client: true
|
||||
default: false
|
||||
discourse_donations_transaction_fee_fixed:
|
||||
client: true
|
||||
default: 0.3
|
||||
discourse_donations_transaction_fee_percent:
|
||||
client: true
|
||||
default: 0.029
|
||||
discourse_donations_amounts:
|
||||
client: true
|
||||
type: list
|
||||
default: '1|2|5|10|20|50'
|
||||
regex: "^[0-9\\|]+$"
|
||||
regex_error: "site_settings.errors.discourse_donations_amount_must_be_number"
|
||||
discourse_donations_types:
|
||||
client: true
|
||||
type: list
|
||||
default: 'once|month'
|
||||
default: "USD"
|
||||
type: enum
|
||||
choices:
|
||||
- year
|
||||
- month
|
||||
- week
|
||||
- once
|
||||
discourse_donations_causes:
|
||||
client: true
|
||||
type: list
|
||||
default: ''
|
||||
discourse_donations_causes_categories:
|
||||
client: true
|
||||
type: category_list
|
||||
default: ''
|
||||
discourse_donations_cause_category:
|
||||
client: true
|
||||
default: false
|
||||
discourse_donations_cause_required:
|
||||
client: true
|
||||
default: false
|
||||
discourse_donations_cause_month:
|
||||
client: true
|
||||
default: false
|
||||
- AUD
|
||||
- CAD
|
||||
- EUR
|
||||
- JPY
|
||||
- GBP
|
||||
- USD
|
||||
|
|
BIN
doc/menulink.png
BIN
doc/menulink.png
Binary file not shown.
Before Width: | Height: | Size: 51 KiB |
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
Binary file not shown.
Before Width: | Height: | Size: 54 KiB |
|
@ -1,8 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ::DiscourseDonations
|
||||
class Engine < ::Rails::Engine
|
||||
engine_name 'discourse-donations'
|
||||
isolate_namespace DiscourseDonations
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ::DiscoursePatrons
|
||||
PLUGIN_NAME = "discourse-patrons"
|
||||
|
||||
class Engine < ::Rails::Engine
|
||||
engine_name DiscoursePatrons::PLUGIN_NAME
|
||||
isolate_namespace DiscoursePatrons
|
||||
end
|
||||
end
|
175
plugin.rb
175
plugin.rb
|
@ -1,172 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# name: discourse-donations
|
||||
# about: Integrates Stripe into Discourse to allow visitors to make donations
|
||||
# version: 1.12.2
|
||||
# url: https://github.com/rimian/discourse-donations
|
||||
# authors: Rimian Perkins, Chris Beach, Angus McLeod
|
||||
# name: discourse-patrons
|
||||
# about: Integrates Stripe into Discourse to allow visitors to make payments
|
||||
# version: 1.0.0
|
||||
# url: https://github.com/rimian/discourse-patrons
|
||||
# authors: Rimian Perkins
|
||||
|
||||
gem 'stripe', '5.1.0'
|
||||
enabled_site_setting :discourse_patrons_enabled
|
||||
|
||||
register_asset "stylesheets/common/discourse-donations.scss"
|
||||
register_asset "stylesheets/mobile/discourse-donations.scss"
|
||||
load File.expand_path('../lib/discourse_patrons/engine.rb', __FILE__)
|
||||
|
||||
enabled_site_setting :discourse_donations_enabled
|
||||
|
||||
register_html_builder('server:before-head-close') do
|
||||
"<script src='https://js.stripe.com/v3/'></script>"
|
||||
end
|
||||
|
||||
extend_content_security_policy(
|
||||
script_src: ['https://js.stripe.com/v3/']
|
||||
)
|
||||
|
||||
after_initialize do
|
||||
load File.expand_path('../lib/discourse_donations/engine.rb', __FILE__)
|
||||
load File.expand_path('../config/routes.rb', __FILE__)
|
||||
load File.expand_path('../app/controllers/controllers.rb', __FILE__)
|
||||
load File.expand_path('../app/jobs/jobs.rb', __FILE__)
|
||||
load File.expand_path('../app/services/services.rb', __FILE__)
|
||||
|
||||
Discourse::Application.routes.append do
|
||||
mount ::DiscourseDonations::Engine, at: 'donate'
|
||||
end
|
||||
|
||||
class ::User
|
||||
def stripe_customer_id
|
||||
if custom_fields['stripe_customer_id']
|
||||
custom_fields['stripe_customer_id'].to_s
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Category.register_custom_field_type('donations_show_amounts', :boolean)
|
||||
|
||||
class ::Category
|
||||
def donations_cause
|
||||
SiteSetting.discourse_donations_causes_categories.split('|').include? self.id.to_s
|
||||
end
|
||||
|
||||
def donations_total
|
||||
if custom_fields['donations_total']
|
||||
custom_fields['donations_total']
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def donations_show_amounts
|
||||
if custom_fields['donations_show_amounts'] != nil
|
||||
custom_fields['donations_show_amounts']
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def donations_month
|
||||
if custom_fields['donations_month']
|
||||
custom_fields['donations_month']
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def donations_backers
|
||||
if custom_fields['donations_backers']
|
||||
[*custom_fields['donations_backers']].map do |user_id|
|
||||
User.find_by(id: user_id.to_i)
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def donations_maintainers
|
||||
if custom_fields['donations_maintainers']
|
||||
custom_fields['donations_maintainers'].split(',').map do |username|
|
||||
User.find_by(username: username)
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def donations_maintainers_label
|
||||
if custom_fields['donations_maintainers_label']
|
||||
custom_fields['donations_maintainers_label']
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def donations_github
|
||||
if custom_fields['donations_github']
|
||||
custom_fields['donations_github']
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def donations_meta
|
||||
if custom_fields['donations_meta']
|
||||
custom_fields['donations_meta']
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def donations_release_latest
|
||||
if custom_fields['donations_release_latest']
|
||||
custom_fields['donations_release_latest']
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def donations_release_oldest
|
||||
if custom_fields['donations_release_oldest']
|
||||
custom_fields['donations_release_oldest']
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
[
|
||||
'donations_cause',
|
||||
'donations_total',
|
||||
'donations_month',
|
||||
'donations_backers',
|
||||
'donations_show_amounts',
|
||||
'donations_maintainers',
|
||||
'donations_maintainers_label',
|
||||
'donations_github',
|
||||
'donations_meta',
|
||||
'donations_release_latest',
|
||||
'donations_release_oldest'
|
||||
].each do |key|
|
||||
Site.preloaded_category_custom_fields << key if Site.respond_to? :preloaded_category_custom_fields
|
||||
end
|
||||
|
||||
add_to_serializer(:basic_category, :donations_cause) { object.donations_cause }
|
||||
add_to_serializer(:basic_category, :donations_total) { object.donations_total }
|
||||
add_to_serializer(:basic_category, :include_donations_total?) { object.donations_show_amounts }
|
||||
add_to_serializer(:basic_category, :donations_month) { object.donations_month }
|
||||
add_to_serializer(:basic_category, :include_donations_month?) { object.donations_show_amounts && SiteSetting.discourse_donations_cause_month }
|
||||
add_to_serializer(:basic_category, :donations_backers) {
|
||||
ActiveModel::ArraySerializer.new(object.donations_backers, each_serializer: BasicUserSerializer).as_json
|
||||
}
|
||||
add_to_serializer(:basic_category, :donations_maintainers) {
|
||||
ActiveModel::ArraySerializer.new(object.donations_maintainers, each_serializer: BasicUserSerializer).as_json
|
||||
}
|
||||
add_to_serializer(:basic_category, :donations_maintainers_label) { object.donations_maintainers_label }
|
||||
add_to_serializer(:basic_category, :include_donations_maintainers_label?) { object.donations_maintainers_label.present? }
|
||||
add_to_serializer(:basic_category, :donations_github) { object.donations_github }
|
||||
add_to_serializer(:basic_category, :donations_meta) { object.donations_meta }
|
||||
add_to_serializer(:basic_category, :donations_release_latest) { object.donations_release_latest }
|
||||
add_to_serializer(:basic_category, :donations_release_oldest) { object.donations_release_oldest }
|
||||
|
||||
DiscourseEvent.trigger(:donations_ready)
|
||||
Discourse::Application.routes.append do
|
||||
mount ::DiscoursePatrons::Engine, at: '/patrons'
|
||||
end
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
shared_examples 'failure response' do |message_key|
|
||||
let(:body) { JSON.parse(response.body) }
|
||||
|
||||
it 'has status 200' do expect(response).to have_http_status(200) end
|
||||
it 'has an error message' do expect(body['messages']).to include(I18n.t(message_key)) end
|
||||
it 'is not successful' do expect(body['success']).to eq false end
|
||||
it 'does not create a payment' do DiscourseDonations::Stripe.expects(:new).never end
|
||||
it 'does not create rewards' do DiscourseDonations::Rewards.expects(:new).never end
|
||||
it 'does not queue up any jobs' do ::Jobs.expects(:enqueue).never end
|
||||
end
|
||||
|
||||
module DiscourseDonations
|
||||
RSpec.describe ChargesController, type: :controller do
|
||||
routes { DiscourseDonations::Engine.routes }
|
||||
let(:body) { JSON.parse(response.body) }
|
||||
let(:current_user) { Fabricate(:user) }
|
||||
# Workaround for rails-5 issue. See https://github.com/thoughtbot/shoulda-matchers/issues/1018#issuecomment-315876453
|
||||
# let(:allowed_params) { { create_account: 'true', email: 'email@example.com', password: 'secret', username: 'mr-pink', name: 'kirsten', amount: 100, stripeToken: 'rrurrrurrrrr' } }
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:disable_discourse_narrative_bot_welcome_post).returns(true)
|
||||
SiteSetting.stubs(:discourse_donations_secret_key).returns('secret-key-yo')
|
||||
SiteSetting.stubs(:discourse_donations_description).returns('charity begins at discourse plugin')
|
||||
SiteSetting.stubs(:discourse_donations_currency).returns('AUD')
|
||||
|
||||
customer = Fabricate(:stripe_customer).to_json
|
||||
|
||||
stub_request(:get, /v1\/customers/).to_return(status: 200, body: customer)
|
||||
|
||||
plans = Fabricate(:stripe_plans).to_json
|
||||
|
||||
stub_request(:get, "https://api.stripe.com/v1/plans").to_return(status: 200, body: plans)
|
||||
stub_request(:post, "https://api.stripe.com/v1/plans").to_return(status: 200, body: plans)
|
||||
|
||||
products = Fabricate(:stripe_products).to_json
|
||||
|
||||
stub_request(:get, "https://api.stripe.com/v1/products?type=service").to_return(status: 200, body: products)
|
||||
stub_request(:post, "https://api.stripe.com/v1/products").to_return(status: 200, body: products)
|
||||
stub_request(:post, "https://api.stripe.com/v1/customers").to_return(status: 200, body: customer)
|
||||
|
||||
subscription = Fabricate(:stripe_subscription).to_json
|
||||
|
||||
stub_request(:post, "https://api.stripe.com/v1/subscriptions").to_return(status: 200, body: subscription)
|
||||
|
||||
invoices = Fabricate(:stripe_invoices).to_json
|
||||
|
||||
stub_request(:get, "https://api.stripe.com/v1/invoices?customer=cus_FhHJDzf0OxYtb8&subscription=sub_8epEF0PuRhmltU")
|
||||
.to_return(status: 200, body: invoices)
|
||||
end
|
||||
|
||||
xit 'whitelists the params' do
|
||||
should permit(:name, :username, :email, :password, :create_account).
|
||||
for(:create, params: { params: allowed_params })
|
||||
end
|
||||
|
||||
it 'responds ok for anonymous users' do
|
||||
controller.expects(:current_user).at_least(1).returns(current_user)
|
||||
|
||||
post :create, params: { email: 'foobar@example.com' }, format: :json
|
||||
|
||||
aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body['messages'][0]).to end_with(I18n.t('donations.payment.success'))
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not expect a username or email if accounts are not being created' do
|
||||
charge = Fabricate(:stripe_charge).to_json
|
||||
stub_request(:post, "https://api.stripe.com/v1/charges").to_return(status: 200, body: charge)
|
||||
|
||||
post :create, params: { create_account: 'false', type: 'once' }, format: :json
|
||||
|
||||
aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body['messages'][0]).to end_with(I18n.t('donations.payment.success'))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create accounts' do
|
||||
describe 'create acccount disabled' do
|
||||
let(:params) { { amount: 100, stripeToken: 'rrurrrurrrrr-rrruurrrr' } }
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:discourse_donations_enable_create_accounts).returns(false)
|
||||
::Jobs.expects(:enqueue).never
|
||||
end
|
||||
|
||||
it 'does not create user accounts' do
|
||||
controller.expects(:current_user).at_least(1).returns(current_user)
|
||||
|
||||
post :create, params: params, format: :json
|
||||
end
|
||||
|
||||
it 'does not create user accounts if the user is logged in' do
|
||||
log_in :coding_horror
|
||||
post :create, params: params, format: :json
|
||||
end
|
||||
|
||||
it 'does not create user accounts when settings are disabled and params are not' do
|
||||
log_in :coding_horror
|
||||
post :create, params: params.merge(create_account: true, email: 'email@example.com', password: 'secret', username: 'mr-brown', name: 'hacker-guy')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'creating an account enabled' do
|
||||
let(:params) { { create_account: 'true', email: 'email@example.com', password: 'secret', username: 'mr-pink', amount: 100, stripeToken: 'rrurrrurrrrr-rrruurrrr' } }
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:discourse_donations_enable_create_accounts).returns(true)
|
||||
Jobs.expects(:enqueue).with(:donation_user, anything)
|
||||
end
|
||||
|
||||
it 'enqueues the user account create' do
|
||||
controller.expects(:current_user).at_least(1).returns(current_user)
|
||||
|
||||
post :create, params: params, format: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,111 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:stripe_charge, from: "DiscourseDonations::StripeResponse") do
|
||||
response = {
|
||||
"id": "ch_1FBxEe2eZvKYlo2CAWyww6QM",
|
||||
"object": "charge",
|
||||
"amount": 100,
|
||||
"amount_refunded": 0,
|
||||
"application": "null",
|
||||
"application_fee": "null",
|
||||
"application_fee_amount": "null",
|
||||
"balance_transaction": "txn_19XJJ02eZvKYlo2ClwuJ1rbA",
|
||||
"billing_details": {
|
||||
"address": {
|
||||
"city": "null",
|
||||
"country": "null",
|
||||
"line1": "null",
|
||||
"line2": "null",
|
||||
"postal_code": "null",
|
||||
"state": "null"
|
||||
},
|
||||
"email": "null",
|
||||
"name": "null",
|
||||
"phone": "null"
|
||||
},
|
||||
"captured": false,
|
||||
"created": 1566883732,
|
||||
"currency": "usd",
|
||||
"customer": "null",
|
||||
"description": "My First Test Charge (created for API docs)",
|
||||
"destination": "null",
|
||||
"dispute": "null",
|
||||
"failure_code": "null",
|
||||
"failure_message": "null",
|
||||
"fraud_details": {},
|
||||
"invoice": "null",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"on_behalf_of": "null",
|
||||
"order": "null",
|
||||
"outcome": "null",
|
||||
"paid": true,
|
||||
"payment_intent": "null",
|
||||
"payment_method": "card_103Z0w2eZvKYlo2CyzMjT1R1",
|
||||
"payment_method_details": {
|
||||
"card": {
|
||||
"brand": "visa",
|
||||
"checks": {
|
||||
"address_line1_check": "null",
|
||||
"address_postal_code_check": "null",
|
||||
"cvc_check": "unchecked"
|
||||
},
|
||||
"country": "US",
|
||||
"exp_month": 2,
|
||||
"exp_year": 2015,
|
||||
"fingerprint": "Xt5EWLLDS7FJjR1c",
|
||||
"funding": "credit",
|
||||
"last4": "4242",
|
||||
"three_d_secure": "null",
|
||||
"wallet": "null"
|
||||
},
|
||||
"type": "card"
|
||||
},
|
||||
"receipt_email": "null",
|
||||
"receipt_number": "null",
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_1032D82eZvKYlo2C/ch_1FBxEe2eZvKYlo2CAWyww6QM/rcpt_FhLw6tME6cvwGXWoL0Hn3f65Gkvyocg",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_1FBxEe2eZvKYlo2CAWyww6QM/refunds"
|
||||
},
|
||||
"review": "null",
|
||||
"shipping": "null",
|
||||
"source": {
|
||||
"id": "card_103Z0w2eZvKYlo2CyzMjT1R1",
|
||||
"object": "card",
|
||||
"address_city": "null",
|
||||
"address_country": "null",
|
||||
"address_line1": "null",
|
||||
"address_line1_check": "null",
|
||||
"address_line2": "null",
|
||||
"address_state": "null",
|
||||
"address_zip": "null",
|
||||
"address_zip_check": "null",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "null",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": "null",
|
||||
"exp_month": 2,
|
||||
"exp_year": 2015,
|
||||
"fingerprint": "Xt5EWLLDS7FJjR1c",
|
||||
"funding": "credit",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "null",
|
||||
"tokenization_method": "null"
|
||||
},
|
||||
"source_transfer": "null",
|
||||
"statement_descriptor": "null",
|
||||
"statement_descriptor_suffix": "null",
|
||||
"status": "succeeded",
|
||||
"transfer_data": "null",
|
||||
"transfer_group": "null"
|
||||
}.to_json
|
||||
|
||||
to_json response
|
||||
end
|
|
@ -1,56 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:stripe_customer, from: "DiscourseDonations::StripeResponse") do
|
||||
response = {
|
||||
"id": "cus_FhHJDzf0OxYtb8",
|
||||
"object": "customer",
|
||||
"account_balance": 0,
|
||||
"address": "null",
|
||||
"balance": 0,
|
||||
"created": 1566866533,
|
||||
"currency": "usd",
|
||||
"default_source": "null",
|
||||
"delinquent": false,
|
||||
"description": "null",
|
||||
"discount": "null",
|
||||
"email": "null",
|
||||
"invoice_prefix": "0BBF354",
|
||||
"invoice_settings": {
|
||||
"custom_fields": "null",
|
||||
"default_payment_method": "null",
|
||||
"footer": "null"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"name": "null",
|
||||
"phone": "null",
|
||||
"preferred_locales": [],
|
||||
"shipping": "null",
|
||||
"sources": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_FhHJDzf0OxYtb8/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_FhHJDzf0OxYtb8/subscriptions"
|
||||
},
|
||||
"tax_exempt": "none",
|
||||
"tax_ids": {
|
||||
"object": "list",
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_FhHJDzf0OxYtb8/tax_ids"
|
||||
},
|
||||
"tax_info": "null",
|
||||
"tax_info_verification": "null"
|
||||
}.to_json
|
||||
|
||||
to_json response
|
||||
end
|
|
@ -1,128 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:stripe_invoices, from: "DiscourseDonations::StripeResponse") do
|
||||
response = {
|
||||
"object": "list",
|
||||
"url": "/v1/invoices",
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "in_1Cc9wc2eZvKYlo2ClBzJbDQz",
|
||||
"object": "invoice",
|
||||
"account_country": "US",
|
||||
"account_name": "Stripe.com",
|
||||
"amount_due": 20,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 20,
|
||||
"application_fee_amount": "null",
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": false,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "subscription_update",
|
||||
"charge": "null",
|
||||
"collection_method": "send_invoice",
|
||||
"created": 1528800106,
|
||||
"currency": "usd",
|
||||
"custom_fields": "null",
|
||||
"customer": "cus_FhHJDzf0OxYtb8",
|
||||
"customer_address": "null",
|
||||
"customer_email": "ziad+123@elysian.team",
|
||||
"customer_name": "null",
|
||||
"customer_phone": "null",
|
||||
"customer_shipping": "null",
|
||||
"customer_tax_exempt": "none",
|
||||
"customer_tax_ids": [],
|
||||
"default_payment_method": "null",
|
||||
"default_source": "null",
|
||||
"default_tax_rates": [],
|
||||
"description": "null",
|
||||
"discount": "null",
|
||||
"due_date": 1529059306,
|
||||
"ending_balance": "null",
|
||||
"footer": "null",
|
||||
"hosted_invoice_url": "null",
|
||||
"invoice_pdf": "null",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"id": "sli_42e8bf79bec714",
|
||||
"object": "line_item",
|
||||
"amount": 999,
|
||||
"currency": "usd",
|
||||
"description": "1 × Ivory Freelance (at $9.99 / month)",
|
||||
"discountable": true,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"period": {
|
||||
"end": 1521326190,
|
||||
"start": 1518906990
|
||||
},
|
||||
"plan": {
|
||||
"id": "ivory-freelance-040",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": "null",
|
||||
"amount": 999,
|
||||
"amount_decimal": "999",
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1466202980,
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "null",
|
||||
"product": "prod_BUthVRQ7KdFfa7",
|
||||
"tiers": "null",
|
||||
"tiers_mode": "null",
|
||||
"transform_usage": "null",
|
||||
"trial_period_days": "null",
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": "sub_8epEF0PuRhmltU",
|
||||
"subscription_item": "si_18NVZi2eZvKYlo2CUtBNGL9x",
|
||||
"tax_amounts": [],
|
||||
"tax_rates": [],
|
||||
"type": "subscription"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices/in_1Cc9wc2eZvKYlo2ClBzJbDQz/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": "null",
|
||||
"number": "8B36FE9-0005",
|
||||
"paid": false,
|
||||
"payment_intent": "null",
|
||||
"period_end": 1528800106,
|
||||
"period_start": 1528800106,
|
||||
"post_payment_credit_notes_amount": 0,
|
||||
"pre_payment_credit_notes_amount": 0,
|
||||
"receipt_number": "null",
|
||||
"starting_balance": 10,
|
||||
"statement_descriptor": "null",
|
||||
"status": "draft",
|
||||
"status_transitions": {
|
||||
"finalized_at": "null",
|
||||
"marked_uncollectible_at": "null",
|
||||
"paid_at": "null",
|
||||
"voided_at": "null"
|
||||
},
|
||||
"subscription": "sub_D2ECXpuEnnXkWU",
|
||||
"subtotal": 10,
|
||||
"tax": "null",
|
||||
"tax_percent": "null",
|
||||
"total": 10,
|
||||
"total_tax_amounts": [],
|
||||
"webhooks_delivered_at": 1528800106
|
||||
},
|
||||
]
|
||||
}.to_json
|
||||
|
||||
to_json response
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:stripe_plans, from: "DiscourseDonations::StripeResponse") do
|
||||
response = {
|
||||
"object": "list",
|
||||
"url": "/v1/plans",
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "plan_EeE4ns3bvb34ZP",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": "null",
|
||||
"amount": 3000,
|
||||
"amount_decimal": "3000",
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1551862832,
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "Pro Plan",
|
||||
"product": "prod_BT942zL7VcClrn",
|
||||
"tiers": "null",
|
||||
"tiers_mode": "null",
|
||||
"transform_usage": "null",
|
||||
"trial_period_days": "null",
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
]
|
||||
}.to_json
|
||||
|
||||
to_json response
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:stripe_products, from: "DiscourseDonations::StripeResponse") do
|
||||
response = {
|
||||
"object": "list",
|
||||
"url": "/v1/products",
|
||||
"has_more": false,
|
||||
"data": [
|
||||
{
|
||||
"id": "prod_FhGJ7clA2xMxGI",
|
||||
"object": "product",
|
||||
"active": true,
|
||||
"attributes": [],
|
||||
"caption": "null",
|
||||
"created": 1566862775,
|
||||
"deactivate_on": [],
|
||||
"description": "null",
|
||||
"images": [],
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"name": "Sapphire Personal",
|
||||
"package_dimensions": "null",
|
||||
"shippable": "null",
|
||||
"statement_descriptor": "null",
|
||||
"type": "service",
|
||||
"unit_label": "null",
|
||||
"updated": 1566862775,
|
||||
"url": "null"
|
||||
},
|
||||
]
|
||||
}.to_json
|
||||
|
||||
to_json response
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This is for building http responses with Fabricate
|
||||
# Usage: Fabricate(:customer).to_json
|
||||
# See: https://stripe.com/docs/api
|
||||
|
||||
module DiscourseDonations
|
||||
class StripeResponse
|
||||
attr_accessor :to_json
|
||||
end
|
||||
end
|
|
@ -1,102 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:stripe_subscription, from: "DiscourseDonations::StripeResponse") do
|
||||
response = {
|
||||
"id": "sub_8epEF0PuRhmltU",
|
||||
"object": "subscription",
|
||||
"application_fee_percent": "null",
|
||||
"billing": "charge_automatically",
|
||||
"billing_cycle_anchor": 1466202990,
|
||||
"billing_thresholds": "null",
|
||||
"cancel_at": "null",
|
||||
"cancel_at_period_end": false,
|
||||
"canceled_at": 1517528245,
|
||||
"collection_method": "charge_automatically",
|
||||
"created": 1466202990,
|
||||
"current_period_end": 1518906990,
|
||||
"current_period_start": 1516228590,
|
||||
"customer": "cus_FhHJDzf0OxYtb8",
|
||||
"days_until_due": "null",
|
||||
"default_payment_method": "null",
|
||||
"default_source": "null",
|
||||
"default_tax_rates": [],
|
||||
"discount": "null",
|
||||
"ended_at": 1517528245,
|
||||
"items": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "si_18NVZi2eZvKYlo2CUtBNGL9x",
|
||||
"object": "subscription_item",
|
||||
"billing_thresholds": "null",
|
||||
"created": 1466202990,
|
||||
"metadata": {},
|
||||
"plan": {
|
||||
"id": "ivory-freelance-040",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": "null",
|
||||
"amount": 999,
|
||||
"amount_decimal": "999",
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1466202980,
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "null",
|
||||
"product": "prod_BUthVRQ7KdFfa7",
|
||||
"tiers": "null",
|
||||
"tiers_mode": "null",
|
||||
"transform_usage": "null",
|
||||
"trial_period_days": "null",
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 1,
|
||||
"subscription": "sub_8epEF0PuRhmltU",
|
||||
"tax_rates": []
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/subscription_items?subscription=sub_8epEF0PuRhmltU"
|
||||
},
|
||||
"latest_invoice": "null",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"pending_setup_intent": "null",
|
||||
"plan": {
|
||||
"id": "ivory-freelance-040",
|
||||
"object": "plan",
|
||||
"active": true,
|
||||
"aggregate_usage": "null",
|
||||
"amount": 999,
|
||||
"amount_decimal": "999",
|
||||
"billing_scheme": "per_unit",
|
||||
"created": 1466202980,
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"interval_count": 1,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"nickname": "null",
|
||||
"product": "prod_BUthVRQ7KdFfa7",
|
||||
"tiers": "null",
|
||||
"tiers_mode": "null",
|
||||
"transform_usage": "null",
|
||||
"trial_period_days": "null",
|
||||
"usage_type": "licensed"
|
||||
},
|
||||
"quantity": 1,
|
||||
"schedule": "null",
|
||||
"start": 1466202990,
|
||||
"start_date": 1466202990,
|
||||
"status": "active",
|
||||
"tax_percent": "null",
|
||||
"trial_end": "null",
|
||||
"trial_start": "null"
|
||||
}.to_json
|
||||
|
||||
to_json response
|
||||
end
|
|
@ -1,94 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Jobs::DonationUser, type: :job do
|
||||
let(:args) { { email: 'captain-sensible@example.com', username: 'wot', name: 'captain', password: 'secret121321' } }
|
||||
|
||||
before do
|
||||
SiteSetting.stubs(:enable_badges).returns(true)
|
||||
end
|
||||
|
||||
it 'creates a new user with no rewards' do
|
||||
aggregate_failures do
|
||||
expect { subject.execute(args) }.to change { User.count }.by(1)
|
||||
user = User.find_by_email(args[:email])
|
||||
expect(user.badges).to be_empty
|
||||
expect(user.groups).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sending the signup email' do
|
||||
let(:user) { User.find_by_email(args[:email]) }
|
||||
|
||||
it 'has an email token' do
|
||||
subject.execute(args)
|
||||
expect(user.email_tokens).not_to be_empty
|
||||
end
|
||||
|
||||
it 'enqueues the signup email' do
|
||||
User.expects(:create!).returns(Fabricate(:user, args))
|
||||
Jobs.expects(:enqueue).with(
|
||||
:critical_user_email,
|
||||
type: :signup, user_id: user.id, email_token: user.email_tokens.first.token
|
||||
)
|
||||
subject.execute(args)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'rewards' do
|
||||
describe 'create user with rewards' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it 'does not create the rewards if the user does not persist' do
|
||||
User.expects(:create!).returns(user)
|
||||
user.expects(:persisted?).returns(false)
|
||||
DiscourseDonations::Rewards.expects(:new).never
|
||||
subject.execute(args)
|
||||
end
|
||||
|
||||
it 'creates a User object without rewards' do
|
||||
User.expects(:create!).with(args).returns(user)
|
||||
subject.execute(args.merge(rewards: [], otherthing: nil))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'User rewards' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:badge) { Fabricate(:badge) }
|
||||
let(:grp) { Fabricate(:group) }
|
||||
|
||||
before do
|
||||
User.stubs(:create!).returns(user)
|
||||
end
|
||||
|
||||
it 'grants the user a badge' do
|
||||
subject.execute(args.merge(rewards: [{ type: 'badge', name: badge.name }]))
|
||||
aggregate_failures do
|
||||
expect(user.badges).to include(badge)
|
||||
expect(user.groups).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds the user to the group' do
|
||||
subject.execute(args.merge(rewards: [{ type: 'group', name: grp.name }]))
|
||||
aggregate_failures do
|
||||
expect(user.badges).to be_empty
|
||||
expect(user.groups).to include(grp)
|
||||
end
|
||||
end
|
||||
|
||||
it 'has no collisions in badges' do
|
||||
Fabricate(:badge, name: 'weiner_schitzel')
|
||||
subject.execute(args.merge(rewards: [{ type: 'group', name: 'weiner_schitzel' }]))
|
||||
expect(user.badges).to be_empty
|
||||
end
|
||||
|
||||
it 'has no collisions in groups' do
|
||||
Fabricate(:group, name: 'dude_ranch')
|
||||
subject.execute(args.merge(rewards: [{ type: 'badge', name: 'dude_ranch' }]))
|
||||
expect(user.groups).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative './fabricators/charge_fabricator.rb'
|
||||
require_relative './fabricators/customer_fabricator.rb'
|
||||
require_relative './fabricators/invoices_fabricator.rb'
|
||||
require_relative './fabricators/plans_fabricator.rb'
|
||||
require_relative './fabricators/products_fabricator.rb'
|
||||
require_relative './fabricators/stripe_response.rb'
|
||||
require_relative './fabricators/subscription_fabricator.rb'
|
|
@ -1,53 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
module DiscourseDonations
|
||||
RSpec.describe DiscourseDonations::Rewards do
|
||||
let(:grp) { Fabricate(:group) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
subject { described_class.new(user) }
|
||||
|
||||
it 'adds the user to a group' do
|
||||
Group.expects(:find_by_name).with(grp.name).returns(grp)
|
||||
grp.expects(:add).with(user)
|
||||
subject.expects(:log_group_add).once
|
||||
subject.add_to_group(grp.name)
|
||||
end
|
||||
|
||||
it 'does not add the user to a group' do
|
||||
Group.expects(:find_by_name).with(grp.name).returns(nil)
|
||||
grp.expects(:add).never
|
||||
subject.expects(:log_group_add).never
|
||||
expect(subject.add_to_group(grp.name)).to be_falsy
|
||||
end
|
||||
|
||||
it 'logs the group add' do
|
||||
GroupActionLogger.any_instance.expects(:log_add_user_to_group)
|
||||
subject.add_to_group(grp.name)
|
||||
end
|
||||
|
||||
describe '.grant_badge' do
|
||||
let(:badge) { Fabricate(:badge) }
|
||||
|
||||
before { SiteSetting.stubs(:enable_badges).returns(true) }
|
||||
|
||||
it 'grants the user a badge' do
|
||||
BadgeGranter.expects(:grant).with(badge, user)
|
||||
subject.grant_badge(badge.name)
|
||||
end
|
||||
|
||||
it 'does not grant the user a badge when the badge does not exist' do
|
||||
Badge.stubs(:find_by_name).returns(nil)
|
||||
BadgeGranter.expects(:grant).never
|
||||
expect(subject.grant_badge('does not exist')).to be_falsy
|
||||
end
|
||||
|
||||
it 'does not grant the user a badge when badges are disabled' do
|
||||
SiteSetting.stubs(:enable_badges).returns(false)
|
||||
BadgeGranter.expects(:grant).never
|
||||
subject.grant_badge(badge.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,91 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require_relative '../../support/dd_helper'
|
||||
|
||||
module DiscourseDonations
|
||||
RSpec.describe DiscourseDonations::Stripe do
|
||||
before { SiteSetting.stubs(:discourse_donations_secret_key).returns('secret-key-yo') }
|
||||
|
||||
let(:stripe_options) { { description: 'hi there', currency: 'AUD' } }
|
||||
let(:email) { 'ray-zintoast@example.com' }
|
||||
let(:customer) { stub(id: 1, email: email) }
|
||||
let!(:subject) { described_class.new('secret-key-yo', stripe_options) }
|
||||
|
||||
it 'sets the api key' do
|
||||
expect(::Stripe.api_key).to eq 'secret-key-yo'
|
||||
end
|
||||
|
||||
describe 'subscribe' do
|
||||
let(:params) { { email: email, stripeToken: 'stripe-token', plan: 'subscription-plan-1234', other: 'redundant param' } }
|
||||
|
||||
xit 'creates a customer and a subscription' do
|
||||
# todo
|
||||
|
||||
::Stripe::Customer.expects(:create).with(
|
||||
email: email,
|
||||
source: nil
|
||||
).returns(customer)
|
||||
|
||||
::Stripe::Customer.expects(:list)
|
||||
|
||||
subject.subscribe(params)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'charge' do
|
||||
let(:params) { { email: email, stripeToken: 'stripe-token', amount: '1234', other: 'redundant param' } }
|
||||
|
||||
xit 'creates a customer and charges them an amount' do
|
||||
::Stripe::Customer.expects(:create).with(
|
||||
email: email,
|
||||
source: 'stripe-token'
|
||||
).returns(customer)
|
||||
::Stripe::Charge.expects(:create).with(
|
||||
customer: customer.id,
|
||||
amount: params[:amount],
|
||||
description: stripe_options[:description],
|
||||
currency: stripe_options[:currency]
|
||||
).returns(
|
||||
paid: true,
|
||||
outcome: { seller_message: 'yay!' }
|
||||
)
|
||||
subject.charge(nil, params)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.successful?' do
|
||||
let(:params) { { email: email, stripeToken: 'stripe-token', amount: '1234', other: 'redundant param' } }
|
||||
let(:charge_options) do
|
||||
{
|
||||
customer: customer.id,
|
||||
amount: params[:amount],
|
||||
description: stripe_options[:description],
|
||||
currency: stripe_options[:currency],
|
||||
receipt_email: customer.email,
|
||||
metadata: { discourse_cause: nil }
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
::Stripe::Customer.expects(:create).returns(customer)
|
||||
end
|
||||
|
||||
it 'is successful' do
|
||||
::Stripe::Charge.expects(:create).with(charge_options).returns(paid: true)
|
||||
|
||||
::Stripe::Customer.expects(:list).returns(data: [])
|
||||
|
||||
subject.charge(nil, params)
|
||||
expect(subject).to be_successful
|
||||
end
|
||||
|
||||
it 'is not successful' do
|
||||
::Stripe::Charge.expects(:create).with(charge_options).returns(paid: false)
|
||||
::Stripe::Customer.expects(:list).returns(data: [])
|
||||
subject.charge(nil, params)
|
||||
expect(subject).not_to be_successful
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,139 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fakeweb'
|
||||
|
||||
#TODO register some fixtures
|
||||
|
||||
FakeWeb.register_uri(:post, 'https://api.stripe.com/v1/customers',
|
||||
body: '{
|
||||
"id": "cus_AJqrL4OU1sffPl",
|
||||
"object": "customer",
|
||||
"account_balance": 0,
|
||||
"created": 1489965018,
|
||||
"currency": "aud",
|
||||
"default_source": "card_19zDADEfVxQsvRbHVooMYHqg",
|
||||
"delinquent": false,
|
||||
"description": null,
|
||||
"discount": null,
|
||||
"email": "jo@example.com",
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
{
|
||||
"id": "card_19zDADEfVxQsvRbHVooMYHqg",
|
||||
"object": "card",
|
||||
"address_city": null,
|
||||
"address_country": null,
|
||||
"address_line1": null,
|
||||
"address_line1_check": null,
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": null,
|
||||
"address_zip_check": null,
|
||||
"brand": "MasterCard",
|
||||
"country": "US",
|
||||
"customer": "cus_AJqrL4OU1sffPl",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 11,
|
||||
"exp_year": 2022,
|
||||
"funding": "credit",
|
||||
"last4": "4444",
|
||||
"metadata": {
|
||||
},
|
||||
"name": null,
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_AJqrL4OU1sffPl/sources"
|
||||
}
|
||||
}',
|
||||
status: ['200', 'OK']
|
||||
)
|
||||
|
||||
FakeWeb.register_uri(:post, 'https://api.stripe.com/v1/charges',
|
||||
body: '{
|
||||
"id": "ch_19zDAFEfVxQsvRbHtAwsCvV0",
|
||||
"object": "charge",
|
||||
"amount": 100,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"balance_transaction": "txn_19wkkaEfVxQsvRbH8rnq3SAK",
|
||||
"captured": true,
|
||||
"created": 1489965019,
|
||||
"currency": "aud",
|
||||
"customer": "cus_AJqrL4OU1sffPl",
|
||||
"description": "Donation",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {
|
||||
},
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
},
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"receipt_email": null,
|
||||
"receipt_number": null,
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"object": "list",
|
||||
"data": [
|
||||
|
||||
],
|
||||
"has_more": false,
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_19zDAFEfVxQsvRbHtAwsCvV0/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"id": "card_19zDADEfVxQsvRbHVooMYHqg",
|
||||
"object": "card",
|
||||
"address_city": null,
|
||||
"address_country": null,
|
||||
"address_line1": null,
|
||||
"address_line1_check": null,
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": null,
|
||||
"address_zip_check": null,
|
||||
"brand": "MasterCard",
|
||||
"country": "US",
|
||||
"customer": "cus_AJqrL4OU1sffPl",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 11,
|
||||
"exp_year": 2022,
|
||||
"funding": "credit",
|
||||
"last4": "4444",
|
||||
"metadata": {
|
||||
},
|
||||
"name": null,
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": null,
|
||||
"status": "succeeded",
|
||||
"transfer_group": null
|
||||
}',
|
||||
status: ['200', 'OK']
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
import componentTest from "helpers/component-test";
|
||||
|
||||
moduleForComponent("donation-form", { integration: true });
|
||||
|
||||
componentTest("donation form has content", {
|
||||
template: `{{donation-form}}`,
|
||||
|
||||
beforeEach() {
|
||||
this.registry.register(
|
||||
"component:stripe-card",
|
||||
Ember.Component.extend({ tagName: "dummy-component-tag" })
|
||||
);
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
assert.ok(find("#payment-form").length, "The form renders");
|
||||
assert.ok(
|
||||
find("dummy-component-tag").length,
|
||||
"The stripe component renders"
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
import componentTest from "helpers/component-test";
|
||||
|
||||
moduleForComponent("donation-row", { integration: true });
|
||||
|
||||
componentTest("donation-row", {
|
||||
template: `{{donation-row currency=3 amount=21 period='monthly'}}`,
|
||||
|
||||
test(assert) {
|
||||
assert.equal(find(".donation-row-currency").text(), "3", "It has currency");
|
||||
assert.equal(find(".donation-row-amount").text(), "21", "It has an amount");
|
||||
assert.equal(
|
||||
find(".donation-row-period").text(),
|
||||
"monthly",
|
||||
"It has a period"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
componentTest("donation-row cancels subscription", {
|
||||
template: `{{donation-row currentUser=currentUser subscription=subscription}}`,
|
||||
|
||||
beforeEach() {
|
||||
this.set("currentUser", true);
|
||||
this.set("subscription", true);
|
||||
},
|
||||
|
||||
async test(assert) {
|
||||
assert.ok(
|
||||
find(".donation-row-subscription").length,
|
||||
"It has a subscription"
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
import componentTest from "helpers/component-test";
|
||||
|
||||
moduleForComponent("stripe-card", { integration: true });
|
||||
|
||||
window.Stripe = function() {
|
||||
return {
|
||||
elements: function() {
|
||||
return {
|
||||
create: function() {
|
||||
return {
|
||||
mount: function() {},
|
||||
card: function() {}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
componentTest("stripe card", {
|
||||
template: `{{stripe-card donateAmounts=donateAmounts}}`,
|
||||
|
||||
skip: true,
|
||||
|
||||
beforeEach() {
|
||||
Discourse.SiteSettings.discourse_donations_types = "";
|
||||
this.set("donateAmounts", [{ value: 2 }]);
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
assert.ok(true);
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue