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 InsertTickets
# Will generate rows for payment, offsite_payment or charge, tickets, activities
# pass in:
# data: {
# tickets: [{quantity, ticket_level_id}],
# event_id,
# nonprofit_id,
# supporter_id,
# event_discount_id,
# card_id, (if a charge)
# offsite_payment: {kind, check_number},
# kind (offsite, charge, or free)
# }
def self . create ( data )
data = data . with_indifferent_access
2019-07-30 21:29:24 +00:00
ParamValidation . new ( data ,
tickets : { required : true , is_array : true } ,
nonprofit_id : { required : true , is_reference : true } ,
supporter_id : { required : true , is_reference : true } ,
event_id : { required : true , is_reference : true } ,
event_discount_id : { is_reference : true } ,
kind : { included_in : %w[ free charge offsite ] } ,
token : { format : UUID :: Regex } ,
offsite_payment : { is_hash : true } )
data [ :tickets ] . each do | t |
ParamValidation . new ( t , quantity : { is_integer : true , required : true , min : 1 } , ticket_level_id : { is_reference : true , required : true } )
end
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
ParamValidation . new ( data [ :offsite_payment ] , kind : { included_in : %w[ cash check ] } ) if data [ :offsite_payment ] && ! data [ :offsite_payment ] [ :kind ] . blank?
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 , Event = > :event_id )
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
entities . merge! ( RetrieveActiveRecordItems . retrieve_from_keys ( data , { EventDiscount = > :event_discount_id } , true ) )
2018-03-25 17:30:42 +00:00
tl_entities = get_ticket_level_entities ( data )
validate_entities ( entities , tl_entities )
2019-07-30 21:29:24 +00:00
# verify that enough tickets_available
2018-03-25 17:30:42 +00:00
QueryTicketLevels . verify_tickets_available ( data [ :tickets ] )
gross_amount = QueryTicketLevels . gross_amount_from_tickets ( data [ :tickets ] , data [ :event_discount_id ] )
result = { }
if gross_amount > 0
# Create offsite payment for tickets
if data [ :kind ] == 'offsite'
current_user = data [ :current_user ]
# offsite can only come from valid nonprofit users
unless current_user && QueryRoles . is_authorized_for_nonprofit? ( current_user . id , entities [ :nonprofit_id ] . id )
raise AuthenticationError
end
# create payment and offsite payment
result [ 'payment' ] = create_payment ( entities , gross_amount )
result [ 'offsite_payment' ] = create_offsite_payment ( entities , gross_amount , data , result [ 'payment' ] )
# Create charge for tickets
elsif data [ 'kind' ] == 'charge' || ! data [ 'kind' ]
2019-07-30 21:29:24 +00:00
source_token = QuerySourceToken . get_and_increment_source_token ( data [ :token ] , nil )
2018-03-25 17:30:42 +00:00
QuerySourceToken . validate_source_token_type ( source_token )
tokenizable = source_token . tokenizable
## 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
2019-07-30 21:29:24 +00:00
result = result . merge ( InsertCharge . with_stripe (
kind : 'Ticket' ,
towards : entities [ :event_id ] . name ,
metadata : { kind : 'Ticket' , event_id : entities [ :event_id ] . id , nonprofit_id : entities [ :nonprofit_id ] . id } ,
statement : " Tickets #{ entities [ :event_id ] . name } " ,
amount : gross_amount ,
nonprofit_id : entities [ :nonprofit_id ] . id ,
supporter_id : entities [ :supporter_id ] . id ,
card_id : tokenizable . id
) )
2018-03-25 17:30:42 +00:00
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
else
2019-07-30 21:29:24 +00:00
raise ParamValidation :: ValidationError . new ( " Ticket costs money but you didn't pay. " , key : :kind )
2018-03-25 17:30:42 +00:00
end
end
# Generate the bid ids
data [ 'tickets' ] = generate_bid_ids ( entities [ :event_id ] . id , tl_entities )
result [ 'tickets' ] = generated_ticket_entities ( data [ 'tickets' ] , result , entities )
# Create the activity rows for the tickets
2019-07-30 21:29:24 +00:00
InsertActivities . for_tickets ( result [ 'tickets' ] . map ( & :id ) )
2018-03-25 17:30:42 +00:00
2019-07-30 21:29:24 +00:00
ticket_ids = result [ 'tickets' ] . map ( & :id )
2018-03-25 17:30:42 +00:00
charge_id = result [ 'charge' ] ? result [ 'charge' ] . id : nil
2020-06-12 18:03:59 +00:00
Houdini . event_publisher . announce ( :ticket_create , result [ 'tickets' ] , result [ 'charge' ] )
2019-07-30 21:29:24 +00:00
result
end
2018-03-25 17:30:42 +00:00
# Generate a set of 'bid ids' (ids for each ticket scoped within the event)
def self . generate_bid_ids ( event_id , tickets )
# Generate the bid ids
last_bid_id = Psql . execute (
2019-07-30 21:29:24 +00:00
Qexpr . new . select ( 'COUNT(*)' ) . from ( :tickets )
. where ( 'event_id=$id' , id : event_id )
) . first [ 'count' ] . to_i
tickets . zip ( last_bid_id + 1 .. last_bid_id + tickets . count ) . map { | h , id | h . merge ( 'bid_id' = > id ) }
2018-03-25 17:30:42 +00:00
end
2019-07-30 21:29:24 +00:00
# not really needed but used for breaking into the unit test and getting the IDs
2018-03-25 17:30:42 +00:00
def self . generated_ticket_entities ( ticket_data , result , entities )
2019-07-30 21:29:24 +00:00
ticket_data . map do | ticket_request |
2018-03-25 17:30:42 +00:00
t = Ticket . new
t . quantity = ticket_request [ 'quantity' ]
t . ticket_level = ticket_request [ 'ticket_level_id' ]
t . event = entities [ :event_id ]
t . supporter = entities [ :supporter_id ]
t . payment = result [ 'payment' ]
t . charge = result [ 'charge' ]
t . bid_id = ticket_request [ 'bid_id' ]
t . event_discount = entities [ :event_discount_id ]
t . save!
t
2019-07-30 21:29:24 +00:00
end . to_a
2018-03-25 17:30:42 +00:00
end
def self . validate_entities ( entities , tl_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
if entities [ :event_id ] . deleted
raise ParamValidation :: ValidationError . new ( " Event #{ entities [ :event_id ] . id } is deleted " , key : :event_id )
end
2019-07-30 21:29:24 +00:00
# verify that enough tickets_available
tl_entities . each do | i |
2018-03-25 17:30:42 +00:00
if i [ :ticket_level_id ] . deleted
raise ParamValidation :: ValidationError . new ( " Ticket level #{ i [ :ticket_level_id ] . id } is deleted " , key : :tickets )
end
if i [ :ticket_level_id ] . event != entities [ :event_id ]
raise ParamValidation :: ValidationError . new ( " Ticket level #{ i [ :ticket_level_id ] . id } does not belong to event #{ entities [ :event_id ] } " , key : :tickets )
end
2019-07-30 21:29:24 +00:00
end
2018-03-25 17:30:42 +00:00
# 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
## does event belong to nonprofit
if 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
if entities [ :event_discount_id ] && entities [ :event_discount_id ] . event != entities [ :event_id ]
raise ParamValidation :: ValidationError . new ( " Event discount #{ entities [ :event_discount_id ] . id } does not belong to event #{ entities [ :event_id ] . id } " , key : :event_discount_id )
end
end
def self . get_ticket_level_entities ( data )
2019-07-30 21:29:24 +00:00
data [ :tickets ] . map do | i |
2018-03-25 17:30:42 +00:00
{
2019-07-30 21:29:24 +00:00
quantity : i [ :quantity ] ,
ticket_level_id : RetrieveActiveRecordItems . retrieve_from_keys ( i , TicketLevel = > :ticket_level_id ) [ :ticket_level_id ]
2018-03-25 17:30:42 +00:00
}
2019-07-30 21:29:24 +00:00
end . to_a
2018-03-25 17:30:42 +00:00
end
def self . create_payment ( entities , gross_amount )
p = Payment . new
2019-07-30 21:29:24 +00:00
p . gross_amount = gross_amount
p . nonprofit = entities [ :nonprofit_id ]
p . supporter = entities [ :supporter_id ]
p . refund_total = 0
2018-03-25 17:30:42 +00:00
p . date = Time . current
p . towards = entities [ :event_id ] . name
p . fee_total = 0
p . net_amount = gross_amount
2019-07-30 21:29:24 +00:00
p . kind = 'OffsitePayment'
2018-03-25 17:30:42 +00:00
p . save!
p
end
def self . create_offsite_payment ( entities , gross_amount , data , payment )
p = OffsitePayment . new
2019-07-30 21:29:24 +00:00
p . gross_amount = gross_amount
p . nonprofit = entities [ :nonprofit_id ]
p . supporter = entities [ :supporter_id ]
p . date = Time . current
2018-03-25 17:30:42 +00:00
p . payment = payment
p . kind = data [ 'offsite_payment' ] [ 'kind' ]
p . check_number = data [ 'offsite_payment' ] [ 'check_number' ]
p . save!
p
end
end