# 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