tms connected

This commit is contained in:
moon 2026-06-10 12:23:21 +06:30
parent 6dc180e791
commit 8b3fb9a1a5
11 changed files with 394 additions and 79 deletions

View File

@ -3,6 +3,7 @@ package com.mob.utsmyanmar.ui.navigation
import android.net.Uri import android.net.Uri
sealed class Routes(val route: String) { sealed class Routes(val route: String) {
data object TmsSetup : Routes("tms_setup")
data object Dashboard : Routes("dashboard") data object Dashboard : Routes("dashboard")
data object Amount : Routes("amount/{action}") { data object Amount : Routes("amount/{action}") {
fun createRoute(action: String): String = "amount/${Uri.encode(action)}" fun createRoute(action: String): String = "amount/${Uri.encode(action)}"

View File

@ -0,0 +1,161 @@
package com.mob.utsmyanmar.ui.tms_setup
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CloudDownload
import androidx.compose.material.icons.filled.ErrorOutline
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.mob.utsmyanmar.ui.theme.Color as AppColor
@Composable
fun TmsSetupRoute(
viewModel: TmsSetupViewModel,
onNavigateDashboard: () -> Unit
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel) {
viewModel.navigateToDashboard.collect { onNavigateDashboard() }
}
TmsSetupScreen(
state = state,
onRetry = viewModel::downloadConfigs
)
}
@Composable
fun TmsSetupScreen(
state: TmsSetupUiState,
onRetry: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColor.IvoryBeige),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Icon(
imageVector = if (state.isError) Icons.Default.ErrorOutline else Icons.Default.CloudDownload,
contentDescription = null,
tint = if (state.isError) AppColor.Error else AppColor.LegacyRed,
modifier = Modifier.size(72.dp)
)
Text(
text = if (state.isError) "Configuration Error" else "Setting Up Terminal",
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = AppColor.LegacyRed,
textAlign = TextAlign.Center
)
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = AppColor.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (state.isLoading) {
CircularProgressIndicator(
color = AppColor.CrimsonRed,
modifier = Modifier.size(40.dp)
)
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
color = AppColor.CrimsonRed,
trackColor = AppColor.GoldenGlow.copy(alpha = 0.3f),
strokeCap = StrokeCap.Round
)
}
Text(
text = state.statusText,
fontSize = 14.sp,
color = AppColor.Black,
textAlign = TextAlign.Center
)
if (state.isError) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
AppColor.Error.copy(alpha = 0.08f),
RoundedCornerShape(12.dp)
)
.padding(12.dp)
) {
Text(
text = state.errorMessage,
fontSize = 13.sp,
color = AppColor.Error,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
if (state.isError) {
Button(
onClick = onRetry,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = AppColor.CrimsonRed)
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = null,
modifier = Modifier
.padding(end = 8.dp)
.size(18.dp)
)
Text(text = "Retry", fontSize = 16.sp)
}
}
}
}
}

View File

@ -0,0 +1,140 @@
package com.mob.utsmyanmar.ui.tms_setup
import android.annotation.SuppressLint
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.sirius.SiriusResponse
import com.mob.utsmyanmar.model.sirius.TMSUpdate
import com.mob.utsmyanmar.model.sirius.ValidityStatus
import com.mob.utsmyanmar.utils.tms.TMSSetupsImpl
import com.mob.utsmyanmar.utils.tms.TMSUtil
import com.utsmyanmar.baselib.BaseApplication
import com.utsmyanmar.baselib.emv.EmvParamOperation
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
import com.utsmyanmar.baselib.repo.Repository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import sunmi.sunmiui.utils.LogUtil
import javax.inject.Inject
data class TmsSetupUiState(
val isLoading: Boolean = false,
val statusText: String = "Initializing...",
val isError: Boolean = false,
val errorMessage: String = ""
)
@HiltViewModel
class TmsSetupViewModel @Inject constructor(
private val repository: Repository,
private val emvParamOperation: EmvParamOperation
) : ViewModel() {
private val _uiState = MutableStateFlow(TmsSetupUiState())
val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow()
private val _navigateToDashboard = MutableSharedFlow<Unit>()
val navigateToDashboard: SharedFlow<Unit> = _navigateToDashboard.asSharedFlow()
private val disposables = CompositeDisposable()
private val tmsSetups = TMSSetupsImpl()
init {
viewModelScope.launch {
waitForHardware()
downloadConfigs()
}
}
fun downloadConfigs() {
_uiState.update {
it.copy(isLoading = true, isError = false, statusText = "Connecting to TMS server...")
}
val disposable = repository.getParams(buildRequest())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ response ->
_uiState.update { it.copy(statusText = "Applying configuration...") }
val appResponse = SiriusResponse(
serial = response.serial.orEmpty(),
ecrKey = response.ecrKey.orEmpty(),
address = response.address.orEmpty(),
merchant = response.merchant,
hosts = response.hosts,
properties = response.properties
)
tmsSetups.initParams(appResponse, TMSUpdate.UPDATE, emvParamOperation)
onConfigApplied()
},
{ error ->
_uiState.update {
it.copy(
isLoading = false,
isError = true,
statusText = "Download failed",
errorMessage = error.message ?: "Network error"
)
}
}
)
disposables.add(disposable)
}
private suspend fun waitForHardware() {
_uiState.update { it.copy(isLoading = true, statusText = "Starting hardware...") }
var elapsed = 0
while (BaseApplication.basicOptV2 == null && elapsed < 10_000) {
delay(500)
elapsed += 500
}
}
private fun onConfigApplied() {
val validity = TMSUtil.getInstance().checkParams()
if (validity.status == ValidityStatus.SUCCESS) {
_uiState.update { it.copy(isLoading = false, statusText = "Ready.") }
viewModelScope.launch { _navigateToDashboard.emit(Unit) }
} else {
_uiState.update {
it.copy(
isLoading = false,
isError = true,
statusText = "Configuration incomplete",
errorMessage = validity.message ?: "Invalid TMS configuration"
)
}
}
}
@SuppressLint("MissingPermission")
private fun buildRequest(): SiriusRequest {
return try {
val tranTime: Long = System.currentTimeMillis()
TMSUtil.getInstance().generateRequestParams("...", tranTime)
} catch (e: Exception) {
LogUtil.e("TmsSetupViewModel", e.message)
SiriusRequest()
}
}
override fun onCleared() {
super.onCleared()
disposables.clear()
}
}

