2019-07-30 21:29:24 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-06-12 20:03:43 +00:00
|
|
|
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
|
|
|
|
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
|
2018-03-25 17:30:42 +00:00
|
|
|
require 'rails_helper'
|
|
|
|
require 'support/payments_for_a_payout'
|
|
|
|
|
|
|
|
describe InsertPayout do
|
2019-07-30 21:29:24 +00:00
|
|
|
let(:bank_name) { 'CHASE *1234' }
|
|
|
|
let(:supporter) { force_create(:supporter) }
|
|
|
|
let(:user_email) { 'uzr@example.com' }
|
|
|
|
let(:user_ip) { '8.8.8.8' }
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
describe '.with_stripe' do
|
|
|
|
describe 'param validation' do
|
|
|
|
it 'basic param validation' do
|
2019-07-30 21:29:24 +00:00
|
|
|
expect { InsertPayout.with_stripe(nil, nil, nil) }.to(raise_error do |error|
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(error).to be_a ParamValidation::ValidationError
|
|
|
|
expect_validation_errors(error.data, [
|
2019-07-30 21:29:24 +00:00
|
|
|
{ key: :np_id, name: :required },
|
|
|
|
{ key: :np_id, name: :is_integer },
|
|
|
|
{ key: :stripe_account_id, name: :required },
|
|
|
|
{ key: :stripe_account_id, name: :not_blank },
|
|
|
|
{ key: :email, name: :required },
|
|
|
|
{ key: :email, name: :not_blank },
|
|
|
|
{ key: :user_ip, name: :required },
|
|
|
|
{ key: :user_ip, name: :not_blank },
|
|
|
|
{ key: :bank_name, name: :required },
|
|
|
|
{ key: :bank_name, name: :not_blank }
|
|
|
|
])
|
|
|
|
end)
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'validates nonprofit' do
|
2019-07-30 21:29:24 +00:00
|
|
|
expect { InsertPayout.with_stripe(666, { stripe_account_id: 'valid', email: 'valid', user_ip: 'valid', bank_name: 'valid' }, nil) }.to(raise_error do |error|
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(error).to be_a ParamValidation::ValidationError
|
2019-07-30 21:29:24 +00:00
|
|
|
expect_validation_errors(error.data, [{ key: :np_id }])
|
|
|
|
end)
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when valid' do
|
2019-07-30 21:29:24 +00:00
|
|
|
let(:stripe_helper) { StripeMock.create_test_helper }
|
2018-03-25 17:30:42 +00:00
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
before(:each) do
|
2018-03-25 17:30:42 +00:00
|
|
|
Timecop.freeze(2020, 5, 4)
|
|
|
|
StripeMock.start
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
after do
|
2018-03-25 17:30:42 +00:00
|
|
|
StripeMock.stop
|
|
|
|
Timecop.return
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
it 'handles no charges to payout' do
|
2020-04-16 20:50:03 +00:00
|
|
|
np = force_create(:nm_justice)
|
2019-07-30 21:29:24 +00:00
|
|
|
expect { InsertPayout.with_stripe(np.id, { stripe_account_id: 'valid', email: 'valid', user_ip: 'valid', bank_name: 'valid' }, nil) }.to(raise_error do |error|
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(error).to be_a ArgumentError
|
2019-07-30 21:29:24 +00:00
|
|
|
expect(error.message).to eq 'No payments are available for disbursal on this account.'
|
|
|
|
end)
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
2019-07-30 21:29:24 +00:00
|
|
|
let(:user) { force_create(:user) }
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# Test one basic charge, one charge with a partial refund, and one charge with a full refund
|
|
|
|
|
|
|
|
# refunded payment
|
|
|
|
# disputed payment
|
|
|
|
|
|
|
|
# Charge which was after given date
|
|
|
|
#
|
|
|
|
# Already paid out charge
|
|
|
|
# Already paid out dispute
|
|
|
|
# already paid out refund
|
|
|
|
|
|
|
|
context 'no date provided' do
|
|
|
|
include_context 'payments for a payout' do
|
2020-04-16 20:50:03 +00:00
|
|
|
let(:np) { force_create(:nm_justice, stripe_account_id: Stripe::Account.create['id'], vetted: true) }
|
2019-07-30 21:29:24 +00:00
|
|
|
let(:date_for_marking) { Time.now }
|
|
|
|
let(:ba) do
|
|
|
|
InsertBankAccount.with_stripe(np, user, stripe_bank_account_token: StripeMock.generate_bank_token, name: bank_name)
|
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
before(:each) do
|
2018-03-25 17:30:42 +00:00
|
|
|
ba
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
|
|
|
let(:expected_totals) { { gross_amount: 5500, fee_total: -1200, net_amount: 4300, count: 8 } }
|
2018-03-25 17:30:42 +00:00
|
|
|
it 'works without a date provided' do
|
|
|
|
stripe_transfer_id = nil
|
2019-07-30 21:29:24 +00:00
|
|
|
expect(Stripe::Transfer).to receive(:create).with({ amount: expected_totals[:net_amount],
|
|
|
|
currency: 'usd',
|
|
|
|
recipient: 'self' },
|
|
|
|
stripe_account: np.stripe_account_id)
|
|
|
|
.and_wrap_original { |m, *args|
|
|
|
|
i = m.call(*args)
|
|
|
|
stripe_transfer_id = i['id']
|
|
|
|
i
|
|
|
|
}
|
2018-03-25 17:30:42 +00:00
|
|
|
all_payments
|
2019-07-30 21:29:24 +00:00
|
|
|
result = InsertPayout.with_stripe(np.id, stripe_account_id: np.stripe_account_id,
|
|
|
|
email: user_email,
|
|
|
|
user_ip: user_ip,
|
|
|
|
bank_name: bank_name)
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
expected_result = {
|
2019-07-30 21:29:24 +00:00
|
|
|
net_amount: expected_totals[:net_amount],
|
|
|
|
nonprofit_id: np.id,
|
|
|
|
status: 'pending',
|
|
|
|
fee_total: expected_totals[:fee_total],
|
|
|
|
gross_amount: expected_totals[:gross_amount],
|
|
|
|
email: user_email,
|
|
|
|
count: expected_totals[:count],
|
|
|
|
stripe_transfer_id: stripe_transfer_id,
|
|
|
|
user_ip: user_ip,
|
|
|
|
ach_fee: 0,
|
|
|
|
bank_name: bank_name,
|
2019-08-02 09:54:17 +00:00
|
|
|
updated_at: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
created_at: Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
2018-03-25 17:30:42 +00:00
|
|
|
}.with_indifferent_access
|
|
|
|
expect(Payout.count).to eq 1
|
|
|
|
resulted_payout = Payout.first
|
|
|
|
expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
empty_db_attributes = { manual: nil, scheduled: nil, failure_message: nil }
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)
|
|
|
|
|
|
|
|
# validate which charges are makred
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:charges].each do |c|
|
|
|
|
c.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(c.status).to eq 'disbursed'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which refunds are marked
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:refunds].each do |r|
|
|
|
|
r.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(r.disbursed).to eq true
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which disputes are marked
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:disputes].each do |d|
|
2018-03-25 17:30:42 +00:00
|
|
|
d.reload
|
|
|
|
expect(d.status).to eq 'lost_and_paid'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
# validate payment payout records
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
expect(resulted_payout.payments.pluck('payments.id')).to eq @expect_marked[:payouts_records].collect(&:id)
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'fails properly when Stripe payout call fails' do
|
2019-07-30 21:29:24 +00:00
|
|
|
StripeMock.prepare_error(Stripe::StripeError.new('Payout failed'), :new_transfer)
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
all_payments
|
2019-07-30 21:29:24 +00:00
|
|
|
result = InsertPayout.with_stripe(np.id, stripe_account_id: np.stripe_account_id,
|
|
|
|
email: user_email,
|
|
|
|
user_ip: user_ip,
|
|
|
|
bank_name: bank_name)
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
expected_result = {
|
2019-07-30 21:29:24 +00:00
|
|
|
net_amount: expected_totals[:net_amount],
|
|
|
|
nonprofit_id: np.id,
|
|
|
|
status: 'failed',
|
|
|
|
fee_total: expected_totals[:fee_total],
|
|
|
|
gross_amount: expected_totals[:gross_amount],
|
|
|
|
email: user_email,
|
|
|
|
count: expected_totals[:count],
|
|
|
|
stripe_transfer_id: nil,
|
|
|
|
user_ip: user_ip,
|
|
|
|
ach_fee: 0,
|
|
|
|
bank_name: bank_name,
|
2019-08-02 09:54:17 +00:00
|
|
|
updated_at: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
created_at: Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
2018-03-25 17:30:42 +00:00
|
|
|
}.with_indifferent_access
|
|
|
|
|
|
|
|
expect(Payout.count).to eq 1
|
|
|
|
resulted_payout = Payout.first
|
|
|
|
expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
empty_db_attributes = { manual: nil, scheduled: nil, failure_message: 'Payout failed' }
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)
|
|
|
|
|
|
|
|
# validate which charges are makred
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:charges].each do |c|
|
|
|
|
c.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(c.status).to eq 'available'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which refunds are marked
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:refunds].each do |r|
|
|
|
|
r.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(r.disbursed).to be_falsey
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which disputes are marked
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:disputes].each do |d|
|
2018-03-25 17:30:42 +00:00
|
|
|
d.reload
|
|
|
|
expect(d.status).to eq 'lost'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
# validate payment payout records
|
|
|
|
|
|
|
|
expect(resulted_payout.payments.count).to eq 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'previous date provided' do
|
|
|
|
include_context 'payments for a payout' do
|
2020-04-16 20:50:03 +00:00
|
|
|
let(:np) { force_create(:nm_justice, stripe_account_id: Stripe::Account.create['id'], vetted: true) }
|
2019-07-30 21:29:24 +00:00
|
|
|
let(:date_for_marking) { Time.now - 1.day }
|
|
|
|
let(:ba) { InsertBankAccount.with_stripe(np, user, stripe_bank_account_token: StripeMock.generate_bank_token, name: bank_name) }
|
|
|
|
end
|
|
|
|
before(:each) do
|
|
|
|
ba
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
let(:expected_totals) { { gross_amount: 3500, fee_total: -800, net_amount: 2700, count: 7 } }
|
2018-03-25 17:30:42 +00:00
|
|
|
it 'works with date provided' do
|
|
|
|
stripe_transfer_id = nil
|
2019-07-30 21:29:24 +00:00
|
|
|
expect(Stripe::Transfer).to receive(:create).with({ amount: expected_totals[:net_amount],
|
|
|
|
currency: 'usd',
|
|
|
|
recipient: 'self' },
|
|
|
|
stripe_account: np.stripe_account_id)
|
|
|
|
.and_wrap_original { |m, *args|
|
|
|
|
i = m.call(*args)
|
|
|
|
stripe_transfer_id = i['id']
|
|
|
|
i
|
|
|
|
}
|
2018-03-25 17:30:42 +00:00
|
|
|
all_payments
|
2019-07-30 21:29:24 +00:00
|
|
|
result = InsertPayout.with_stripe(np.id, { stripe_account_id: np.stripe_account_id,
|
|
|
|
email: user_email,
|
|
|
|
user_ip: user_ip,
|
|
|
|
bank_name: bank_name }, date: Time.now - 1.day)
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
expected_result = {
|
2019-07-30 21:29:24 +00:00
|
|
|
net_amount: expected_totals[:net_amount],
|
|
|
|
nonprofit_id: np.id,
|
|
|
|
status: 'pending',
|
|
|
|
fee_total: expected_totals[:fee_total],
|
|
|
|
gross_amount: expected_totals[:gross_amount],
|
|
|
|
email: user_email,
|
|
|
|
count: expected_totals[:count],
|
|
|
|
stripe_transfer_id: stripe_transfer_id,
|
|
|
|
user_ip: user_ip,
|
|
|
|
ach_fee: 0,
|
|
|
|
bank_name: bank_name,
|
2019-08-02 09:54:17 +00:00
|
|
|
updated_at: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
created_at: Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
2018-03-25 17:30:42 +00:00
|
|
|
}.with_indifferent_access
|
|
|
|
expect(Payout.count).to eq 1
|
|
|
|
resulted_payout = Payout.first
|
|
|
|
expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
empty_db_attributes = { manual: nil, scheduled: nil, failure_message: nil }
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)
|
|
|
|
|
|
|
|
# validate which charges are makred
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:charges].each do |c|
|
|
|
|
c.reload
|
|
|
|
expect(c.status).to(eq('disbursed'), c.attributes.to_s)
|
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which refunds are marked
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:refunds].each do |r|
|
|
|
|
r.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(r.disbursed).to eq true
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which disputes are marked
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:disputes].each do |d|
|
2018-03-25 17:30:42 +00:00
|
|
|
d.reload
|
|
|
|
expect(d.status).to eq 'lost_and_paid'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'fails properly when Stripe payout call fails' do
|
2019-07-30 21:29:24 +00:00
|
|
|
StripeMock.prepare_error(Stripe::StripeError.new('Payout failed'), :new_transfer)
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
all_payments
|
2019-07-30 21:29:24 +00:00
|
|
|
result = InsertPayout.with_stripe(np.id, { stripe_account_id: np.stripe_account_id,
|
|
|
|
email: user_email,
|
|
|
|
user_ip: user_ip,
|
|
|
|
bank_name: bank_name }, date: Time.now - 1.day)
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
expected_result = {
|
2019-07-30 21:29:24 +00:00
|
|
|
net_amount: expected_totals[:net_amount],
|
|
|
|
nonprofit_id: np.id,
|
|
|
|
status: 'failed',
|
|
|
|
fee_total: expected_totals[:fee_total],
|
|
|
|
gross_amount: expected_totals[:gross_amount],
|
|
|
|
email: user_email,
|
|
|
|
count: expected_totals[:count],
|
|
|
|
stripe_transfer_id: nil,
|
|
|
|
user_ip: user_ip,
|
|
|
|
ach_fee: 0,
|
|
|
|
bank_name: bank_name,
|
2019-08-02 09:54:17 +00:00
|
|
|
updated_at: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
|
|
|
|
created_at: Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
2018-03-25 17:30:42 +00:00
|
|
|
}.with_indifferent_access
|
|
|
|
|
|
|
|
expect(Payout.count).to eq 1
|
|
|
|
resulted_payout = Payout.first
|
|
|
|
expect(result.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id)
|
|
|
|
|
2019-07-30 21:29:24 +00:00
|
|
|
empty_db_attributes = { manual: nil, scheduled: nil, failure_message: 'Payout failed' }
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(resulted_payout.attributes.with_indifferent_access).to eq expected_result.merge(id: resulted_payout.id).merge(empty_db_attributes)
|
|
|
|
|
|
|
|
# validate which charges are makred
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:charges].each do |c|
|
|
|
|
c.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(c.status).to eq 'available'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which refunds are marked
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:refunds].each do |r|
|
|
|
|
r.reload
|
2018-03-25 17:30:42 +00:00
|
|
|
expect(r.disbursed).to be_falsey
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
|
|
|
|
# validate which disputes are marked
|
2019-07-30 21:29:24 +00:00
|
|
|
@expect_marked[:disputes].each do |d|
|
2018-03-25 17:30:42 +00:00
|
|
|
d.reload
|
|
|
|
expect(d.status).to eq 'lost'
|
2019-07-30 21:29:24 +00:00
|
|
|
end
|
2018-03-25 17:30:42 +00:00
|
|
|
# validate payment payout records
|
|
|
|
|
|
|
|
expect(resulted_payout.payments.count).to eq 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|