6772312ea7
The primary license of the project is changing to: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later with some specific files to be licensed under the one of two licenses: CC0-1.0 LGPL-3.0-or-later This commit is one of the many steps to relicense the entire codebase. Documentation granting permission for this relicensing (from all past contributors who hold copyrights) is on file with Software Freedom Conservancy, Inc.
213 lines
7.1 KiB
Ruby
213 lines
7.1 KiB
Ruby
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
|
require 'psql'
|
|
require 'qexpr'
|
|
require 'calculate/calculate_fees'
|
|
require 'stripe'
|
|
require 'get_data'
|
|
require 'active_support/core_ext'
|
|
require 'query/billing_plans'
|
|
require 'stripe_account' unless !Settings.payment_provider.stripe_connect
|
|
|
|
module InsertCharge
|
|
|
|
# In data, pass in: amount, nonprofit_id, supporter_id, card_id, statement
|
|
# Optionally pass in :metadata for stripe and donation_id to connect to donation?
|
|
# @raise [ParamValidation::ValidationError] parameter validation occurred
|
|
# @raise [Stripe::StripeError] the stripe account couldn't be accessed or created
|
|
def self.with_stripe(data)
|
|
begin
|
|
ParamValidation.new(data || {}, {
|
|
:amount => {
|
|
:required => true,
|
|
:is_integer => true,
|
|
:min => 0
|
|
},
|
|
:nonprofit_id => {
|
|
:required => true,
|
|
:is_integer => true
|
|
},
|
|
:supporter_id => {
|
|
:required => true,
|
|
:is_integer => true
|
|
},
|
|
:card_id => {
|
|
:required => true,
|
|
:is_integer => true
|
|
},
|
|
:statement => {
|
|
:required => true,
|
|
:not_blank => true
|
|
}
|
|
})
|
|
|
|
np = Nonprofit.where('id = ?', data[:nonprofit_id]).first
|
|
|
|
unless np
|
|
raise ParamValidation::ValidationError.new("#{data[:nonprofit_id]} is not a valid Nonprofit", {:key => :nonprofit_id})
|
|
end
|
|
|
|
supporter = Supporter.where('id = ?', data[:supporter_id]).first
|
|
|
|
unless supporter
|
|
raise ParamValidation::ValidationError.new("#{data[:supporter_id]} is not a valid Supporter", {:key => :supporter_id})
|
|
end
|
|
|
|
card = Card.where('id = ?', data[:card_id]).first
|
|
|
|
unless card
|
|
raise ParamValidation::ValidationError.new("#{data[:card_id]} is not a valid card", {:key => :card_id})
|
|
end
|
|
|
|
unless np == supporter.nonprofit
|
|
raise ParamValidation::ValidationError.new("#{data[:supporter_id]} does not belong to this nonprofit #{np.id}", {:key => :supporter_id})
|
|
end
|
|
|
|
unless card.holder == supporter
|
|
if (data[:old_donation])
|
|
#these are not new donations so we let them fly (for now)
|
|
Airbrake.notify(ParamValidation::ValidationError.new("#{data[:card_id]} does not belong to this supporter #{supporter.id} as warning", {:key => :card_id}))
|
|
else
|
|
raise ParamValidation::ValidationError.new("#{data[:card_id]} does not belong to this supporter #{supporter.id}", {:key => :card_id})
|
|
end
|
|
end
|
|
|
|
result = {}
|
|
# Catch errors thrown by the stripe gem so we can respond with a 422 with an error message rather than 500
|
|
begin
|
|
stripe_customer_id = card.stripe_customer_id
|
|
stripe_account_id = StripeAccount.find_or_create(data[:nonprofit_id])
|
|
rescue => e
|
|
Airbrake.notify(e, other_data: data)
|
|
raise e
|
|
end
|
|
nonprofit_currency = Qx.select(:currency).from(:nonprofits).where("id=$id", id: data[:nonprofit_id]).execute.first['currency']
|
|
|
|
stripe_charge_data = {
|
|
customer: stripe_customer_id,
|
|
amount: data[:amount],
|
|
currency: nonprofit_currency,
|
|
description: data[:statement],
|
|
statement_descriptor: data[:statement][0..21].gsub(/[<>"']/,''),
|
|
metadata: data[:metadata]
|
|
}
|
|
|
|
if Settings.payment_provider.stripe_connect
|
|
stripe_account_id = StripeAccount.find_or_create(data[:nonprofit_id])
|
|
# Get the percentage fee on the nonprofit's billing plan
|
|
platform_fee = BillingPlans.get_percentage_fee(data[:nonprofit_id])
|
|
fee = CalculateFees.for_single_amount(data[:amount], platform_fee)
|
|
stripe_charge_data[:application_fee]= fee
|
|
|
|
# For backwards compatibility, see if the customer exists in the primary or the connected account
|
|
# If it's a legacy customer, charge to the primary account and transfer with .destination
|
|
# Otherwise, charge directly to the connected account
|
|
begin
|
|
stripe_cust = Stripe::Customer.retrieve(stripe_customer_id)
|
|
params = [stripe_charge_data.merge(destination: stripe_account_id), {}]
|
|
rescue
|
|
params = [stripe_charge_data, {stripe_account: stripe_account_id}]
|
|
end
|
|
else
|
|
fee=0
|
|
stripe_charge_data[:source]=card['stripe_card_id']
|
|
params = [stripe_charge_data, {}]
|
|
end
|
|
|
|
begin
|
|
stripe_charge = Stripe::Charge.create(*params)
|
|
rescue Stripe::CardError => e
|
|
failure_message = "There was an error with your card: #{e.json_body[:error][:message]}"
|
|
Airbrake.notify(e)
|
|
rescue Stripe::StripeError => e
|
|
failure_message = "We're sorry, but something went wrong. We've been notified about this issue."
|
|
Airbrake.notify(e)
|
|
end
|
|
|
|
|
|
charge = Charge.new
|
|
|
|
charge.amount = data[:amount]
|
|
charge.fee = fee
|
|
|
|
charge.stripe_charge_id = GetData.chain(stripe_charge, :id)
|
|
charge.failure_message = failure_message
|
|
charge.status = GetData.chain(stripe_charge, :paid) ? 'pending' : 'failed'
|
|
charge.card = card
|
|
charge.donation = Donation.where('id = ?', data[:donation_id]).first
|
|
charge.supporter = supporter
|
|
charge.nonprofit = np
|
|
charge.save!
|
|
result['charge'] = charge
|
|
|
|
if stripe_charge && stripe_charge.status != 'failed'
|
|
payment = Payment.new
|
|
payment.gross_amount = data[:amount]
|
|
payment.fee_total = -fee
|
|
payment.net_amount = data[:amount] - fee
|
|
payment.towards = data[:towards]
|
|
payment.kind = data[:kind]
|
|
payment.donation = Donation.where('id = ?', data[:donation_id]).first
|
|
payment.nonprofit = np
|
|
payment.supporter = supporter
|
|
payment.refund_total = 0
|
|
payment.date = data[:date] || result['charge'].created_at
|
|
payment.save!
|
|
|
|
|
|
result['payment'] = payment
|
|
|
|
charge.payment = payment
|
|
charge.save!
|
|
result['charge'] = charge
|
|
end
|
|
|
|
return result
|
|
rescue => e
|
|
Airbrake.notify(e)
|
|
raise e
|
|
end
|
|
end
|
|
|
|
def self.with_sepa(data)
|
|
result = {}
|
|
entities = RetrieveActiveRecordItems.retrieve_from_keys(data, DirectDebitDetail => :direct_debit_detail_id, Supporter => :supporter_id, Nonprofit => :nonprofit_id)
|
|
nonprofit_currency = entities[:nonprofit_id].currency
|
|
|
|
# TODO
|
|
fee = 0
|
|
|
|
#todo charge should be changed to SEPA charge
|
|
|
|
c = Charge.new
|
|
c.direct_debit_detail = entities[:direct_debit_detail_id]
|
|
c.amount = data[:amount]
|
|
c.fee = fee
|
|
c.status = 'pending'
|
|
c.nonprofit = entities[:nonprofit_id]
|
|
c.supporter = entities[:supporter_id]
|
|
c.save!
|
|
|
|
result['charge'] = c
|
|
|
|
p = Payment.new
|
|
|
|
p.gross_amount= data[:amount]
|
|
p.fee_total= -fee
|
|
p.net_amount= data[:amount] - fee
|
|
p.towards = data[:towards]
|
|
p.kind = data[:kind]
|
|
p.nonprofit = entities[:nonprofit_id]
|
|
p.supporter = entities[:supporter_id]
|
|
p.refund_total = 0
|
|
p.date = data[:date] || result['charge'].created_at
|
|
p.save!
|
|
|
|
result['payment'] = p
|
|
|
|
c.payment = p
|
|
c.save!
|
|
p.save!
|
|
|
|
result
|
|
end
|
|
end
|