donation.* event publishing

This commit is contained in:
Eric Schultz 2021-02-08 14:55:31 -06:00 committed by Eric Schultz
parent a401f4bea4
commit 6ab7473ef7
18 changed files with 304 additions and 22 deletions

View file

@ -45,5 +45,7 @@ class Donation < ApplicationRecord
belongs_to :campaign belongs_to :campaign
belongs_to :event belongs_to :event
has_one :modern_donation, inverse_of: :legacy_donation
scope :anonymous, -> { where(anonymous: true) } scope :anonymous, -> { where(anonymous: true) }
end end

View file

@ -0,0 +1,55 @@
# 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
class ModernDonation < ApplicationRecord
include Model::Houidable
include Model::Jbuilder
include Model::Eventable
setup_houid :don
# TODO must associate with events and campaigns somehow
add_builder_expansion :nonprofit, :supporter
add_builder_expansion :trx,
json_attrib: :transaction
has_one :transaction_assignment, as: :assignable
has_one :trx, through: :transaction_assignment
has_one :supporter, through: :trx
has_one :nonprofit, through: :supporter
belongs_to :legacy_donation, class_name: 'Donation', foreign_key: :donation_id, inverse_of: :modern_donation
delegate :designation, :dedication, to: :legacy_donation
def to_builder(*expand)
init_builder(*expand) do |json|
json.(self, :id, :designation)
json.object 'donation'
json.dedication do
json.type dedication['type']
json.name dedication['name']
contact = dedication['contact']
json.contact do
json.email contact['email'] if contact['email']
json.address contact['address'] if contact['address']
json.phone contact['phone'] if contact['phone']
end if contact
end if dedication
# TODO the line above is a hacky solution
json.amount do
json.value_in_cents amount
json.currency nonprofit.currency
end
end
end
def publish_created
Houdini.event_publisher.announce(:donation_created, to_event('donation.created', :nonprofit, :supporter, :trx).attributes!)
end
def publish_updated
Houdini.event_publisher.announce(:donation_updated, to_event('donation.updated', :nonprofit, :supporter, :trx).attributes!)
end
end

View file

@ -15,6 +15,7 @@ class Transaction < ApplicationRecord
has_many :transaction_assignments has_many :transaction_assignments
has_many :donations, through: :transaction_assignments, source: :assignable, source_type: 'ModernDonation'
has_many :ticket_purchases, through: :transaction_assignments, source: :assignable, source_type: 'TicketPurchase' has_many :ticket_purchases, through: :transaction_assignments, source: :assignable, source_type: 'TicketPurchase'
validates :supporter, presence: true validates :supporter, presence: true

View file

@ -0,0 +1,10 @@
class CreateModernDonations < ActiveRecord::Migration[6.1]
def change
create_table :modern_donations, id: :string do |t|
t.integer :amount
t.references :donation
t.timestamps
end
end
end

View file

@ -0,0 +1,17 @@
# 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
class ConvertDonationDedicationToJsonb < ActiveRecord::Migration[6.1]
def up
execute <<-SQL
ALTER TABLE "donations" ALTER COLUMN "dedication" TYPE jsonb USING dedication::jsonb
SQL
end
def down
execute <<-SQL
ALTER TABLE "donations" ALTER COLUMN "dedication" TYPE text USING dedication::text
SQL
end
end

View file

