refund cached certificate fixed

This commit is contained in:
MooN 2025-12-29 01:03:47 +06:30
parent 062533662f
commit cd7cfa2a7d
13 changed files with 136 additions and 92 deletions

View File

@ -72,7 +72,7 @@ public class InputAmountFragment extends DataBindingFragment implements DataBind
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
setUpTimer(); // setUpTimer();
setupBackButtonPressDetection(this); setupBackButtonPressDetection(this);
} }

View File

@ -132,7 +132,13 @@ public class QRRefundFragment extends DataBindingFragment {
List<PayDetail> filteredList = new ArrayList<>(); List<PayDetail> filteredList = new ArrayList<>();
for(PayDetail item: originalList){ for(PayDetail item: originalList){
if(item.getReferNo() != null && item.getReferNo().contains(keyword)){
boolean match =
(item.getReferNo() != null && item.getReferNo().contains(keyword)) ||
(item.getInvoiceNo() != null && item.getInvoiceNo().contains(keyword)) ||
(item.getVoucherNo() != null && item.getVoucherNo().contains(keyword));
if(match){
filteredList.add(item); filteredList.add(item);
} }
} }
@ -140,4 +146,5 @@ public class QRRefundFragment extends DataBindingFragment {
updateList(filteredList); updateList(filteredList);
} }
} }

View File

