# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later require 'rails_helper' require 'stripe_mock' describe InsertCharge do include_context :shared_donation_charge_context let!(:donation) {force_create(:donation, id: 555)} describe '.with_stripe' do before(:each){ Settings.payment_provider.stripe_connect = true } describe 'param validation' do it 'does basic validation' do expect { InsertCharge.with_stripe(nil) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ {:key => :amount, :name => :required}, {:key => :amount, :name => :is_integer}, {:key => :amount, :name => :min}, {:key => :nonprofit_id, :name => :required}, {:key => :nonprofit_id, :name => :is_integer}, {:key => :supporter_id, :name => :required}, {:key => :supporter_id, :name => :is_integer}, {:key => :card_id, :name => :required}, {:key => :card_id, :name => :is_integer}, {:key => :statement, :name => :required}, {:key => :statement, :name => :not_blank} ]) }) end it 'verify the amount minimum works' do expect { InsertCharge.with_stripe({amount: -1}) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ {:key => :amount, :name => :min}, {:key => :nonprofit_id, :name => :required}, {:key => :nonprofit_id, :name => :is_integer}, {:key => :supporter_id, :name => :required}, {:key => :supporter_id, :name => :is_integer}, {:key => :card_id, :name => :required}, {:key => :card_id, :name => :is_integer}, {:key => :statement, :name => :required}, {:key => :statement, :name => :not_blank} ]) }) end it 'verify that we check for valid nonprofit' do expect { InsertCharge.with_stripe({amount: 100, :nonprofit_id => 5555, :supporter_id => 5555, :card_id => 5555, :statement => 'our statement'}) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ {:key => :nonprofit_id} ]) }) end it 'verify that we check for valid supporter' do expect { InsertCharge.with_stripe({amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => 5555, :card_id => 5555, :statement => 'our statement'}) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ {:key => :supporter_id} ]) }) end it 'verify that we check for valid card' do expect { InsertCharge.with_stripe({amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => 5555, :statement => 'our statement'}) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect_validation_errors(error.data, [ {:key => :card_id} ]) }) end it 'verify that we check that the supporter belongs to the correct nonprofit' do expect { InsertCharge.with_stripe({amount: 100, :nonprofit_id => other_nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :statement => 'our statement'}) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect(error.message).to eq "#{supporter.id} does not belong to this nonprofit #{other_nonprofit.id}" expect_validation_errors(error.data, [ {:key => :supporter_id} ]) }) end it 'verify that we check that the card belongs to the correct supporter' do expect { InsertCharge.with_stripe({amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card_for_other_supporter.id, :statement => 'our statement'}) }.to(raise_error {|error| expect(error).to be_a ParamValidation::ValidationError expect(error.message).to eq "#{card_for_other_supporter.id} does not belong to this supporter #{supporter.id}" expect_validation_errors(error.data, [ {:key => :card_id} ]) }) end end describe 'handle StripeAccount Find and Create failure' do before(:each){ StripeMock.prepare_error(Stripe::StripeError.new("chaos"), :new_account) } it 'does it fail properly' do expect{ InsertCharge.with_stripe(amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :statement => 'our statement') }.to( raise_error{|error| expect(error).to be_a Stripe::StripeError }) expect(Charge).to_not be_exists expect(Payment).to_not be_exists end end describe 'charge when customer belongs to client' do before(:each){ nonprofit.stripe_account_id = Stripe::Account.create()['id'] nonprofit.save! card.stripe_customer_id = 'some other id' card.save! StripeMock.prepare_error(Stripe::StripeError.new("chaos"), :get_customer) } it 'handles card error' do expect(Stripe::Charge).to receive(:create).with({application_fee: 33, customer: card.stripe_customer_id, amount: 100, currency: 'usd', description: 'our statement<> blah-no-way', statement_descriptor: 'our statement blah-n', metadata: nil }, {stripe_account: nonprofit.stripe_account_id}).and_wrap_original{|m, *args| m.call(*args)} StripeMock.prepare_card_error(:card_declined) finished_result = InsertCharge.with_stripe(amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :statement => 'our statement<> blah-no-way') common_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id: nil, status: 'failed', failure_message: 'There was an error with your card: The card was declined', created_at: Time.now, updated_at: Time.now, disbursed: nil} result_expected = common_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: nil, supporter_id: supporter.id, ticket_id: nil, payment_id: nil, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_expected expect(Charge.first.attributes).to eq result_expected expect(Payment).to_not be_exists end it 'handles general Stripe error' do expect(Stripe::Charge).to receive(:create).with({application_fee: 33, customer: card.stripe_customer_id, amount: 100, currency: 'usd', description: 'our statement<> blah-no-way', statement_descriptor: 'our statement blah-n', metadata: nil }, {stripe_account: nonprofit.stripe_account_id}).and_wrap_original{|m, *args| m.call(*args)} StripeMock.prepare_error(Stripe::StripeError.new("blah"), :new_charge) finished_result = InsertCharge.with_stripe(amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :statement => 'our statement<> blah-no-way') common_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id: nil, status: 'failed', failure_message: "We're sorry, but something went wrong. We've been notified about this issue.", created_at: Time.now, updated_at: Time.now, disbursed: nil} result_expected = common_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: nil, supporter_id: supporter.id, ticket_id: nil, payment_id: nil, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_expected expect(Charge.first.attributes).to eq result_expected expect(Payment).to_not be_exists end describe 'input success' do let(:valid_input) { {amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :donation_id => 555, :towards => 'blah', :kind => 'kind', :statement => 'our statement<> blah-no-way'} } let (:date) { Time.new(2002,10,31)} let(:valid_input_with_date) { valid_input.merge(:date => date)} it 'saves the payment and updates the charge' do stripe_charge_id = nil expect(Stripe::Charge).to receive(:create).with({application_fee: 33, customer: card.stripe_customer_id, amount: 100, currency: 'usd', description: 'our statement<> blah-no-way', statement_descriptor: 'our statement blah-n', metadata: nil }, {stripe_account: nonprofit.stripe_account_id}).and_wrap_original{|m, *args| a= m.call(*args); stripe_charge_id = a['id'] a} finished_result = InsertCharge.with_stripe(valid_input) common_charge_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id:stripe_charge_id, status: 'pending', failure_message: nil, created_at: Time.now, updated_at: Time.now, disbursed: nil} result_charge_expected = common_charge_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: 555, supporter_id: supporter.id, ticket_id: nil, payment_id: Payment.first.id, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_charge_expected expect(Charge.first.attributes).to eq result_charge_expected expect(Charge.count).to eq 1 common_payment_expected = {id: Payment.first.id, gross_amount: 100, fee_total: -33, net_amount: 67, towards: 'blah', kind: 'kind', donation_id: 555, nonprofit_id: nonprofit.id, supporter_id: supporter.id, refund_total: 0, date: Time.now, created_at: Time.now, updated_at: Time.now, search_vectors: nil }.with_indifferent_access expect(finished_result['payment'].attributes).to eq common_payment_expected expect(Payment.first.attributes).to eq common_payment_expected expect(Payment.count).to eq 1 end it 'saves the payment and updates the charge with passed date' do stripe_charge_id = nil expect(Stripe::Charge).to receive(:create).with({application_fee: 33, customer: card.stripe_customer_id, amount: 100, currency: 'usd', description: 'our statement<> blah-no-way', statement_descriptor: 'our statement blah-n', metadata: nil }, {stripe_account: nonprofit.stripe_account_id}).and_wrap_original{|m, *args| a= m.call(*args); stripe_charge_id = a['id'] a} finished_result = InsertCharge.with_stripe(valid_input_with_date) common_charge_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id:stripe_charge_id, status: 'pending', failure_message: nil, created_at: Time.now, updated_at: Time.now, disbursed: nil} result_charge_expected = common_charge_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: 555, supporter_id: supporter.id, ticket_id: nil, payment_id: Payment.first.id, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_charge_expected expect(Charge.first.attributes).to eq result_charge_expected expect(Charge.count).to eq 1 common_payment_expected = {id: Payment.first.id, gross_amount: 100, fee_total: -33, net_amount: 67, towards: 'blah', kind: 'kind', donation_id: 555, nonprofit_id: nonprofit.id, supporter_id: supporter.id, refund_total: 0, date: date, created_at: Time.now, updated_at: Time.now, search_vectors: nil }.with_indifferent_access expect(finished_result['payment'].attributes).to eq common_payment_expected expect(Payment.first.attributes).to eq common_payment_expected expect(Payment.count).to eq 1 end end end describe 'charge when customer belongs to us' do before(:each){ nonprofit.stripe_account_id = Stripe::Account.create()['id'] nonprofit.save! card.stripe_customer_id = 'some other id' cust = Stripe::Customer.create() card.stripe_customer_id = cust['id'] card.save! new_cust = Stripe::Customer.create() card_for_other_supporter.stripe_customer_id = new_cust['id'] card_for_other_supporter.save! # StripeMock.prepare_error(Stripe::StripeError.new("chaos"), :get_customer) } def create_expected_charge_args(expected_card) [{application_fee: 33, customer: expected_card.stripe_customer_id, amount: 100, currency: 'usd', description: 'our statement<> blah-no-way', statement_descriptor: 'our statement blah-n', metadata: nil, destination: nonprofit.stripe_account_id }, {}] end it 'handles card error' do expect(Stripe::Charge).to receive(:create).with(*create_expected_charge_args(card)).and_wrap_original{|m, *args| m.call(*args)} StripeMock.prepare_card_error(:card_declined) finished_result = InsertCharge.with_stripe(amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :statement => 'our statement<> blah-no-way') common_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id: nil, status: 'failed', failure_message: 'There was an error with your card: The card was declined', created_at: Time.now, updated_at: Time.now, disbursed: nil} result_expected = common_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: nil, supporter_id: supporter.id, ticket_id: nil, payment_id: nil, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_expected expect(Charge.first.attributes).to eq result_expected expect(Payment).to_not be_exists end it 'handles general Stripe error' do expect(Stripe::Charge).to receive(:create).with(*create_expected_charge_args(card)).and_wrap_original{|m, *args| m.call(*args)} StripeMock.prepare_error(Stripe::StripeError.new("blah"), :new_charge) finished_result = InsertCharge.with_stripe(amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, :card_id => card.id, :statement => 'our statement<> blah-no-way') common_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id: nil, status: 'failed', failure_message: "We're sorry, but something went wrong. We've been notified about this issue.", created_at: Time.now, updated_at: Time.now, disbursed: nil} result_expected = common_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: nil, supporter_id: supporter.id, ticket_id: nil, payment_id: nil, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_expected expect(Charge.first.attributes).to eq result_expected expect(Payment).to_not be_exists end describe 'input success' do let (:date) { Time.new(2002,10,31)} it 'saves the payment and updates the charge' do saves_the_payment_updates_the_charge(card) end it 'saves the payment and updates the charge, if old rd and using wrong card' do saves_the_payment_updates_the_charge(card_for_other_supporter, true) end it 'saves the payment and updates the charge with passed date' do saves_the_payment_and_updates_the_charge_with_passed_date(card) end it 'saves the payment and updates the charge with passed date, if old rd and using wrong card' do saves_the_payment_and_updates_the_charge_with_passed_date(card, true) end def insert_charge_input(expected_card, pass_old_donation=nil, pass_date=nil) inner = {amount: 100, :nonprofit_id => nonprofit.id, :supporter_id => supporter.id, card_id: expected_card.id, :donation_id => 555, :towards => 'blah', :kind => 'kind', :statement => 'our statement<> blah-no-way', } if pass_old_donation inner = inner.merge(old_donation: true) end if pass_date inner = inner.merge(date: date) end inner end def saves_the_payment_updates_the_charge(expected_card, pass_old_donation=nil) stripe_charge_id = nil expect(Stripe::Charge).to receive(:create).with(*create_expected_charge_args(expected_card)).and_wrap_original{|m, *args| a= m.call(*args); stripe_charge_id = a['id'] a} finished_result = InsertCharge.with_stripe(insert_charge_input(expected_card, pass_old_donation)) common_charge_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id:stripe_charge_id, status: 'pending', failure_message: nil, created_at: Time.now, updated_at: Time.now, disbursed: nil} result_charge_expected = common_charge_expected.merge({card_id: expected_card.id, nonprofit_id: nonprofit.id, donation_id: 555, supporter_id: supporter.id, ticket_id: nil, payment_id: Payment.first.id, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_charge_expected expect(Charge.first.attributes).to eq result_charge_expected expect(Charge.count).to eq 1 common_payment_expected = {id: Payment.first.id, gross_amount: 100, fee_total: -33, net_amount: 67, towards: 'blah', kind: 'kind', donation_id: 555, nonprofit_id: nonprofit.id, supporter_id: supporter.id, refund_total: 0, date: Time.now, created_at: Time.now, updated_at: Time.now, search_vectors: nil }.with_indifferent_access expect(finished_result['payment'].attributes).to eq common_payment_expected expect(Payment.first.attributes).to eq common_payment_expected expect(Payment.count).to eq 1 end def saves_the_payment_and_updates_the_charge_with_passed_date(expected_card, pass_old_donation=nil) stripe_charge_id = nil expect(Stripe::Charge).to receive(:create).with(*create_expected_charge_args(expected_card)).and_wrap_original{|m, *args| a= m.call(*args); stripe_charge_id = a['id'] a} finished_result = InsertCharge.with_stripe(insert_charge_input(expected_card, pass_old_donation, true)) common_charge_expected = {id: Charge.first.id, amount: 100, fee: 33, stripe_charge_id:stripe_charge_id, status: 'pending', failure_message: nil, created_at: Time.now, updated_at: Time.now, disbursed: nil} result_charge_expected = common_charge_expected.merge({card_id: card.id, nonprofit_id: nonprofit.id, donation_id: 555, supporter_id: supporter.id, ticket_id: nil, payment_id: Payment.first.id, profile_id: nil, direct_debit_detail_id: nil}).with_indifferent_access expect(finished_result['charge'].attributes).to eq result_charge_expected expect(Charge.first.attributes).to eq result_charge_expected expect(Charge.count).to eq 1 common_payment_expected = {id: Payment.first.id, gross_amount: 100, fee_total: -33, net_amount: 67, towards: 'blah', kind: 'kind', donation_id: 555, nonprofit_id: nonprofit.id, supporter_id: supporter.id, refund_total: 0, date: date, created_at: Time.now, updated_at: Time.now, search_vectors: nil }.with_indifferent_access expect(finished_result['payment'].attributes).to eq common_payment_expected expect(Payment.first.attributes).to eq common_payment_expected expect(Payment.count).to eq 1 end end end end end