@ -848,7 +848,7 @@ CREATE TABLE public.donations (
recurring_donation_id integer, recurring_donation_id integer,
comment text, comment text,
recurring boolean, recurring boolean,
dedication text, dedication jsonb,
event_id integer, event_id integer,
imported_at timestamp without time zone, imported_at timestamp without time zone,
charge_id integer, charge_id integer,
@ -1436,6 +1436,19 @@ CREATE SEQUENCE public.miscellaneous_np_infos_id_seq
ALTER SEQUENCE public.miscellaneous_np_infos_id_seq OWNED BY public.miscellaneous_np_infos.id; ALTER SEQUENCE public.miscellaneous_np_infos_id_seq OWNED BY public.miscellaneous_np_infos.id;
--
-- Name: modern_donations; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.modern_donations (
id character varying NOT NULL,
amount integer,
donation_id bigint,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
-- --
-- Name: nonprofit_keys; Type: TABLE; Schema: public; Owner: - -- Name: nonprofit_keys; Type: TABLE; Schema: public; Owner: -
-- --
@ -3062,6 +3075,14 @@ ALTER TABLE ONLY public.miscellaneous_np_infos
ADD CONSTRAINT miscellaneous_np_infos_pkey PRIMARY KEY (id); ADD CONSTRAINT miscellaneous_np_infos_pkey PRIMARY KEY (id);
--
-- Name: modern_donations modern_donations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.modern_donations
ADD CONSTRAINT modern_donations_pkey PRIMARY KEY (id);
-- --
-- Name: bank_accounts nonprofit_bank_accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- Name: bank_accounts nonprofit_bank_accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --
@ -3423,6 +3444,13 @@ CREATE INDEX index_exports_on_user_id ON public.exports USING btree (user_id);
CREATE INDEX index_import_requests_on_nonprofit_id ON public.import_requests USING btree (nonprofit_id); CREATE INDEX index_import_requests_on_nonprofit_id ON public.import_requests USING btree (nonprofit_id);
--
-- Name: index_modern_donations_on_donation_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_modern_donations_on_donation_id ON public.modern_donations USING btree (donation_id);
-- --
-- Name: index_payments_on_created_at; Type: INDEX; Schema: public; Owner: - -- Name: index_payments_on_created_at; Type: INDEX; Schema: public; Owner: -
-- --
@ -4286,7 +4314,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20210122203303'), ('20210122203303'),
('20210127193411'), ('20210127193411'),
('20210128215402'), ('20210128215402'),
('20210204013426'), ('20210204172319'),
('20210204172319'); ('20210204174909'),
('20210204210627'),
('20210204223643');

View file

@ -0,0 +1,30 @@
// License: LGPL-3.0-or-later
import type { Amount, HoudiniObject, IdType, HouID, HoudiniEvent } from "../../common";
import type Nonprofit from '../';
import type Supporter from "../Supporter";
import type Transaction from './';
interface Dedication {
contact?: {
address?: string;
email?: string;
phone?: string;
};
name: string;
note?: string;
type: 'honor' | 'memory';
}
export interface Donation extends HoudiniObject<HouID> {
amount: Amount;
dedication?: Dedication | null;
designation?: string | null;
nonprofit: IdType | Nonprofit;
object: 'donation';
supporter: IdType | Supporter;
transaction: HouID | Transaction;
}
export type DonationCreated = HoudiniEvent<'donation.created', Donation>;
export type DonationUpdated = HoudiniEvent<'donation.updated', Donation>;
export type DonationDeleted = HoudiniEvent<'donation.deleted', Donation>;

View file

@ -42,7 +42,13 @@ module InsertDonation
# Create the donation record # Create the donation record
result['donation'] = insert_donation(data, entities) result['donation'] = insert_donation(data, entities)
trx = entities[:supporter_id].transactions.build(amount: data['amount'])
update_donation_keys(result) update_donation_keys(result)
don = trx.donations.build(amount: result['donation'].amount, legacy_donation: result['donation'])
trx.save!
don.save!
don.publish_created
result['activity'] = InsertActivities.for_one_time_donations([result['payment'].id]) result['activity'] = InsertActivities.for_one_time_donations([result['payment'].id])
Houdini.event_publisher.announce(:donation_create, result['donation'], result['donation'].supporter.locale) Houdini.event_publisher.announce(:donation_create, result['donation'], result['donation'].supporter.locale)
result result
@ -222,7 +228,7 @@ module InsertDonation
nonprofit_id: { required: true, is_reference: true }, nonprofit_id: { required: true, is_reference: true },
supporter_id: { required: true, is_reference: true }, supporter_id: { required: true, is_reference: true },
designation: { is_a: String }, designation: { is_a: String },
dedication: { is_a: String }, dedication: { is_a: Hash },
campaign_id: { is_reference: true }, campaign_id: { is_reference: true },
event_id: { is_reference: true } event_id: { is_reference: true }
} }

View file