@ -1,13 +1,12 @@
package com.utsmm.kbz.ui.qr_pay; package com.utsmm.kbz.ui.qr_pay;
import android.util.Log;
import com.utsmm.kbz.util.DownloadUtil; import com.utsmm.kbz.util.DownloadUtil;
import com.utsmyanmar.baselib.util.EReceiptHelper; import com.utsmyanmar.baselib.util.EReceiptHelper;
import com.utsmyanmar.paylibs.utils.LogUtil; import com.utsmyanmar.paylibs.utils.LogUtil;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -15,55 +14,94 @@ import javax.inject.Singleton;
public class RefundCertificateManager { public class RefundCertificateManager {
private static final String TAG = RefundCertificateManager.class.getSimpleName(); private static final String TAG = RefundCertificateManager.class.getSimpleName();
private static final String CERTIFICATE_FILENAME = "refund_client_certificate"; private static final String E_RECEIPT_SECRET =
private static final String CERTIFICATE_CLIENT = "refund_client_certificate"; com.utsmyanmar.baselib.BuildConfig.ERECEIPT_SECRET;
// String tmsAddress = SystemParamsOperation.getInstance().getTmsAddress(); //for uat
String tmsAddress = SystemParamsOperation.getInstance().getTmsAddress() + "/api/v1";
String downloadAddress = tmsAddress + "/file/download?filePath=";
String certificate_url = SystemParamsOperation.getInstance().getCertificateUrl();
String certificate_client = SystemParamsOperation.getInstance().getCertificateClientUrl(); // Prevent duplicate parallel downloads
private static final String E_RECEIPT_SECRET = com.utsmyanmar.baselib.BuildConfig.ERECEIPT_SECRET; private final AtomicBoolean isUpdating = new AtomicBoolean(false);
String timestamp = String.valueOf(System.currentTimeMillis());
@Inject @Inject
public RefundCertificateManager(){} public RefundCertificateManager() {
}
/**
* Download & refresh refund certificates.
* Safe to call multiple times.
*/
public void updateCertificateFiles() { public void updateCertificateFiles() {
if (!isUpdating.compareAndSet(false, true)) {
LogUtil.d(TAG, "Certificate update already in progress");
return;
}
try {
String tmsAddress = SystemParamsOperation.getInstance().getTmsAddress();
if (tmsAddress == null || tmsAddress.trim().isEmpty()) {
LogUtil.e(TAG, "TMS address is empty");
return;
}
String downloadBase =
tmsAddress.trim() + "/api/v1/file/download?filePath=";
String certUrl =
SystemParamsOperation.getInstance().getCertificateUrl();
String clientCertUrl =
SystemParamsOperation.getInstance().getCertificateClientUrl();
if (certUrl == null || clientCertUrl == null) {
LogUtil.e(TAG, "Certificate URLs are missing");
return;
}
String timestamp = String.valueOf(System.currentTimeMillis());
String signature = generateSignature(timestamp); String signature = generateSignature(timestamp);
LogUtil.d(TAG, "Download base => " + downloadBase);
LogUtil.d(TAG, "Cert URL => " + certUrl);
LogUtil.d(TAG, "Client Cert URL => " + clientCertUrl);
// ---------- CA CERT ----------
DownloadUtil.downloadCertificateRx( DownloadUtil.downloadCertificateRx(
downloadAddress + certificate_url, downloadBase + certUrl,
CERTIFICATE_FILENAME, "refund_ca_" + timestamp, // IMPORTANT: unique filename
timestamp, timestamp,
signature, signature,
path -> { path -> {
if (path != null) { if (path != null) {
SystemParamsOperation.getInstance().setCertClientFilePath(path); SystemParamsOperation.getInstance()
LogUtil.d(TAG, "Cert file path saved in SystemParams => " + path); .setCertFilePath(path);
LogUtil.d(TAG, "CA cert saved => " + path);
} else { } else {
LogUtil.e(TAG, "Failed to download certificate file"); LogUtil.e(TAG, "Failed to download CA cert");
} }
} }
); );
// ---------- CLIENT CERT ----------
DownloadUtil.downloadCertificateRx( DownloadUtil.downloadCertificateRx(
downloadAddress + certificate_client, downloadBase + clientCertUrl,
CERTIFICATE_CLIENT, "refund_client_" + timestamp, // IMPORTANT: unique filename
timestamp, timestamp,
signature, signature,
path -> { path -> {
if (path != null) { if (path != null) {
SystemParamsOperation.getInstance().setCertClientFilePath(path); SystemParamsOperation.getInstance()
LogUtil.d(TAG, "Cert client file path saved in SystemParams => " + path); .setCertClientFilePath(path);
LogUtil.d(TAG, "Client cert saved => " + path);
} else { } else {
LogUtil.e(TAG, "Failed to download certificate client file"); LogUtil.e(TAG, "Failed to download client cert");
} }
} }
); );
} finally {
isUpdating.set(false);
}
} }
private static String generateSignature(String timestamp) { private static String generateSignature(String timestamp) {
LogUtil.d(TAG, "timestamp " + timestamp);
String bodyString = "{}"; String bodyString = "{}";
String dataToHash = bodyString + E_RECEIPT_SECRET + timestamp; String dataToHash = bodyString + E_RECEIPT_SECRET + timestamp;
return EReceiptHelper.sha256(dataToHash); return EReceiptHelper.sha256(dataToHash);

View File

@ -1,34 +1,12 @@
package com.utsmm.kbz.ui.settings; package com.utsmm.kbz.ui.settings;
import static androidx.databinding.DataBindingUtil.getBinding;
import static com.utsmyanmar.baselib.BaseApplication.sunmiPrinterService;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import com.nexgo.oaf.apiv3.APIProxy;
import com.nexgo.oaf.apiv3.DeviceEngine;
import com.nexgo.oaf.apiv3.DeviceInfo;
import com.nexgo.oaf.apiv3.SdkResult;
import com.nexgo.oaf.apiv3.device.printer.AlignEnum;
import com.nexgo.oaf.apiv3.device.printer.GrayLevelEnum;
import com.nexgo.oaf.apiv3.device.printer.Printer; import com.nexgo.oaf.apiv3.device.printer.Printer;
import com.utsmm.kbz.BR; import com.utsmm.kbz.BR;
import com.utsmm.kbz.R; import com.utsmm.kbz.R;
@ -37,15 +15,7 @@ import com.utsmm.kbz.databinding.FragmentHostConfigBinding;
import com.utsmm.kbz.ui.core_viewmodel.SharedViewModel; import com.utsmm.kbz.ui.core_viewmodel.SharedViewModel;
import com.utsmyanmar.baselib.fragment.DataBindingFragment; import com.utsmyanmar.baselib.fragment.DataBindingFragment;
import com.utsmyanmar.baselib.util.DataBindingConfig; import com.utsmyanmar.baselib.util.DataBindingConfig;
import com.utsmyanmar.baselib.util.DialogCallback;
import com.utsmyanmar.paylibs.PayLibsUtils;
import com.utsmyanmar.paylibs.print.PaperRollStatusCallback;
import com.utsmyanmar.paylibs.print.PrintHelper;
import com.utsmyanmar.paylibs.utils.LogUtil;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import com.utsmyanmar.paylibs.utils.print_utils.BitmapUtils;
import java.util.Objects;
public class HostConfigFragment extends DataBindingFragment { public class HostConfigFragment extends DataBindingFragment {

View File

@ -2,9 +2,12 @@ package com.utsmm.kbz.ui.settings;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import com.utsmm.kbz.BuildConfig;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
public class HostConfigViewModel extends ViewModel { public class HostConfigViewModel extends ViewModel {
public String appVersion = BuildConfig.VERSION_NAME;
public MutableLiveData<String> merchantName = new MutableLiveData<>(); public MutableLiveData<String> merchantName = new MutableLiveData<>();
public MutableLiveData<String> merchantPhone = new MutableLiveData<>(); public MutableLiveData<String> merchantPhone = new MutableLiveData<>();

View File

@ -52,8 +52,8 @@ public class DownloadUtil {
String dynamicFilename, String dynamicFilename,
String timestamp, String timestamp,
String signature) { String signature) {
LogUtil.d(TAG, "cert timestamp => " + timestamp); // LogUtil.d(TAG, "cert timestamp => " + timestamp);
LogUtil.d(TAG, "cert signature => " + signature); // LogUtil.d(TAG, "cert signature => " + signature);
OkHttpClient client = new OkHttpClient.Builder() OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS) .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)

View File

@ -639,6 +639,7 @@ public class TMSSetupsImpl implements TMSSetups{
continue; continue;
} }
SystemParamsOperation.getInstance().setCertificateClientUrl(data); SystemParamsOperation.getInstance().setCertificateClientUrl(data);
} }
else if (TextUtils.equals(name, "certificate_password")) { else if (TextUtils.equals(name, "certificate_password")) {
SystemParamsOperation.getInstance().setCertificatePassword(data); SystemParamsOperation.getInstance().setCertificatePassword(data);
@ -649,7 +650,7 @@ public class TMSSetupsImpl implements TMSSetups{
} }
private static String generateSignature(String apiSecret, String timestamp) { private static String generateSignature(String apiSecret, String timestamp) {
String bodyString = ""; String bodyString = "{}";
String dataToHash = bodyString + apiSecret + timestamp; String dataToHash = bodyString + apiSecret + timestamp;
return EReceiptHelper.sha256(dataToHash); return EReceiptHelper.sha256(dataToHash);
} }

View File

@ -81,6 +81,7 @@
app:layout_constraintHorizontal_bias="1.0" app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bannerCard" app:layout_constraintTop_toBottomOf="@id/bannerCard"
android:paddingBottom="32dp"
app:layout_constraintVertical_bias="1.0"> app:layout_constraintVertical_bias="1.0">
<FrameLayout <FrameLayout

View File

@ -310,12 +310,26 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="--------------------------------------------------------------------------" android:text="*************************************************"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
tools:text="--------------------------------------------------------------------------"/> tools:text="********************************************************"/>
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:visibility="visible">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{"App Version : " + viewModel.appVersion}'
android:fontFamily="@font/rubik_regular"
android:textSize="14sp"
android:textStyle="bold"
tools:text="App Version : 1.0"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
</FrameLayout> </FrameLayout>

View File

@ -449,11 +449,14 @@ public class DatabaseModule {
DemoQRApiService demoQRApiService, DemoQRApiService demoQRApiService,
KPayApiService kPayApiService, KPayApiService kPayApiService,
KPayRefundApiService kPayRefundApiService, KPayRefundApiService kPayRefundApiService,
EReceiptApiService eReceiptApiService EReceiptApiService eReceiptApiService,
Provider<KPayRefundApiService> kPayRefundApiProvider
) { ) {
return new com.utsmyanmar.baselib.repo.Repository(payDetailDao, return new com.utsmyanmar.baselib.repo.Repository(payDetailDao,
siriusApiService, emvDetailDao, siriusApiService, emvDetailDao,
payWaveRepository, demoQRApiService, kPayApiService, kPayRefundApiService, eReceiptApiService); payWaveRepository, demoQRApiService, kPayApiService, kPayRefundApiService, eReceiptApiService,
kPayRefundApiProvider
);
} }
@Provides @Provides

View File

@ -54,6 +54,7 @@ import dagger.hilt.InstallIn;
import dagger.hilt.android.qualifiers.ApplicationContext; import dagger.hilt.android.qualifiers.ApplicationContext;
import dagger.hilt.components.SingletonComponent; import dagger.hilt.components.SingletonComponent;
import hu.akarnokd.rxjava3.retrofit.RxJava3CallAdapterFactory; import hu.akarnokd.rxjava3.retrofit.RxJava3CallAdapterFactory;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit; import retrofit2.Retrofit;
@ -389,7 +390,6 @@ public class NetworkModule {
//@Reusable //@Reusable
//@KPayRefundRetrofit //@KPayRefundRetrofit
@Provides @Provides
@Singleton
@KPayRefundRetrofit @KPayRefundRetrofit
public Retrofit provideKPayRefundRetrofit(@ApplicationContext Context context) { public Retrofit provideKPayRefundRetrofit(@ApplicationContext Context context) {
@ -408,7 +408,9 @@ public Retrofit provideKPayRefundRetrofit(@ApplicationContext Context context) {
Security.addProvider(new BouncyCastleProvider()); Security.addProvider(new BouncyCastleProvider());
String clientPath = SystemParamsOperation.getInstance().getCertClientFilePath(); String clientPath = SystemParamsOperation.getInstance().getCertClientFilePath();
LogUtil.d("cert", "ca client path => " + clientPath);
String caPath = SystemParamsOperation.getInstance().getCertFilePath(); String caPath = SystemParamsOperation.getInstance().getCertFilePath();
LogUtil.d("cert", "capath => " + caPath);
if (TextUtils.isEmpty(clientPath) || TextUtils.isEmpty(caPath)) { if (TextUtils.isEmpty(clientPath) || TextUtils.isEmpty(caPath)) {
LogUtil.e("RefundCert", "Certificate files missing."); LogUtil.e("RefundCert", "Certificate files missing.");
@ -450,6 +452,7 @@ public Retrofit provideKPayRefundRetrofit(@ApplicationContext Context context) {
.sslSocketFactory(sslContext.getSocketFactory(), x509TrustManager) .sslSocketFactory(sslContext.getSocketFactory(), x509TrustManager)
.addInterceptor(interceptor) .addInterceptor(interceptor)
.hostnameVerifier((h, s) -> true) .hostnameVerifier((h, s) -> true)
.connectionPool(new ConnectionPool(0, 1, TimeUnit.SECONDS))
.build(); .build();
return new Retrofit.Builder() return new Retrofit.Builder()
@ -475,7 +478,6 @@ public Retrofit provideKPayRefundRetrofit(@ApplicationContext Context context) {
@Provides @Provides
@Singleton
public KPayRefundApiService provideKPayRefundApiService(@KPayRefundRetrofit Retrofit retrofit) { public KPayRefundApiService provideKPayRefundApiService(@KPayRefundRetrofit Retrofit retrofit) {
return retrofit.create(KPayRefundApiService.class); return retrofit.create(KPayRefundApiService.class);
} }

View File

@ -46,6 +46,7 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider;
import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Flowable;
@ -64,9 +65,11 @@ public class Repository {
private DemoQRApiService demoQRApiService; private DemoQRApiService demoQRApiService;
private KPayRefundApiService kPayRefundApiService; private KPayRefundApiService kPayRefundApiService;
private Provider<KPayRefundApiService> kPayRefundApiProvider;
private EReceiptApiService eReceiptApiService; private EReceiptApiService eReceiptApiService;
@Inject @Inject
public Repository(PayDetailDao payDetailDao, SiriusApiService siriusApiService, EmvDetailDao emvDetailDao, PayWaveRepository payWaveRepository, DemoQRApiService demoQRApiService, KPayApiService kPayApiService, KPayRefundApiService kPayRefundApiService, EReceiptApiService eReceiptApiService) { public Repository(PayDetailDao payDetailDao, SiriusApiService siriusApiService, EmvDetailDao emvDetailDao, PayWaveRepository payWaveRepository, DemoQRApiService demoQRApiService, KPayApiService kPayApiService, KPayRefundApiService kPayRefundApiService, EReceiptApiService eReceiptApiService, Provider<KPayRefundApiService> kPayRefundApiProvider) {
this.payDetailDao = payDetailDao; this.payDetailDao = payDetailDao;
this.siriusApiService = siriusApiService; this.siriusApiService = siriusApiService;
this.emvDetailDao = emvDetailDao; this.emvDetailDao = emvDetailDao;
@ -74,6 +77,7 @@ public class Repository {
this.demoQRApiService = demoQRApiService; this.demoQRApiService = demoQRApiService;
this.kPayApiService = kPayApiService; this.kPayApiService = kPayApiService;
this.kPayRefundApiService = kPayRefundApiService; this.kPayRefundApiService = kPayRefundApiService;
this.kPayRefundApiProvider = kPayRefundApiProvider;
this.eReceiptApiService = eReceiptApiService; this.eReceiptApiService = eReceiptApiService;
} }
@ -92,7 +96,8 @@ public class Repository {
} }
public Observable<KPayRefund.RefundResponse> kPayRefund(KPayRefund.RefundRequest request) { public Observable<KPayRefund.RefundResponse> kPayRefund(KPayRefund.RefundRequest request) {
return kPayRefundApiService.refundRequest(request); // return kPayRefundApiService.refundRequest(request);
return kPayRefundApiProvider.get().refundRequest(request);
} }
public Observable<DemoQRResponse> demoQRGenerate(DemoQRRequest demoQRRequest) { public Observable<DemoQRResponse> demoQRGenerate(DemoQRRequest demoQRRequest) {
return demoQRApiService.qrRequest(demoQRRequest); return demoQRApiService.qrRequest(demoQRRequest);