# frozen_string_literal: true # 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 require 'rails_helper' describe InsertRefunds do let!(:nonprofit) { create(:nm_justice) } let!(:supporter) { create(:supporter, nonprofit: nonprofit) } let!(:payment) do force_create( :payment, gross_amount: 500, net_amount: 500 + CalculateFees.for_single_amount(500), fee_total: CalculateFees.for_single_amount(500), date: Time.zone.now, nonprofit: nonprofit, supporter: supporter, refund_total: 0 ) end let!(:charge) do create( :charge, payment: payment, stripe_charge_id: 'ch_s0m3th1ng', nonprofit: nonprofit, supporter: supporter, amount: 500 ) end before do trx = supporter.transactions.build(amount: 500) trx.build_subtransaction( subtransactable: StripeTransaction.new(amount: 500), subtransaction_payments: [ build(:subtransaction_payment, paymentable: create(:stripe_charge, payment: payment)) ] ) trx.save! trx end describe '.with_stripe' do context 'when invalid' do it 'raises an error with an invalid charge' do charge.update(stripe_charge_id: 'xxx') expect { described_class.with_stripe(charge, amount: 1) }.to raise_error(ParamValidation::ValidationError) end it 'sets a failure message an error with an invalid amount' do charge.update(amount: 0) expect { described_class.with_stripe(charge, amount: 0) }.to raise_error(ParamValidation::ValidationError) end it 'returns err if refund amount is greater than payment gross minus payment refund total' do expect { described_class.with_stripe(charge, 'amount' => 600) }.to raise_error(RuntimeError) end end context 'when valid' do let(:result) { described_class.with_stripe(charge, 'amount' => 100) } let(:retrieved_stripe_charge) { double } let(:stripe_charge_refunds) { double } let(:created_stripe_charge_refund) { double } before do allow(Stripe::Charge) .to receive(:retrieve) .with('ch_s0m3th1ng') .and_return(retrieved_stripe_charge) allow(retrieved_stripe_charge) .to receive(:refunds) .and_return(stripe_charge_refunds) allow(stripe_charge_refunds) .to receive(:create) .with({ 'amount' => 100, 'refund_application_fee' => true, 'reverse_transfer' => true }) .and_return(created_stripe_charge_refund) allow(created_stripe_charge_refund) .to receive(:id) .and_return('re_f@k3') end it 'sets the stripe refund id' do expect(result['refund']['stripe_refund_id']).to match(/^re_/) end it 'creates a negative payment for the refund with the gross amount' do expect(result['payment']['gross_amount']).to eq(-100) end it 'creates a negative payment for the refund with the net amount' do expect(result['payment']['net_amount']).to eq(-109) end it 'updates the payment_id on the refund' do expect(result['refund']['payment_id']).to eq(result['payment']['id']) end it 'increments the payment refund total by the gross amount' do result expect(payment.reload['refund_total']).to eq(100) end it 'sets the payment supporter id' do expect(result['payment']['supporter_id']).to eq(supporter['id']) end describe 'event publishing' do let(:event_publisher) { double } let(:expected_event) do { 'data' => { 'object' => { 'created' => kind_of(Numeric), 'fee_total' => { 'cents' => -9, 'currency' => nonprofit.currency }, 'gross_amount' => { 'cents' => -100, 'currency' => nonprofit.currency }, 'id' => match_houid('striperef'), 'stripe_id' => kind_of(String), 'net_amount' => { 'cents' => -109, 'currency' => nonprofit.currency }, 'nonprofit' => { 'id' => nonprofit.id, 'name' => nonprofit.name, 'object' => 'nonprofit' }, 'object' => 'stripe_transaction_refund', 'subtransaction' => { 'created' => kind_of(Numeric), 'id' => match_houid('stripetrx'), 'initial_amount' => { 'cents' => 500, 'currency' => nonprofit.currency }, 'net_amount' => { 'cents' => 432, 'currency' => nonprofit.currency }, 'nonprofit' => nonprofit.id, 'object' => 'stripe_transaction', 'payments' => [ { 'id' => match_houid('stripechrg'), 'object' => 'stripe_transaction_charge', 'type' => 'payment' }, { 'id' => match_houid('striperef'), 'object' => 'stripe_transaction_refund', 'type' => 'payment' } ], 'supporter' => supporter.id, 'transaction' => match_houid('trx'), 'type' => 'subtransaction' }, 'supporter' => { 'anonymous' => supporter.anonymous, 'deleted' => supporter.deleted, 'id' => supporter.id, 'merged_into' => supporter.merged_into, 'name' => supporter.name, 'nonprofit' => nonprofit.id, 'object' => 'supporter', 'organization' => supporter.organization, 'phone' => supporter.phone, 'supporter_addresses' => [kind_of(Numeric)] }, 'transaction' => { 'amount' => { 'cents' => 400, 'currency' => nonprofit.currency }, 'created' => kind_of(Numeric), 'id' => match_houid('trx'), 'nonprofit' => nonprofit.id, 'object' => 'transaction', 'subtransaction' => { 'id' => match_houid('stripetrx'), 'object' => 'stripe_transaction', 'type' => 'subtransaction' }, 'subtransaction_payments' => [ { 'id' => match_houid('stripechrg'), 'object' => 'stripe_transaction_charge', 'type' => 'payment' }, { 'id' => match_houid('striperef'), 'object' => 'stripe_transaction_refund', 'type' => 'payment' } ], 'supporter' => supporter.id, 'transaction_assignments' => [] }, 'type' => 'payment' } }, 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'event_type' } end let(:expected_transaction_event) do { 'data' => { 'object' => { 'amount' => { 'cents' => 400, 'currency' => nonprofit.currency }, 'created' => kind_of(Numeric), 'id' => match_houid('trx'), 'nonprofit' => { 'id' => nonprofit.id, 'name' => nonprofit.name, 'object' => 'nonprofit' }, 'object' => 'transaction', 'subtransaction' => { 'created' => kind_of(Numeric), 'id' => match_houid('stripetrx'), 'initial_amount' => { 'cents' => 500, 'currency' => nonprofit.currency }, 'net_amount' => { 'cents' => 432, 'currency' => nonprofit.currency }, 'nonprofit' => nonprofit.id, 'object' => 'stripe_transaction', 'payments' => [ { 'id' => match_houid('stripechrg'), 'object' => 'stripe_transaction_charge', 'type' => 'payment' }, { 'id' => match_houid('striperef'), 'object' => 'stripe_transaction_refund', 'type' => 'payment' } ], 'supporter' => supporter.id, 'transaction' => match_houid('trx'), 'type' => 'subtransaction' }, 'subtransaction_payments' => [ { 'created' => kind_of(Numeric), 'fee_total' => { 'cents' => 41, 'currency' => nonprofit.currency }, 'gross_amount' => { 'cents' => 500, 'currency' => nonprofit.currency }, 'id' => match_houid('stripechrg'), 'net_amount' => { 'cents' => 541, 'currency' => nonprofit.currency }, 'nonprofit' => nonprofit.id, 'object' => 'stripe_transaction_charge', 'stripe_id' => 'ch_s0m3th1ng', 'subtransaction' => { 'id' => match_houid('stripetrx'), 'object' => 'stripe_transaction', 'type' => 'subtransaction' }, 'supporter' => supporter.id, 'transaction' => match_houid('trx'), 'type' => 'payment' }, { 'created' => kind_of(Numeric), 'fee_total' => { 'cents' => -9, 'currency' => nonprofit.currency }, 'gross_amount' => { 'cents' => -100, 'currency' => nonprofit.currency }, 'id' => match_houid('striperef'), 'net_amount' => { 'cents' => -109, 'currency' => nonprofit.currency }, 'nonprofit' => nonprofit.id, 'object' => 'stripe_transaction_refund', 'stripe_id' => 're_f@k3', 'subtransaction' => { 'id' => match_houid('stripetrx'), 'object' => 'stripe_transaction', 'type' => 'subtransaction' }, 'supporter' => supporter.id, 'transaction' => match_houid('trx'), 'type' => 'payment' } ], 'supporter' => { 'anonymous' => false, 'deleted' => false, 'id' => supporter.id, 'merged_into' => nil, 'name' => supporter.name, 'nonprofit' => nonprofit.id, 'object' => 'supporter', 'organization' => nil, 'phone' => nil, 'supporter_addresses' => [kind_of(Numeric)] }, 'transaction_assignments' => [] } }, 'id' => match_houid('objevt'), 'object' => 'object_event', 'type' => 'transaction.updated' } end before do allow(Houdini) .to receive(:event_publisher) .and_return(event_publisher) allow(event_publisher) .to receive(:announce) .with(:payment_created, anything) allow(event_publisher) .to receive(:announce) .with(:stripe_transaction_refund_created, anything) allow(event_publisher) .to receive(:announce) .with(:transaction_refunded, anything) allow(event_publisher) .to receive(:announce) .with(:transaction_updated, anything) allow(event_publisher) .to receive(:announce) .with(:create_refund, anything) result end it 'publishes that a transaction was updated' do expected_transaction_event['type'] = 'transaction.updated' expect(event_publisher) .to have_received(:announce) .with(:transaction_updated, expected_transaction_event) end it 'publishes that a transaction was refunded' do expected_transaction_event['type'] = 'transaction.refunded' expect(event_publisher) .to have_received(:announce) .with(:transaction_refunded, expected_transaction_event) end it 'publishes that a stripe_transaction_refund was created' do expected_event['type'] = 'stripe_transaction_refund.created' expect(event_publisher) .to have_received(:announce) .with(:stripe_transaction_refund_created, expected_event) end it 'publishes that a payment was created' do expected_event['type'] = 'payment.created' expect(event_publisher) .to have_received(:announce) .with(:payment_created, expected_event) end it 'publishes that a refund was created' do expect(event_publisher) .to have_received(:announce) .with(:create_refund, kind_of(Refund)) end end end end end