Class: TicketTransaction

Inherits:
ApplicationRecord show all
Includes:
ArTransactionChanges, ModelExt::CreatedFromWebsite, OmiseProviderChecker
Defined in:
app/models/ticket_transaction.rb

Overview

Schema Information

Table name: ticket_transactions

id                                         :bigint           not null, primary key
active                                     :boolean          default(TRUE)
alipay_provider                            :string(191)
cancelled_by                               :string(191)
cc_provider                                :string(191)
channel                                    :string(191)
charge_price_cents                         :integer          default(0), not null
charge_price_currency                      :string(191)      default("THB"), not null
created_by                                 :string(191)      default("user")
email                                      :string(191)
for_locking_system                         :boolean          default(FALSE)
medium                                     :string(191)
phone                                      :string(191)
promptpay_provider                         :string(191)
quantity                                   :integer
shopee_pay_provider                        :string(191)
total_price_cents                          :integer          default(0), not null
total_price_currency                       :string(191)      default("THB"), not null
true_wallet_provider                       :string(191)
used_voucher_amount_by_hh_cents            :integer          default(0), not null
used_voucher_amount_by_hh_currency         :string(191)      default("THB"), not null
used_voucher_amount_by_restaurant_cents    :integer          default(0), not null
used_voucher_amount_by_restaurant_currency :string(191)      default("THB"), not null
wechat_pay_provider                        :string(191)
created_at                                 :datetime         not null
updated_at                                 :datetime         not null
guest_id                                   :bigint
restaurant_id                              :bigint
session_id                                 :string(191)
user_id                                    :bigint
vendor_order_id                            :string(191)

Indexes

index_ticket_transactions_on_for_locking_system  (for_locking_system)
index_ticket_transactions_on_guest_id            (guest_id)
index_ticket_transactions_on_restaurant_id       (restaurant_id)
index_ticket_transactions_on_user_id             (user_id)
index_ticket_transactions_on_vendor_order_id     (vendor_order_id)
index_tt_on_channel_and_vendor_order_id          (channel,vendor_order_id) UNIQUE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ModelExt::CreatedFromWebsite

#created_from_website?

Methods included from OmiseProviderChecker

omise_provider?

Methods inherited from ApplicationRecord

sync_carrierwave_url

Instance Attribute Details

#session_idString

Returns we use session ID to prevent old Sidekiq Job to cancel the transaction. everytime user extend the transaction, we re-new the session ID, so the old job on Sidekiq would be invalid.

Returns:

  • (String)

    we use session ID to prevent old Sidekiq Job to cancel the transaction. everytime user extend the transaction, we re-new the session ID, so the old job on Sidekiq would be invalid



# File 'app/models/ticket_transaction.rb', line 77

Class Method Details

.generate_klick_digital_client_order_id(id) ⇒ String

Generates a Klick Digital client order ID

Parameters:

  • id (String, Integer)

    The vendor order ID to prefix

Returns:

  • (String)

    The generated Klick Digital client order ID



90
91
92
# File 'app/models/ticket_transaction.rb', line 90

def self.generate_klick_digital_client_order_id(id)
  "KD#{id}"
end

.klick_digital_client_order_id?(id) ⇒ Boolean

Checks if an ID is from Klick Digital client

Parameters:

  • id (String, Integer)

    The ID to check

Returns:

  • (Boolean)

    true if ID starts with 'KD', false otherwise



83
84
85
# File 'app/models/ticket_transaction.rb', line 83

def self.klick_digital_client_order_id?(id)
  id.to_s.start_with?('KD')
end

Instance Method Details

#cancelled?Boolean

Returns:

  • (Boolean)


220
221
222
# File 'app/models/ticket_transaction.rb', line 220

def cancelled?
  !active?
end

#channel_nameObject



257
258
259
# File 'app/models/ticket_transaction.rb', line 257

def channel_name
  booking_channel&.name
end

#eligible_to_get_reward?Boolean

Returns:

  • (Boolean)


162
163
164
165
166
167
168
169
170
# File 'app/models/ticket_transaction.rb', line 162

def eligible_to_get_reward?
  today = Time.now_in_tz(restaurant.time_zone)

  return false if !restaurant.active || restaurant.date_offer_expired?(today)

  return false unless restaurant.eligible_for_rewards?

  !user_id.nil? && active?
end

#firebase_name_spaceObject



349
350
351
# File 'app/models/ticket_transaction.rb', line 349

def firebase_name_space
  'ticket_transaction'
end

#firebase_tracking_keyString

Returns the Firebase tracking key for this ticket transaction Used for real-time status updates in Firebase Realtime Database

Returns:

  • (String)

    Firebase path for this transaction



323
324
325
# File 'app/models/ticket_transaction.rb', line 323

