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
module InsertDonation
# Make a one-time donation (call InsertRecurringDonation.with_stripe to create a recurring donation)
# In data, pass in:
# amount, card_id, nonprofit_id, supporter_id
# designation, dedication
# recurring_donation if is recurring
2019-07-30 21:29:24 +00:00
def self . with_stripe ( data , current_user = nil )
2018-03-25 17:30:42 +00:00
data = data . with_indifferent_access
ParamValidation . new ( data , common_param_validations
2019-07-30 21:29:24 +00:00
. merge ( token : { required : true , format : UUID :: Regex } ) )
2018-03-25 17:30:42 +00:00
source_token = QuerySourceToken . get_and_increment_source_token ( data [ :token ] , current_user )
tokenizable = source_token . tokenizable
QuerySourceToken . validate_source_token_type ( source_token )
2019-07-30 21:29:24 +00:00
entities = RetrieveActiveRecordItems . retrieve_from_keys ( data , Supporter = > :supporter_id , Nonprofit = > :nonprofit_id )
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
entities = entities . merge ( RetrieveActiveRecordItems . retrieve_from_keys ( data , { Campaign = > :campaign_id , Event = > :event_id , Profile = > :profile_id } , true ) )
2018-03-25 17:30:42 +00:00
validate_entities ( entities )
## does the card belong to the supporter?
if tokenizable . holder != entities [ :supporter_id ]
raise ParamValidation :: ValidationError . new ( " Supporter #{ entities [ :supporter_id ] . id } does not own card #{ tokenizable . id } " , key : :token )
end
data [ 'card_id' ] = tokenizable . id
result = { }
data [ :date ] = Time . now
data = data . except ( :old_donation ) . except ( 'old_donation' )
result = result . merge ( insert_charge ( data ) )
if result [ 'charge' ] [ 'status' ] == 'failed'
2019-07-30 21:29:24 +00:00
raise ChargeError , result [ 'charge' ] [ 'failure_message' ]
2018-03-25 17:30:42 +00:00
end
2019-07-30 21:29:24 +00:00
2018-03-25 17:30:42 +00:00
# Create the donation record
2019-07-30 21:29:24 +00:00
result [ 'donation' ] = insert_donation ( data , entities )
2021-02-08 20:55:31 +00:00
trx = entities [ :supporter_id ] . transactions . build ( amount : data [ 'amount' ] )
2018-03-25 17:30:42 +00:00
update_donation_keys ( result )
2021-02-08 20:55:31 +00:00
don = trx . donations . build ( amount : result [ 'donation' ] . amount , legacy_donation : result [ 'donation' ] )
2021-05-05 16:35:17 +00:00
stripe_t = trx . build_subtransaction (
subtransactable : StripeTransaction . new ( amount : data [ 'amount' ] ) ,
subtransaction_payments : [
SubtransactionPayment . new (
paymentable : StripeCharge . new ( payment : Payment . find ( result [ 'payment' ] [ 'id' ] ) ) )
] ,
created : data [ 'date' ]
) ;
2021-02-08 20:55:31 +00:00
trx . save!
don . save!
2021-05-05 16:35:17 +00:00
stripe_t . save!
stripe_t . subtransaction_payments . each { | stp | stp . publish_created }
stripe_t . publish_created
2021-02-08 20:55:31 +00:00
don . publish_created
2021-04-07 21:43:58 +00:00
trx . publish_created
2018-03-25 17:30:42 +00:00
result [ 'activity' ] = InsertActivities . for_one_time_donations ( [ result [ 'payment' ] . id ] )
2020-06-12 18:03:59 +00:00
Houdini . event_publisher . announce ( :donation_create , result [ 'donation' ] , result [ 'donation' ] . supporter . locale )
2018-03-25 17:30:42 +00:00
result
end
# Update the charge to have the payment and donation id
# Update the payment to have the donation id
def self . update_donation_keys ( result )
result [ 'charge' ] . donation = result [ 'donation' ]
result [ 'charge' ] . save!
result [ 'payment' ] . donation = result [ 'donation' ]
result [ 'payment' ] . save!
end
# Insert a donation made from an offsite payment
# Creates an offsite payment, payment, and donation
# pass in amount, nonprofit_id, supporter_id, check_number
# also pass in offsite_payment sub-hash (can be empty)
def self . offsite ( data )
2019-07-30 21:29:24 +00:00
ParamValidation . new ( data , common_param_validations . merge ( offsite_payment : { is_hash : true } ) )
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
entities = RetrieveActiveRecordItems . retrieve_from_keys ( data , Supporter = > :supporter_id , Nonprofit = > :nonprofit_id )
entities = entities . merge ( RetrieveActiveRecordItems . retrieve_from_keys ( data , { Campaign = > :campaign_id , Event = > :event_id , Profile = > :profile_id } , true ) )
2018-03-25 17:30:42 +00:00
validate_entities ( entities )
data = date_from_data ( data )
2019-07-30 21:29:24 +00:00
result = { 'donation' = > insert_donation ( data . except ( 'offsite_payment' ) , entities ) }
2021-04-07 21:43:58 +00:00
trx = entities [ :supporter_id ] . transactions . build ( amount : data [ 'amount' ] , created : data [ 'date' ] )
don = trx . donations . build ( amount : result [ 'donation' ] . amount , legacy_donation : result [ 'donation' ] )
2019-07-30 21:29:24 +00:00
result [ 'payment' ] = insert_payment ( 'OffsitePayment' , 0 , result [ 'donation' ] [ 'id' ] , data )
2018-03-25 17:30:42 +00:00
result [ 'offsite_payment' ] = Psql . execute (
Qexpr . new . insert ( :offsite_payments , [
2019-07-30 21:29:24 +00:00
( data [ 'offsite_payment' ] || { } ) . merge (
gross_amount : data [ 'amount' ] ,
nonprofit_id : data [ 'nonprofit_id' ] ,
supporter_id : data [ 'supporter_id' ] ,
donation_id : result [ 'donation' ] [ 'id' ] ,
payment_id : result [ 'payment' ] [ 'id' ] ,
date : data [ 'date' ]
)
] ) . returning ( '*' )
2018-03-25 17:30:42 +00:00
) . first
2021-04-07 21:43:58 +00:00
off_t = trx . build_subtransaction (
subtransactable : OfflineTransaction . new ( amount : data [ 'amount' ] ) ,
subtransaction_payments : [
SubtransactionPayment . new (
paymentable : OfflineTransactionCharge . new ( payment : Payment . find ( result [ 'payment' ] [ 'id' ] ) ) )
] ,
created : data [ 'date' ]
) ;
trx . save!
don . save!
off_t . save!
off_t . subtransaction_payments . each { | stp | stp . publish_created }
off_t . publish_created
don . publish_created
trx . publish_created
2018-03-25 17:30:42 +00:00
result [ 'activity' ] = InsertActivities . for_offsite_donations ( [ result [ 'payment' ] [ 'id' ] ] )
2019-07-30 21:29:24 +00:00
{ status : 200 , json : result }
2018-03-25 17:30:42 +00:00
end
def self . with_sepa ( data )
data = data . with_indifferent_access
ParamValidation . new ( data , common_param_validations
2019-07-30 21:29:24 +00:00
. merge ( direct_debit_detail_id : { required : true , is_reference : true } ) )
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
entities = RetrieveActiveRecordItems . retrieve_from_keys ( data , Supporter = > :supporter_id , Nonprofit = > :nonprofit_id )
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
entities = entities . merge ( RetrieveActiveRecordItems . retrieve_from_keys ( data , { Campaign = > :campaign_id , Event = > :event_id , Profile = > :profile_id } , true ) )
2018-03-25 17:30:42 +00:00
result = { }
data [ :date ] = Time . now
result = result . merge ( insert_charge ( data ) )
2019-07-30 21:29:24 +00:00
result [ 'donation' ] = insert_donation ( data , entities )
2018-03-25 17:30:42 +00:00
update_donation_keys ( result )
2020-06-12 18:03:59 +00:00
Houdini . event_publisher . announce ( :donation_create , result [ 'donation' ] , locale_for_supporter ( result [ 'donation' ] . supporter . id ) )
2018-03-25 17:30:42 +00:00
# do this for making test consistent
result [ 'activity' ] = { }
result
end
2019-07-30 21:29:24 +00:00
private
2018-03-25 17:30:42 +00:00
def self . get_nonprofit_data ( nonprofit_id )
Psql . execute (
Qexpr . new . select ( :statement , :name ) . from ( :nonprofits )
2019-07-30 21:29:24 +00:00
. where ( 'id=$id' , id : nonprofit_id )
2018-03-25 17:30:42 +00:00
) . first
end
def self . insert_charge ( data )
payment_provider = payment_provider ( data )
nonprofit_data = get_nonprofit_data ( data [ 'nonprofit_id' ] )
2019-07-30 21:29:24 +00:00
kind = data [ 'recurring_donation' ] ? 'RecurringDonation' : 'Donation'
2018-03-25 17:30:42 +00:00
if payment_provider == :credit_card
2019-07-30 21:29:24 +00:00
return InsertCharge . with_stripe (
2018-03-25 17:30:42 +00:00
donation_id : data [ 'donation_id' ] ,
kind : kind ,
towards : data [ 'designation' ] ,
2019-07-30 21:29:24 +00:00
metadata : { kind : kind , nonprofit_id : data [ 'nonprofit_id' ] } ,
2018-03-25 17:30:42 +00:00
statement : " Donation #{ nonprofit_data [ 'statement' ] || nonprofit_data [ 'name' ] } " ,
amount : data [ 'amount' ] ,
nonprofit_id : data [ 'nonprofit_id' ] ,
supporter_id : data [ 'supporter_id' ] ,
card_id : data [ 'card_id' ] ,
old_donation : data [ 'old_donation' ] ? true : false
2019-07-30 21:29:24 +00:00
)
2018-03-25 17:30:42 +00:00
elsif payment_provider == :sepa
2019-07-30 21:29:24 +00:00
return InsertCharge . with_sepa (
2018-03-25 17:30:42 +00:00
donation_id : data [ 'donation_id' ] ,
kind : kind ,
towards : data [ 'designation' ] ,
2019-07-30 21:29:24 +00:00
metadata : { kind : kind , nonprofit_id : data [ 'nonprofit_id' ] } ,
2018-03-25 17:30:42 +00:00
statement : " Donation #{ nonprofit_data [ 'statement' ] || nonprofit_data [ 'name' ] } " ,
amount : data [ 'amount' ] ,
nonprofit_id : data [ 'nonprofit_id' ] ,
supporter_id : data [ 'supporter_id' ] ,
direct_debit_detail_id : data [ 'direct_debit_detail_id' ]
2019-07-30 21:29:24 +00:00
)
2018-03-25 17:30:42 +00:00
end
end
2021-04-07 21:43:58 +00:00
# Insert a payment row for a donationValidationError
2018-03-25 17:30:42 +00:00
def self . insert_payment ( kind , fee_total , donation_id , data )
Psql . execute (
Qexpr . new . insert ( :payments , [ {
2019-07-30 21:29:24 +00:00
donation_id : donation_id ,
gross_amount : data [ 'amount' ] ,
nonprofit_id : data [ 'nonprofit_id' ] ,
supporter_id : data [ 'supporter_id' ] ,
refund_total : 0 ,
date : data [ 'date' ] ,
towards : data [ 'designation' ] ,
kind : kind ,
fee_total : fee_total ,
net_amount : data [ 'amount' ] - fee_total
} ] ) . returning ( '*' )
2018-03-25 17:30:42 +00:00
) . first
end
# Insert a donation row
def self . insert_donation ( data , entities )
d = Donation . new
d . date = data [ 'date' ]
d . anonymous = data [ 'anonymous' ]
d . designation = data [ 'designation' ]
d . dedication = data [ 'dedication' ]
d . comment = data [ 'comment' ]
d . amount = data [ 'amount' ]
d . card = Card . find ( data [ 'card_id' ] ) if data [ 'card_id' ]
d . direct_debit_detail = DirectDebitDetail . find ( data [ 'direct_debit_detail_id' ] ) if data [ 'direct_debit_detail_id' ]
d . nonprofit = entities [ :nonprofit_id ]
d . supporter = entities [ :supporter_id ]
d . profile = entities [ :profile_id ] || nil
d . campaign = entities [ :campaign_id ] || nil
d . event = entities [ :event_id ] || nil
d . payment_provider = payment_provider ( data ) . to_s
d . save!
d
end
# Return either the parsed DateTime from a date in data, or right now
def self . date_from_data ( data )
data . merge ( 'date' = > data [ 'date' ] . blank? ? Time . current : Chronic . parse ( data [ 'date' ] ) )
end
def self . locale_for_supporter ( supporter_id )
Psql . execute (
Qexpr . new . select ( :locale ) . from ( :supporters )
2019-07-30 21:29:24 +00:00
. where ( 'id=$id' , id : supporter_id )
2018-03-25 17:30:42 +00:00
) . first [ 'locale' ]
end
def self . payment_provider ( data )
2019-07-30 21:29:24 +00:00
if data [ :card_id ] || data [ 'card_id' ]
2018-03-25 17:30:42 +00:00
:credit_card
2019-07-30 21:29:24 +00:00
elsif data [ :direct_debit_detail_id ] || data [ 'direct_debit_detail_id' ]
2018-03-25 17:30:42 +00:00
:sepa
end
end
2019-07-30 21:29:24 +00:00
def self . parse_date ( date )
2018-03-25 17:30:42 +00:00
date . blank? ? Time . current : Chronic . parse ( date )
2019-07-30 21:29:24 +00:00
end
2018-03-25 17:30:42 +00:00
def self . common_param_validations
{
2019-07-30 21:29:24 +00:00
amount : { required : true , is_integer : true } ,
nonprofit_id : { required : true , is_reference : true } ,
supporter_id : { required : true , is_reference : true } ,
designation : { is_a : String } ,
2021-02-08 20:55:31 +00:00
dedication : { is_a : Hash } ,
2019-07-30 21:29:24 +00:00
campaign_id : { is_reference : true } ,
event_id : { is_reference : true }
2018-03-25 17:30:42 +00:00
}
end
def self . validate_entities ( entities )
## is supporter deleted? If supporter is deleted, we error!
if entities [ :supporter_id ] . deleted
raise ParamValidation :: ValidationError . new ( " Supporter #{ entities [ :supporter_id ] . id } is deleted " , key : :supporter_id )
end
2019-07-30 21:29:24 +00:00
if entities [ :event_id ] & . deleted
2018-03-25 17:30:42 +00:00
raise ParamValidation :: ValidationError . new ( " Event #{ entities [ :event_id ] . id } is deleted " , key : :event_id )
end
2019-07-30 21:29:24 +00:00
if entities [ :campaign_id ] & . deleted
2018-03-25 17:30:42 +00:00
raise ParamValidation :: ValidationError . new ( " Campaign #{ entities [ :campaign_id ] . id } is deleted " , key : :campaign_id )
end
# Does the supporter belong to the nonprofit?
if entities [ :supporter_id ] . nonprofit != entities [ :nonprofit_id ]
raise ParamValidation :: ValidationError . new ( " Supporter #{ entities [ :supporter_id ] . id } does not belong to nonprofit #{ entities [ :nonprofit_id ] . id } " , key : :supporter_id )
end
### if we have campaign, does it belong to nonprofit
if entities [ :campaign_id ] && entities [ :campaign_id ] . nonprofit != entities [ :nonprofit_id ]
raise ParamValidation :: ValidationError . new ( " Campaign #{ entities [ :campaign_id ] . id } does not belong to nonprofit #{ entities [ :nonprofit_id ] } " , key : :campaign_id )
end
## if we have event, does it belong to nonprofit
if entities [ :event_id ] && entities [ :event_id ] . nonprofit != entities [ :nonprofit_id ]
raise ParamValidation :: ValidationError . new ( " Event #{ entities [ :event_id ] . id } does not belong to nonprofit #{ entities [ :nonprofit_id ] } " , key : :event_id )
end
end
end