Files
sx-fc/app/controllers/foodcourt/qrpay_controller.rb
2025-07-04 16:37:24 +06:30

770 lines
25 KiB
Ruby

class Foodcourt::QrpayController < BaseFoodcourtController
class PaymentProcessingError < StandardError; end
require 'rqrcode'
skip_before_action :authenticate, only: [:create]
skip_before_action :verify_authenticity_token, only: [:create]
def precreate
@cash_exist = PaymentMethodSetting.cash_exist
@credit_exist = PaymentMethodSetting.credit_exist
display_type = Lookup.find_by_lookup_type("display_type")
if !display_type.nil? && display_type.value.to_i == 2
@display_type = display_type.value
else
@display_type = nil
end
path = request.fullpath
sale_id = params[:sale_id]
@trans_flag = true
@cashier_type = params[:cashier_type] || session[:cashier_type] || "cashier"
Rails.logger.info "Precreate action called with params: #{@cashier_type}"
# if params[:type] == "transaction"
# @trans_flag = false
# @cashier_type = "cashier"
# else
# @cashier_type = params[:type]
# end
if path.include? ("credit_payment")
@sale_payment = SalePayment.get_credit_amount_due_left(sale_id)
end
@member_discount = MembershipSetting.find_by_discount(1)
@membership_rebate_balance=0
if Sale.exists?(sale_id)
begin
@cash = 0.0
@kbz_pay_amount = 0.0
@other = 0.0
@ppamount = 0.0
@visacount= 0.0
@jcbcount= 0.0
@mastercount = 0.0
@unionpaycount = 0.0
@alipaycount = 0.0
@junctionpaycount = 0.0
@credit = 0.0
@paymalcount = 0.0
@dingacount = 0.0
@giftvouchercount = 0.0
@sale_data = Sale.find_by_sale_id(sale_id)
@balance = 0
@accountable_type = ''
@table_no = ''
@dining = ''
@other_payment = 0.0
@pdf_view = nil
@lookup_pdf = Lookup.find_by_lookup_type("ReceiptPdfView")
if !@lookup_pdf.nil?
@pdf_view = @lookup_pdf.value
end
amount = SalePayment.get_kbz_pay_amount(sale_id, current_user)
@kbz_pay_amount += amount.to_f
# @shop = shop_detail #show shop info
@customer_lists = Customer.where(name: ["WALK-IN", "TAKEAWAY"])
saleObj = Sale.find(sale_id)
#total customer with individual total amount
@individual_total = Array.new
if !saleObj.equal_persons.nil?
per_person_amount = saleObj.grand_total.to_f / saleObj.equal_persons.to_i
@individual_total.push({'total_customer' => saleObj.equal_persons.to_i, 'per_person_amount' => per_person_amount.to_f })
end
if current_shop.is_rounding_adj
a = saleObj.grand_total % 25 # Modulus
b = saleObj.grand_total / 25 # Division
#not calculate rounding if modulus is 0 and division is even
#calculate rounding if modulus is zero or not zero and division are not even
if (a != 0.0 && b%2 != 0.0) || (a==0.0 && b%2 !=0)
new_total = Sale.get_rounding_adjustment(saleObj.grand_total)
@rounding_adj = new_total-saleObj.grand_total
saleObj.update_attributes(grand_total: new_total,old_grand_total: saleObj.grand_total,rounding_adjustment:@rounding_adj)
@sale_data.grand_total = new_total
@sale_data.old_grand_total = saleObj.grand_total
@sale_data.rounding_adjustment = @rounding_adj
else
@rounding_adj = @sale_data.rounding_adjustment
end
else
@rounding_adj = @sale_data.rounding_adjustment
end
#end rounding adjustment
# get printer info
@print_settings = PrintSetting.get_precision_delimiter()
#get customer amount
@customer = Customer.find(@sale_data.customer_id)
# accounts = @customer.tax_profiles
accounts = TaxProfile.where("group_type = ?",@cashier_type).order("order_by ASC")
@account_arr =[]
@tax_arr =[]
accounts.each do |acc|
account = TaxProfile.find(acc.id)
# @account_arr.push(account)
@tax_arr.push(account.name)
end
sale_taxes = SaleTax.where("sale_id = ?", saleObj.sale_id)
if !sale_taxes.empty?
sale_taxes.each do |sale_tax|
@account_arr.push(sale_tax)
end
end
rebate = MembershipSetting.find_by_rebate(1)
# get member information
if @customer.membership_id != nil && rebate
response = Customer.get_member_account(@customer)
if response["status"]==true
response["account_data"].each do |res|
if res["accountable_type"] == "RebateAccount" || res["accountable_type"] == "RebatebonusAccount"
@balance = @balance.to_f + res["balance"].to_f
# @accountable_type = res["accountable_type"]
@accountable_type = "Rebate Balance"
end
end
end
end
#end customer amount
#paymal payment
@sale_data.bookings.each do |sbk|
if sbk.dining_facility_id.to_i >0
df = DiningFacility.find(sbk.dining_facility_id)
@table_no = df.type + ' ' + df.name
@checkin_time = sbk.checkin_at
@dining = df
break
else
@table_no = nil
@checkin_time = nil
@dining = nil
end
end
if path.include? ("credit_payment")
@sale_payment_data = SalePayment.get_sale_payment_for_credit(@sale_data)
else
@sale_payment_data = SalePayment.get_sale_payments(@sale_data)
end
@sale_payment_data.each do |spay|
if spay.payment_method == "cash"
@cash += spay.payment_amount
end
if spay.payment_method !="creditnote"
@other_payment += spay.payment_amount
end
if spay.payment_method == "mpu"
@other += spay.payment_amount
elsif spay.payment_method == "paypar"
@ppamount += spay.payment_amount
elsif spay.payment_method == "visa"
@visacount += spay.payment_amount
elsif spay.payment_method == "jcb"
@jcbcount += spay.payment_amount
elsif spay.payment_method == "master"
@mastercount += spay.payment_amount
elsif spay.payment_method == "unionpay"
@unionpaycount += spay.payment_amount
elsif spay.payment_method == "JunctionPay"
@junctionpaycount += spay.payment_amount
elsif spay.payment_method == "creditnote"
@credit += spay.payment_amount
elsif spay.payment_method == "paymal"
@paymalcount += spay.payment_amount
elsif spay.payment_method == "alipay"
@alipaycount += spay.payment_amount
elsif spay.payment_method == "dinga"
@dingacount += spay.payment_amount
elsif spay.payment_method == "giftvoucher"
@giftvouchercount += spay.payment_amount
end
end
end
end
end
def init
@cash_exist = PaymentMethodSetting.cash_exist
@credit_exist = PaymentMethodSetting.credit_exist
display_type = Lookup.find_by_lookup_type("display_type")
if !display_type.nil? && display_type.value.to_i == 2
@display_type = display_type.value
else
@display_type = nil
end
path = request.fullpath
sale_id = params[:sale_id]
@trans_flag = true
if params[:type] == "transaction"
@trans_flag = false
@cashier_type = "cashier"
else
@cashier_type = params[:type]
end
if path.include? ("credit_payment")
@sale_payment = SalePayment.get_credit_amount_due_left(sale_id)
end
@member_discount = MembershipSetting.find_by_discount(1)
@membership_rebate_balance=0
if Sale.exists?(sale_id)
begin
@cash = 0.0
@kbz_pay_amount = 0.0
@other = 0.0
@ppamount = 0.0
@visacount= 0.0
@jcbcount= 0.0
@mastercount = 0.0
@unionpaycount = 0.0
@alipaycount = 0.0
@junctionpaycount = 0.0
@credit = 0.0
@paymalcount = 0.0
@dingacount = 0.0
@giftvouchercount = 0.0
@sale_data = Sale.find_by_sale_id(sale_id)
@balance = 0
@accountable_type = ''
@table_no = ''
@dining = ''
@other_payment = 0.0
@pdf_view = nil
@lookup_pdf = Lookup.find_by_lookup_type("ReceiptPdfView")
if !@lookup_pdf.nil?
@pdf_view = @lookup_pdf.value
end
amount = SalePayment.get_kbz_pay_amount(sale_id, current_user)
@kbz_pay_amount += amount.to_f
# @shop = shop_detail #show shop info
@customer_lists = Customer.where(name: ["WALK-IN", "TAKEAWAY"])
saleObj = Sale.find(sale_id)
#total customer with individual total amount
@individual_total = Array.new
if !saleObj.equal_persons.nil?
per_person_amount = saleObj.grand_total.to_f / saleObj.equal_persons.to_i
@individual_total.push({'total_customer' => saleObj.equal_persons.to_i, 'per_person_amount' => per_person_amount.to_f })
end
if current_shop.is_rounding_adj
a = saleObj.grand_total % 25 # Modulus
b = saleObj.grand_total / 25 # Division
#not calculate rounding if modulus is 0 and division is even
#calculate rounding if modulus is zero or not zero and division are not even
if (a != 0.0 && b%2 != 0.0) || (a==0.0 && b%2 !=0)
new_total = Sale.get_rounding_adjustment(saleObj.grand_total)
@rounding_adj = new_total-saleObj.grand_total
saleObj.update_attributes(grand_total: new_total,old_grand_total: saleObj.grand_total,rounding_adjustment:@rounding_adj)
@sale_data.grand_total = new_total
@sale_data.old_grand_total = saleObj.grand_total
@sale_data.rounding_adjustment = @rounding_adj
else
@rounding_adj = @sale_data.rounding_adjustment
end
else
@rounding_adj = @sale_data.rounding_adjustment
end
#end rounding adjustment
# get printer info
@print_settings = PrintSetting.get_precision_delimiter()
#get customer amount
@customer = Customer.find(@sale_data.customer_id)
# accounts = @customer.tax_profiles
accounts = TaxProfile.where("group_type = ?",@cashier_type).order("order_by ASC")
@account_arr =[]
@tax_arr =[]
accounts.each do |acc|
account = TaxProfile.find(acc.id)
# @account_arr.push(account)
@tax_arr.push(account.name)
end
sale_taxes = SaleTax.where("sale_id = ?", saleObj.sale_id)
if !sale_taxes.empty?
sale_taxes.each do |sale_tax|
@account_arr.push(sale_tax)
end
end
rebate = MembershipSetting.find_by_rebate(1)
# get member information
if @customer.membership_id != nil && rebate
response = Customer.get_member_account(@customer)
if response["status"]==true
response["account_data"].each do |res|
if res["accountable_type"] == "RebateAccount" || res["accountable_type"] == "RebatebonusAccount"
@balance = @balance.to_f + res["balance"].to_f
# @accountable_type = res["accountable_type"]
@accountable_type = "Rebate Balance"
end
end
end
end
#end customer amount
#paymal payment
@sale_data.bookings.each do |sbk|
if sbk.dining_facility_id.to_i >0
df = DiningFacility.find(sbk.dining_facility_id)
@table_no = df.type + ' ' + df.name
@checkin_time = sbk.checkin_at
@dining = df
break
else
@table_no = nil
@checkin_time = nil
@dining = nil
end
end
if path.include? ("credit_payment")
@sale_payment_data = SalePayment.get_sale_payment_for_credit(@sale_data)
else
@sale_payment_data = SalePayment.get_sale_payments(@sale_data)
end
@sale_payment_data.each do |spay|
if spay.payment_method == "cash"
@cash += spay.payment_amount
end
if spay.payment_method !="creditnote"
@other_payment += spay.payment_amount
end
if spay.payment_method == "mpu"
@other += spay.payment_amount
elsif spay.payment_method == "paypar"
@ppamount += spay.payment_amount
elsif spay.payment_method == "visa"
@visacount += spay.payment_amount
elsif spay.payment_method == "jcb"
@jcbcount += spay.payment_amount
elsif spay.payment_method == "master"
@mastercount += spay.payment_amount
elsif spay.payment_method == "unionpay"
@unionpaycount += spay.payment_amount
elsif spay.payment_method == "JunctionPay"
@junctionpaycount += spay.payment_amount
elsif spay.payment_method == "creditnote"
@credit += spay.payment_amount
elsif spay.payment_method == "paymal"
@paymalcount += spay.payment_amount
elsif spay.payment_method == "alipay"
@alipaycount += spay.payment_amount
elsif spay.payment_method == "dinga"
@dingacount += spay.payment_amount
elsif spay.payment_method == "giftvoucher"
@giftvouchercount += spay.payment_amount
end
end
@paymethod = PaymentMethodSetting.find_by(payment_method: "MMQR")
if @paymethod.nil?
raise "MMQR payment method not configured"
end
@merchant = KbzMerchant.new(@paymethod)
@response = @merchant.create_order(amount: @sale_data.grand_total, merch_order_id: @sale_data.receipt_no)
case @response[:status]
when 'success'
@qr_string = @response[:data]["qrCode"]
qrcode = RQRCode::QRCode.new(@qr_string)
@qr_svg = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 3,
standalone: true,
use_path: true
)
ActionCable.server.broadcast('second_display_view_channel', { data: @qr_string, qr_svg: @qr_svg, grand_total: @sale_data.grand_total, invoice_no: @sale_data.receipt_no })
when 'error'
@error = @response[:message]
raise PaymentProcessingError, @response[:message]
when 'failed'
@error = @response[:message]
raise PaymentProcessingError, @response[:message]
end
rescue PaymentProcessingError, StandardError => e
Rails.logger.error("Payment processing error: #{e.message}")
cancel_order_and_sale(sale_id, e.message)
flash[:error] = "Payment failed: #{e.message}"
redirect_to foodcourt_food_court_path
return
end
end
end
def create
sale_id = params[:sale_id]
unless current_login_employee
render json: { status: false, message: "User not authenticated or employee context missing." }, status: :unauthorized
return
end
qrpayment_service = QrPaymentService.new(sale_id, current_login_employee)
result = qrpayment_service.process
PrintReceiptJob.perform_later(current_shop.shop_code, sale_id)
PaymentGatewayAuditJob.perform_later({
receipt_no: Sale.find_by(sale_id: sale_id).receipt_no,
gateway_name: "MMQR",
endpoint_url: "",
event_type: "kbz.payment.success",
request_body: {},
response_body: {},
request_method: nil,
shop_code: Shop.current_shop.shop_code
})
if result[:status]
render json: result, status: :ok
else
status_code = result[:message].include?("not found") ? :not_found : :unprocessable_entity
render json: result, status: status_code
end
end
def req_bill
sale_data =[]
customer_id = params[:customer_id]
if !ShiftSale.current_shift.nil?
order_id = params[:order_id]
order = Order.find(order_id)
booking = order.booking
if Customer.exists?(customer_id: customer_id)
booking.orders.update_all(customer_id: customer_id)
end
if booking.checkin_at.utc > Time.now.utc && booking.checkout_at.nil?
@status = false
@error_message = "Operation failed, Could not request bill!"
else
table = DiningFacility.find_by(id: booking.dining_facility_id)
if booking.sale_id.nil?
if sale_data = Sale.generate_invoice_from_booking(booking, current_login_employee, current_user, order.source, params[:current_checkin_induties_count])
# in-duty update
in_duties = InDuty.where("booking_id=?", booking.id)
if !in_duties.empty?
in_duties.each do |in_duty|
induty = InDuty.find(in_duty.id)
induty.sale_id = sale_data.sale_id
induty.out_time = Time.now
induty.save
end
end
action_by = current_user.name
type = "REQUEST_BILL"
remark = "Request bill Receipt No #{sale_data.receipt_no}"
sale_audit = SaleAudit.record_audit_sale(sale_data.sale_id,remark,action_by,type )
# Promotion Activation
Promotion.promo_activate(sale_data)
#bill channel
if ENV["SERVER_MODE"] == 'cloud'
from = request.host
else
from = ""
end
if ["quick_service", "cashier"].include? order.source
ActionCable.server.broadcast "bill_channel", table: table, from: from
end
unless ["quick_service", "food_court"].include? order.source
#check checkInOut pdf print
checkout_time = Lookup.collection_of('checkout_time')
if !booking.dining_facility_id.nil?
terminal = DiningFacility.find_by_id(booking.dining_facility_id)
cashier_terminal = CashierTerminal.find_by_id(terminal.zone_id)
if (!checkout_time.empty?) && (ENV["SERVER_MODE"] != "cloud") #no print in cloud server
unique_code = "CheckInOutPdf"
printer = PrintSetting.find_by_unique_code(unique_code)
# print when complete click
order_queue_printer = Printer::OrderQueuePrinter.new(printer)
if !printer.nil?
order_queue_printer.print_check_in_out(printer, cashier_terminal, booking, table)
end
end
end
end
@status = true
sale_id = sale_data.sale_id
else
@status = false
sale_id = nil
end
else
@status = true
sale_id = booking.sale_id
end
end
respond_to do |format|
format.json { render :json => { :status => @status, :sale_id => sale_id } }
end
else
respond_to do |format|
format.json {
render :json => { :status => false, :error_message => "No Current Open Shift for This Employee" } }
end
end
end
def test_pay
end
def check_payment_status
receipt_no = params[:receipt_no]
if receipt_no.blank?
render json: { status: 'ERROR', message: "Receipt number is missing" }, status: :unprocessable_entity
return
end
@paymethod = PaymentMethodSetting.find_by(payment_method: "MMQR")
if @paymethod.nil?
render json: { status: 'CONFIG_ERROR', message: "MMQR payment method not configured" }, status: :ok
return
end
begin
@merchant = KbzMerchant.new(@paymethod)
response = @merchant.query_order(merch_order_id: receipt_no)
if response[:data]['code'] == '0'
case response[:data]['trade_status']
when 'PAY_SUCCESS'
render json: { status: 'PAY_SUCCESS' }
else
render json: { status: response[:data]['trade_status'] || 'PENDING' }
end
else
render json: {
status: 'KBZ_ERROR',
message: "KBZ Error #{response[:data]['code']}: #{response['msg']}"
}, status: :ok
end
rescue StandardError => e
render json: {
status: 'SERVER_ERROR',
message: "Payment check failed: #{e.message}"
}, status: :internal_server_error
end
end
def cancel
# cancel orders and related
sale_id = params[:sale_id]
sale = Sale.find_by_sale_id(sale_id)
order = sale.orders.first
booking = order.booking
if !sale_id.present?
respond_to do |format|
format.json { render :json => { :status => false, :error_message => "Sale does not exist" } }
end
end
if !order.present?
respond_to do |format|
format.json { render :json => { :status => false, :error_message => "Order does not exist" } }
end
end
if !booking.present?
respond_to do |format|
format.json { render :json => { :status => false, :error_message => "Booking does not exist" } }
end
end
if order.order_items.present?
order.order_items.update_all(order_item_status: 'cancelled')
end
order.status = 'cancelled'
order.save
booking.booking_status = 'cancelled'
booking.save
# void order
remark = "MMQR payment cancellation"
order_source = 'foodcourt'
access_code = 'null'
# update count for shift sale
if(sale.sale_status == "completed")
if sale.shift_sale_id != nil
shift = ShiftSale.find(sale.shift_sale_id)
shift.calculate(sale_id, "void")
end
else
# void before sale payment complete
if sale.shift_sale_id != nil
shift = ShiftSale.find(sale.shift_sale_id)
shift.total_void = shift.total_void + sale.grand_total
shift.save
end
end
if sale.discount_type == "member_discount"
sale.update_attributes(total_discount: 0)
sale.compute_by_sale_items(0, nil, order_source)
end
sale.rounding_adjustment = 0.0
sale.payment_status = 'void'
sale.sale_status = 'void'
sale.save
# call close order to qr pay api
kbz_merchant = KbzMerchant.new(PaymentMethodSetting.find_by(payment_method: 'MMQR'))
response = kbz_merchant.close_order(merch_order_id: sale.receipt_no)
Rails.logger.info ">>>>>>>>>>>>>>>>>>>> #{response}"
PrintReceiptJob.perform_later(current_shop.shop_code, sale.sale_id)
if table = sale.booking.dining_facility
unless table.current_bookings.exists?
table.update_attributes(status: 'available')
end
end
action_by = current_user.name
if access_code != "null" && current_user.role == "cashier"
action_by = Employee.find_by_emp_id(access_code).name
end
# remark = "Void Sale ID #{sale_id} | Receipt No #{sale.receipt_no} | Receipt No #{sale.receipt_no} | Table ->#{table.name}"
sale_audit = SaleAudit.record_audit_for_edit(sale.sale_id, current_user.name, action_by,remark, "CANCEL_MMQR_PAYMENT")
# update complete order items in oqs
SaleOrder.where("sale_id = '#{sale.sale_id}'").find_each do |sodr|
AssignedOrderItem.where("order_id = '#{ sodr.order_id }'").find_each do |aoi|
aoi.delivery_status = 1
aoi.save
end
end
respond_to do |format|
format.json { render :json => { status: true, order_id: order.order_id } }
end
end
private
def cancel_order_and_sale(sale_id, error_message = nil)
result = { success: false, error: nil }
sale = Sale.find_by(sale_id: sale_id)
unless sale
result[:error] = "Sale not found"
return result
end
order = sale.orders.first
unless order
result[:error] = "Order not found"
return result
end
booking = order.booking
unless booking
result[:error] = "Booking not found"
return result
end
begin
Sale.transaction do
order.order_items.update_all(order_item_status: 'cancelled') if order.order_items.present?
order.update!(status: 'cancelled')
booking.update!(booking_status: 'cancelled')
if sale.shift_sale_id
shift = ShiftSale.find(sale.shift_sale_id)
if sale.sale_status == "completed"
shift.calculate(sale_id, "void")
else
shift.update!(total_void: shift.total_void + sale.grand_total)
end
end
if sale.discount_type == "member_discount"
sale.update!(total_discount: 0)
sale.compute_by_sale_items(0, nil, 'foodcourt')
end
sale.update!(
rounding_adjustment: 0.0,
payment_status: 'void',
sale_status: 'void'
)
if table = booking.dining_facility
table.update!(status: 'available') unless table.current_bookings.exists?
end
action_by = current_user.role == "cashier" && params[:access_code].present? ?
Employee.find_by(emp_id: params[:access_code])&.name :
current_user.name
remark = error_message ? "Payment failed: #{error_message}" : "Manually cancelled"
SaleAudit.record_audit_for_edit(
sale.sale_id,
current_user.name,
action_by,
remark,
"CANCEL_MMQR_PAYMENT"
)
SaleOrder.where(sale_id: sale.sale_id).find_each do |sodr|
AssignedOrderItem.where(order_id: sodr.order_id).update_all(delivery_status: 1)
end
result[:success] = true
end
rescue StandardError => e
result[:error] = "Cancellation failed: #{e.message}"
Rails.logger.error "Cancellation error: #{e.message}\n#{e.backtrace.join("\n")}"
end
result
end
end