def firebase_tracking_key
  "ticket_transaction/#{id}"
end

#loyalty_level_logoObject



195
196
197
198
199
200
201
202
203
204
# File 'app/models/ticket_transaction.rb', line 195

def 
  if user.present?
    LoyaltyLevel.find(
      user.user_loyalty.loyalty_level_id ||
      LoyaltyLevel.hunger.id,
    ).icon_badge
  else
    LoyaltyLevel.hunger.icon_badge
  end
end

#mark_paid_charges_as_refunded!Object



367
368
369
# File 'app/models/ticket_transaction.rb', line 367

def mark_paid_charges_as_refunded!
  paid_charges.update_all(status: 'refund')
end

#mark_partner_tickets_as_available!Object



291
292
293
# File 'app/models/ticket_transaction.rb', line 291

def mark_partner_tickets_as_available!
  update_partner_ticket_status(:available)
end

#mark_partner_tickets_as_on_keep!Object



283
284
285
# File 'app/models/ticket_transaction.rb', line 283

def mark_partner_tickets_as_on_keep!
  update_partner_ticket_status(:on_keep)
end

#mark_partner_tickets_as_sold!Object



287
288
289
# File 'app/models/ticket_transaction.rb', line 287

def mark_partner_tickets_as_sold!
  update_partner_ticket_status(:sold)
end

#mark_tickets_as_active!Object



275
276
277
# File 'app/models/ticket_transaction.rb', line 275

def mark_tickets_as_active!
  update_ticket_activity(true)
end

#mark_tickets_as_inactive!Object



279
280
281
# File 'app/models/ticket_transaction.rb', line 279

def mark_tickets_as_inactive!
  update_ticket_activity(false)
end

#omise_payment_gateway?Boolean

Check if the ticket transaction is using Omise as the payment gateway

Returns:

  • (Boolean)

    true if using Omise for any payment type (credit card, promptpay)



310
311
312
313
# File 'app/models/ticket_transaction.rb', line 310

def omise_payment_gateway?
  # Check if any payment method is using Omise
  uses_omise_provider?(cc_provider) || uses_omise_provider?(promptpay_provider)
end

#oversold?Boolean

Returns:

  • (Boolean)


109
110
111
# File 'app/models/ticket_transaction.rb', line 109

def oversold?
  ticket_bundles.detect { |tb| tb.ticket_group&.oversold? }
end


212
213
214
# File 'app/models/ticket_transaction.rb', line 212

def paid_charges
  charges.success_scope
end

#payment_failed_urlObject



315
316
317
318
# File 'app/models/ticket_transaction.rb', line 315

def payment_failed_url
  url = AdminSetting.web_v2_host
  "#{url}/restaurants/hungryhub-voucher/payment-failed?type=ticket"
end

#payment_statusObject



99
100
101
102
103
104
105
106
107
# File 'app/models/ticket_transaction.rb', line 99

def payment_status
  if paid_charges.present?
    'Paid (100%)'
  elsif refund_charges.present?
    'Refunded'
  else
    'Unpaid'
  end
end

#payment_success_urlObject



295
296
297
298
299
300
301
302
303
304
305
306
# File 'app/models/ticket_transaction.rb', line 295

def payment_success_url
  url = AdminSetting.web_v2_host

  # Use 'payment-landing' instead of 'payment-success' when payment gateway is Omise
  path_segment = if omise_payment_gateway?
                   'payment-landing'
                 else
                   'payment-success'
                 end

  "#{url}/restaurants/hungryhub-voucher/#{path_segment}/#{encrypted_id}?type=ticket"
end

#payment_type_providerObject



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'app/models/ticket_transaction.rb', line 172

def payment_type_provider
  if promptpay_provider.present?
    'promptpay'
  elsif cc_provider.present?
    'credit_card'
  elsif true_wallet_provider.present?
    'true_wallet'
  elsif shopee_pay_provider.present?
    'shopee_pay'
  elsif alipay_provider.present?
    'alipay_plus'
  elsif wechat_pay_provider.present?
    'wechat_pay'
  elsif vendor_ticket_transaction_payment.present?
    booking_channel&.uri_name
  end
end

#pending_chargesObject



206
207
208
209
210
# File 'app/models/ticket_transaction.rb', line 206

def pending_charges
  return @pending_charges if defined?(@pending_charges)

  @pending_charges = charges.pending_scope
end

#phone_formatObject



94
95
96
97
# File 'app/models/ticket_transaction.rb', line 94

def phone_format
  phone_lib = Phonelib.parse(phone || '-')
  "+#{phone_lib.country_code} #{phone_lib.raw_national}"
end

#prevent_oversoldObject



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
# File 'app/models/ticket_transaction.rb', line 113

