qr pay updates
- subscribe to action cable from cloud and listen - callback from cloud to local and show success payment in real time - payment service and process payment after callback
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
class Foodcourt::QrpayController < BaseFoodcourtController
|
class Foodcourt::QrpayController < BaseFoodcourtController
|
||||||
require 'rqrcode'
|
require 'rqrcode'
|
||||||
|
skip_before_action :authenticate, only: [:create]
|
||||||
|
skip_before_action :verify_authenticity_token, only: [:create]
|
||||||
|
|
||||||
|
|
||||||
def init
|
def init
|
||||||
@cash_exist = PaymentMethodSetting.cash_exist
|
@cash_exist = PaymentMethodSetting.cash_exist
|
||||||
@@ -217,6 +220,28 @@ class Foodcourt::QrpayController < BaseFoodcourtController
|
|||||||
end
|
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
|
||||||
|
|
||||||
|
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 test_pay
|
||||||
|
end
|
||||||
|
|
||||||
def cancel
|
def cancel
|
||||||
# cancel orders and related
|
# cancel orders and related
|
||||||
sale_id = params[:sale_id]
|
sale_id = params[:sale_id]
|
||||||
|
|||||||
61
app/services/qr_payment_service.rb
Normal file
61
app/services/qr_payment_service.rb
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
class QrPaymentService
|
||||||
|
attr_reader :sale_id, :current_login_employee, :sale, :booking, :processed_sale_payment
|
||||||
|
|
||||||
|
def initialize(sale_id, current_login_employee)
|
||||||
|
@sale_id = sale_id
|
||||||
|
@current_login_employee = current_login_employee
|
||||||
|
end
|
||||||
|
|
||||||
|
def process
|
||||||
|
return { status: false, message: "Sale ID is required." } unless sale_id.present?
|
||||||
|
|
||||||
|
SalePayment.transaction do
|
||||||
|
find_sale_and_booking
|
||||||
|
return { status: false, message: "There is no sale for '#{sale_id}'!" } unless sale
|
||||||
|
return { status: false, message: "Already paid for '#{sale_id}'!" } unless sale.sale_status == "new"
|
||||||
|
|
||||||
|
status, payment_attempt = process_the_payment
|
||||||
|
unless status
|
||||||
|
error_message = payment_attempt&.errors&.full_messages&.join(', ') || "Payment processing failed."
|
||||||
|
return { status: false, message: error_message }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
process_orders_and_broadcast
|
||||||
|
return { status: true, message: "Payment successful." }
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
{ status: false, message: "Sale not found for ID '#{sale_id}'." }
|
||||||
|
rescue StandardError => e
|
||||||
|
Rails.logger.error "QrPaymentService Error: #{e.message}\n#{e.backtrace.join("\n")}"
|
||||||
|
{ status: false, message: "An unexpected error occurred: #{e.message}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_sale_and_booking
|
||||||
|
@sale = Sale.find_by_sale_id(sale_id) # Consider find_by_sale_id! if you want RecordNotFound earlier
|
||||||
|
@booking = @sale&.booking
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_the_payment
|
||||||
|
sale_payment_creator = SalePayment.new
|
||||||
|
status, payment = sale_payment_creator.process_payment(
|
||||||
|
sale,
|
||||||
|
current_login_employee,
|
||||||
|
sale.grand_total,
|
||||||
|
"MMQR"
|
||||||
|
)
|
||||||
|
[status, payment]
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_orders_and_broadcast
|
||||||
|
booking.orders.each do |order|
|
||||||
|
oqs = OrderQueueStation.new
|
||||||
|
oqs.pay_process_order_queue(order.order_id, booking.dining_facility_id)
|
||||||
|
|
||||||
|
assign_order = AssignedOrderItem.assigned_order_item_by_job(order.order_id)
|
||||||
|
ActionCable.server.broadcast "order_queue_station_channel", order: assign_order
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -247,29 +247,87 @@
|
|||||||
<script defer type="text/javascript">
|
<script defer type="text/javascript">
|
||||||
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) %>;
|
||||||
|
const ws = new WebSocket("wss://juicecorner-0mo.sx-fc.app/cable");
|
||||||
|
|
||||||
function checkPaymentStatus() {
|
ws.onopen = () => {
|
||||||
// fetch('')
|
console.log("Nagato channel connected");
|
||||||
// .then(response => response.json())
|
|
||||||
// .then(data => {
|
ws.send(JSON.stringify({
|
||||||
// if (data.paid) {
|
command: "subscribe",
|
||||||
// paymentWaiting.innerHTML = `
|
identifier: JSON.stringify({
|
||||||
// <div class="payment-success text-center">
|
channel: "TestChannel"
|
||||||
// <i class="material-icons" style="font-size: 50px; color: #4CAF50;">check_circle</i>
|
})
|
||||||
// <h3 class="m-t-20" style="color: #4CAF50;">Payment Successful!</h3>
|
}));
|
||||||
// <p>Amount Received: ${amountToReceive}</p>
|
|
||||||
// </div>
|
|
||||||
// `;
|
|
||||||
// } else {
|
|
||||||
// setTimeout(checkPaymentStatus, 3000);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start checking payment status
|
ws.onmessage = (e) => {
|
||||||
checkPaymentStatus();
|
const msg = JSON.parse(e.data);
|
||||||
|
console.log("Received:", msg)
|
||||||
|
|
||||||
|
if(msg.type === 'confirm_subscription') {
|
||||||
|
console.log("This world shall know pain");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg?.message?.status === "PAY_SUCCESS") {
|
||||||
|
paymentWaiting.innerHTML = `
|
||||||
|
<div class="payment-success text-center">
|
||||||
|
<i class="material-icons" style="font-size: 50px; color: #4CAF50;">check_circle</i>
|
||||||
|
<h3 class="m-t-20" style="color: #4CAF50;">Payment Successful!</h3>
|
||||||
|
<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',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ sale_id: "<%= @sale_data.sale_id %>" })
|
||||||
|
}).then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if(data.status) {
|
||||||
|
customer_display_view(null, "reload");
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/";
|
||||||
|
}, 3000)
|
||||||
|
}else {
|
||||||
|
console.log("error:", data);
|
||||||
|
}
|
||||||
|
}).catch((e) => console.log(e))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onerror = (e) => {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
function customer_display_view(data, status) {
|
||||||
|
let url = '/foodcourt/customer_view';
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: url,
|
||||||
|
data: { data: data, status: status },
|
||||||
|
dataType: "json",
|
||||||
|
success:function(result){
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel order
|
||||||
document.querySelector('#cancel-btn').addEventListener('click', function(e) {
|
document.querySelector('#cancel-btn').addEventListener('click', function(e) {
|
||||||
const data = {
|
const data = {
|
||||||
sale_id: "<%= @sale_data.sale_id %>",
|
sale_id: "<%= @sale_data.sale_id %>",
|
||||||
|
|||||||
43
app/views/foodcourt/qrpay/test_payment.html.erb
Normal file
43
app/views/foodcourt/qrpay/test_payment.html.erb
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<div id="status">Connecting...</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const ws = new WebSocket("wss://juicecorner-0mo.sx-fc.app/cable")
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
console.log("WS Connected");
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
command: "subscribe",
|
||||||
|
identifier: JSON.stringify({
|
||||||
|
channel: "TestChannel"
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
document.getElementById('status').textContent = 'Waiting for payment...'
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onmessage = (e) => {
|
||||||
|
const msg = JSON.parse(e.data)
|
||||||
|
console.log("Received:", msg)
|
||||||
|
|
||||||
|
// Handle subscription confirmation
|
||||||
|
if(msg.type === "confirm_subscription") {
|
||||||
|
console.log("Successfully subscribed!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle payment success
|
||||||
|
if(msg?.message?.status === "PAY_SUCCESS") {
|
||||||
|
document.getElementById('status').innerHTML = `
|
||||||
|
<h3 style="color: green;">Payment Received!</h3>
|
||||||
|
<p>Amount: ${msg.message.amount} ${msg.message.currency}</p>
|
||||||
|
`
|
||||||
|
ws.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onerror = (e) => {
|
||||||
|
console.error("WS Error:", e)
|
||||||
|
document.getElementById('status').textContent = 'Connection failed'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -677,8 +677,6 @@ scope "(:locale)", locale: /en|mm/ do
|
|||||||
get 'sale/:sale_id' => 'sales#show'
|
get 'sale/:sale_id' => 'sales#show'
|
||||||
get 'order/:order_id' => "orders#show"
|
get 'order/:order_id' => "orders#show"
|
||||||
|
|
||||||
get 'qrpay/payment_loading' => 'qrpay#payment_loading'
|
|
||||||
|
|
||||||
# Other Charges
|
# Other Charges
|
||||||
get "/:sale_id/:type/other_charges" => "other_charges#index"
|
get "/:sale_id/:type/other_charges" => "other_charges#index"
|
||||||
post "/:sale_id/other_charges" => "other_charges#create"
|
post "/:sale_id/other_charges" => "other_charges#create"
|
||||||
@@ -750,6 +748,8 @@ scope "(:locale)", locale: /en|mm/ do
|
|||||||
|
|
||||||
get '/:sale_id/qrpay/init' => 'qrpay#init'
|
get '/:sale_id/qrpay/init' => 'qrpay#init'
|
||||||
post 'qrpay/cancel' => 'qrpay#cancel'
|
post 'qrpay/cancel' => 'qrpay#cancel'
|
||||||
|
get 'qrpay/test-payment' => 'qrpay#test_payment'
|
||||||
|
post 'qrpay/process_payment' => 'qrpay#create'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user