houdini/lib/insert/insert_tickets.rb
Bradley M. Kuhn 6772312ea7 Relicense all .rb files under new project license.
The primary license of the project is changing to:
  AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later

with some specific files to be licensed under the one of two licenses:
   CC0-1.0
   LGPL-3.0-or-later

This commit is one of the many steps to relicense the entire codebase.

Documentation granting permission for this relicensing (from all past
contributors who hold copyrights) is on file with Software Freedom
Conservancy, Inc.
2018-03-25 15:10:40 -04:00

206 lines
8 KiB
Ruby

# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
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
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: ['free', 'charge', 'offsite']},
token: {format: UUID::Regex},
offsite_payment: {is_hash: true}
})
data[:tickets].each {|t|
ParamValidation.new(t, {quantity: {is_integer: true, required: true, min: 1}, ticket_level_id: {is_reference: true, required: true}})
}
ParamValidation.new(data[:offsite_payment], {kind: {included_in: %w(cash check)}}) if data[:offsite_payment]
entities = RetrieveActiveRecordItems.retrieve_from_keys(data, {Supporter => :supporter_id, Nonprofit => :nonprofit_id, Event => :event_id})
entities.merge!(RetrieveActiveRecordItems.retrieve_from_keys(data, {EventDiscount => :event_discount_id}, true))
tl_entities = get_ticket_level_entities(data)
validate_entities(entities, tl_entities)
#verify that enough tickets_available
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']
source_token = QuerySourceToken.get_and_increment_source_token(data[:token],nil)
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
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
}))
if result['charge']['status'] == 'failed'
raise ChargeError.new(result['charge']['failure_message'])
end
else
raise ParamValidation::ValidationError.new("Ticket costs money but you didn't pay.", {key: :kind})
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
InsertActivities.for_tickets(result['tickets'].map{|t| t.id})
ticket_ids = result['tickets'].map{|t| t.id}
charge_id = result['charge'] ? result['charge'].id : nil
EmailJobQueue.queue(JobTypes::TicketMailerReceiptAdminJob, ticket_ids)
EmailJobQueue.queue(JobTypes::TicketMailerFollowupJob, ticket_ids, charge_id)
return result
end
# 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(
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)}
end
#not really needed but used for breaking into the unit test and getting the IDs
def self.generated_ticket_entities(ticket_data, result, entities)
ticket_data.map{|ticket_request|
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
}.to_a
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
#verify that enough tickets_available
tl_entities.each {|i|
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
}
# 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)
data[:tickets].map{|i|
{
quantity: i[:quantity],
ticket_level_id: RetrieveActiveRecordItems.retrieve_from_keys(i, TicketLevel => :ticket_level_id)[:ticket_level_id]
}
}.to_a
end
def self.create_payment(entities, gross_amount)
p = Payment.new
p.gross_amount= gross_amount
p.nonprofit= entities[:nonprofit_id]
p.supporter= entities[:supporter_id]
p.refund_total= 0
p.date = Time.current
p.towards = entities[:event_id].name
p.fee_total = 0
p.net_amount = gross_amount
p.kind= "OffsitePayment"
p.save!
p
end
def self.create_offsite_payment(entities, gross_amount, data, payment)
p = OffsitePayment.new
p.gross_amount= gross_amount
p.nonprofit= entities[:nonprofit_id]
p.supporter= entities[:supporter_id]
p.date= Time.current
p.payment = payment
p.kind = data['offsite_payment']['kind']
p.check_number = data['offsite_payment']['check_number']
p.save!
p
end
end