6772312ea7
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.
206 lines
8 KiB
Ruby
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
|