@ -64,6 +64,7 @@ module PayRecurringDonation
raise ParamValidation::ValidationError.new("#{rd['donation_id']} is not a valid donation", {}) raise ParamValidation::ValidationError.new("#{rd['donation_id']} is not a valid donation", {})
end end
trx = nonprofit.transactions.build(amount: donation['amount'])
result = {} result = {}
result = result.merge(InsertDonation.insert_charge( result = result.merge(InsertDonation.insert_charge(
'card_id' => donation['card_id'], 'card_id' => donation['card_id'],
@ -78,7 +79,13 @@ module PayRecurringDonation
if result['charge']['status'] != 'failed' if result['charge']['status'] != 'failed'
rd.update(n_failures: 0) rd.update(n_failures: 0)
result['recurring_donation'] = rd result['recurring_donation'] = rd
Houdini.event_publisher.announce(:recurring_donation_payment_succeeded, donation, donation&.supporter&.locale || 'en') Houdini.event_publisher.announce(:recurring_donation_payment_succeeded, donation, donation&.supporter&.locale || 'en')
donation = trx.donations.build(amount: donation['amount'], designation: donation['designation'], dedication: donation["dedication"])
trx.save!
donation.save!
donation.publish_created
InsertActivities.for_recurring_donations([result['payment']['id']]) InsertActivities.for_recurring_donations([result['payment']['id']])
else else

View file

@ -328,7 +328,7 @@ module QueryPayments
end end
def self.get_dedication_or_empty(*path) def self.get_dedication_or_empty(*path)
"json_extract_path_text(coalesce(nullif(trim(both from donations.dedication), ''), '{}')::json, #{path.map { |i| "'#{i}'" }.join(',')})" "json_extract_path_text(coalesce(donations.dedication, '{}')::json, #{path.map { |i| "'#{i}'" }.join(',')})"
end end
def self.export_selects def self.export_selects

View file

@ -86,6 +86,8 @@ module UpdateDonation
donation.date = data[:date] if data[:date] donation.date = data[:date] if data[:date]
end end
modern_donation = donation.modern_donation
trx = modern_donation.trx
# edits_to_payments # edits_to_payments
if is_offsite if is_offsite
# if offline, set date, gross_amount, fee_total, net_amount # if offline, set date, gross_amount, fee_total, net_amount
@ -95,7 +97,13 @@ module UpdateDonation
existing_payment.fee_total = data[:fee_total] if data[:fee_total] existing_payment.fee_total = data[:fee_total] if data[:fee_total]
existing_payment.net_amount = existing_payment.gross_amount - existing_payment.fee_total existing_payment.net_amount = existing_payment.gross_amount - existing_payment.fee_total
existing_payment.save! if existing_payment.changed? if existing_payment.changed?
existing_payment.save!
if existing_payment.previous_changes.has_key? 'gross_amount'
modern_donation.amount = existing_payment.gross_amount
trx.amount = existing_payment.gross_amount
end
end
else else
if donation.designation if donation.designation
Payment.where('donation_id = ?', donation.id).update_all(towards: donation.designation, updated_at: Time.now) Payment.where('donation_id = ?', donation.id).update_all(towards: donation.designation, updated_at: Time.now)
@ -111,7 +119,14 @@ module UpdateDonation
offsite_payment.save! if offsite_payment.changed? offsite_payment.save! if offsite_payment.changed?
end end
trx.save! if trx.changed?
donation.save! if donation.changed? donation.save! if donation.changed?
md_changed = modern_donation.changed?
modern_donation.save! if modern_donation.changed?
if (['dedication', 'designation'].any? {|i| donation.previous_changes.has_key? i} || md_changed)
modern_donation.publish_updated
end
existing_payment.reload existing_payment.reload

View file

@ -0,0 +1,9 @@
# 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
FactoryBot.define do
factory :modern_donation do
amount { 1 }
end
end

View file

@ -13,6 +13,9 @@ describe InsertDonation do
include_context :shared_rd_donation_value_context include_context :shared_rd_donation_value_context
describe 'param validation' do describe 'param validation' do
before(:each) do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_created,any_args)
end
it 'does basic validation' do it 'does basic validation' do
validation_basic_validation { InsertDonation.with_stripe(designation: 34_124, dedication: 35_141, event_id: 'bad', campaign_id: 'bad') } validation_basic_validation { InsertDonation.with_stripe(designation: 34_124, dedication: 35_141, event_id: 'bad', campaign_id: 'bad') }
end end
@ -83,24 +86,27 @@ describe InsertDonation do
end end
it 'charge returns failed' do it 'charge returns failed' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_created,any_args)
handle_charge_failed { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token) } handle_charge_failed { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token) }
end end
describe 'success' do describe 'success' do
before(:each) do before(:each) do
before_each_success before_each_success
allow(Houdini.event_publisher).to receive(:announce)
expect(Houdini.event_publisher).to receive(:announce).with(:donation_created,any_args)
end end
it 'process event donation' do it 'process event donation' do
process_event_donation { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') } process_event_donation { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') }
end end
it 'process campaign donation' do it 'process campaign donation' do
expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args)
process_campaign_donation { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') } process_campaign_donation { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') }
end end
it 'processes general donation' do it 'processes general donation' do
process_general_donation { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') } process_general_donation { InsertDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') }
end end
end end
end end
@ -113,18 +119,17 @@ describe InsertDonation do
before_each_sepa_success before_each_sepa_success
end end
it 'process event donation' do it 'process event donation' do
process_event_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') } process_event_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') }
end end
it 'process campaign donation' do it 'process campaign donation' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything) allow(Houdini.event_publisher).to receive(:announce)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args)
process_campaign_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') } process_campaign_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') }
end end
it 'processes general donation' do it 'processes general donation' do
process_general_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') } process_general_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation') }
end end
end end
end end

