[FIX] 修復掃碼支付(玉山)結案前購物車被清空導致「無商品紀錄」

1. 根因:order.items 在結案 build() 當下才即時讀 live buyList,掃碼支付(玉山)流程較慢(掃碼+玉山API約20s),結案前 buyList 會被取消/倒數路徑清空,造成 order.items 變空陣列(無商品紀錄),但出貨其實成功(dispense 有商品)。
2. TransactionFinalizeBuilder 新增 itemsSnapshot 商品明細快照欄位與 captureItems() 方法;buyList 為空時不覆寫既有快照。
3. 抽出共用 buildItemRecords() helper,captureItems() 與 build() fallback 共用。
4. build()/buildOrderPayload() 改為優先採用快照,無快照時才 fallback 讀 live buyList。
5. DispatchDialog onCreate 建立 finalizeBuilder 後立即 captureItems(),此時 buyList 必完整。
6. 升版 verPatch 95 → 96 (S9XYU versionName 21_01_96_R / versionCode 210196)。

正常情況快照==live,逐字等價;僅在 buyList 被清空的 bug 情境修正。純 App 端,不影響後台與線上 main。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-16 13:13:46 +08:00
parent 89c7d04f7a
commit c7bc554eca
3 changed files with 56 additions and 15 deletions

View File

@ -10,7 +10,7 @@ plugins {
//
// =========================================================================
def verMinor = 1
def verPatch = 94
def verPatch = 96

View File

@ -47,6 +47,14 @@ public class TransactionFinalizeBuilder {
/** 累積的出貨結果清單 */
private final List<TransactionFinalizePayload.DispenseRecord> dispenseRecords = new ArrayList<>();
/**
* 商品明細快照buyList 仍完整時擷取例如進入出貨流程當下
* 之後 build() 會優先採用此快照不再即時讀 live buyList
* 用以修正掃碼支付(玉山)等較慢流程結案前 buyList 可能被取消/倒數路徑清空
* 導致 order.items 變成空陣列出現無商品紀錄但出貨其實成功
*/
private List<TransactionFinalizePayload.ItemRecord> itemsSnapshot = null;
private String codeId = "0";
/** 交易生命週期狀態pending/completed/failed/abandoned預設 completed 維持成功路徑原行為。 */
@ -151,6 +159,42 @@ public class TransactionFinalizeBuilder {
}
/**
* 擷取目前 buyList 的商品明細快照
* 應在buyList 仍完整時呼叫例如 DispatchDialog 進入出貨流程當下
* 之後 {@link #build()} 會優先採用此快照不再即時讀 live buyList
* 避免結案前 buyList 被取消/倒數等其他執行緒清空導致 order.items 變成空陣列
* buyList 為空時不覆寫既有快照避免把有效快照洗成空
*/
public void captureItems() {
List<BuyStructure> buyList = MyApp.getInstance().getBuyList();
if (buyList == null || buyList.isEmpty()) {
return;
}
this.itemsSnapshot = buildItemRecords(buyList);
Log.i(TAG, "captureItems: snapshot " + this.itemsSnapshot.size()
+ " item(s), flow_id=" + flowId);
}
/** 由 buyList 組出 items 明細captureItems 與 build() fallback 共用)。 */
private List<TransactionFinalizePayload.ItemRecord> buildItemRecords(List<BuyStructure> buyList) {
List<TransactionFinalizePayload.ItemRecord> items = new ArrayList<>();
if (buyList != null) {
for (BuyStructure buy : buyList) {
int productId = 0;
try {
productId = Integer.parseInt(buy.getProduct().getProductID());
} catch (NumberFormatException e) {
Log.w(TAG, "Cannot parse productID to int: " + buy.getProduct().getProductID());
}
int price = buy.getProduct().getRealPrice();
int quantity = buy.getCount() > 0 ? buy.getCount() : 1;
items.add(new TransactionFinalizePayload.ItemRecord(productId, price, quantity));
}
}
return items;
}
/**
* 加入一筆出貨結果記錄每個貨道出貨後呼叫一次
*
@ -309,20 +353,13 @@ public class TransactionFinalizeBuilder {
order.setMachine_time(orderMachineTime);
// --- 組裝購買商品清單 (items) ---
List<TransactionFinalizePayload.ItemRecord> items = new ArrayList<>();
List<BuyStructure> buyList = MyApp.getInstance().getBuyList();
if (buyList != null) {
for (BuyStructure buy : buyList) {
int productId = 0;
try {
productId = Integer.parseInt(buy.getProduct().getProductID());
} catch (NumberFormatException e) {
Log.w(TAG, "Cannot parse productID to int: " + buy.getProduct().getProductID());
}
int price = buy.getProduct().getRealPrice();
int quantity = buy.getCount() > 0 ? buy.getCount() : 1;
items.add(new TransactionFinalizePayload.ItemRecord(productId, price, quantity));
}
// 優先採用 captureItems() 在出貨流程開始時擷取的快照無快照時才即時讀 live buyList
// 快照可避免結案前 buyList 被其他執行緒清空掃碼支付等慢流程的競態造成無商品紀錄
List<TransactionFinalizePayload.ItemRecord> items;
if (itemsSnapshot != null && !itemsSnapshot.isEmpty()) {
items = new ArrayList<>(itemsSnapshot);
} else {
items = buildItemRecords(MyApp.getInstance().getBuyList());
}
order.setItems(items);

View File

@ -258,6 +258,10 @@ public class DispatchDialog extends DialogAbstract {
httpAPI = new HttpAPI(getHandlerMain());
// 初始化 Finalize Builder在支付完成進入出貨流程時建立
finalizeBuilder = new TransactionFinalizeBuilder();
// 進入出貨流程當下 buyList 必為完整下方即用它組 dispatchList先擷取商品明細快照
// 之後結案 build() 會優先採用此快照避免掃碼支付(玉山)等慢流程在結案前 buyList
// 被取消/倒數路徑清空導致 order.items 變空無商品紀錄但出貨其實成功
finalizeBuilder.captureItems();
if (com.unibuy.smartdevice.AppEntry.isCmuh()) {
MyApp.getInstance().getReportFlowInfoData().setFlowType(42);
}