def prevent_oversold
  return true if transaction_changed_attributes.blank?

  changed_keys = transaction_changed_attributes.keys
  return unless changed_keys.include?('active') || changed_keys.include?('for_locking_system')

  if in_locked_state? # locked
    success = true
    ticket_bundles.includes(:ticket_group).find_each do |ticket_bundle|
      ticket_bundle.ticket_group.with_lock do
        if ticket_bundle.ticket_group.will_oversold?(ticket_bundle.quantity)
          success = false
          throw :abort, "Voucher #{ticket_bundle.ticket_group.name} is full"
        end
        ticket_bundle.ticket_group.total_orders = ticket_bundle.ticket_group.total_orders.to_i + ticket_bundle.quantity
        ticket_bundle.ticket_group.save!
      end
    end
    throw :abort unless success
  elsif in_waiting_for_payment_state?
    success = true
    ticket_bundles.includes(:ticket_group).find_each do |ticket_bundle|
      ticket_bundle.ticket_group.with_lock do
        if ticket_bundle.ticket_group.is_full?
          success = false
          throw :abort, "Voucher #{ticket_bundle.ticket_group.name} is full"
        end
      end
    end
    throw :abort unless success
  elsif in_paid_state?
    # do nothing
  elsif !active? # canceled
    ticket_bundles.includes(:ticket_group).find_each do |ticket_bundle|
      ticket_bundle.ticket_group.with_lock do
        ticket_bundle.ticket_group.total_orders -= ticket_bundle.quantity
        if ticket_bundle.ticket_group.total_orders.negative?
          HH_LOGGER.error("Ticket restored but total_orders value is negative #{ticket_group.total_orders}",
                          ticket_transaction: ticket_transaction&.attributes)
          ticket_group.total_orders = 0
        end
        ticket_bundle.ticket_group.save!
      end
    end
  else
    raise NotImplementedError
  end
end

#qr_code_for_paymentObject



327
328
329
330
# File 'app/models/ticket_transaction.rb', line 327

def qr_code_for_payment
  charge = charges.present? && charges.pending_scope.promptpay_source.last
  charge.source.qr_code.url if charge
end

#qr_code_for_payment_expired_atObject



261
262
263
# File 'app/models/ticket_transaction.rb', line 261

def qr_code_for_payment_expired_at
  created_at + AdminSetting.prompt_pay_count_down_in_minute.to_i.minute
end

#qrcode_data_urlObject



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'app/models/ticket_transaction.rb', line 332

def qrcode_data_url
  qrcode = RQRCode::QRCode.new(id.to_s)
  png = qrcode.as_png(
    bit_depth: 1,
    border_modules: 4,
    color_mode: ChunkyPNG::COLOR_GRAYSCALE,
    color: 'black',
    file: nil,
    fill: 'white',
    module_px_size: 6,
    resize_exactly_to: false,
    resize_gte_to: false,
    size: 500,
  )
  png.to_data_url
end

#reached_goal?Boolean

Returns:

  • (Boolean)


353
354
355
# File 'app/models/ticket_transaction.rb', line 353

def reached_goal?
  active? && status_as_symbol == :paid
end

#refund_chargesObject



216
217
218
# File 'app/models/ticket_transaction.rb', line 216

def refund_charges
  charges.refund_scope
end

#renew_session_idString

Returns:

  • (String)


191
192
193
# File 'app/models/ticket_transaction.rb', line 191

def renew_session_id
  self.session_id = SecureRandom.hex(10)
end

#status_as_symbolObject



265
266
267
268
269
270
271
272
273
# File 'app/models/ticket_transaction.rb', line 265

def status_as_symbol
  if !active? && paid_charges.blank?
    :cancelled
  elsif paid_charges.present?
    :paid
  else
    :waiting_for_payment
  end
end

#status_propertyObject



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
# File 'app/models/ticket_transaction.rb', line 224

def status_property
  status = case status_as_symbol.to_sym
           when :waiting_for_payment
             'Unpaid'
           when :paid
             'Paid'
           when :cancelled
             'Canceled'
           else
             status_as_symbol ? status_as_symbol.to_s.split('_').join(' ').titleize : '-'
           end

  text_color = case status_as_symbol.to_sym
               when :paid
                 '#2EC743'
               else
                 '#E40028'
               end

  bg_color = case status_as_symbol.to_sym
             when :paid
               '#EEFCEF'
             else
               '#FFEAEB'
             end

  {
    'status' => status,
    'text_color' => text_color,
    'bg_color' => bg_color,
  }
end

#third_party_indicatorObject



357
358
359
360
361
362
363
364
365
# File 'app/models/ticket_transaction.rb', line 357

def third_party_indicator
  if [
    ApiVendorV1::Constants::VENDOR_CHANNEL_GROUP_NAME,
  ].include?(booking_channel.group_name)
    channel_name
  else
    ''
  end
end