View file

@ -128,17 +128,17 @@ describe InsertRecurringDonation do
before_each_success before_each_success
end end
it 'process event donation' do it 'process event donation' do
process_event_donation(recurring_donation: { paydate: nil, interval: 1, time_unit: 'year', start_date: Time.current.beginning_of_day }) { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation', recurring_donation: { time_unit: 'year' }) } process_event_donation(recurring_donation: { paydate: nil, interval: 1, time_unit: 'year', start_date: Time.current.beginning_of_day }) { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation', recurring_donation: { time_unit: 'year' }) }
end end
it 'process campaign donation' do it 'process campaign donation' do
expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args) expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args)
process_campaign_donation(recurring_donation: { paydate: nil, interval: 2, time_unit: 'month', start_date: Time.current.beginning_of_day }) { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation', recurring_donation: { interval: 2 }) } process_campaign_donation(recurring_donation: { paydate: nil, interval: 2, time_unit: 'month', start_date: Time.current.beginning_of_day }) { InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation', recurring_donation: { interval: 2 }) }
end end
it 'processes general donation with no recurring donation hash' do it 'processes general donation with no recurring donation hash' do
process_general_donation(recurring_donation: { paydate: Time.now.day, interval: 1, time_unit: 'month', start_date: Time.now.beginning_of_day }) do process_general_donation(recurring_donation: { paydate: Time.now.day, interval: 1, time_unit: 'month', start_date: Time.now.beginning_of_day }) do
InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: Time.now.to_s, dedication: 'dedication', designation: 'designation') InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: Time.now.to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation')
end end
end end
end end
@ -150,7 +150,7 @@ describe InsertRecurringDonation do
it 'processes general donation' do it 'processes general donation' do
process_general_donation(expect_payment: false, expect_charge: false, recurring_donation: { paydate: (Time.now + 5.days).day, interval: 1, time_unit: 'month', start_date: (Time.now + 5.days).beginning_of_day }) do process_general_donation(expect_payment: false, expect_charge: false, recurring_donation: { paydate: (Time.now + 5.days).day, interval: 1, time_unit: 'month', start_date: (Time.now + 5.days).beginning_of_day }) do
InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation', recurring_donation: { start_date: (Time.now + 5.days).to_s }) InsertRecurringDonation.with_stripe(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, profile_id: profile.id, date: (Time.now + 1.day).to_s, dedication: {'type' => 'honor', 'name' => 'a name'}, designation: 'designation', recurring_donation: { start_date: (Time.now + 5.days).to_s })
end end
end end
end end

View file

