add expire time on QR

This commit is contained in:
MooN 2025-12-11 03:02:40 +06:30
parent 7d894116dd
commit 7452443a45
17 changed files with 216 additions and 25 deletions

View File

@ -14,8 +14,8 @@ android {
applicationId "com.utsmm.kbz"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
versionCode 2
versionName "1.01"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,37 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.utsmm.kbz.sit",
"variantName": "sitRelease",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 2,
"versionName": "1.01-sit",
"outputFile": "app-sit-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-sit-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-sit-release.dm"
]
}
],
"minSdkVersionForDexing": 24
}

View File

@ -42,8 +42,11 @@ import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsSettings;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
@ -190,10 +193,37 @@ public class KPayViewModel extends ViewModel {
return createQR(amount, String.valueOf(System.currentTimeMillis()),mid);
}
private int expireTime = 1;
private void calculateTimeoutMinutes() {
String data = SystemParamsOperation.getInstance().getWaveIntervalTime();
if (data == null || !data.contains("-")) {
data = "15-60"; // fallback
}
try {
String[] parts = data.split("-");
int waitingTimeSec = Integer.parseInt(parts[1]); // only take second part
// convert seconds minutes
expireTime = waitingTimeSec / 60;
// enforce limits 1120
if (expireTime < 1) expireTime = 1;
if (expireTime > 120) expireTime = 120;
} catch (Exception e) {
expireTime = 1; // safety fallback
LogUtil.d(TAG, "Invalid format for timeout config: " + data);
}
}
public KPayQRRequest.QrRequest createQR(String amount, String time,String mid) {
String merchOrderId = "NEX"+SystemParamsOperation.getInstance().getCurrentSerialNum()+generateRandomTwoChars();
String serialNum = TerminalUtil.getInstance().getSerialNo();
String nonceStr = generateNonceStr(); // Generate random nonce_str
calculateTimeoutMinutes();
String timeoutExpress = expireTime + "m";
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("merch_order_id", merchOrderId);
@ -203,7 +233,7 @@ public class KPayViewModel extends ViewModel {
bizContent.put("total_amount", amount);
bizContent.put("title", "testing");
// bizContent.put("operator_id", serialNum);
bizContent.put("timeout_express", "100m");
bizContent.put("timeout_express", timeoutExpress);
bizContent.put("trans_currency", "MMK");
bizContent.put("callback_info", "callback");
@ -226,7 +256,7 @@ public class KPayViewModel extends ViewModel {
"testing",
amount,
"MMK",
"100m",
timeoutExpress,
"callback"
);

View File

@ -58,7 +58,7 @@ public class QRPayFragment extends DataBindingFragment {
List<QRPayItem> features = new ArrayList<>();
features.add(new QRPayItem("Sale", R.drawable.ic_qr_pay, true));
if(isQrRefundEnable){
features.add(new QRPayItem("Refund", R.drawable.ic_refund, false));
features.add(new QRPayItem("Refund", R.drawable.ic_refund, true));
}
features.add(new QRPayItem("History", R.drawable.ic_history, true));

View File

@ -6,33 +6,88 @@ import com.utsmyanmar.paylibs.utils.LogUtil;
import java.io.File;
import java.io.FileOutputStream;
import okhttp3.OkHttp;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class DownloadUtil {
final static String TAG = DownloadUtil.class.getSimpleName();
private void downloadFile(String url){
private static final String TAG = DownloadUtil.class.getSimpleName();
// ==============================
// CALLBACK INTERFACE
// ==============================
public interface DownloadCallback {
void onDownloadSuccess(String path);
}
// ==============================
// RX ASYNC DOWNLOAD METHOD
// ==============================
public static void downloadCertificateRx(String url,
String dynamicFilename,
DownloadCallback callback) {
Observable.fromCallable(() -> downloadCert(url, dynamicFilename))
.subscribeOn(Schedulers.io()) // download on background thread
.observeOn(AndroidSchedulers.mainThread()) // callback on main thread
.subscribe(path -> {
if (path != null) {
LogUtil.d(TAG, "Certificate saved at: " + path);
if (callback != null) callback.onDownloadSuccess(path);
} else {
LogUtil.e(TAG, "Certificate download failed.");
if (callback != null) callback.onDownloadSuccess(null);
}
}, error -> {
error.printStackTrace();
if (callback != null) callback.onDownloadSuccess(null);
});
}
// ==============================
// ACTUAL DOWNLOAD LOGIC
// ==============================
public static String downloadCert(String url, String dynamicFilename) {
try {
OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = okHttpClient.newCall(request).execute();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
LogUtil.e(TAG, "file download failed");
return;
LogUtil.e(TAG, "Download failed: " + response.code());
return null;
}
byte[] bytes = response.body().bytes();
saveFile("cert_file.ctr", bytes);
LogUtil.d(TAG, "file saved successfully.");
// Detect extension (MIME or URL fallback)
String contentType = response.header("Content-Type", "");
String ext = getExtensionFromContentType(contentType);
if (ext.isEmpty()) ext = getExtensionFromUrl(url);
if (ext.isEmpty()) ext = ".bin"; // final fallback
// build dynamic filename
String filename = dynamicFilename + ext;
byte[] data = response.body().bytes();
String savedPath = saveFile(filename, data);
return savedPath;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private void saveFile(String filename, byte[] data){
// ==============================
// SAVE FILE TO INTERNAL STORAGE
// ==============================
private static String saveFile(String filename, byte[] data) {
try {
File dir = MyApplication.getInstance().getFilesDir();
File file = new File(dir, filename);
@ -42,9 +97,42 @@ public class DownloadUtil {
fos.flush();
fos.close();
LogUtil.d(TAG, "Saved file at => " + file.getAbsolutePath());
return file.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// ==============================
// MIME TYPE EXTENSION
// ==============================
private static String getExtensionFromContentType(String contentType) {
if (contentType == null) return "";
switch (contentType) {
case "application/x-x509-ca-cert":
return ".crt";
case "application/pkix-cert":
return ".cer";
case "application/x-pem-file":
case "application/octet-stream":
return ".pem";
case "application/x-pkcs12":
return ".p12";
default:
return "";
}
}
// ==============================
// URL EXTENSION PARSER
// ==============================
private static String getExtensionFromUrl(String url) {
if (url == null) return "";
int lastDot = url.lastIndexOf('.');
if (lastDot == -1) return "";
return url.substring(lastDot);
}
}

View File

@ -4,6 +4,7 @@ import android.content.pm.PackageInfo;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.utsmm.kbz.util.DownloadUtil;
import com.utsmyanmar.baselib.emv.EmvParamOperation;
import com.utsmyanmar.baselib.network.model.sirius.SiriusHost;
import com.utsmyanmar.baselib.network.model.sirius.SiriusMerchant;
@ -608,6 +609,17 @@ public class TMSSetupsImpl implements TMSSetups{
SystemParamsOperation.getInstance().setQrRefundEnable(parseBoolean(data));
} else if (TextUtils.equals(name, "tpdu_value")){
SystemParamsOperation.getInstance().setTpduValue(data);
} else if (TextUtils.equals(name, "certificate_file")){
String tmsAddress = SystemParamsOperation.getInstance().getTmsAddress();
String url = tmsAddress+"/file/download?filePath="+data;
DownloadUtil.downloadCertificateRx(url, "certificate_file", path -> {
if(path != null){
SystemParamsOperation.getInstance().setCertFilePath(path);
LogUtil.d(TAG, "Cert file path saved in SystemParams => " + path);
}else{
LogUtil.e(TAG, "Failed to download certificate file");
}
});
}
}

View File

@ -285,8 +285,8 @@ public class NetworkModule {
tmsAddress = getTMSUrlFromNative();
}
String baseUrl = tmsAddress.trim() + "/api/v1/";
// String baseUrl = tmsAddress.trim() + "/";
// String baseUrl = tmsAddress.trim() + "/api/v1/";
String baseUrl = tmsAddress.trim() + "/";
final Gson gson =
new GsonBuilder().create();

View File

@ -0,0 +1 @@
i/nexgo-sdk-dlkey-1.0.3-runtime_dex

View File

@ -0,0 +1 @@
i/jars/classes.jar

View File

@ -0,0 +1 @@
o/nexgo-sdk-dlkey-1.0.3-runtime

View File

@ -0,0 +1 @@
i/nexgo-sdk-dlkey-1.0.3-runtime_global-synthetics

View File

@ -1605,4 +1605,15 @@ public class SystemParamsOperation {
SystemParamsSettings params = getSystemParamsSettings();
return params.getQrRefundEnable();
}
public void setCertFilePath(String path) {
SystemParamsSettings params = getSystemParamsSettings();
params.setCertFilePath(path);
saveSystemParamsSettings(params);
}
public String getCertFilePath(){
SystemParamsSettings params = getSystemParamsSettings();
return params.getCertFilePath();
}
}

View File

@ -42,8 +42,8 @@ public class SystemParamsSettings implements Serializable {
// private String tmsAddress = "https://tms.smile-mm.com";
// private String tmsAddress = "http://128.199.170.203";
private String tmsAddress = "http://sirius-nest.utsmyanmar.com";
// private String tmsAddress = "https://api-tms-uat.kbzbank.com:8443/sirius";
// private String tmsAddress = "http://sirius-nest.utsmyanmar.com";
private String tmsAddress = "https://api-tms-uat.kbzbank.com:8443/sirius";
private String ereceiptAddress = "http://receipt-nest.utsmyanmar.com";
private String terminalCapability = "E0E8C8";
@ -242,6 +242,7 @@ public class SystemParamsSettings implements Serializable {
private boolean qrDecimalEnable = false;
private boolean qrRefundEnable = false;
private String certFilePath = "";
public boolean isQrPartialRefundEnable(){
return qrPartialRefundEnable;
@ -961,6 +962,14 @@ public class SystemParamsSettings implements Serializable {
return qrRefundEnable;
}
public void setCertFilePath(String path) {
this.certFilePath = path;
}
public String getCertFilePath() {
return certFilePath;
}
/* // 流水号起始
private String serialNum = Configs.getInstance().SERIAL_NUM();
// 批次号起始