QR standalone settlement is working now, enhanced on dual settlement .

This commit is contained in:
kizzy 2026-09-03 18:09:09 +07:00
parent 5894d02fc6
commit 54a0b47920
6 changed files with 234 additions and 113 deletions

View File

@ -153,11 +153,12 @@ public class QRConnectingFragment extends DataBindingFragment implements DataBin
.subscribe(
response -> {
if(response.getResponse().getQrCode() != null && !response.getResponse().getQrCode().isEmpty()) {
LogUtil.d(TAG,response.getResponse().getQrCode());
if(response.getResponse().getQrCode() == null || response.getResponse().getQrCode().isEmpty()) {
endAction();
endAction(response.getResponse().getMsg());
}
refLabel = response.getResponse().getMerchOrderId();
@ -191,7 +192,7 @@ public class QRConnectingFragment extends DataBindingFragment implements DataBin
} else {
dismissLoadingDialog();
// showDeclineDialog("Failed to generate QR!\n"+response.getMessage());
endAction();
endAction(response.getResponse().getMsg());
}
},
@ -199,7 +200,7 @@ public class QRConnectingFragment extends DataBindingFragment implements DataBin
Throwable.printStackTrace();
dismissLoadingDialog();
// showDeclineDialog("Failed to generate QR!\nMessage - "+Throwable.getMessage());
endAction();
endAction("");
},
() -> {
LogUtil.d(TAG,"On Complete!");
@ -210,8 +211,8 @@ public class QRConnectingFragment extends DataBindingFragment implements DataBin
}
private void endAction() {
showDeclineDialog("Failed to generate QR!\n Communication Error! ");
private void endAction(String msg) {
showDeclineDialog("Failed to generate QR!\n "+(msg.isEmpty()? "Communication Error!" : msg));
ecrActionCancel(getResourceString(R.string.txt_qr_generate_failed));
navigateToMain();
}

View File

@ -67,6 +67,8 @@ public class SettlementTransactionFragment extends DataBindingFragment implement
private final CurrencyBucket mmkBucket = new CurrencyBucket();
private final CurrencyBucket usdBucket = new CurrencyBucket();
/** Accumulates card lines from USD + MMK settle passes for one combined slip. */
private final List<CardSettleData> accumulatedCardSettleData = new ArrayList<>();
private List<PayDetail> cardTransactionsList = new ArrayList<>();
private final ArrayList<PayDetail> qrTransactionsList = new ArrayList<>();
@ -671,6 +673,11 @@ public class SettlementTransactionFragment extends DataBindingFragment implement
}
private void performAutoSettle() {
accumulatedCardSettleData.clear();
if (!isCardSettleNeeded()) {
finishQrOnlySettlement();
return;
}
if (isUSDSettleNeeded()) {
currencySelectionHelper.applyCardCurrencyAndHost("USD");
settlementViewModel.startSettlementProcess(usdBucket, false);
@ -685,6 +692,59 @@ public class SettlementTransactionFragment extends DataBindingFragment implement
return (usdBucket.saleCount != 0 || usdBucket.preCount != 0 || usdBucket.refundCount != 0
|| usdBucket.caCount != 0 || usdBucket.voidCount != 0);
}
private boolean isMmkSettleNeeded() {
return (mmkBucket.saleCount != 0 || mmkBucket.preCount != 0 || mmkBucket.refundCount != 0
|| mmkBucket.caCount != 0 || mmkBucket.voidCount != 0);
}
private boolean isCardSettleNeeded() {
return isUSDSettleNeeded() || isMmkSettleNeeded();
}
private void finishQrOnlySettlement() {
if (qrTransactionsList.isEmpty()) {
showDeclineDialog(getResourceString(R.string.txt_there_is_no_trans_to_clear));
return;
}
showLoadingDialog("Settling ...");
TradeData tradeData = Params.newTrade(false);
PayDetail payDetail = tradeData.getPayDetail();
SettleData settleData = new SettleData(qrSaleCount, qrSaleAmount, 0, 0L, qrRefundCount, qrRefundAmount, 0, 0L);
payDetail.setSettleDataObj(settleData);
payDetail.setTransactionType(TransactionsType.MMQR_SETTLEMENT.value);
payDetail.setTransType(TransactionsType.MMQR_SETTLEMENT.name);
payDetail.setAmount(qrTotalAmount);
payDetail.setTradeAnswerCode("000");
payDetail.setBatchNo(SystemParamsOperation.getInstance().getCurrentBatchNum());
payDetail.setQrSettleData(QRSettleData.convertFromPayDetail(qrTransactionsList));
if (sharedViewModel.getTransMenu().getValue() != TransMenu.LAST_SETTLEMENT) {
sharedViewModel.insertPayDetail(payDetail);
}
sharedViewModel.payDetail.setValue(payDetail);
sharedViewModel.payDetails.setValue(new ArrayList<>(qrTransactionsList));
settlementViewModel.markAllUnsettledQRTransactionsSettled();
for (PayDetail pay : qrTransactionsList) {
settlementViewModel.markQRTransactionSettled(pay);
}
for (PayDetail pay : qrTransListAll) {
settlementViewModel.markQRTransactionSettled(pay);
}
EReceiptRequest request = EReceiptUtil.getInstance()
.generateQRSettlement(payDetail, QRSettleData.convertFromPayDetail(qrTransactionsList));
sharedViewModel.pushReceipt(request);
dismissLoadingDialog();
navigateToNext();
}
private void networkCutOver() {
EchoTestProcess.getInstance().enqueueCutOver().startSignOn(new SignOnListener() {
@ -737,12 +797,18 @@ public class SettlementTransactionFragment extends DataBindingFragment implement
case ON_SUCCESS:
updateData();
dismissLoadingDialog();
networkCutOver();
// Cut-over only between card currency passes — not for QR-only.
if (isCardSettleNeeded()) {
networkCutOver();
} else {
navigateToNext();
}
break;
case ON_FINAL_SUCCESS:
dismissLoadingDialog();
updateData();
if (!SystemParamsOperation.getInstance().getDemoStatus()) {
// QR-only / empty card batch must not send network cut-over.
if (isCardSettleNeeded() && !SystemParamsOperation.getInstance().getDemoStatus()) {
networkCutOver(); // bpc
} else {
navigateToNext();
@ -764,17 +830,22 @@ public class SettlementTransactionFragment extends DataBindingFragment implement
private void updateData() {
PayDetail payDetail = settlementViewModel.getPayDetail();
// Keep cardSettleData from ViewModel (built before updateDB clears payDetails).
List<CardSettleData> passCards = payDetail.getCardSettleData();
if (passCards != null && !passCards.isEmpty()) {
accumulatedCardSettleData.addAll(passCards);
}
if (qrTransactionsList != null && !qrTransactionsList.isEmpty()) {
payDetail.setQrSettleData(QRSettleData.convertFromPayDetail(qrTransactionsList));
}
sharedViewModel.payDetail.setValue(payDetail);
EReceiptRequest request = EReceiptUtil.getInstance().generateMPUReceipt(sharedViewModel.payDetail.getValue());
// Per-currency e-receipt first (pass cards only).
EReceiptRequest request = EReceiptUtil.getInstance().generateMPUReceipt(payDetail);
sharedViewModel.pushReceipt(request);
// Combined slip for print: USD + MMK card lines.
payDetail.setCardSettleData(new ArrayList<>(accumulatedCardSettleData));
sharedViewModel.payDetail.setValue(payDetail);
settleQr();
}
@ -855,6 +926,12 @@ public class SettlementTransactionFragment extends DataBindingFragment implement
if(sharedViewModel.getTransMenu().getValue() == TransMenu.SETTLEMENT) {
/* April 10 , 2024 Smile requested not to send to host even settlement button was clicked */
accumulatedCardSettleData.clear();
if (!isCardSettleNeeded()) {
// QR-only: settle QR locally, skip card ISO + cut-over
finishQrOnlySettlement();
return;
}
if(isUSDSettleNeeded()) {
currencySelectionHelper.applyCardCurrencyAndHost("USD");
settlementViewModel.startSettlementProcess(usdBucket,false);

View File

@ -69,6 +69,8 @@ public class SettlementViewModel extends ViewModel {
private boolean isSecondCall = false;
private int i = 0;
private List<PayDetail> batchUploadQueue;
private int batchUploadIndex = 0;
public SingleLiveEvent<List<PayDetail>> payDetailList = new SingleLiveEvent<>();
private List<PayDetail> payDetails;
@ -344,6 +346,12 @@ public class SettlementViewModel extends ViewModel {
@SuppressWarnings("ConstantConditions")
public void startSettlementProcess(CurrencyBucket currencyBucket,boolean isLast) {
this.isLast = isLast;
this.flag = false;
this.i = 0;
this.batchUploadQueue = null;
this.batchUploadIndex = 0;
this.errorFlag = false;
this.isSecondCall = false;
// Switch TID/MID/IP/currency before building ISO
// isLast=false => USD pass; isLast=true => MMK pass.
@ -354,10 +362,19 @@ public class SettlementViewModel extends ViewModel {
SystemParamsOperation.getInstance().getIncrementBatchNo();
// Void-only currency (e.g. USD sale then void) must not be treated as empty:
// previously that skipped 0500 and posted ON_FINAL_SUCCESS, which also skipped MMK.
boolean bucketIsEmpty = currencyBucket.saleCount == 0
&& currencyBucket.preCount == 0
&& currencyBucket.refundCount == 0
&& currencyBucket.caCount == 0;
&& currencyBucket.caCount == 0
&& currencyBucket.voidCount == 0;
LogUtil.d(TAG, "Settlement bucket sale=" + currencyBucket.saleCount
+ " void=" + currencyBucket.voidCount
+ " empty=" + bucketIsEmpty
+ " demo=" + SystemParamsOperation.getInstance().getDemoStatus()
+ " isLast=" + isLast);
if (bucketIsEmpty || SystemParamsOperation.getInstance().getDemoStatus()) {
requestDemoProcessSettlement(currencyBucket);
@ -411,33 +428,61 @@ public class SettlementViewModel extends ViewModel {
enrichSettleDataWithVoids(settleData);
payDetail.setSettleDataObj(settleData);
payDetail.setCardSettleData(CardSettleData.convertFromPayDetail(buildCardPayDetailList()));
long totalAmount = currencyBucket.saleAmount + currencyBucket.preAmount + currencyBucket.refundAmount + currencyBucket.caAmount;
String settlementData = "";
if(currencyBucket.refundAmount != 0L) {
long creditTotal = currencyBucket.saleAmount + currencyBucket.preAmount + currencyBucket.caAmount;
long subTotal = creditTotal - currencyBucket.refundAmount;
if(subTotal < 0L) {
settlementData = "D"+ String.format(Locale.getDefault(), "%012d", Math.abs(subTotal));
} else {
settlementData = "C"+ String.format(Locale.getDefault(), "%012d", subTotal);
}
} else {
settlementData = "C"+ String.format(Locale.getDefault(), "%012d", totalAmount);
}
payDetail.setSettleData(settlementData);
payDetail.setAmount(totalAmount);
applySettlementIsoAmounts(payDetail, settleData);
payDetail.setTradeAnswerCode("000");
updateDB();
// Only clear batch after the final (MMK) pass — clearing on USD wiped MMK txns.
if (isLast) {
updateDB();
}
insertPayDetail(payDetail);
resultString.postValue(TransactionStatus.ON_FINAL_SUCCESS);
if (isLast) {
resultString.postValue(TransactionStatus.ON_FINAL_SUCCESS);
} else {
resultString.postValue(TransactionStatus.ON_SUCCESS);
}
}
private void applySettlementIsoAmounts(PayDetail payDetail, SettleData settleData) {
HostName hostName = HostName.BPC;
long saleAmount = settleData.getSaleAmount();
long preAmount = settleData.getPreAuthCompAmount();
long refundAmount = settleData.getRefundAmount();
long caAmount = settleData.getCashAdvanceAmount();
if (hostName == HostName.BPC) {
long totalAmount = saleAmount + preAmount + refundAmount + caAmount;
String settlementData;
if (refundAmount != 0L) {
long creditTotal = saleAmount + preAmount + caAmount;
long subTotal = creditTotal - refundAmount;
if (subTotal < 0L) {
settlementData = "D" + String.format(Locale.getDefault(), "%012d", Math.abs(subTotal));
} else {
settlementData = "C" + String.format(Locale.getDefault(), "%012d", subTotal);
}
} else {
settlementData = "C" + String.format(Locale.getDefault(), "%012d", totalAmount);
}
payDetail.setSettleData(settlementData);
payDetail.setAmount(totalAmount);
LogUtil.d(TAG, "Settlement ISO amount=" + settlementData
+ " sale=" + saleAmount
+ " void=" + settleData.getVoidSaleAmount());
} else {
int sale2Count = settleData.getSaleCount() + settleData.getPreAuthCompCount();
long sale2Amount = saleAmount + preAmount;
String totalSaleCount = String.format(Locale.getDefault(), "%03d", sale2Count);
String totalSaleAmount = String.format(Locale.getDefault(), "%010d00", sale2Amount);
String totalRefundCount = String.format(Locale.getDefault(), "%03d", settleData.getRefundCount());
String totalRefundAmount = String.format(Locale.getDefault(), "%010d00", refundAmount == 0L ? 0 : refundAmount);
String totalDebitSaleCount = String.format(Locale.getDefault(), "%03d", settleData.getCashAdvanceCount());
String totalDebitSaleAmount = String.format(Locale.getDefault(), "%010d00", caAmount == 0L ? 0 : caAmount);
payDetail.setSettleData(totalSaleCount + totalSaleAmount + totalRefundCount + totalRefundAmount
+ totalDebitSaleCount + totalDebitSaleAmount);
payDetail.setAmount(sale2Amount + refundAmount + caAmount);
}
}
private void requestOnlineProcessSettlement(CurrencyBucket currencyBucket) {
@ -446,22 +491,8 @@ public class SettlementViewModel extends ViewModel {
HostName hostName = HostName.BPC;
String field60 = SystemParamsOperation.getInstance().getCurrentBatchNum();
int sale2Count = currencyBucket.saleCount + currencyBucket.preCount;
long sale2Amount = currencyBucket.saleAmount + currencyBucket.preAmount;
String totalSaleCount = String.format(Locale.getDefault(), "%03d", sale2Count);
String totalSaleAmount = String.format(Locale.getDefault(), "%010d00", sale2Amount);
String totalRefundCount = String.format(Locale.getDefault(), "%03d", currencyBucket.refundCount);
String totalRefundAmount = String.format(Locale.getDefault(), "%010d00", currencyBucket.refundAmount == 0L ? 0 : currencyBucket.refundAmount);
String totalDebitSaleCount = String.format(Locale.getDefault(), "%03d", currencyBucket.caCount);
String totalDebitSaleAmount = String.format(Locale.getDefault(), "%010d00", currencyBucket.caAmount == 0L ? 0 : currencyBucket.caAmount);
String totalERefundCount = String.format(Locale.getDefault(), "%03d", 0);
String totalERefundAmount = String.format(Locale.getDefault(), "%010d00", 0);
TradeData tradeData = Params.newTrade(true);
payDetail = tradeData.getPayDetail();
@ -508,33 +539,8 @@ public class SettlementViewModel extends ViewModel {
payDetail.setSettleDataObj(settleData);
payDetail.setCardSettleData(CardSettleData.convertFromPayDetail(buildCardPayDetailList()));
if(hostName == HostName.BPC) {
long totalAmount = currencyBucket.saleAmount + currencyBucket.preAmount + currencyBucket.refundAmount + currencyBucket.caAmount;
String settlementData = "";
if(currencyBucket.refundAmount != 0L) {
long creditTotal = currencyBucket.saleAmount + currencyBucket.preAmount + currencyBucket.caAmount;
long subTotal = creditTotal - currencyBucket.refundAmount;
if(subTotal < 0L) {
settlementData = "D"+ String.format(Locale.getDefault(), "%012d", Math.abs(subTotal));
} else {
settlementData = "C"+ String.format(Locale.getDefault(), "%012d", subTotal);
}
} else {
settlementData = "C"+ String.format(Locale.getDefault(), "%012d", totalAmount);
}
payDetail.setSettleData(settlementData);
payDetail.setAmount(totalAmount);
} else {
// payDetail.setSettleData(totalSaleCount + totalSaleAmount + totalRefundCount + totalRefundAmount + totalDebitSaleCount + totalDebitSaleAmount + totalERefundCount + totalERefundAmount); //field 63 BPC
payDetail.setSettleData(totalSaleCount + totalSaleAmount + totalRefundCount + totalRefundAmount + totalDebitSaleCount + totalDebitSaleAmount ); //field 63
}
// ISO F005 must include voided/canceled sales (host still holds them until batch upload).
applySettlementIsoAmounts(payDetail, settleData);
tradeData.setPayDetail(payDetail);
tradeData.setField60(field60);
@ -607,6 +613,8 @@ public class SettlementViewModel extends ViewModel {
/* reset batch upload count...*/
i = 0;
batchUploadQueue = null;
batchUploadIndex = 0;
// to leave data for testing
// if (settlementType.getValue() == SettlementType.NORMAL) {
@ -686,51 +694,37 @@ public class SettlementViewModel extends ViewModel {
private void batchUploadProcess(boolean isLast) {
resultString.postValue(TransactionStatus.ON_BATCH_UPLOAD);
if (payDetails == null || payDetails.isEmpty()) {
if (batchUploadQueue == null) {
batchUploadQueue = buildBatchUploadList(isLast);
batchUploadIndex = 0;
LogUtil.d(TAG, "Batch upload queue size=" + batchUploadQueue.size()
+ " currency=" + (isLast ? "MMK" : "USD"));
}
if (batchUploadQueue.isEmpty() || batchUploadIndex >= batchUploadQueue.size()) {
batchUploadQueue = null;
batchUploadIndex = 0;
requestOnlineProcessSettlement(currentBucket);
return;
}
// Advance index, skipping entries that don't belong to this pass
while (i < payDetails.size()) {
PayDetail payDetail = payDetails.get(i);
boolean isMMK = payDetail.getCurrencyCode().equals("104");
if (isLast && !isMMK) {
// isLast = true → MMK pass, skip USD
LogUtil.d(TAG, "Skipping USD transaction at index: " + i);
i++;
continue;
} else if (!isLast && isMMK) {
// isLast = false → USD pass, skip MMK
LogUtil.d(TAG, "Skipping MMK transaction at index: " + i);
i++;
continue;
}
break;
}
if (i >= payDetails.size()) {
requestOnlineProcessSettlement(currentBucket);
return;
}
PayDetail payDetail = payDetails.get(i);
PayDetail uploadPay = batchUploadQueue.get(batchUploadIndex);
LogUtil.d(TAG, "Batch upload [" + batchUploadIndex + "/" + batchUploadQueue.size() + "] type="
+ uploadPay.getTransType() + " amt=" + uploadPay.getAmount()
+ " currency=" + uploadPay.getCurrencyCode());
TradeData tradeData = new TradeData();
tradeData.setPayDetail(payDetail);
tradeData.setPayDetail(uploadPay);
BatchUploadProcess.getInstance().enqueue(tradeData).startBatchUpload(new BatchListener() {
@Override
public void onSuccessBatch() {
LogUtil.e(TAG, "Batch Upload Success");
LogUtil.d(TAG, "Pay detail Size: " + payDetails.size());
LogUtil.d(TAG, "Count value: " + i);
if (i < payDetails.size() - 1) {
i++;
batchUploadIndex++;
if (batchUploadIndex < batchUploadQueue.size()) {
batchUploadProcess(isLast);
} else {
batchUploadQueue = null;
batchUploadIndex = 0;
requestOnlineProcessSettlement(currentBucket);
}
}
@ -743,4 +737,38 @@ public class SettlementViewModel extends ViewModel {
}
private List<PayDetail> buildBatchUploadList(boolean isLast) {
String expectedCurrency = isLast ? "104" : "840";
List<PayDetail> list = new ArrayList<>();
if (payDetails != null) {
for (PayDetail pay : payDetails) {
if (matchesSettlementCurrency(pay.getCurrencyCode(), expectedCurrency)) {
list.add(pay);
}
}
}
for (PayDetail pay : deleteTrans) {
if (!matchesSettlementCurrency(pay.getCurrencyCode(), expectedCurrency)) {
continue;
}
// Canceled original sale first, then void record.
if (pay.getTransactionType() == TransactionType.SALE && pay.isCanceled) {
list.add(pay);
}
}
for (PayDetail pay : deleteTrans) {
if (!matchesSettlementCurrency(pay.getCurrencyCode(), expectedCurrency)) {
continue;
}
if (pay.getTransactionType() == TransactionType.VOID && !pay.isCanceled) {
list.add(pay);
}
}
return list;
}
}

View File

@ -288,6 +288,18 @@ public class TMSSetupsImpl implements TMSSetups{
SystemParamsOperation.getInstance().setShortCode("");
}
}
} else {
SystemParamsOperation.getInstance().setSecHostId("");
SystemParamsOperation.getInstance().setSecHostName("");
SystemParamsOperation.getInstance().setSecHostTerminalId("");
SystemParamsOperation.getInstance().setSecHostMerchantId("");
SystemParamsOperation.getInstance().setShortCode("");
SystemParamsOperation.getInstance().setSecHostIpAddress("");
SystemParamsOperation.getInstance().setSecHostSecIpAddress("");
SystemParamsOperation.getInstance().setSecHostCurrency("");
}

View File

@ -262,7 +262,10 @@ public class TMSUtil {
String shortCode = SystemParamsOperation.getInstance().getShortCode();
String appId = SystemParamsOperation.getInstance().getAppId();
String appKey = SystemParamsOperation.getInstance().getAppKey();
LogUtil.d(TAG,"shortCode:"+shortCode);
LogUtil.d(TAG,"appId:"+appId);
LogUtil.d(TAG,"appKey:"+appKey);
if(shortCode.length() == 11 && !appId.isEmpty() && !appKey.isEmpty()) {
tmsValidity = new TMSValidity(ValidityStatus.SUCCESS,"Success");

View File

@ -383,7 +383,7 @@ public abstract class BaseXPrint {
if ("SALE".equals(transType)) {
summary.saleCount++;
summary.saleAmount += amount;
} else if ("VOID SALE".equals(transType) || "VOID_SALE".equals(transType)) {
} else if ("VOID SALE".equals(transType) || "VOID_SALE".equals(transType) || "VOID".equals(transType)) {
summary.voidSaleCount++;
summary.voidSaleAmount += amount;
} else if ("PREAUTH COMPLETION".equals(transType) || "PRE_AUTH_COMPLETE".equals(transType)) {