diff --git a/app/build.gradle b/app/build.gradle index 3375b12..8106e2d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ plugins { // 避免不同尺寸機台間版本號產生衝突。 // ========================================================================= def verMinor = 1 -def verPatch = 76 +def verPatch = 79 diff --git a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java index fef91c9..e152c22 100644 --- a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java +++ b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java @@ -610,6 +610,15 @@ public class StarCloudAPI { void onFailure(String message); } + /** + * 來店禮驗證回呼(B690):成功回傳折扣資訊。 + * discountType:amount(折抵金額)/ percentage(折扣百分比,discountValue 為折扣趴數,八折=20)。 + */ + public interface WelcomeGiftCallback { + void onSuccess(String codeId, String discountType, int discountValue, String discountLabel, String name); + void onFailure(String message); + } + /** * B016:系統方(identity=system)回寫機台系統設定到雲端 star-cloud。 * 使用 B000 登入核發的使用者 Token(auth:sanctum),後端再驗一次系統方權限。 @@ -876,6 +885,97 @@ public class StarCloudAPI { }).start(); } + /** + * B690: 來店禮驗證(邏輯比照 B660/B670,驗證通道不同)。 + * 成功回傳 data.code_id 與折扣資訊(discount_type/discount_value/discount_label/name)。 + */ + public void verifyWelcomeGift(final String code, final WelcomeGiftCallback callback) { + final String url = getBaseUrl() + "machine/welcome-gift/verify/B690"; + Log.i(TAG, "Starting B690 request for code: " + code); + + new Thread(new Runnable() { + @Override + public void run() { + int responseCode = -1; + String receive = ""; + try { + JSONObject json = new JSONObject(); + json.put("code", code); + + java.net.URL reqUrl = new java.net.URL(url); + java.net.HttpURLConnection conn = (java.net.HttpURLConnection) reqUrl.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + conn.setRequestProperty("Accept", "application/json"); + String machineToken = MyApp.getInstance().getMachine().getApiKey(); + if (machineToken != null && !machineToken.isEmpty()) { + conn.setRequestProperty("Authorization", "Bearer " + machineToken); + } + conn.setConnectTimeout(10000); + conn.setReadTimeout(10000); + conn.setDoOutput(true); + conn.getOutputStream().write(json.toString().getBytes("UTF-8")); + conn.getOutputStream().flush(); + + responseCode = conn.getResponseCode(); + java.io.InputStream is = (responseCode >= 200 && responseCode < 300) + ? conn.getInputStream() : conn.getErrorStream(); + if (is != null) { + java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(is, "UTF-8")); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) sb.append(line); + receive = sb.toString(); + } + conn.disconnect(); + Log.d(TAG, "B690 Response (" + responseCode + "): " + receive); + } catch (final Exception e) { + Log.e(TAG, "B690 Network Error", e); + new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> callback.onFailure("連線失敗: " + e.getMessage())); + return; + } + + final int finalCode = responseCode; + final String finalReceive = receive; + new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> { + if (finalCode >= 200 && finalCode < 300) { + try { + JSONObject res = new JSONObject(finalReceive); + if (res.optBoolean("success", false)) { + JSONObject data = res.optJSONObject("data"); + String codeId = data != null ? data.optString("code_id", "") : ""; + String discountType = data != null ? data.optString("discount_type", "") : ""; + int discountValue = data != null ? data.optInt("discount_value", 0) : 0; + String discountLabel = data != null ? data.optString("discount_label", "") : ""; + String name = data != null ? data.optString("name", "") : ""; + callback.onSuccess(codeId, discountType, discountValue, discountLabel, name); + } else { + callback.onFailure(res.optString("message", "來店禮驗證失敗")); + } + } catch (Exception e) { + callback.onFailure("解析錯誤: " + finalCode); + } + } else if (finalCode == 404) { + callback.onFailure("查無此來店禮或已失效"); + } else if (finalCode == 429) { + callback.onFailure("錯誤次數過多,請稍後再試"); + } else if (finalCode == 401) { + callback.onFailure("系統認證失效,請聯繫管理員"); + } else if (finalCode == 400) { + callback.onFailure("來店禮格式錯誤(需 8 碼)"); + } else { + try { + JSONObject res = new JSONObject(finalReceive); + callback.onFailure(res.optString("message", "系統連線錯誤 (" + finalCode + ")")); + } catch (Exception e) { + callback.onFailure("系統連線錯誤 (" + finalCode + ")"); + } + } + }); + } + }).start(); + } + private void saveSettings(JSONObject config) { // 使用 optString 避免欄位缺失導致 JSONException diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java index b5d00d1..35a9fb8 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/BuyDialog.java @@ -219,6 +219,12 @@ public class BuyDialog extends DialogAbstract { this.binding.buttonFreeGift.setVisibility( MyApp.getInstance().getDevSet().isWelcomeGift() ? View.VISIBLE : View.GONE); + // 通行碼入口(付款格內 buttonAccessCode,flowType 5):權威判斷,涵蓋購物車/單品所有模式。 + // 從屬取貨模組,PickupModule 或 PassCode 任一關閉即隱藏;購物車模式下方不會再覆寫此值。 + this.binding.buttonAccessCode.setVisibility( + (MyApp.getInstance().getDevSet().isPickupModule() + && MyApp.getInstance().getDevSet().isPassCode()) ? View.VISIBLE : View.GONE); + this.binding.buttonAddProduct.setOnClickListener(v -> gotoAddProduct()); this.binding.buttonCancel.setOnClickListener(v -> dialogCancel()); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutAccessCodeDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutAccessCodeDialog.java index f5bdaf2..6244a70 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutAccessCodeDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutAccessCodeDialog.java @@ -247,7 +247,8 @@ public class CheckoutAccessCodeDialog extends DialogAbstract { closeDialogCountdown.setCountdown(40); }); - new ReadBarCodeOnScheduler(getHandlerMain()).start(5, TimeUnit.SECONDS); + // 立即開始聽掃描器(原本延遲 5 秒,導致對話框剛彈出時掃碼被整個丟掉 → 時好時壞、無回饋) + new ReadBarCodeOnScheduler(getHandlerMain()).start(0, TimeUnit.SECONDS); } public class ReadBarCodeOnScheduler extends HandlerMainScheduler { @@ -271,7 +272,7 @@ public class CheckoutAccessCodeDialog extends DialogAbstract { UsbDev usbDev = new UsbDev(DeviceType.QRCODE_SCANNER); byte[] buffer = new byte[1000]; usbDev.read(buffer, 100); - for (int i=0; i<7; i++) { + for (int i=0; i<9; i++) { // 約 45 秒讀取窗口,覆蓋 40 秒對話框存活時間 if (isRun() && openBarcode) { int size = usbDev.read(buffer, 5000); if (size > 5) { diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutFreeGiftCodeDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutFreeGiftCodeDialog.java index 79253a0..94aa446 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutFreeGiftCodeDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/CheckoutFreeGiftCodeDialog.java @@ -39,6 +39,7 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract { CHANGE_DISPATCH(6), CHANGE_BUY(7), SET_STATUS_TEXT(8), + SET_SCAN_INPUT(9), ; private int option; @@ -106,6 +107,11 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract { case SET_STATUS_TEXT: setStatusText(message); break; + case SET_SCAN_INPUT: + // 掃碼即時回饋:把掃到的內容顯示在輸入框,讓使用者確認有掃到 + binding.editTextNumber.setText(message); + binding.editTextNumber.setSelection(binding.editTextNumber.getText().length()); + break; } } } @@ -232,7 +238,8 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract { closeDialogCountdown.setCountdown(40); }); - new ReadBarCodeOnScheduler(getHandlerMain()).start(5, TimeUnit.SECONDS); + // 立即開始聽掃描器(原本延遲 5 秒,導致對話框剛彈出時掃碼被整個丟掉 → 時好時壞、無回饋) + new ReadBarCodeOnScheduler(getHandlerMain()).start(0, TimeUnit.SECONDS); } public class ReadBarCodeOnScheduler extends HandlerMainScheduler { @@ -256,20 +263,14 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract { UsbDev usbDev = new UsbDev(DeviceType.QRCODE_SCANNER); byte[] buffer = new byte[1000]; usbDev.read(buffer, 100); - for (int i=0; i<7; i++) { + for (int i=0; i<9; i++) { // 約 45 秒讀取窗口,覆蓋 40 秒對話框存活時間 if (isRun() && openBarcode) { int size = usbDev.read(buffer, 5000); if (size > 5) { - String barCodeString = new String(buffer, 0, size); - getHandlerMain().start(getLogs().getClassName(), getCtx().getString(R.string.receive_barcode)+":"+ Tools.randomReplace(barCodeString, 0.3)+","+getCtx().getString(R.string.send_transaction));//收到條碼,送出交易 - - try { - MyApp.getInstance().getFreeGiftStructure().setPassCode(barCodeString); - httpAPI.freeGiftCode(MyApp.getInstance().getFreeGiftStructure()); - } catch (LogsParseException | LogsSettingEmptyException e) { - getLogs().warning(e); - } - + String barCodeString = new String(buffer, 0, size).trim(); + // 掃碼即時回饋:先把掃到的內容顯示在輸入框,再送出驗證 + getHandlerMain().start(getClass().getSimpleName(), Option.SET_SCAN_INPUT.getOption(), barCodeString); + submitWelcomeGift(barCodeString); openBarcode = false; } } @@ -304,15 +305,57 @@ public class CheckoutFreeGiftCodeDialog extends DialogAbstract { } public void changeOk() { - try { - String number = binding.editTextNumber.getText().toString(); - MyApp.getInstance().getFreeGiftStructure().setPassCode(number); - httpAPI.freeGiftCode(MyApp.getInstance().getFreeGiftStructure()); - } catch (LogsParseException e) { - throw new RuntimeException(e); - } catch (LogsSettingEmptyException e) { - throw new RuntimeException(e); + String number = binding.editTextNumber.getText().toString().trim(); + if (number.isEmpty() || number.length() < 8) { + getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "來店禮代碼需 8 碼"); + return; } + submitWelcomeGift(number); + } + + /** + * 來店禮驗證改走 StarCloudAPI B690(取代舊 HttpAPI.freeGiftCode)。 + * B690 只回折扣(amount/percentage),無「免費商品」型;成功即套折扣並返回 BuyDialog 結帳。 + */ + private void submitWelcomeGift(String code) { + // 防護:來店禮關閉即不開放呼叫 B690(即使 UI 漏出入口也擋下) + if (!MyApp.getInstance().getDevSet().isWelcomeGift()) { + openBarcode = true; + getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "來店禮功能未開放"); + return; + } + getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "驗證中…"); + new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain()) + .verifyWelcomeGift(code, new com.unibuy.smartdevice.external.StarCloudAPI.WelcomeGiftCallback() { + @Override + public void onSuccess(String codeId, String discountType, int discountValue, String discountLabel, String name) { + // 映射到既有折扣機制:mode 1=折抵金額、mode 2=折扣百分比(乘數)。 + MyApp.getInstance().getReportFreeGiftCodeStructure().setFreeGiftId(codeId); + MyApp.getInstance().getReportFreeGiftCodeStructure().setPassCode(code); + MyApp.getInstance().getReportFlowInfoData().setReportFlowId(code); + MyApp.getInstance().getReportFlowInfoData().setFlowBarCode(code); + if ("amount".equals(discountType)) { + MyApp.getInstance().getReportFreeGiftCodeStructure().setMode(1); + MyApp.getInstance().getReportFreeGiftCodeStructure().setDiscountAmount(discountValue); + } else if ("percentage".equals(discountType)) { + MyApp.getInstance().getReportFreeGiftCodeStructure().setMode(2); + // discountValue 為折扣趴數(八折=20)→ 換算成總價乘數 0.8 + MyApp.getInstance().getReportFreeGiftCodeStructure() + .setDiscountPercentage((100 - discountValue) / 100f); + } else { + openBarcode = true; + getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), "不支援的折扣類型"); + return; + } + getHandlerMain().start(getClass().getSimpleName(), Option.CHANGE_BUY.getOption(), "change buy"); + } + + @Override + public void onFailure(String message) { + openBarcode = true; // 允許重新掃碼/輸入 + getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), message); + } + }); } public void changeBuy() { diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/FontendPickupCodeDialog.java b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/FontendPickupCodeDialog.java index da33ae7..c0ae77f 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/dialog/FontendPickupCodeDialog.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/dialog/FontendPickupCodeDialog.java @@ -284,7 +284,8 @@ public class FontendPickupCodeDialog extends DialogAbstract { closeDialogCountdown.setCountdown(40); }); - new ReadBarCodeOnScheduler(getHandlerMain()).start(5, TimeUnit.SECONDS); + // 立即開始聽掃描器(原本延遲 5 秒,導致對話框剛彈出時掃碼被整個丟掉 → 時好時壞、無回饋) + new ReadBarCodeOnScheduler(getHandlerMain()).start(0, TimeUnit.SECONDS); // 進入頁面時立即檢查取貨口 (使用貨道1作為代表) // callback 為 null,表示檢查通過後不做額外動作,僅在檢查失敗時顯示警告 @@ -315,7 +316,7 @@ public class FontendPickupCodeDialog extends DialogAbstract { UsbDev usbDev = new UsbDev(DeviceType.QRCODE_SCANNER); byte[] buffer = new byte[1000]; usbDev.read(buffer, 100); - for (int i=0; i<7; i++) { + for (int i=0; i<9; i++) { // 約 45 秒讀取窗口,覆蓋 40 秒對話框存活時間 if (isRun() && openBarcode) { int size = usbDev.read(buffer, 5000); if (size > 5) {