Merge remote-tracking branch 'origin/feat/general-cart-and-payment' into ui-design
This commit is contained in:
commit
f700881788
@ -10,7 +10,7 @@ plugins {
|
||||
// 避免不同尺寸機台間版本號產生衝突。
|
||||
// =========================================================================
|
||||
def verMinor = 1
|
||||
def verPatch = 91
|
||||
def verPatch = 93
|
||||
|
||||
|
||||
|
||||
|
||||
@ -151,30 +151,17 @@ public class DevNFCPayController extends HandlerMainScheduler {
|
||||
getLogs().info("電文接收:" + readData);
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowRequestInfo(readData);
|
||||
|
||||
while (isRun() && MyApp.getInstance().isShowDialog()) { // ✅ 增加狀態檢查
|
||||
byte[] buffer = comPort2.readListen(30, 200);
|
||||
// 【修復 付款成功卻不出貨】交易結果(含 responseCode)就在這段 404 byte 電文裡,
|
||||
// 直接解析本段即可。先前版本誤改成再用 readListen 等「第二段」電文才解析,
|
||||
// 但刷卡機只送這一段,第二段永遠等不到 → 倒數逾時 → RESPONSE 從未送出 → 不出貨。
|
||||
HashMap<String, String> readMap = devNFCPay.read(readData);
|
||||
String code = readMap.get("code");
|
||||
getLogs().info("交易完成,回傳代碼: " + code);
|
||||
|
||||
if (buffer.length > 0) {
|
||||
getLogs().debug("NFC 收到資料: " + PortTools.showHex(buffer));
|
||||
String rxData = new String(buffer, StandardCharsets.UTF_8);
|
||||
|
||||
if (rxData.contains(String.valueOf((char) 0x03))) { // ETX
|
||||
HashMap<String, String> response = devNFCPay.read(rxData);
|
||||
String code = response.get("code");
|
||||
|
||||
if (isRun() && !isShuttingDown && MyApp.getInstance().isShowDialog()) { // ✅ 最後回報前的檢查
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.RESPONSE.getOption(), code);
|
||||
} else {
|
||||
getLogs().info("🚫 控制器已停止或對話框已關閉,捨棄 NFC 回傳結果");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isRun() || isShuttingDown || !MyApp.getInstance().isShowDialog()) {
|
||||
getLogs().info("NFC 讀取中斷: 控制器關閉或對話框消失");
|
||||
break;
|
||||
}
|
||||
if (isRun() && !isShuttingDown && MyApp.getInstance().isShowDialog()) { // ✅ 回報前的生命週期檢查
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.RESPONSE.getOption(), code);
|
||||
} else {
|
||||
getLogs().info("🚫 控制器已停止或對話框已關閉,捨棄 NFC 回傳結果");
|
||||
}
|
||||
} catch (LogsParseException | LogsIOException e) {
|
||||
getLogs().warning(e);
|
||||
|
||||
@ -15,6 +15,9 @@ public class ReportFlowInfoStructure {
|
||||
int giveChange;
|
||||
int usePoints;
|
||||
String reportFlowId;
|
||||
// MQTT 交易結案流水號:進入付款時生成一次,pending → completed/failed 全程共用同一個,
|
||||
// 讓後台依同一 flow_id 做狀態轉移。與 reportFlowId(取貨碼/通行碼驗證碼、B600 legacy)語意不同,獨立一欄。
|
||||
String mqttFlowId;
|
||||
JSONArray shoppingCartInfoByJsonArray;
|
||||
|
||||
public ReportFlowInfoStructure(String orderId, int flowType, String flowSendInfo, String flowRequestInfo, String productId, int totalPrice, String invoiceInfo, String flowBarCode, int flowStatusType, int giveChange, int usePoints, JSONArray shoppingCartInfoByJsonArray) {
|
||||
@ -136,6 +139,14 @@ public class ReportFlowInfoStructure {
|
||||
this.reportFlowId = reportFlowId;
|
||||
}
|
||||
|
||||
public String getMqttFlowId() {
|
||||
return mqttFlowId;
|
||||
}
|
||||
|
||||
public void setMqttFlowId(String mqttFlowId) {
|
||||
this.mqttFlowId = mqttFlowId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReportFlowInfoStructure{" +
|
||||
|
||||
@ -191,10 +191,17 @@ public class TransactionFinalizePayload {
|
||||
private int payment_type;
|
||||
|
||||
/**
|
||||
* 支付狀態:1 = 成功
|
||||
* 支付狀態:1 = 成功, 0 = 失敗/未付款
|
||||
*/
|
||||
private int payment_status;
|
||||
|
||||
/**
|
||||
* 交易生命週期狀態(後台 Order.status):
|
||||
* "pending"(進付款待結果)/ "completed"(付款成功)/ "failed"(付款失敗)/ "abandoned"。
|
||||
* 不帶(null)時後台預設 "completed",確保線上 main 機台行為不變。
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 訂單出貨狀態:0 = 全部失敗, 1 = 全部成功, 2 = 部分失敗
|
||||
*/
|
||||
@ -291,6 +298,14 @@ public class TransactionFinalizePayload {
|
||||
this.payment_status = payment_status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getDelivery_status() {
|
||||
return delivery_status;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.unibuy.smartdevice.MyApp;
|
||||
import com.unibuy.smartdevice.external.mqtt.MqttManager;
|
||||
import com.unibuy.smartdevice.structure.BuyStructure;
|
||||
import com.unibuy.smartdevice.structure.InvoiceStructure;
|
||||
import com.unibuy.smartdevice.structure.ReportFlowInfoStructure;
|
||||
@ -48,14 +49,45 @@ public class TransactionFinalizeBuilder {
|
||||
|
||||
private String codeId = "0";
|
||||
|
||||
/** 交易生命週期狀態(pending/completed/failed/abandoned);預設 completed 維持成功路徑原行為。 */
|
||||
private String status = "completed";
|
||||
|
||||
/** 支付狀態:1=成功、0=失敗/未付款;預設 1 維持成功路徑原行為。 */
|
||||
private int paymentStatus = 1;
|
||||
|
||||
/** 失敗結果碼(如刷卡機 0015);非空時折進 payment_response 方便後台查閱。 */
|
||||
private String resultCode = null;
|
||||
|
||||
public TransactionFinalizeBuilder() {
|
||||
this.orderMachineTime = SDF.format(new Date());
|
||||
// 在進入出貨流程時立即生成本次交易流水號(一次交易只生成一次)
|
||||
this.flowId = FlowIdGenerator.next(MyApp.getInstance());
|
||||
// flow_id 共用「進入付款時生成的 mqttFlowId」,讓 pending → completed/failed 為同一筆,
|
||||
// 後台才能依 flow_id 做狀態轉移。若尚未生成(防禦)則即時生成並回存 flowInfo。
|
||||
ReportFlowInfoStructure flowInfo = MyApp.getInstance().getReportFlowInfoData();
|
||||
String existing = flowInfo != null ? flowInfo.getMqttFlowId() : null;
|
||||
if (existing != null && !existing.isEmpty()) {
|
||||
this.flowId = existing;
|
||||
} else {
|
||||
this.flowId = FlowIdGenerator.next(MyApp.getInstance());
|
||||
if (flowInfo != null) {
|
||||
flowInfo.setMqttFlowId(this.flowId);
|
||||
}
|
||||
}
|
||||
this.orderNo = this.flowId;
|
||||
Log.i(TAG, "TransactionFinalizeBuilder created, flow_id=" + this.flowId);
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public void setPaymentStatus(int paymentStatus) {
|
||||
this.paymentStatus = paymentStatus;
|
||||
}
|
||||
|
||||
public void setResultCode(String resultCode) {
|
||||
this.resultCode = resultCode;
|
||||
}
|
||||
|
||||
public void setFlowId(String flowId) {
|
||||
this.flowId = flowId;
|
||||
}
|
||||
@ -72,6 +104,52 @@ public class TransactionFinalizeBuilder {
|
||||
this.codeId = codeId;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 交易生命週期上報(pending / failed)。只在 basic 購物模式(本專案 general/demo)才送:
|
||||
// - basic:以「進付款時生成的同一 flow_id」上報,後台依 flow_id 做 pending → completed/failed 轉移。
|
||||
// - employee_card(晟崴)/pickup_sheet(中國醫):無實際付款,直接 return 不送,後台資料不受影響。
|
||||
// 用 shopping_mode 判斷(雲端 B014 已逐機台下發),不另設旗標。
|
||||
// 成功(completed)仍由 DispatchDialog 出貨完成後的既有流程上報,不在此處。
|
||||
// =========================================================================
|
||||
|
||||
/** 僅 basic 購物模式才上報生命週期;其餘模式不送。 */
|
||||
private static boolean lifecycleLoggingEnabled() {
|
||||
return "basic".equals(MyApp.getInstance().getShoppingMode());
|
||||
}
|
||||
|
||||
/** 進入付款時上報 pending(進付款一律留底)。非 basic 模式則不送。 */
|
||||
public static void publishPending() {
|
||||
if (!lifecycleLoggingEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
TransactionFinalizeBuilder b = new TransactionFinalizeBuilder();
|
||||
b.setStatus("pending");
|
||||
b.setPaymentStatus(0);
|
||||
MqttManager.getInstance().publishTransactionFinalize(b.build());
|
||||
Log.i(TAG, "published pending finalize, flow_id=" + b.getFlowId());
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "publishPending failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 付款失敗/逾時時上報 failed(救回原本默默消失的失敗單)。非 basic 模式則不送。 */
|
||||
public static void publishFailed(String resultCode) {
|
||||
if (!lifecycleLoggingEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
TransactionFinalizeBuilder b = new TransactionFinalizeBuilder();
|
||||
b.setStatus("failed");
|
||||
b.setPaymentStatus(0);
|
||||
b.setResultCode(resultCode);
|
||||
MqttManager.getInstance().publishTransactionFinalize(b.build());
|
||||
Log.i(TAG, "published failed finalize code=" + resultCode + ", flow_id=" + b.getFlowId());
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "publishFailed failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加入一筆出貨結果記錄(每個貨道出貨後呼叫一次)。
|
||||
@ -141,7 +219,9 @@ public class TransactionFinalizeBuilder {
|
||||
// --- 電子發票(改由後台開立)---
|
||||
// 機台不開票,只把「開立輸入」(載具/愛心碼/統編/金額)帶上去,狀態固定 pending;
|
||||
// 後台建 pending 發票後派 Job 去綠界開立。無發票需求(invoiceInfo 為空)時不帶,後台不建立發票。
|
||||
if (flowInfo.getInvoiceInfo() != null && !flowInfo.getInvoiceInfo().isEmpty()) {
|
||||
// 只有付款成功(completed)才建立發票;pending/failed 不開票(避免對未成交的交易開立)。
|
||||
if ("completed".equals(status)
|
||||
&& flowInfo.getInvoiceInfo() != null && !flowInfo.getInvoiceInfo().isEmpty()) {
|
||||
TransactionFinalizePayload.InvoicePayload invoicePayload =
|
||||
new TransactionFinalizePayload.InvoicePayload();
|
||||
invoicePayload.setStatus("pending");
|
||||
@ -204,13 +284,18 @@ public class TransactionFinalizeBuilder {
|
||||
|
||||
// 支付類型:flowType 直接對應 payment_type
|
||||
order.setPayment_type(flowInfo.getFlowType());
|
||||
order.setPayment_status(1); // 走到出貨流程代表支付已成功
|
||||
// 生命週期狀態與支付狀態:成功路徑沿用預設(completed/1);pending/failed 由 setter 覆寫。
|
||||
order.setStatus(status);
|
||||
order.setPayment_status(paymentStatus);
|
||||
|
||||
// 支付流程原始資料
|
||||
order.setPayment_request(
|
||||
flowInfo.getFlowSendInfo() != null ? flowInfo.getFlowSendInfo() : "");
|
||||
order.setPayment_response(
|
||||
flowInfo.getFlowRequestInfo() != null ? flowInfo.getFlowRequestInfo() : "");
|
||||
String paymentResponse = flowInfo.getFlowRequestInfo() != null ? flowInfo.getFlowRequestInfo() : "";
|
||||
if (resultCode != null && !resultCode.isEmpty()) {
|
||||
paymentResponse = paymentResponse + " | code=" + resultCode;
|
||||
}
|
||||
order.setPayment_response(paymentResponse);
|
||||
|
||||
// 會員資訊
|
||||
order.setMember_barcode(
|
||||
|
||||
@ -428,6 +428,21 @@ public class BuyDialog extends DialogAbstract {
|
||||
dialogCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 進入付款:先清掉上一筆殘留的 MQTT 交易流水號,避免被本筆誤用(flow_id 撞單)。
|
||||
com.unibuy.smartdevice.structure.ReportFlowInfoStructure flowInfo =
|
||||
MyApp.getInstance().getReportFlowInfoData();
|
||||
flowInfo.setMqttFlowId("");
|
||||
// basic 購物模式(本專案 general/demo) 且為實際付款方式(1/2/3/4/9/30/70) 時:
|
||||
// 為本筆生成新流水號,並上報 pending(「進付款一律留底」)。pending → completed/failed 全程共用此 flow_id。
|
||||
// 通行碼(5)/來店禮(7) 非付款,不在此處理;employee_card(晟崴)/pickup_sheet(中國醫) 無付款不送,後台資料不受影響。
|
||||
boolean realPayment = (flowType == 1 || flowType == 2 || flowType == 3 || flowType == 4
|
||||
|| flowType == 9 || flowType == 30 || flowType == 70);
|
||||
if ("basic".equals(MyApp.getInstance().getShoppingMode()) && realPayment) {
|
||||
flowInfo.setMqttFlowId(com.unibuy.smartdevice.tools.FlowIdGenerator.next(MyApp.getInstance()));
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishPending();
|
||||
}
|
||||
|
||||
switch (flowType) {
|
||||
case 1:
|
||||
changeDialog(CheckoutNfcCardDialog.class);
|
||||
|
||||
@ -111,6 +111,8 @@ public class CheckoutEsunPayDialog extends DialogAbstract {
|
||||
if (isTransaction) {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
// [MQTT Finalize 重構] 交易失敗不再經 B600;直接提示並返回購買頁,不進出貨。
|
||||
// [交易生命週期] 掃碼支付失敗:上報 failed(flag 開才送),救回原本默默消失的失敗單。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed("scan_failed");
|
||||
isTransaction = false;
|
||||
start(getSrcClassName(), "@" + getContext().getString(R.string.transaction_failure));
|
||||
changeBuy();
|
||||
|
||||
@ -112,6 +112,8 @@ public class CheckoutLinePayDialog extends DialogAbstract {
|
||||
if (isTransaction) {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
// [MQTT Finalize 重構] 交易失敗不再經 B600;直接提示並返回購買頁,不進出貨。
|
||||
// [交易生命週期] LINE Pay 失敗:上報 failed(flag 開才送),救回原本默默消失的失敗單。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed("scan_failed");
|
||||
isTransaction = false;
|
||||
start(getSrcClassName(), "@" + getContext().getString(R.string.transaction_failure));
|
||||
changeBuy();
|
||||
|
||||
@ -116,10 +116,14 @@ public class CheckoutNfcCardDialog extends DialogAbstract {
|
||||
} else {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
// [交易生命週期] 刷卡機回非 0000:上報 failed(帶代碼),救回原本默默消失的失敗單(flag 開才送)。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed(message);
|
||||
}
|
||||
} else {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
// [交易生命週期] 刷卡機回空電文:視為失敗,上報 failed(flag 開才送)。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed(message);
|
||||
}
|
||||
|
||||
// binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
|
||||
@ -118,10 +118,14 @@ public class CheckoutNfcPhoneDialog extends DialogAbstract {
|
||||
} else {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
// [交易生命週期] 刷卡機回非 0000:上報 failed(帶代碼),救回原本默默消失的失敗單(flag 開才送)。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed(message);
|
||||
}
|
||||
} else {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
// [交易生命週期] 刷卡機回空電文:視為失敗,上報 failed(flag 開才送)。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed(message);
|
||||
}
|
||||
|
||||
// binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
|
||||
@ -115,10 +115,14 @@ public class CheckoutNfcRechargeDialog extends DialogAbstract {
|
||||
} else {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
// [交易生命週期] 刷卡機回非 0000:上報 failed(帶代碼),救回原本默默消失的失敗單(flag 開才送)。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed(message);
|
||||
}
|
||||
} else {
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
// [交易生命週期] 刷卡機回空電文:視為失敗,上報 failed(flag 開才送)。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed(message);
|
||||
}
|
||||
|
||||
// binding.textStatus.setText(DevNFCPay.recordMessage(message));
|
||||
|
||||
@ -134,6 +134,8 @@ public class CheckoutTapPayDialog extends DialogAbstract {
|
||||
isTransaction = false; // 🚨 馬上關閉
|
||||
MyApp.getInstance().getReportFlowInfoData().setFlowStatusType(0);
|
||||
// [MQTT Finalize 重構] 交易失敗不再經 B600;直接提示並返回購買頁,不進出貨。
|
||||
// [交易生命週期] 掃碼支付失敗:上報 failed(flag 開才送),救回原本默默消失的失敗單。
|
||||
com.unibuy.smartdevice.tools.TransactionFinalizeBuilder.publishFailed("scan_failed");
|
||||
start(getSrcClassName(), "@" + getContext().getString(R.string.transaction_failure));
|
||||
changeBuy();
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user