houdini/lib/insert/insert_charge.rb
Bradley M. Kuhn 6772312ea7 Relicense all .rb files under new project license.
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.
2018-03-25 15:10:40 -04:00

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