houdini/lib/insert/insert_tickets.rb

205 lines
7.9 KiB
Ruby

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