diff --git a/app/src/main/java/com/unibuy/smartdevice/MyApp.java b/app/src/main/java/com/unibuy/smartdevice/MyApp.java index a0c5509..37db3d8 100644 --- a/app/src/main/java/com/unibuy/smartdevice/MyApp.java +++ b/app/src/main/java/com/unibuy/smartdevice/MyApp.java @@ -119,6 +119,8 @@ public class MyApp extends Application { public ReportDispatchInfoStructure reportDispatchInfoData; public CreditCardReceiptStructure creditCardReceiptInfoData; public boolean isShowDialog; + public volatile boolean isNfcPaymentActive; // 用於控制 NFC 支付感應器的活動門禁 + public boolean isDetectHost; public String suAccess; @@ -830,5 +832,14 @@ public class MyApp extends Application { isDetectHost = detectHost; } + public boolean isNfcPaymentActive() { + return isNfcPaymentActive; + } + + public void setNfcPaymentActive(boolean nfcPaymentActive) { + isNfcPaymentActive = nfcPaymentActive; + } + + } diff --git a/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java b/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java index f70e6a3..2471eef 100644 --- a/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java +++ b/app/src/main/java/com/unibuy/smartdevice/controller/DevNFCPayController.java @@ -57,6 +57,7 @@ public class DevNFCPayController extends HandlerMainScheduler { this.payType = payType; this.totalPrice = totalPrice; clearOldBuffer(); + MyApp.getInstance().setNfcPaymentActive(true); byte[] sendBuffer = devNFCPay.swipe(payType, totalPrice); String sendData = new String(sendBuffer); getLogs().info("電文送出:" + sendData); @@ -113,19 +114,22 @@ public class DevNFCPayController extends HandlerMainScheduler { @Override public void shutdown() { - isShuttingDown = true; // ✅ 標記關閉 + isShuttingDown = true; // ✅ 標記正在關閉,避免再次回報錯誤 + MyApp.getInstance().setNfcPaymentActive(false); // ✅ 關閉 NFC 支付感應 super.shutdown(); close(getHandlerMain()); } @Override protected void execute(HandlerMain handlerMain) { - if (!MyApp.getInstance().isShowDialog()) { - getLogs().info("⚠️ App 不在對話框狀態,忽略 NFC 感應訊號並停止服務"); + if (!MyApp.getInstance().isShowDialog() || !MyApp.getInstance().isNfcPaymentActive()) { + getLogs().info("⚠️ NFC 支付狀態未啟動或對話框已關閉,停止服務"); shutdown(); return; } + + try { byte[] readBuffer = comPort2.readToMatch(new byte[]{0x03}, 4096); getLogs().info("readBuffer length:" + readBuffer.length); diff --git a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java index a50a1c5..187ee2d 100644 --- a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java +++ b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java @@ -17,8 +17,15 @@ import androidx.core.app.NotificationCompat; import com.unibuy.smartdevice.HomeActivity; import com.unibuy.smartdevice.R; +import com.unibuy.smartdevice.MyApp; +import com.unibuy.smartdevice.devices.SlotField; +import com.unibuy.smartdevice.exception.LogsEmptyException; import com.unibuy.smartdevice.external.mqtt.MqttManager; +import com.unibuy.smartdevice.structure.BuyStructure; +import com.unibuy.smartdevice.structure.ReportFlowInfoStructure; +import com.unibuy.smartdevice.structure.SlotStructure; import com.unibuy.smartdevice.structure.mqtt.TransactionFinalizePayload; +import com.unibuy.smartdevice.tools.TransactionFinalizeBuilder; import java.text.SimpleDateFormat; import java.util.Collections; @@ -225,51 +232,56 @@ public class MqttService extends Service { Log.i(TAG, "Remote dispense success for slot: " + finalSlot + " (cmdId: " + cmdId + ")"); mqttManager.publishCommandAck(cmdId, "success", "Product dispensed from slot: " + finalSlot); - // 觸發交易結案流程 (使庫存扣除與訂單紀錄正式化) + // [修正] 使用 TransactionFinalizeBuilder 走完整交易流程,確保後台有訂單紀錄 try { - Log.i(TAG, "Preparing to send Transaction Finalize for remote dispense..."); - String flowId = com.unibuy.smartdevice.tools.FlowIdGenerator.next(getApplicationContext()); - String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.TAIWAN).format(new Date()); - - TransactionFinalizePayload finalize = new TransactionFinalizePayload(); - finalize.setFlow_id(flowId); - finalize.setPayment_type(100); // 遠端管理出貨 - - // 嘗試解析 cmdId 為數字。若為非數字(例如 UUID),則填 0 - int numericCmdId = 0; - try { - if (cmdId != null) numericCmdId = Integer.parseInt(cmdId); - } catch (NumberFormatException e) { - Log.w(TAG, "cmdId is not a number: " + cmdId + ", using 0 for code_id"); + // Step 1: 從記憶體中找到對應貨道的商品資料 + SlotStructure slotStructure = null; + for (SlotField slotField : SlotField.values()) { + try { + slotStructure = MyApp.getInstance().getSlotData(slotField.getField(), finalSlot); + break; // 找到就停止 + } catch (LogsEmptyException ignored) { + // 此 field 找不到,繼續找下一個 + } } - - finalize.setCode_id(numericCmdId); - finalize.setRemote_command_id(numericCmdId); - TransactionFinalizePayload.OrderPayload order = new TransactionFinalizePayload.OrderPayload(); - order.setOrder_no(flowId); - order.setPayment_type(100); - order.setPayment_status(1); - order.setMachine_time(time); - order.setTotal_amount(0); - order.setPay_amount(0); - finalize.setOrder(order); + if (slotStructure == null) { + Log.e(TAG, "Remote dispense finalize failed: slot " + finalSlot + " not found in memory"); + return; + } - TransactionFinalizePayload.DispenseRecord disp = new TransactionFinalizePayload.DispenseRecord( - String.valueOf(finalSlot), - 0, // 此處不帶商品 ID,由後端根據貨道對應 - 0, - 0, - 1, // 成功 - -1, // 庫存由後端遞減 - time + // Step 2: 清空並重新設定 BuyList(模擬使用者點選此商品) + MyApp.getInstance().getBuyList().clear(); + MyApp.getInstance().getBuyList().add( + new BuyStructure(slotStructure.getField(), finalSlot, slotStructure.getProduct(), 1) ); - finalize.setDispense(Collections.singletonList(disp)); - mqttManager.publishTransactionFinalize(finalize); - Log.i(TAG, "Remote dispense transaction finalized sent (flow_id: " + flowId + ")"); + // Step 3: 設定 ReportFlowInfoData(payment_type=100 代表遠端管理出貨) + ReportFlowInfoStructure flowInfo = MyApp.getInstance().getReportFlowInfoData(); + flowInfo.setFlowType(100); // 遠端管理出貨 + flowInfo.setTotalPrice(0); // 免費出貨 + flowInfo.setGiveChange(0); + flowInfo.setUsePoints(0); + flowInfo.setFlowBarCode(""); + flowInfo.setFlowSendInfo(""); // 無支付請求封包 + flowInfo.setFlowRequestInfo(""); // 無支付回應封包 + flowInfo.setInvoiceInfo(""); + + // Step 4: 用 Builder 組裝並送出 Finalize Payload + TransactionFinalizeBuilder builder = new TransactionFinalizeBuilder(); + int remaining = slotStructure.getCount() - 1; + int productIdInt = 0; + try { productIdInt = Integer.parseInt(slotStructure.getProduct().getProductID()); } catch (Exception ignored) {} + builder.addDispenseRecord(finalSlot, productIdInt, 0, 0, true, remaining); + + MqttManager.getInstance().publishTransactionFinalize(builder.build()); + Log.i(TAG, "[Remote Dispense] Transaction finalize sent via TransactionFinalizeBuilder, slot=" + finalSlot); + + // 清空 BuyList 避免影響後續正常交易 + MyApp.getInstance().getBuyList().clear(); + } catch (Exception e) { - Log.e(TAG, "Failed to construct/send finalize for remote dispense: " + e.getMessage(), e); + Log.e(TAG, "Failed to send finalize for remote dispense: " + e.getMessage(), e); } } diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java index 7c3715a..da68277 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java @@ -1008,6 +1008,16 @@ public class FontendActivity extends AppCompatActivityAbstract { @Override public boolean dispatchKeyEvent(KeyEvent event) { + // [修正] 防止讀卡機 (HID) 在購物畫面送出 Enter 導致誤點擊商品 + if (!MyApp.getInstance().isShowDialog()) { + if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { + if (event.getAction() == KeyEvent.ACTION_UP) { + getLogs().info("攔截到購物畫面非預期的 Enter 鍵,已忽略防止誤觸"); + } + return true; // 消耗事件 + } + } + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { if (event.getAction() == KeyEvent.ACTION_DOWN && event.isLongPress()) { Toast.makeText(this, getString(R.string.back_home), Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/devs/electric/RecyclerElectricBuyListAdpter.java b/app/src/main/java/com/unibuy/smartdevice/ui/devs/electric/RecyclerElectricBuyListAdpter.java index 62d5d28..0503750 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/devs/electric/RecyclerElectricBuyListAdpter.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/devs/electric/RecyclerElectricBuyListAdpter.java @@ -101,6 +101,8 @@ public class RecyclerElectricBuyListAdpter extends RecyclerView.Adapter 0 && !isLock) { + holder.binding.imageProductPicture.setFocusable(false); + holder.binding.imageProductPicture.setFocusableInTouchMode(false); holder.binding.imageProductPicture.setOnClickListener(new ImageProductPictureOnClickListener(position)); } else { holder.binding.imageProductPicture.setOnClickListener(null); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java index 0fd8458..3b01d31 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/devs/vmc/RecyclerVmcBuyListAdpter.java @@ -117,7 +117,9 @@ public class RecyclerVmcBuyListAdpter extends RecyclerView.Adapter 0 && !isLock) { - holder.binding.imageProductPicture.setOnClickListener(new imageProductPictureOnClickListener(position)); + holder.binding.imageProductPicture.setFocusable(false); + holder.binding.imageProductPicture.setFocusableInTouchMode(false); + holder.binding.imageProductPicture.setOnClickListener(new imageProductPictureOnClickListener(position)); } else { holder.binding.imageProductPicture.setOnClickListener(null); } diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java index 9aca423..4c42bae 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcCardDialog.java @@ -336,10 +336,14 @@ public class CheckoutNfcCardDialog extends DialogAbstract { } public void cancelOnThreadCountdown() { + if (nfcPayController != null) { + nfcPayController.shutdown(); + } MyApp.getInstance().getBuyList().clear(); cancel(); } + public void setButtonCancelText(long countdown) { binding.buttonCancel.setText("取消("+countdown+")"); } diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java index 6c0898b..ad7919b 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcPhoneDialog.java @@ -339,10 +339,14 @@ public class CheckoutNfcPhoneDialog extends DialogAbstract { } public void cancelOnThreadCountdown() { + if (nfcPayController != null) { + nfcPayController.shutdown(); + } MyApp.getInstance().getBuyList().clear(); cancel(); } + public void setButtonCancelText(long countdown) { binding.buttonCancel.setText("取消("+countdown+")"); } diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java index a0cc661..bbe9675 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutNfcRechargeDialog.java @@ -337,10 +337,14 @@ public class CheckoutNfcRechargeDialog extends DialogAbstract { } public void cancelOnThreadCountdown() { + if (nfcPayController != null) { + nfcPayController.shutdown(); + } MyApp.getInstance().getBuyList().clear(); cancel(); } + public void setButtonCancelText(long countdown) { binding.buttonCancel.setText("取消("+countdown+")"); }