View File

@ -12,7 +12,6 @@ import com.sunmi.pay.hardware.aidl.AidlConstants
import com.utsmyanmar.baselib.BaseApplication import com.utsmyanmar.baselib.BaseApplication
import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest import com.utsmyanmar.baselib.network.model.sirius.SiriusRequest
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import sunmi.sunmiui.BuildConfig
import sunmi.sunmiui.utils.LogUtil import sunmi.sunmiui.utils.LogUtil
class TMSUtil private constructor() { class TMSUtil private constructor() {
@ -57,15 +56,19 @@ class TMSUtil private constructor() {
}.getOrDefault("") }.getOrDefault("")
@RequiresPermission(Manifest.permission.READ_PHONE_STATE) @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
fun generateRequestParams(lastTransName: String, lastTransTime: String): SiriusRequest = fun generateRequestParams(lastTransName: String, lastTransTime: Long): SiriusRequest =
SiriusRequest().apply { SiriusRequest().apply {
serial = getSerialNumber() serial = getSerialNumber()
appPackage = BuildConfig.APPLICATION_ID appPackage = "com.mob.utsmyanmar"
androidVersion = Build.VERSION.RELEASE androidVersion = Build.VERSION.RELEASE
firmwareVersion = getSystemParams(AidlConstants.SysParam.FIRMWARE_VERSION) firmwareVersion = getSystemParams(AidlConstants.SysParam.FIRMWARE_VERSION)
applicationVersion = "1.0"
currentNetwork = getNetworkType() currentNetwork = getNetworkType()
lastTransaction = lastTransName lastTransaction = lastTransName
lastTranTime = lastTransTime lastTranTime = lastTransTime
latitude = 0.000000
longitude = 0.000000
value = "YourValueHere"
} }
//---logging-- //---logging--

View File

@ -1,7 +1,5 @@
package com.utsmyanmar.baselib.di; package com.utsmyanmar.baselib.di;
import android.text.TextUtils;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.utsmyanmar.baselib.BuildConfig; import com.utsmyanmar.baselib.BuildConfig;
@ -11,8 +9,6 @@ import com.utsmyanmar.baselib.network.WaveTokenApiService;
import com.utsmyanmar.baselib.network.interceptor.HostSelectionInterceptor; import com.utsmyanmar.baselib.network.interceptor.HostSelectionInterceptor;
import com.utsmyanmar.baselib.network.interceptor.QRAuthInterceptor; import com.utsmyanmar.baselib.network.interceptor.QRAuthInterceptor;
import com.utsmyanmar.baselib.network.interceptor.SiriusInterceptor; import com.utsmyanmar.baselib.network.interceptor.SiriusInterceptor;
import com.utsmyanmar.baselib.network.interceptor.WaveAuthInterceptor;
import com.utsmyanmar.paylibs.Constant;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation; import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -38,7 +34,6 @@ import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit; import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.gson.GsonConverterFactory;
import sunmi.sunmiui.utils.LogUtil;
@Module @Module
@InstallIn(SingletonComponent.class) @InstallIn(SingletonComponent.class)
@ -355,7 +350,7 @@ public class NetworkModule {
tmsAddress = getTMSUrlFromNative(); tmsAddress = getTMSUrlFromNative();
} }
String baseUrl = tmsAddress.trim() + "/api/v1/"; String baseUrl = tmsAddress.trim() + "/";
final Gson gson = final Gson gson =

