houdini/lib/insert/insert_charge.rb

214 lines
7.1 KiB
Ruby
Raw Normal View History

# 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