@ -186,7 +186,7 @@ describe QueryPayments do
token: token, token: token,
date: date, date: date,
dedication: 'dedication', dedication: {'type' => 'honor', 'name' => 'a name'},
designation: 'designation' } designation: 'designation' }
input[:event_id] = h[:event_id] if h[:event_id] input[:event_id] = h[:event_id] if h[:event_id]

View file

@ -24,6 +24,13 @@ describe UpdateDonation do
supporter: supporter) supporter: supporter)
end end
let(:trx) {
trx = force_create(:transaction, supporter: supporter, amount: initial_amount)
trx.donations.create(amount: initial_amount, legacy_donation: donation)
trx
}
let(:payment) do let(:payment) do
force_create(:payment, nonprofit: np, donation: donation, force_create(:payment, nonprofit: np, donation: donation,
towards: initial_designation, towards: initial_designation,
@ -76,12 +83,14 @@ describe UpdateDonation do
let(:payment2_date) { initial_date + 10.days } let(:payment2_date) { initial_date + 10.days }
before(:each) do before(:each) do
allow(Houdini.event_publisher).to receive(:announce)
initial_time initial_time
payment payment
end end
describe '.update_payment' do describe '.update_payment' do
describe 'param validation' do describe 'param validation' do
it 'basic validation' do it 'basic validation' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect { UpdateDonation.update_payment(nil, nil) }.to raise_error { |error| expect { UpdateDonation.update_payment(nil, nil) }.to raise_error { |error|
expect(error).to be_a ParamValidation::ValidationError expect(error).to be_a ParamValidation::ValidationError
expect_validation_errors(error.data, [{ key: :id, name: :required }, expect_validation_errors(error.data, [{ key: :id, name: :required },
@ -92,6 +101,7 @@ describe UpdateDonation do
end end
it 'validates whether payment is valid' do it 'validates whether payment is valid' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect { UpdateDonation.update_payment(5555, {}) }.to raise_error { |error| expect { UpdateDonation.update_payment(5555, {}) }.to raise_error { |error|
expect(error).to be_a ParamValidation::ValidationError expect(error).to be_a ParamValidation::ValidationError
expect_validation_errors(error.data, [{ key: :id }]) expect_validation_errors(error.data, [{ key: :id }])
@ -132,6 +142,7 @@ describe UpdateDonation do
end end
it 'for offsite donations' do it 'for offsite donations' do
offsite_payment offsite_payment
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect { UpdateDonation.update_payment(donation.id, expanded_invalid_arguments) }.to(raise_error do |error| expect { UpdateDonation.update_payment(donation.id, expanded_invalid_arguments) }.to(raise_error do |error|
expect(error).to be_a ParamValidation::ValidationError expect(error).to be_a ParamValidation::ValidationError
expect_validation_errors(error.data, initial_validation_errors.concat([ expect_validation_errors(error.data, initial_validation_errors.concat([
@ -146,6 +157,7 @@ describe UpdateDonation do
end end
it 'for online donation' do it 'for online donation' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect { UpdateDonation.update_payment(donation.id, expanded_invalid_arguments) }.to(raise_error do |error| expect { UpdateDonation.update_payment(donation.id, expanded_invalid_arguments) }.to(raise_error do |error|
expect(error).to be_a ParamValidation::ValidationError expect(error).to be_a ParamValidation::ValidationError
expect_validation_errors(error.data, initial_validation_errors) expect_validation_errors(error.data, initial_validation_errors)
@ -154,6 +166,7 @@ describe UpdateDonation do
end end
it 'validate campaign_id' do it 'validate campaign_id' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect { UpdateDonation.update_payment(donation.id, campaign_id: 444, event_id: 444) }.to(raise_error do |error| expect { UpdateDonation.update_payment(donation.id, campaign_id: 444, event_id: 444) }.to(raise_error do |error|
expect(error).to be_a ParamValidation::ValidationError expect(error).to be_a ParamValidation::ValidationError
expect_validation_errors(error.data, [{ key: :campaign_id }]) expect_validation_errors(error.data, [{ key: :campaign_id }])
@ -161,6 +174,7 @@ describe UpdateDonation do
end end
it 'validate event_id' do it 'validate event_id' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect { UpdateDonation.update_payment(donation.id, event_id: 4444, campaign_id: campaign.id) }.to(raise_error do |error| expect { UpdateDonation.update_payment(donation.id, event_id: 4444, campaign_id: campaign.id) }.to(raise_error do |error|
expect(error).to be_a ParamValidation::ValidationError expect(error).to be_a ParamValidation::ValidationError
expect_validation_errors(error.data, [{ key: :event_id }]) expect_validation_errors(error.data, [{ key: :event_id }])
@ -168,26 +182,33 @@ describe UpdateDonation do
end end
it 'validates campaign belongs to payment org' do it 'validates campaign belongs to payment org' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
campaign_belongs { UpdateDonation.update_payment(donation.id, campaign_id: other_campaign.id, event_id: event.id) } campaign_belongs { UpdateDonation.update_payment(donation.id, campaign_id: other_campaign.id, event_id: event.id) }
end end
it 'validates event belongs to payment org' do it 'validates event belongs to payment org' do
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
event_belongs { UpdateDonation.update_payment(donation.id, event_id: other_event.id, campaign_id: campaign.id) } event_belongs { UpdateDonation.update_payment(donation.id, event_id: other_event.id, campaign_id: campaign.id) }
end end
end end
describe 'most of the values arent changed if not provided' do describe 'most of the values arent changed if not provided' do
it 'online donation' do it 'online donation' do
trx
payment2 payment2
result = verify_nothing_changed result = verify_nothing_changed
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
expect(result).to eq donation.attributes.merge(payment: payment2.attributes) expect(result).to eq donation.attributes.merge(payment: payment2.attributes)
trx.reload
expect(trx.amount).to eq initial_amount
expect(trx.donations.first.amount).to eq initial_amount
end end
it 'offsite donation' do it 'offsite donation' do
trx
offsite_payment offsite_payment
result = verify_nothing_changed result = verify_nothing_changed
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
p2_attributes = payment2.attributes p2_attributes = payment2.attributes
payment2.reload payment2.reload
expect(p2_attributes).to eq payment2.attributes expect(p2_attributes).to eq payment2.attributes
@ -196,9 +217,13 @@ describe UpdateDonation do
offsite_payment.reload offsite_payment.reload
expect(o_attributes).to eq offsite_payment.attributes expect(o_attributes).to eq offsite_payment.attributes
expect(result).to eq donation.attributes.merge(payment: payment.attributes, offsite_payment: offsite_payment.attributes) expect(result).to eq donation.attributes.merge(payment: payment.attributes, offsite_payment: offsite_payment.attributes)
trx.reload
expect(trx.amount).to eq initial_amount
expect(trx.donations.first.amount).to eq initial_amount
end end
def verify_nothing_changed def verify_nothing_changed
expect(Houdini.event_publisher).to_not receive(:announce).with(:donation_updated,anything)
result = UpdateDonation.update_payment(donation.id, campaign_id: '', event_id: '') result = UpdateDonation.update_payment(donation.id, campaign_id: '', event_id: '')
p_attributes = payment.attributes p_attributes = payment.attributes
@ -209,6 +234,9 @@ describe UpdateDonation do
donation.reload donation.reload
expect(d_attributes).to eq donation.attributes expect(d_attributes).to eq donation.attributes
trx.reload
expect(trx.amount).to eq initial_amount
expect(trx.donations.first.amount).to eq initial_amount
result result
end end
end end
@ -227,8 +255,10 @@ describe UpdateDonation do
date: new_date_input } date: new_date_input }
end end
it 'online donation' do it 'online donation' do
trx
payment2 payment2
Timecop.freeze(1.day) do Timecop.freeze(1.day) do
expect(Houdini.event_publisher).to receive(:announce).with(:donation_updated,anything)
result = UpdateDonation.update_payment(donation.id, new_data) result = UpdateDonation.update_payment(donation.id, new_data)
expected_donation = donation.attributes.merge(designation: new_designation, expected_donation = donation.attributes.merge(designation: new_designation,
@ -259,8 +289,10 @@ describe UpdateDonation do
end end
it 'offline donation' do it 'offline donation' do
trx
offsite_payment offsite_payment
Timecop.freeze(1.day) do Timecop.freeze(1.day) do
expect(Houdini.event_publisher).to receive(:announce).with(:donation_updated,anything)
result = UpdateDonation.update_payment(donation.id, new_data) result = UpdateDonation.update_payment(donation.id, new_data)
expected_donation = donation.attributes.merge( expected_donation = donation.attributes.merge(
@ -276,6 +308,7 @@ describe UpdateDonation do
updated_at: Time.now updated_at: Time.now
).with_indifferent_access ).with_indifferent_access
trx.reload
donation.reload donation.reload
expect(donation.attributes).to eq expected_donation expect(donation.attributes).to eq expected_donation
@ -291,6 +324,10 @@ describe UpdateDonation do
expect(offsite_payment.attributes).to eq expected_offsite_payment expect(offsite_payment.attributes).to eq expected_offsite_payment
expect(result).to eq create_expected_result(donation, payment, offsite_payment) expect(result).to eq create_expected_result(donation, payment, offsite_payment)
expect(trx.amount).to eq new_amount
expect(trx.donations.first.amount).to eq new_amount
end end
end end
@ -309,8 +346,10 @@ describe UpdateDonation do
end end
it 'online donation' do it 'online donation' do
trx
payment2 payment2
Timecop.freeze(1.day) do Timecop.freeze(1.day) do
expect(Houdini.event_publisher).to receive(:announce).with(:donation_updated,anything)
UpdateDonation.update_payment(donation.id, new_data) UpdateDonation.update_payment(donation.id, new_data)
result = UpdateDonation.update_payment(donation.id, blank_data) result = UpdateDonation.update_payment(donation.id, blank_data)
@ -342,8 +381,10 @@ describe UpdateDonation do
end end
it 'offline donation' do it 'offline donation' do
trx
offsite_payment offsite_payment
Timecop.freeze(1.day) do Timecop.freeze(1.day) do
expect(Houdini.event_publisher).to receive(:announce).with(:donation_updated,anything)
UpdateDonation.update_payment(donation.id, new_data) UpdateDonation.update_payment(donation.id, new_data)
result = UpdateDonation.update_payment(donation.id, blank_data) result = UpdateDonation.update_payment(donation.id, blank_data)

View file

@ -0,0 +1,54 @@
# 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'
RSpec.describe ModernDonation, type: :model do
include_context :shared_donation_charge_context
# TODO Why are we manually setting everything here? It's not clear what order things should
# go in for a transaction. Therefore, we don't assume the order for now and just make sure the
# the output of to_builder is right
let(:trx) { force_create(:transaction, supporter: supporter, amount: 1200)}
let(:legacy_donation) { force_create(:donation, amount: 1200) }
let(:dedication) {{
type: 'honor',
name: "Grandma Schultz"
}}
let(:legacy_donation_with_dedication_and_designation) { force_create(:donation, amount: 1200, designation: 'designation', dedication: dedication) }
describe 'to_builder' do
let(:don_default) do
{
'id' => match_houid('don'),
'object' => 'donation',
'nonprofit' => nonprofit.id,
'supporter' => supporter.id,
'amount' => {'currency' => 'usd', 'value_in_cents' => 1200},
'transaction' => trx.id,
'designation' => nil
}
end
it 'without dedication or designation' do
donation = trx.donations.create(amount: 1200)
donation.legacy_donation = legacy_donation
donation.save!
expect(donation.to_builder.attributes!).to match(don_default)
end
it 'with designation and dedication' do
donation = trx.donations.create(amount: 1200)
donation.legacy_donation = legacy_donation_with_dedication_and_designation
donation.save!
expect(donation.to_builder.attributes!).to match(don_default.merge({
'designation' => 'designation',
'dedication' => {
'type' => 'honor',
'name' => 'Grandma Schultz'
}
}))
end
end
end

View file

@ -48,7 +48,7 @@ RSpec.shared_context :shared_rd_donation_value_context do
amount: charge_amount, amount: charge_amount,
comment: nil, comment: nil,
category: nil, category: nil,
dedication: 'dedication', dedication: {'type' => 'honor', 'name' => 'a name'},
designation: 'designation', designation: 'designation',
imported_at: nil, imported_at: nil,
manual: nil, manual: nil,