Class: TicketService::Transaction
- Inherits:
-
ApplicationService
- Object
- ApplicationService
- TicketService::Transaction
- Includes:
- DefaultErrorContainer, OmiseHelper, OmiseProviderChecker
- Defined in:
- app/services/ticket_service/transaction.rb
Defined Under Namespace
Classes: TicketTransactionAdapter
Instance Attribute Summary collapse
-
#expiry_time ⇒ Object
Returns the value of attribute expiry_time.
-
#ip_address ⇒ Object
Returns the value of attribute ip_address.
-
#outcome ⇒ Object
readonly
Returns the value of attribute outcome.
-
#params ⇒ Object
Returns the value of attribute params.
-
#transaction ⇒ Object
Returns the value of attribute transaction.
-
#user ⇒ Object
Returns the value of attribute user.
-
#vendor_payment ⇒ Object
Returns the value of attribute vendor_payment.
Instance Method Summary collapse
-
#create ⇒ Object
same as #execute, but this is for Locking System feature this controller is to record user and payment data the TicketTransaction already paid by #build_charge_record method when for_locking_system is true, then it means the user hasn't start any payment process.
-
#execute ⇒ Object
this action is to accept TicketTransaction without Locking System we should remove this action after Locking System is implemented.
-
#initialize(params, user = nil, ip_address = nil) ⇒ Transaction
constructor
A new instance of Transaction.
- #lock ⇒ Object
- #set_transaction(transaction_id) ⇒ Object
Methods included from OmiseProviderChecker
Methods included from OmiseHelper
#configure_omise_keys, #omise_public_key
Methods included from DefaultErrorContainer
#error, #error_message_simple, #merge_errors
Constructor Details
#initialize(params, user = nil, ip_address = nil) ⇒ Transaction
Returns a new instance of Transaction.
13 14 15 16 17 18 19 20 |
# File 'app/services/ticket_service/transaction.rb', line 13 def initialize(params, user = nil, ip_address = nil) @params = params @user = user @transaction = nil @expiry_time = nil @vendor_payment = params[:vendor_payment].presence || {} @ip_address = ip_address end |
Instance Attribute Details
#expiry_time ⇒ Object
Returns the value of attribute expiry_time.
7 8 9 |
# File 'app/services/ticket_service/transaction.rb', line 7 def expiry_time @expiry_time end |
#ip_address ⇒ Object
Returns the value of attribute ip_address.
7 8 9 |
# File 'app/services/ticket_service/transaction.rb', line 7 def ip_address @ip_address end |
#outcome ⇒ Object (readonly)
Returns the value of attribute outcome.
6 7 8 |
# File 'app/services/ticket_service/transaction.rb', line 6 def outcome @outcome end |
#params ⇒ Object
Returns the value of attribute params.
7 8 9 |
# File 'app/services/ticket_service/transaction.rb', line 7 def params @params end |
#transaction ⇒ Object
Returns the value of attribute transaction.
7 8 9 |
# File 'app/services/ticket_service/transaction.rb', line 7 def transaction @transaction end |
#user ⇒ Object
Returns the value of attribute user.
7 8 9 |
# File 'app/services/ticket_service/transaction.rb', line 7 def user @user end |
#vendor_payment ⇒ Object
Returns the value of attribute vendor_payment.
7 8 9 |
# File 'app/services/ticket_service/transaction.rb', line 7 def vendor_payment @vendor_payment end |
Instance Method Details
#create ⇒ Object
same as #execute, but this is for Locking System feature this controller is to record user and payment data the TicketTransaction already paid by #build_charge_record method when for_locking_system is true, then it means the user hasn't start any payment process
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'app/services/ticket_service/transaction.rb', line 200 def create if transaction.blank? errors.add :base, 'Failed to complete process, ticket transaction is blank' end result = false ActiveRecord::Base.transaction do unless transaction.active? raise InvalidVoucherPurchase, 'Sorry, No active transaction found' end @params = params.tap do |h| h[:ticket_groups] = [] end form = TicketService::Form.new(params, user) form.error_container = errors raise ActiveRecord::Rollback, form. unless form.valid? transaction.assign_attributes(form.transaction_attributes) transaction.ticket_bundles.each do |tb| if tb.ticket_group.total_orders > tb.ticket_group.quantity raise ActiveRecord::Rollback, "We're sorry this voucher is not available anymore" end end # since guest params provided in the second step, we need to update guest_id for each ticket if transaction.guest.present? transaction.tickets.each do |ticket| ticket.guest_id = transaction.guest_id end end transaction.active = true transaction.for_locking_system = false transaction.charge_price = if form.vendor_payment? vendor_payment[:amount_cents].to_i / 100 else transaction.total_price # TODO: update after implement voucher end unless transaction.save merge_errors(transaction.errors) raise ActiveRecord::Rollback end update_firebase(transaction) # initial create pending payment status raise ActiveRecord::Rollback unless build_charge_record(transaction, form) unless transaction.save merge_errors(transaction.errors) raise ActiveRecord::Rollback end if transaction.status_as_symbol == :paid activate_tickets(transaction, form) end @outcome = transaction result = true rescue UncaughtThrowError raise ActiveRecord::Rollback, 'Failed to complete process, ticket transaction quota is not available' end if result update_firebase(@outcome) # update payment status count_down = AdminSetting.prompt_pay_count_down_in_minute [1, 5, 7, 10].each do |number| Workers::Payments::CheckPaymentWorker.perform_in(number.to_i.minutes, :ticket_transaction, @outcome.id) end Workers::Payments::CancelLaterWorker.perform_in(count_down.to_i.minutes, :ticket_transaction, @outcome.id) end result rescue InvalidVoucherPurchase, ActiveRecord::Rollback => e count_down = 10.minutes Workers::TicketTransactions::CancelTemporaryWorker.perform_in(count_down, transaction.id, transaction.session_id) Rails.logger.error(e) errors.add(:base, e.) false end |
#execute ⇒ Object
this action is to accept TicketTransaction without Locking System we should remove this action after Locking System is implemented
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/services/ticket_service/transaction.rb', line 28 def execute result = false ActiveRecord::Base.transaction do ticket_groups = find_ticket_group_from_params ticket_groups.each &:lock! form = TicketService::OldForm.new(params, user) form.error_container = errors raise ActiveRecord::Rollback, form. unless form.valid? transaction = TicketTransaction.new(form.transaction_attributes) transaction.active = true transaction.charge_price = transaction.total_price # TODO: update after implement voucher unless transaction.save merge_errors(transaction.errors) raise ActiveRecord::Rollback end transaction.ticket_bundles.each do |ticket_bundle| ticket_group = ticket_groups.select { |tg| tg.id == ticket_bundle.ticket_group_id }.last total_quantity_sold = ticket_bundle.quantity ticket_group.total_orders = ticket_group.total_orders.to_i + total_quantity_sold # prevent overbook if ticket_group.total_orders > ticket_group.quantity errors.add :base, "We're sorry this product is not available anymore" raise ActiveRecord::Rollback end end transaction.mark_partner_tickets_as_on_keep! update_firebase(transaction) # initial create pending payment status raise ActiveRecord::Rollback unless build_charge_record(transaction, form) unless transaction.save merge_errors(transaction.errors) raise ActiveRecord::Rollback end if transaction.status_as_symbol == :paid activate_tickets(transaction, form) update_firebase(transaction) # update success payment status end @outcome = transaction ticket_groups.each &:save! result = true if result count_down = AdminSetting.prompt_pay_count_down_in_minute [1, 5, 7, 10].each do |number| Workers::Payments::CheckPaymentWorker.perform_in(number.to_i.minutes, :ticket_transaction, transaction.id) end Workers::Payments::CancelLaterWorker.perform_in(count_down.to_i.minutes, :ticket_transaction, transaction.id) end end result rescue InvalidVoucherPurchase, ActiveRecord::Rollback => e errors.add(:base, e.) false end |
#lock ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'app/services/ticket_service/transaction.rb', line 94 def lock success = false fetched_tickets = {} # we use session_id to track the locking request ID # if the request ID is not the same, then there is a new request updating the session ID value new_session_id = nil ticket_groups = TicketGroup.where(id: params[:ticket_groups].pluck(:id).compact) quota_witness = InvQuotaWitness.new keys_to_watch = ticket_groups.map { |tg| quota_witness.quota_key(tg.id) } quota_witness.transaction(*keys_to_watch.join(',')) do |redis| ActiveRecord::Base.transaction do form = TicketService::Form.new(params, user) form.enable_locking_system form.error_container = errors raise ActiveRecord::Rollback, form. unless form.valid? transaction = TicketTransaction.new(form.transaction_attributes) transaction.active = true transaction.for_locking_system = true transaction.charge_price = transaction.total_price # TODO: update after implementing vouchers all_available = true # Check availability for each event transaction.ticket_bundles.each do |ticket_bundle| tickets = quota_witness.pick_tickets(ticket_bundle.ticket_group_id, ticket_bundle.quantity, redis) if tickets.length == ticket_bundle.quantity fetched_tickets[ticket_bundle.ticket_group_id] = tickets ticket_bundle.booked_quantity = tickets else all_available = false break end end if all_available result = [] redis.multi do |multi| fetched_tickets.each do |ticket_group_id, tickets| tickets.each do |ticket| result.push multi.srem(quota_witness.quota_key(ticket_group_id), ticket) end end end if result Rails.logger.warn 'Transaction executed: User got tickets for multiple events.' transaction.restaurant_id = params[:restaurant_id] transaction.mark_partner_tickets_as_on_keep! transaction.save! ticket_groups.each &:save! new_session_id = transaction.renew_session_id transaction.save! @outcome = transaction success = true else = 'Conflict' errors.add(:base, ) = "Transaction aborted: #{}" Rails.logger.warn redis.unwatch raise ActiveRecord::Rollback, end else = 'Insufficient available tickets' errors.add(:base, ) = "Transaction aborted: #{}" Rails.logger.warn redis.unwatch raise ActiveRecord::Rollback, end rescue ActiveRecord::Deadlocked = 'Transaction aborted: please try again later' errors.add(:base, ) raise ActiveRecord::Rollback, end end if success && @outcome.present? count_down = Time.zone.now + AdminSetting.reservation_session_timeout.to_i.minutes @expiry_time = count_down.utc.iso8601 Workers::TicketTransactions::CancelTemporaryWorker.perform_in(count_down, @outcome.id, new_session_id) elsif fetched_tickets.present? fetched_tickets.each do |ticket_group_id, tickets| Workers::TicketTransactions::RestoreQuotaWorker.perform_async(ticket_group_id, tickets) end end success rescue StandardError => e Rails.logger.error(e) fetched_tickets.each do |ticket_group_id, tickets| Workers::TicketTransactions::RestoreQuotaWorker.perform_async(ticket_group_id, tickets) end false end |
#set_transaction(transaction_id) ⇒ Object
22 23 24 |
# File 'app/services/ticket_service/transaction.rb', line 22 def set_transaction(transaction_id) @transaction = TicketTransaction.find_by(id: transaction_id) end |