- 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( <p class="mb-1"><strong>Amount:</strong> <span id="qr-amount">${new Intl.NumberFormat(
"en-IN", "en-IN",
).format(Number(data.grand_total))} MMK</span></p> ).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("#qrpay_svg").innerHTML = data.qr_svg;
document.querySelector(".payment-details").innerHTML = html; document.querySelector(".payment-details").innerHTML = html;
$("#mmqr_payment").removeClass("hidden"); $("#mmqr_payment").removeClass("hidden");
$("#second_display_order_items").addClass("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) @merchant = KbzMerchant.new(@paymethod)
@response = @merchant.create_order(amount: @sale_data.grand_total, merch_order_id: @sale_data.receipt_no) @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( when 'error'
color: "000", @error = @response[:message]
shape_rendering: "crispEdges", when 'failed'
module_size: 2, @error = @response[:message]
standalone: true, end
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 })
end end
end end

View File

@@ -132,22 +132,68 @@ class KbzMerchant
) )
puts "Response: #{response}" puts "Response: #{response}"
response.body JSON.parse(response.body)
# puts "Response: #{response}"
rescue HTTParty::Error => e 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 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
end end
def handle_response(response) def handle_response(response)
json_response = JSON.parse(response) if response['error']
if json_response.dig('Response', 'result') == 'SUCCESS' {
json_response['Response'] status: 'error',
code: response['code'],
message: response['message']
}
elsif response.dig('Response', 'result') == 'SUCCESS'
{
status: 'success',
data: response['Response']
}
else 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 end
end end

View File

