[FEAT] 取貨碼/通行碼改走 StarCloudAPI B660/B670(比照員工卡 B680)

舊的 httpAPI(memberVerification / checkAccessCode B024)改為新 StarCloudAPI:
- StarCloudAPI 新增 verifyPickupCode(B660,回 code_id+slot_no)、
  verifyPassCode(B670,回 code_id),及 PickupVerificationCallback。
- 通行碼 CheckoutAccessCodeDialog:手動+掃碼兩入口改走 verifyPassCode,
  成功設 flowType=5、保存 accessCodeId 供 MQTT finalize(payment_type=5)。
- 取貨碼 FontendPickupCodeDialog:兩入口改走 verifyPickupCode,依回傳 slot_no
  建立 BuyList、設 flowType=6、保存 accessCodeId(payment_type=6),
  進入 BuyPickupCodeDialog 確認 → DispatchDialog;文案修正為「取貨碼」。

finalize 的 code_id 取自 ReportAccessCodeStructure.accessCodeId、payment_type
取自 flowType,App 端 TransactionFinalizeBuilder 既有支援。三 flavor 編譯/打包通過。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-10 09:52:20 +08:00
parent f10f6f5879
commit 1720d2007e
3 changed files with 267 additions and 34 deletions

View File

@ -313,15 +313,7 @@ public class FontendPickupCodeDialog extends DialogAbstract {
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().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(barCodeString);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} catch (LogsParseException | LogsSettingEmptyException e) {
getLogs().warning(e);
}
submitPickupCode(barCodeString.trim());
openBarcode = false;
}
@ -357,23 +349,65 @@ public class FontendPickupCodeDialog extends DialogAbstract {
}
public void changeOk() {
try {
String number = binding.editTextNumber.getText().toString();
// 長度檢查
if (number.length() < 8) {
getHandlerMain().start(getClass().getSimpleName(), "統一編號長度不能少於 8 碼");
return;
}
MyApp.getInstance().getMemberVerificationStructure().setProductId("0");
MyApp.getInstance().getMemberVerificationStructure().setProductPrice(0);
MyApp.getInstance().getMemberVerificationStructure().setMemberBarCode(number);
MyApp.getInstance().getMemberVerificationStructure().setVerificationType(2);
httpAPI.memberVerification(MyApp.getInstance().getMemberVerificationStructure());
} 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(), "取貨碼長度不能少於 8 碼");
return;
}
submitPickupCode(number);
}
/**
* 取貨碼驗證改走 StarCloudAPI B660邏輯比照員工卡 B680
* 成功回傳 code_id 與要出貨的 slot_no據此建立 BuyList保存 code_id MQTT
* finalizepayment_type=6 取貨碼再進入取貨確認 出貨
*/
private void submitPickupCode(String code) {
new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain())
.verifyPickupCode(code, new com.unibuy.smartdevice.external.StarCloudAPI.PickupVerificationCallback() {
@Override
public void onSuccess(String codeId, int slotNo) {
try {
SlotStructure slotProduct = MyApp.getInstance().getSlotData(SlotField.VMC.getField(), slotNo);
if (slotProduct == null) {
setStatusText(getContext().getString(R.string.product_not_found));
openBarcode = true;
return;
}
if (slotProduct.isLock()) {
getSrcHandlerMain().start("FontendPickupCodeDialog", "@" + getContext().getString(R.string.product_sales_suspended));
dialogCancel();
return;
}
if (slotProduct.getCount() == 0) {
getSrcHandlerMain().start("FontendPickupCodeDialog", "@" + getContext().getString(R.string.product_out_of_stock));
dialogCancel();
return;
}
MyApp.getInstance().getBuyList().clear();
MyApp.getInstance().getBuyList().add(
new BuyStructure(slotProduct.getField(), slotProduct.getSlot(), slotProduct.getProduct(), 1));
MyApp.getInstance().getReportFlowInfoData().setFlowType(6); // 取貨碼 payment_type 6
MyApp.getInstance().getReportFlowInfoData().setFlowBarCode(code);
MyApp.getInstance().getReportFlowInfoData().setReportFlowId(code);
MyApp.getInstance().getReportFlowInfoData().setOrderId(Tools.generateTimeBasedUUID());
MyApp.getInstance().getReportAccessCodeStructure().setAccessCodeId(codeId);
changeBuy(); // 開啟取貨確認 BuyPickupCodeDialog 出貨
} catch (LogsEmptyException e) {
setStatusText(getContext().getString(R.string.product_not_found));
openBarcode = true;
}
}
@Override
public void onFailure(String message) {
openBarcode = true;
setStatusText(message);
}
});
}
public void changeBuy() {

View File

@ -593,6 +593,187 @@ public class StarCloudAPI {
void onFailure(String message);
}
/** 取貨碼驗證回呼:成功時回傳 code_id 與要出貨的貨道 slot_no。 */
public interface PickupVerificationCallback {
void onSuccess(String codeId, int slotNo);
void onFailure(String message);
}
/**
* B660: 取貨碼驗證邏輯比照 B680 員工卡驗證通道不同
* 成功回傳 data.code_id data.slot_no要出貨的貨道
*/
public void verifyPickupCode(final String code, final PickupVerificationCallback callback) {
final String url = getBaseUrl() + "machine/pickup/verify/B660";
Log.i(TAG, "Starting B660 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, "B660 Response (" + responseCode + "): " + receive);
} catch (final Exception e) {
Log.e(TAG, "B660 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", "") : "";
int slotNo = data != null ? data.optInt("slot_no", -1) : -1;
callback.onSuccess(codeId, slotNo);
} 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 {
try {
JSONObject res = new JSONObject(finalReceive);
callback.onFailure(res.optString("message", "系統連線錯誤 (" + finalCode + ")"));
} catch (Exception e) {
callback.onFailure("系統連線錯誤 (" + finalCode + ")");
}
}
});
}
}).start();
}
/**
* B670: 通行碼驗證技師用邏輯比照 B680驗證通道不同
* 成功回傳 data.code_id
*/
public void verifyPassCode(final String code, final VerificationCallback callback) {
final String url = getBaseUrl() + "machine/passcode/verify/B670";
Log.i(TAG, "Starting B670 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, "B670 Response (" + responseCode + "): " + receive);
} catch (final Exception e) {
Log.e(TAG, "B670 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)) {
String codeId = "";
if (res.has("data")) {
codeId = res.optJSONObject("data").optString("code_id", "");
} else {
codeId = res.optString("code_id", "");
}
callback.onSuccess(codeId);
} 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 {
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

View File

@ -272,12 +272,7 @@ public class CheckoutAccessCodeDialog extends DialogAbstract {
String barCodeString = new String(buffer, 0, size);
getHandlerMain().start(getLogs().getClassName(), getContext().getString(R.string.receive_barcode)+""+ Tools.randomReplace(barCodeString, 0.3)+""+getContext().getString(R.string.send_transaction));//收到條碼送出交易
try {
MyApp.getInstance().getAccessCodeStructure().setPassCode(barCodeString);
httpAPI.checkAccessCode(MyApp.getInstance().getAccessCodeStructure());
} catch (LogsParseException | LogsSettingEmptyException e) {
getLogs().warning(e);
}
submitPassCode(barCodeString.trim());
openBarcode = false;
}
@ -329,13 +324,36 @@ public class CheckoutAccessCodeDialog extends DialogAbstract {
getHandlerMain().start(getClass().getSimpleName(), "通行碼長度不能少於 8 碼");
return;
}
MyApp.getInstance().getAccessCodeStructure().setPassCode(number);
httpAPI.checkAccessCode(MyApp.getInstance().getAccessCodeStructure());
} catch (LogsParseException | LogsSettingEmptyException e) {
submitPassCode(number);
} catch (Exception e) {
getLogs().warning(e);
}
}
/**
* 通行碼驗證改走 StarCloudAPI B670邏輯比照員工卡 B680
* 成功後保存 code_id MQTT finalizepayment_type=5 通行碼再進入出貨
*/
private void submitPassCode(String code) {
new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain())
.verifyPassCode(code, new com.unibuy.smartdevice.external.StarCloudAPI.VerificationCallback() {
@Override
public void onSuccess(String codeId) {
MyApp.getInstance().getReportFlowInfoData().setFlowType(5); // 通行碼 payment_type 5
MyApp.getInstance().getReportFlowInfoData().setFlowBarCode(code);
MyApp.getInstance().getReportFlowInfoData().setReportFlowId(code);
MyApp.getInstance().getReportAccessCodeStructure().setAccessCodeId(codeId);
getHandlerMain().send(getClass().getSimpleName(), Option.CHANGE_DISPATCH.getOption(), "change dispatch");
}
@Override
public void onFailure(String message) {
openBarcode = true; // 允許重新掃碼/輸入
getHandlerMain().start(getClass().getSimpleName(), Option.SET_STATUS_TEXT.getOption(), message);
}
});
}
public void changeBuy() {
closeDialogCountdown.shutdown();
changeDialog(BuyDialog.class);