- make second display to be responsive
This commit is contained in:
aungthetkhaing
2025-05-26 18:07:55 +06:30
parent da6a80a9bb
commit 613cb3a0cb
6 changed files with 320 additions and 83 deletions

View File

@@ -265,12 +265,29 @@ App.checkin = App.cable.subscriptions.create("SecondDisplayViewChannel", {
<p class="mb-1"><strong>Amount:</strong> <span id="qr-amount">${new Intl.NumberFormat(
"en-IN",
).format(Number(data.grand_total))} MMK</span></p>
<p class="mb-1"><strong>Invoice #:</strong> <span id="qr-invoice"> ${data.invoice_no} </span></p>
<p class="mb-1"><strong>Receipt NO:</strong> <span id="qr-invoice"> ${data.invoice_no} </span></p>
`;
document.querySelector("#qrpay_svg").innerHTML = data.qr_svg;
document.querySelector(".payment-details").innerHTML = html;
$("#mmqr_payment").removeClass("hidden");
$("#second_display_order_items").addClass("hidden");
}
if (status == "pay_success") {
$("#mmqr_payment").addClass("hidden");
$("#payment_success").removeClass("hidden");
console.log(data);
const html = `
<p class="mb-1"><strong>Amount Paid:</strong> <span id="success-amount">${new Intl.NumberFormat(
"en-IN",
).format(Number(data.data.paid_amount))} MMK</span></p>
<p class="mb-1"><strong>Receipt No:</strong> <span id="success-invoice">${data.data.receipt_no}</span></p>
`;
document.querySelector(".payment-summary").innerHTML = html;
setTimeout(() => {
jQuery("#s_reload").click();
}, 3000);
}
},
});

View File

@@ -191,32 +191,25 @@ class Foodcourt::QrpayController < BaseFoodcourtController
@merchant = KbzMerchant.new(@paymethod)
@response = @merchant.create_order(amount: @sale_data.grand_total, merch_order_id: @sale_data.receipt_no)
@qr_string = @response['qrCode']
case @response[:status]
when 'success'
@qr_string = @response[:data]["qrCode"]
qrcode = RQRCode::QRCode.new(@qr_string)
qrcode = RQRCode::QRCode.new(@qr_string)
@qr_svg = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 2,
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 })
@qr_svg = qrcode.as_svg(
color: "000",
shape_rendering: "crispEdges",
module_size: 2,
standalone: true,
use_path: true
)
# png_data = qrcode.as_png(
# bit_depth: 1,
# border_modules: 4,
# color_mode: ChunkyPNG::COLOR_GRAYSCALE,
# color: 'black',
# file: nil,
# fill: 'white',
# module_px_size: 8, # Pixel size of each module
# resize_exactly_to: false,
# resize_gte_to: false,
# size: 240 # Approximate size of the image in pixels (e.g., 240x240)
# ).to_s
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]
when 'failed'
@error = @response[:message]
end
end
end

View File

@@ -132,22 +132,68 @@ class KbzMerchant
)
puts "Response: #{response}"
response.body
# puts "Response: #{response}"
JSON.parse(response.body)
rescue HTTParty::Error => e
raise PaymentError, "HTTP error: #{e.message}"
{
error: true,
code: 'http_error',
message: "HTTP error: #{e.message}",
alert: "Payment service unavailable. Please try again later."
}
rescue SocketError => e
raise PaymentError, "Network error: #{e.message}"
{
error: true,
code: 'network_error',
message: "Network error: #{e.message}",
alert: "Network connection failed. Please check your internet."
}
rescue JSON::ParserError => e
{
error: true,
code: 'invalid_response',
message: "Invalid response format: #{e.message}",
alert: "Received invalid payment response. Please contact support."
}
rescue StandardError => e
{
error: true,
code: 'unexpected_error',
message: "Unexpected error: #{e.message}",
alert: "An unexpected error occurred. Please try again."
}
end
end
def handle_response(response)
json_response = JSON.parse(response)
if json_response.dig('Response', 'result') == 'SUCCESS'
json_response['Response']
if response['error']
{
status: 'error',
code: response['code'],
message: response['message']
}
elsif response.dig('Response', 'result') == 'SUCCESS'
{
status: 'success',
data: response['Response']
}
else
raise PaymentError, "Payment failed: #{json_response['Response']['msg']}"
error_code = response.dig('Response', 'code')
error_message = response.dig('Response', 'msg')
case error_code
when 'OrderCenter.FAILED_CREATE_ORDER_FOR_DUPLICATED_MERCHANT_ORDER_ID'
{
status: 'failed',
code: error_code,
message: 'Duplicate order ID detected. Please use a unique reference ID.'
}
else
{
status: 'failed',
code: error_code || 'unknown_error',
message: error_message || 'Payment processing failed'
}
end
end
end
end

View File

@@ -48,6 +48,10 @@
<% end %>
<div class="row clearfix h-100">
<% if @error %>
<input type="hidden" name="error_message" id="error_message" value="<%= json_escape(@error.to_json) %>">
<% end %>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 d-flex flex-column h-100">
<div class="card h-100" style="margin-bottom: 15px">
<div class="card-header m-l-5 m-r-5">
@@ -248,8 +252,12 @@
document.addEventListener('DOMContentLoaded', function() {
const paymentWaiting = document.querySelector('.payment-waiting');
let amountToReceive = <%= number_with_precision(@sale_data.grand_total, precision: precision.to_i) %>;
let receipt_no = document.querySelector('#receipt_no').textContent;
console.log(receipt_no);
const ws = new WebSocket("wss://juicecorner-0mo.sx-fc.app/cable");
ws.onopen = () => {
console.log("Nagato channel connected");
@@ -277,16 +285,6 @@
<p>Amount Received: ${amountToReceive}</p>
</div>
`;
ws.close();
// fetch('/foodcourt/qrpay/process_payment', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
// },
// body: JSON.stringify({ sale_id: "SAL-0qg000000671" })
// })
fetch('/foodcourt/qrpay/process_payment', {
method: 'POST',
@@ -298,7 +296,7 @@
}).then((res) => res.json())
.then((data) => {
if(data.status) {
customer_display_view(null, "reload");
customer_display_view({msg: "Payment Successful", paid_amount: amountToReceive,receipt_no:receipt_no}, "pay_success");
setTimeout(() => {
window.location.href = "/";
}, 3000)
@@ -317,6 +315,8 @@
function customer_display_view(data, status) {
let url = '/foodcourt/customer_view';
console.log(data);
$.ajax({
type: "POST",
url: url,

View File

@@ -1,81 +1,83 @@
<div class="row clearfix h-100">
<div class="col-lg-12 col-md-12 col-sm-12 h-100" id="second_display_order_items">
<!-- First Column: Invoice Details -->
<div class="col-12 h-100" id="second_display_order_items">
<div class="card h-100" style="opacity: 0.85; background-color: #f8f9fa;">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">Invoice Details</h4>
</div>
<div class="card-block">
<div class="card-body p-0">
<div class="card-text">
<div id="order-detail-slimscroll" style="max-height: 55vh; overflow-y: auto;">
<table class="table table-striped second_display_items" id="order-items-table">
<thead class="thead-light">
<tr>
<th>#</th>
<th class="item-name">Items</th>
<th class="item-qty">QTY</th>
<th class="item-price">Price</th>
</tr>
</thead>
<tbody>
<!-- Items will be populated here -->
</tbody>
</table>
<div id="order-detail-slimscroll" style="min-height: 55vh; height: 55vh; overflow-y: auto;">
<div class="table-responsive p-3">
<table class="table table-striped second_display_items mb-0" id="order-items-table">
<thead class="thead-light">
<tr>
<th>#</th>
<th class="item-name">Items</th>
<th class="item-qty">QTY</th>
<th class="item-price">Price</th>
</tr>
</thead>
<tbody>
<!-- Items will be populated by JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card-footer bg-light">
<table class="table table-borderless">
<table class="table table-borderless mb-0">
<tr>
<td class="charges-name"><strong>Sub Total:</strong></td>
<td class="text-right item-attr"><strong id="s_sub_total">0.00</strong></td>
<td class="text-right item-attr"><strong id="s_sub_total"></strong></td>
</tr>
<tr>
<td class="charges-name"><strong>Discount:</strong></td>
<td class="text-right item-attr"><strong id="s_total_discount">0.00</strong></td>
<td class="text-right item-attr"><strong id="s_total_discount"></strong></td>
</tr>
<tr>
<td class="charges-name"><strong>Tax Amount:</strong></td>
<td class="text-right item-attr"><strong id="s_tatal_tax">0.00</strong></td>
<td class="charges-name"><strong>Tax Amount (5%):</strong></td>
<td class="text-right item-attr"><strong id="s_tatal_tax"></strong></td>
</tr>
<tr class="table-active">
<td class="charges-name"><strong>Grand Total:</strong></td>
<td class="text-right item-attr"><strong id="s_grand_total">0.00</strong></td>
<td class="text-right item-attr"><strong id="s_grand_total"></strong></td>
</tr>
</table>
</div>
</div>
</div>
<div class="col-lg-12 col-md-12 col-sm-12 d-flex flex-column hidden" style="min-height: 80%; overflow-y: hidden;" id="mmqr_payment" >
<!-- Second Column: MMQR Payment -->
<div class="col-12 d-flex flex-column h-100 hidden" id="mmqr_payment" >
<div class="card h-100" style="opacity: 0.85;">
<div class="card-header text-white" style="background-color: #ffc107;">
<h4 class="mb-0">MMQR Payment Option</h4>
</div>
<div class="card-body d-flex flex-column align-items-center justify-content-center">
<img src="/image/mmqr.webp" alt="MMQR Payment" style="max-width: 120px; margin-bottom: 10px;">
<div class="text-center">
<h5>Scan to Pay</h5>
<p class="text-muted">Use your mobile wallet app</p>
</div>
<!-- QR Code Container -->
<div id="qr-payment-container" class="text-center p-3 bg-white rounded border" style="max-width: 300px;">
<div id="qr-code" class="mb-2">
<!-- QR Code will be generated here -->
<div class="">
<div id="qrpay_svg">
</div>
<div id="qrpay_svg">
<svg width="150" height="150" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" fill="#e0e0e0"/>
<text x="50" y="55" font-family="Arial, sans-serif" font-size="10" fill="#333" text-anchor="middle">QR Code Here</text>
</svg>
</div>
</div>
<div class="payment-details text-dark">
<p class="mb-1"><strong>Amount:</strong> <span id="qr-amount"><%= number_to_currency(@total_amount) %></span></p>
<p class="mb-1"><strong>Invoice #:</strong> <span id="qr-invoice"><%= @invoice_id %></span></p>
<p class="text-muted small">Expires in 15 minutes</p>
</div>
</div>
<div class="payment-instructions mt-4 text-center">
<h6>How to Pay:</h6>
<ol class="text-left small pl-3">
<ol class="text-left small pl-3 mb-0">
<li>Open your mobile wallet app</li>
<li>Tap on 'Scan QR Code'</li>
<li>Point your camera at this code</li>
@@ -83,25 +85,90 @@
</ol>
</div>
</div>
<div class="supported-partners-container mt-auto pt-3 pb-3">
<h6 class="supported-partners-title">Supported Partners</h6>
<div class="partner-logos">
<img src="/image/logo/AYA-Pay.webp" alt="AYA Pay" class="partner-logo">
<img src="/image/logo/KBZ-Pay.webp" alt="KBZ Pay" class="partner-logo">
<img src="/image/logo/OK-Dollor.webp" alt="OK Dollar" class="partner-logo">
<img src="/image/logo/Wave.webp" alt="Wave Money" class="partner-logo">
</div>
</div>
</div>
</div>
<!-- Third Column: Payment Success -->
<div class="col-12 d-flex flex-column h-100 hidden" id="payment_success">
<div class="card h-100 align-items-center justify-content-center text-center p-md-4 p-3" style="background-color: #f0fff0; opacity: 0.95; border: 1px solid #28a745;">
<div class="success-icon mb-3">
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" fill="currentColor" class="bi bi-check-circle-fill text-success" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
</svg>
</div>
<h2 class="mb-2 text-success success-title">Payment Successful!</h2>
<p class="lead mb-3 success-message">Your transaction has been completed.</p>
<div class="payment-summary bg-white p-3 rounded shadow-sm mb-4" style="max-width: 380px; border-left: 5px solid #28a745;color: #000000">
</div>
</div>
</div>
</div>
<style>
html, body, .row.h-100 {
height: 100%;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
background-color: #e9ecef;
}
.card {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border: none;
border-radius: 8px;
display: flex;
flex-direction: column;
}
.card-header {
border-radius: 8px 8px 0 0 !important;
padding: 12px 20px;
padding: 0.75rem 1.25rem;
}
.card-header h4 {
font-size: 1.25rem;
}
.card-body {
flex-grow: 1;
padding: 1.25rem;
overflow-y: auto;
}
#second_display_order_items .card-body.p-0 {
padding: 0 !important;
}
#order-detail-slimscroll .table-responsive.p-3 {
padding: 1.25rem !important;
}
.table {
margin-bottom: 1rem;
}
.table thead th {
border-bottom: 2px solid #dee2e6;
background-color: #e9ecef;
white-space: nowrap;
}
.table td, .table th {
vertical-align: middle;
}
#order-items-table .item-qty,
#order-items-table .item-price {
white-space: nowrap;
}
#qr-payment-container {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
@@ -112,13 +179,127 @@
padding: 15px;
border-radius: 8px;
max-width: 300px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.payment-instructions ol {
margin-bottom: 0;
}
.text-right {
text-align: right;
}
.text-right { text-align: right; }
.text-center { text-align: center; }
.table-active {
background-color: rgba(0,0,0,0.05);
background-color: rgba(0,0,0,0.07) !important;
font-size: 1.05em;
}
.table-active td strong { font-weight: bold; }
#order-items-table th, #order-items-table td {
padding: 0.65rem 0.75rem;
}
.charges-name { padding-left: 0.75rem; }
.item-attr {
padding-right: 0.75rem;
white-space: nowrap;
}
.table-borderless td, .table-borderless th {
border: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
.table-borderless.mb-0 { margin-bottom: 0 !important; }
.supported-partners-container {
text-align: center;
background-color: #f8f9fa;
padding-top: 1rem;
padding-bottom: 1rem;
border-top: 1px solid #e0e0e0;
border-radius: 0 0 8px 8px;
}
#mmqr_payment .card .supported-partners-container {
margin-top: auto;
}
.supported-partners-title {
color: #495057;
font-weight: 600;
font-size: 1rem;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.partner-logos {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
.partner-logo {
height: 35px;
width: auto;
max-width: 60px;
border-radius: 4px;
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.partner-logo:hover {
transform: scale(1.1);
box-shadow: 0 3px 6px rgba(0,0,0,0.12);
}
.h-100 { height: 100% !important; }
.row.h-100 > [class*="col-"] {
display: flex;
flex-direction: column;
/* Removed padding-top/bottom from here to let card manage its own full height */
}
#qrpay_svg {
display: flex;
justify-content: center;
align-items: center;
}
.hidden { display: none !important; }
/* Payment Success Screen Specific Styles */
#payment_success .card {
background-color: #f0fff0; /* Light green background */
opacity: 0.97; /* Slightly less transparent */
border: 1px solid #28a745; /* Success green border */
}
#payment_success .success-title {
font-weight: 600;
}
#payment_success .success-message {
color: #155724; /* Darker green for text */
}
#payment_success .payment-summary {
border-left: 5px solid #28a745; /* Accent border */
text-align: left; /* Align text to left inside summary box */
}
#payment_success .payment-summary p {
font-size: 0.95rem;
}
@media (max-width: 576px) {
.card-header h4 { font-size: 1.1rem; }
#order-items-table th, #order-items-table td {
padding: 0.5rem 0.5rem;
font-size: 0.9rem;
}
.charges-name, .item-attr { font-size: 0.9rem; }
.table-active { font-size: 1em; }
.payment-instructions h6 { font-size: 1rem; }
.payment-instructions .small { font-size: 85%; }
#payment_success .success-icon svg { width: 60px; height: 60px; }
#payment_success .success-title { font-size: 1.5rem; }
#payment_success .lead { font-size: 1rem; }
}
</style>

View File

@@ -88,13 +88,13 @@
}
</style>
<div class="container-fluid h-100" style="margin-top: -48px;">
<div class="container-fluid h-100">
<button type="button" class="hidden" id="s_reload">Reload</button>
<div class="slider" id="second_display_slider">
<%= render 'slider' %>
</div>
<div class="item hidden" id="second_display_items" style="max-height: 80%;">
<div class="item hidden" id="second_display_items" style="min-height: 80%;">
<%= render 'second_display' %>
</div>
</div>