View File

@ -3,12 +3,10 @@ package com.utsmyanmar.baselib.network.interceptor;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.utsmyanmar.baselib.util.TerminalUtil; import com.utsmyanmar.baselib.util.TerminalUtil;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import okhttp3.HttpUrl;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@ -18,13 +16,6 @@ public class SiriusInterceptor implements Interceptor {
private static final String TAG = SiriusInterceptor.class.getSimpleName(); private static final String TAG = SiriusInterceptor.class.getSimpleName();
public static native String getHiddenFromNative();
static {
System.loadLibrary("native-lib");
}
@NonNull @NonNull
@Override @Override
public Response intercept(@NonNull Chain chain) throws IOException { public Response intercept(@NonNull Chain chain) throws IOException {
@ -35,9 +26,9 @@ public class SiriusInterceptor implements Interceptor {
String hashed = ""; String hashed = "";
String nonce = TerminalUtil.getInstance().generateRandomNumbers(); String nonce = TerminalUtil.getInstance().generateRandomNumbers();
try { try {
hashed = TerminalUtil.getInstance().generateHashedString(nonce); hashed = TerminalUtil.getInstance().generateHashedString(nonce).toLowerCase();
// LogUtil.d(TAG,"hashed :"+ hashed); LogUtil.d(TAG,"hashed :"+ hashed);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -48,8 +39,13 @@ public class SiriusInterceptor implements Interceptor {
.addHeader("request-id", hashed) .addHeader("request-id", hashed)
.addHeader("request-code",nonce) .addHeader("request-code",nonce)
.build(); .build();
LogUtil.d(TAG, "URL: " + newRequest.url());
LogUtil.d(TAG, "Method: " + newRequest.method());
LogUtil.d(TAG, "Headers: " + newRequest.headers());
LogUtil.d(TAG, "request-id: " + hashed);
LogUtil.d(TAG, "request-code: " + nonce);
return chain.proceed(newRequest); return chain.proceed(newRequest);
} }
} }

View File

@ -2,7 +2,7 @@ package com.utsmyanmar.baselib.network.model.sirius;
public class SiriusMerchant { public class SiriusMerchant {
private int id; private String id;
private String name; private String name;
@ -16,7 +16,7 @@ public class SiriusMerchant {
private String mobile; private String mobile;
public SiriusMerchant(int id, String name, String description, String address,String address2,String phone,String mobile) { public SiriusMerchant(String id, String name, String description, String address,String address2,String phone,String mobile) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.description = description; this.description = description;
@ -50,7 +50,7 @@ public class SiriusMerchant {
return mobile; return mobile;
} }
public void setId(int id) { public void setId(String id) {
this.id = id; this.id = id;
} }
@ -66,7 +66,7 @@ public class SiriusMerchant {
this.address = address; this.address = address;
} }
public int getId() { public String getId() {
return id; return id;
} }

View File

@ -2,12 +2,12 @@ package com.utsmyanmar.baselib.network.model.sirius;
public class SiriusProperty { public class SiriusProperty {
private int id; private String id;
private int terId; private String terId;
private int appId; private String appId;
private int configId; private String configId;
private String name; private String name;
@ -17,7 +17,7 @@ public class SiriusProperty {
private String property; private String property;
public SiriusProperty(int id, int terId, int appId, int configId, String name, String description, String type, String property) { public SiriusProperty(String id, String terId, String appId, String configId, String name, String description, String type, String property) {
this.id = id; this.id = id;
this.terId = terId; this.terId = terId;
this.appId = appId; this.appId = appId;
@ -28,19 +28,19 @@ public class SiriusProperty {
this.property = property; this.property = property;
} }
public void setId(int id) { public void setId(String id) {
this.id = id; this.id = id;
} }
public void setTerId(int terId) { public void setTerId(String terId) {
this.terId = terId; this.terId = terId;
} }
public void setAppId(int appId) { public void setAppId(String appId) {
this.appId = appId; this.appId = appId;
} }
public void setConfigId(int configId) { public void setConfigId(String configId) {
this.configId = configId; this.configId = configId;
} }
@ -60,19 +60,19 @@ public class SiriusProperty {
this.property = property; this.property = property;
} }
public int getId() { public String getId() {
return id; return id;
} }
public int getTerId() { public String getTerId() {
return terId; return terId;
} }
public int getAppId() { public String getAppId() {
return appId; return appId;
} }
public int getConfigId() { public String getConfigId() {
return configId; return configId;
} }

View File

@ -15,10 +15,30 @@ public class SiriusRequest {
private String lastTransaction; private String lastTransaction;
private String lastTranTime; private Long lastTranTime;
public SiriusRequest() {} private String value;
public SiriusRequest(String serial, String appPackage, String androidVersion, String firmwareVersion, String applicationVersion, String currentNetwork, String lastTransaction, String lastTranTime) {
private double latitude;
private double longitude;
public SiriusRequest() {
}
public SiriusRequest(
String serial,
String appPackage,
String androidVersion,
String firmwareVersion,
String applicationVersion,
String currentNetwork,
String lastTransaction,
Long lastTranTime,
String value,
double latitude,
double longitude
) {
this.serial = serial; this.serial = serial;
this.appPackage = appPackage; this.appPackage = appPackage;
this.androidVersion = androidVersion; this.androidVersion = androidVersion;
@ -27,6 +47,9 @@ public class SiriusRequest {
this.currentNetwork = currentNetwork; this.currentNetwork = currentNetwork;
this.lastTransaction = lastTransaction; this.lastTransaction = lastTransaction;
this.lastTranTime = lastTranTime; this.lastTranTime = lastTranTime;
this.value = value;
this.latitude = latitude;
this.longitude = longitude;
} }
public void setSerial(String serial) { public void setSerial(String serial) {
@ -57,7 +80,7 @@ public class SiriusRequest {
this.lastTransaction = lastTransaction; this.lastTransaction = lastTransaction;
} }
public void setLastTranTime(String lastTranTime) { public void setLastTranTime(Long lastTranTime) {
this.lastTranTime = lastTranTime; this.lastTranTime = lastTranTime;
} }
@ -89,7 +112,31 @@ public class SiriusRequest {
return lastTransaction; return lastTransaction;
} }
public String getLastTranTime() { public Long getLastTranTime() {
return lastTranTime; return lastTranTime;
} }
public String getValue(){
return this.value;
}
public void setValue(String value){
this.value = value;
}
public double getLatitude(){
return this.latitude;
}
public void setLatitude(double latitude){
this.latitude = latitude;
}
public double getLongitude(){
return this.longitude;
}
public void setLongitude(double longitude){
this.longitude = longitude;
}
} }

View File

@ -1,9 +1,6 @@
package com.utsmyanmar.baselib.util; package com.utsmyanmar.baselib.util;
import android.os.RemoteException;
import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2; import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2;
import com.utsmyanmar.baselib.BaseApplication;
import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil; import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -12,53 +9,28 @@ import java.security.NoSuchAlgorithmException;
import java.util.Random; import java.util.Random;
public class TerminalUtilsImpl implements TerminalUtils{ public class TerminalUtilsImpl implements TerminalUtils{
long number = 2485718;
long sub = 1251151;
public static native String getHiddenFromNative();
public static native String getEncryptedFromNative();
static {
System.loadLibrary("native-lib");
}
@Override @Override
public String generateHashString(String random,BasicOptV2 basicOptV2) throws NoSuchAlgorithmException { public String generateHashString(String random,BasicOptV2 basicOptV2) throws NoSuchAlgorithmException {
String sn = getSerialNumber(basicOptV2); String sn = "P30224BSJ0276";
String snPN = BaseApplication.getInstance().getPackageName(); String snPN = "com.mob.utsmyanmar";
String nonce = random; String text = sn + snPN + random;
String text = sn + snPN + nonce;
// LogUtil.d(TAG,"Plain Text: "+text);
MessageDigest digest = MessageDigest.getInstance("SHA-256"); MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8)); byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
return ByteUtil.bytes2HexStr(hash); return ByteUtil.bytes2HexStr(hash);
} }
@Override @Override
public String generateRandom() { public String generateRandom() {
Random rnd = new Random(); Random rnd = new Random();
StringBuilder sb = new StringBuilder((1000000 + rnd.nextInt(9000000))); StringBuilder sb = new StringBuilder();
sb.append(1000000 + rnd.nextInt(9000000));
return sb.toString(); return sb.toString();
} }
@Override @Override
public String getSerialNumber(BasicOptV2 basicOptV2){ public String getSerialNumber(BasicOptV2 basicOptV2){
String data = ""; return "abcd";
try {
data = basicOptV2.getSysParam(getEncryptedFromNative());
} catch (RemoteException e) {
e.printStackTrace();
}
return data;
} }

View File

@ -38,7 +38,7 @@ public class SystemParamsSettings implements Serializable {
private boolean checkExpSwitch = false; private boolean checkExpSwitch = false;
private String tmsAddress = "https://sirius-nest.utsmyanmar.com"; private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1";
// private String tmsAddress = "http://128.199.170.203"; // private String tmsAddress = "http://128.199.170.203";
private String terminalCapability = "E0E8C8"; private String terminalCapability = "E0E8C8";