@@ -48,6 +48,10 @@
<% end %> <% end %>
<div class="row clearfix h-100"> <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="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 h-100" style="margin-bottom: 15px">
<div class="card-header m-l-5 m-r-5"> <div class="card-header m-l-5 m-r-5">
@@ -248,8 +252,12 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const paymentWaiting = document.querySelector('.payment-waiting'); const paymentWaiting = document.querySelector('.payment-waiting');
let amountToReceive = <%= number_with_precision(@sale_data.grand_total, precision: precision.to_i) %>; 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"); const ws = new WebSocket("wss://juicecorner-0mo.sx-fc.app/cable");
ws.onopen = () => { ws.onopen = () => {
console.log("Nagato channel connected"); console.log("Nagato channel connected");
@@ -277,16 +285,6 @@
<p>Amount Received: ${amountToReceive}</p> <p>Amount Received: ${amountToReceive}</p>
</div> </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', { fetch('/foodcourt/qrpay/process_payment', {
method: 'POST', method: 'POST',
@@ -298,7 +296,7 @@
}).then((res) => res.json()) }).then((res) => res.json())
.then((data) => { .then((data) => {
if(data.status) { if(data.status) {
customer_display_view(null, "reload"); customer_display_view({msg: "Payment Successful", paid_amount: amountToReceive,receipt_no:receipt_no}, "pay_success");
setTimeout(() => { setTimeout(() => {
window.location.href = "/"; window.location.href = "/";
}, 3000) }, 3000)
@@ -317,6 +315,8 @@
function customer_display_view(data, status) { function customer_display_view(data, status) {
let url = '/foodcourt/customer_view'; let url = '/foodcourt/customer_view';
console.log(data);
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: url, url: url,

View File

@@ -1,81 +1,83 @@
<div class="row clearfix h-100"> <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 h-100" style="opacity: 0.85; background-color: #f8f9fa;">
<div class="card-header bg-primary text-white"> <div class="card-header bg-primary text-white">
<h4 class="mb-0">Invoice Details</h4> <h4 class="mb-0">Invoice Details</h4>
</div> </div>
<div class="card-block"> <div class="card-body p-0">
<div class="card-text"> <div class="card-text">
<div id="order-detail-slimscroll" style="max-height: 55vh; overflow-y: auto;"> <div id="order-detail-slimscroll" style="min-height: 55vh; height: 55vh; overflow-y: auto;">
<table class="table table-striped second_display_items" id="order-items-table"> <div class="table-responsive p-3">
<thead class="thead-light"> <table class="table table-striped second_display_items mb-0" id="order-items-table">
<tr> <thead class="thead-light">
<th>#</th> <tr>
<th class="item-name">Items</th> <th>#</th>
<th class="item-qty">QTY</th> <th class="item-name">Items</th>
<th class="item-price">Price</th> <th class="item-qty">QTY</th>
</tr> <th class="item-price">Price</th>
</thead> </tr>
<tbody> </thead>
<!-- Items will be populated here --> <tbody>
</tbody> <!-- Items will be populated by JS -->
</table> </tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="card-footer bg-light"> <div class="card-footer bg-light">
<table class="table table-borderless"> <table class="table table-borderless mb-0">
<tr> <tr>
<td class="charges-name"><strong>Sub Total:</strong></td> <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>
<tr> <tr>
<td class="charges-name"><strong>Discount:</strong></td> <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>
<tr> <tr>
<td class="charges-name"><strong>Tax Amount:</strong></td> <td class="charges-name"><strong>Tax Amount (5%):</strong></td>
<td class="text-right item-attr"><strong id="s_tatal_tax">0.00</strong></td> <td class="text-right item-attr"><strong id="s_tatal_tax"></strong></td>
</tr> </tr>
<tr class="table-active"> <tr class="table-active">
<td class="charges-name"><strong>Grand Total:</strong></td> <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> </tr>
</table> </table>
</div> </div>
</div> </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 h-100" style="opacity: 0.85;">
<div class="card-header text-white" style="background-color: #ffc107;"> <div class="card-header text-white" style="background-color: #ffc107;">
<h4 class="mb-0">MMQR Payment Option</h4> <h4 class="mb-0">MMQR Payment Option</h4>
</div> </div>
<div class="card-body d-flex flex-column align-items-center justify-content-center"> <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"> <div class="text-center">
<h5>Scan to Pay</h5> <h5>Scan to Pay</h5>
<p class="text-muted">Use your mobile wallet app</p> <p class="text-muted">Use your mobile wallet app</p>
</div> </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-payment-container" class="text-center p-3 bg-white rounded border" style="max-width: 300px;">
<div id="qr-code" class="mb-2"> <div id="qr-code" class="mb-2">
<!-- QR Code will be generated here --> <div id="qrpay_svg">
<div class=""> <svg width="150" height="150" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<div id="qrpay_svg"> <rect width="100" height="100" fill="#e0e0e0"/>
</div> <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> </div>
<div class="payment-details text-dark"> <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> </div>
<div class="payment-instructions mt-4 text-center"> <div class="payment-instructions mt-4 text-center">
<h6>How to Pay:</h6> <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>Open your mobile wallet app</li>
<li>Tap on 'Scan QR Code'</li> <li>Tap on 'Scan QR Code'</li>
<li>Point your camera at this code</li> <li>Point your camera at this code</li>
@@ -83,25 +85,90 @@
</ol> </ol>
</div> </div>
</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> </div>
</div> </div>
<style> <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 { .card {
box-shadow: 0 4px 8px rgba(0,0,0,0.1); box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border: none; border: none;
border-radius: 8px; border-radius: 8px;
display: flex;
flex-direction: column;
} }
.card-header { .card-header {
border-radius: 8px 8px 0 0 !important; 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 { .table thead th {
border-bottom: 2px solid #dee2e6; 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 { #qr-payment-container {
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 2px 10px rgba(0,0,0,0.1);
@@ -112,13 +179,127 @@
padding: 15px; padding: 15px;
border-radius: 8px; border-radius: 8px;
max-width: 300px; max-width: 300px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.payment-instructions ol {
margin-bottom: 0;
} }
.text-right { .text-right { text-align: right; }
text-align: right; .text-center { text-align: center; }
}
.table-active { .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> </style>

View File

@@ -88,13 +88,13 @@
} }
</style> </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> <button type="button" class="hidden" id="s_reload">Reload</button>
<div class="slider" id="second_display_slider"> <div class="slider" id="second_display_slider">
<%= render 'slider' %> <%= render 'slider' %>
</div> </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' %> <%= render 'second_display' %>
</div> </div>
</div> </div>