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 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 = "" 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, "SALEVOID") # 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, "SALEVOID" ) 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