diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d1938f4..fe9f537 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -102,11 +102,6 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/java/com/utsmm/kbz/MainActivity.java b/app/src/main/java/com/utsmm/kbz/MainActivity.java
index e94de27..55ba8df 100644
--- a/app/src/main/java/com/utsmm/kbz/MainActivity.java
+++ b/app/src/main/java/com/utsmm/kbz/MainActivity.java
@@ -40,10 +40,6 @@ import com.nexgo.oaf.apiv3.APIProxy;
import com.nexgo.oaf.apiv3.DeviceEngine;
import com.nexgo.oaf.apiv3.device.pinpad.PinPad;
import com.nexgo.oaf.apiv3.device.pinpad.WorkKeyTypeEnum;
-import com.nexgo.oaf.apiv3.device.serialport.SerialPortDriver;
-import com.nexgo.oaf.apiv3.device.usbserial.OnUsbSerialReadListener;
-import com.nexgo.oaf.apiv3.device.usbserial.UsbSerial;
-import com.nexgo.oaf.apiv3.device.usbserial.UsbSerialCfgEntity;
import com.utsmyanmar.baselib.BaseApplication;
import com.utsmyanmar.baselib.repo.Repository;
import com.utsmyanmar.baselib.ui.AnimationDialog;
@@ -561,8 +557,8 @@ public class MainActivity extends AppCompatActivity implements
// Clean up RxJava disposables
compositeDisposable.clear();
// Reset screen mode
- // Disconnect ECR
- ECRHelper.INSTANCE.disconnect();
+ // Disconnect serial port
+ ECRHelper.INSTANCE.releaseSerialPort();
BaseApplication.getInstance().deviceEngine.getPlatform().showNavigationBar();
}
diff --git a/app/src/main/java/com/utsmm/kbz/MainFragment.java b/app/src/main/java/com/utsmm/kbz/MainFragment.java
index bd15c0d..cb6e22f 100644
--- a/app/src/main/java/com/utsmm/kbz/MainFragment.java
+++ b/app/src/main/java/com/utsmm/kbz/MainFragment.java
@@ -1,16 +1,7 @@
package com.utsmm.kbz;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-
-import android.os.Build;
import android.os.Bundle;
-import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -38,16 +29,9 @@ import com.kizzy.cmhl.models.SaleRequest;
import com.kizzy.cmhl.models.SettlementRequest;
import com.kizzy.cmhl.models.VoidQrPaymentRequest;
import com.kizzy.cmhl.models.VoidRequest;
-import com.nexgo.oaf.apiv3.APIProxy;
-import com.nexgo.oaf.apiv3.DeviceEngine;
-import com.nexgo.oaf.apiv3.SdkResult;
-import com.nexgo.oaf.apiv3.device.serialport.SerialCfgEntity;
-import com.nexgo.oaf.apiv3.device.serialport.SerialPortDriver;
import com.nexgo.oaf.apiv3.emv.AidEntity;
import com.nexgo.oaf.apiv3.emv.CapkEntity;
-//import com.pos.connection.bridge.binder.ECRConstant;
-import com.utsmm.kbz.service.SerialPortService;
import com.utsmm.kbz.util.MockData;
//import com.utsmm.kbz.util.ecr.ModernECRFactory;
//import com.utsmm.kbz.util.ecr.ModernECRManager;
@@ -59,6 +43,7 @@ import com.utsmyanmar.baselib.util.DataBindingConfig;
import com.utsmyanmar.baselib.util.DialogCallback;
import com.utsmyanmar.baselib.util.TerminalUtil;
import com.utsmyanmar.checkxread.sdk.NexGoSDK;
+import com.utsmyanmar.ecr.ECRHelper;
import com.utsmyanmar.ecr.ECRProcess;
import com.utsmyanmar.ecr.data.ResultOf;
import com.utsmyanmar.ecr.data.model.Transactions;
@@ -68,7 +53,6 @@ import com.utsmyanmar.paylibs.model.TradeData;
import com.utsmyanmar.paylibs.print.PrintHelper;
import com.utsmyanmar.paylibs.print.printx.PrintXReceipt;
import com.utsmyanmar.paylibs.utils.POSUtil;
-import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil;
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation;
import com.utsmyanmar.paylibs.utils.enums.CurrencyType;
import com.utsmyanmar.paylibs.utils.enums.TransMenu;
@@ -110,7 +94,6 @@ import com.utsmyanmar.paylibs.utils.params.Params;
//import com.utsmm.kbz.util.ecr.ModernECRFactory;
//import com.utsmm.kbz.util.ecr.ModernECRManager;
import androidx.lifecycle.ViewModelProvider;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@AndroidEntryPoint
public class MainFragment extends DataBindingFragment {
@@ -150,38 +133,43 @@ public class MainFragment extends DataBindingFragment {
mainViewModel = getFragmentScopeViewModel(MainViewModel.class);
tmsProcessViewModel = getFragmentScopeViewModel(TMSProcessViewModel.class);
- // Initialize ModernECRManager
-// ModernECRFactory factory = new ModernECRFactory(requireContext(), sharedViewModel);
-// modernECRManager = new ViewModelProvider(this, factory).get(ModernECRManager.class);
+
}
@Override
public void onStart() {
super.onStart();
- // Register to listen for serial data
- // Setup ECR Service
- if(SystemParamsOperation.getInstance().getECRStatus()){
- LocalBroadcastManager.getInstance(requireContext())
- .registerReceiver(serialReceiver,
- new IntentFilter(SerialPortService.ACTION_DATA_RECEIVED));
-
- // Start the service
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- requireContext().startForegroundService(
- new Intent(requireContext(), SerialPortService.class));
- }
-
- LogUtil.d(TAG,"ECR service initialized!");
+ if (SystemParamsOperation.getInstance().getECRStatus()) {
+ ECRHelper.INSTANCE.setOnECRReceive(bytes -> {
+ if (getActivity() == null) {
+ return null;
+ }
+ requireActivity().runOnUiThread(() -> handleSerialData(bytes));
+ return null;
+ });
+ ECRHelper.INSTANCE.initSerialPort(requireContext().getApplicationContext());
+ LogUtil.d(TAG, "ECR SerialPortDriver listening");
}
-
}
@Override
public void onStop() {
super.onStop();
- LocalBroadcastManager.getInstance(requireContext())
- .unregisterReceiver(serialReceiver);
+ ECRHelper.INSTANCE.setOnECRReceive(bytes -> null);
+ }
+
+ private void handleSerialData(byte[] data) {
+ if (data == null || data.length == 0) {
+ return;
+ }
+ LogUtil.d(TAG, "Serial received len=" + data.length);
+
+ if (SystemParamsOperation.getInstance().isCMHLEnabled()) {
+ ECRActionCHML(data);
+ } else {
+ ECRAction(new String(data, StandardCharsets.UTF_8));
+ }
}
@Override
@@ -244,33 +232,6 @@ public class MainFragment extends DataBindingFragment {
}
}
- private BroadcastReceiver serialReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String data = intent.getStringExtra(SerialPortService.EXTRA_DATA);
- LogUtil.d("USB", "UI received: " + data);
- // update your UI here
-
-// LogUtil.d(TAG, "Received : " + msg);
-
-
- if (sharedViewModel.isEcrFinished.getValue() != null && sharedViewModel.isEcrFinished.getValue()) {
- if(SystemParamsOperation.getInstance().isCMHLEnabled()) {
- ECRActionCHML(ByteUtil.hexStr2Bytes(data));
- } else {
- ECRAction(data);
- }
-
- } else {
- if(SystemParamsOperation.getInstance().isCMHLEnabled()) {
- ECRActionCHML(ByteUtil.hexStr2Bytes(data));
- } else {
- ECRAction(data);
- }
- }
- }
- };
-
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -314,9 +275,6 @@ public class MainFragment extends DataBindingFragment {
// enableHomeButton();
}
- private SerialPortDriver port;
- private boolean isRunning = false;
-
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -412,19 +370,6 @@ public class MainFragment extends DataBindingFragment {
}
- private boolean checkECRServiceAppExistOrNot() {
- PackageInfo packageInfo;
- try {
- packageInfo = requireActivity().getPackageManager().getPackageInfo("com.kizzy.ecrservice", 0);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- return false;
- }
-
- return packageInfo != null;
- }
-
-
@Override
protected DataBindingConfig getDataBindingConfig() {
diff --git a/app/src/main/java/com/utsmm/kbz/service/SerialPortService.java b/app/src/main/java/com/utsmm/kbz/service/SerialPortService.java
deleted file mode 100644
index 6308756..0000000
--- a/app/src/main/java/com/utsmm/kbz/service/SerialPortService.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.utsmm.kbz.service;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Build;
-import android.os.IBinder;
-
-import androidx.annotation.Nullable;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-
-import com.nexgo.oaf.apiv3.APIProxy;
-import com.nexgo.oaf.apiv3.DeviceEngine;
-import com.nexgo.oaf.apiv3.SdkResult;
-import com.nexgo.oaf.apiv3.device.serialport.SerialCfgEntity;
-import com.nexgo.oaf.apiv3.device.serialport.SerialPortDriver;
-import com.utsmm.kbz.R;
-import com.utsmyanmar.paylibs.utils.LogUtil;
-
-import java.nio.charset.StandardCharsets;
-
-public class SerialPortService extends Service {
-
- private static final String TAG = "USB";
- private SerialPortDriver port;
- private volatile boolean isRunning = false;
- private Thread receiveThread;
-
- // Broadcast action so any Activity/Fragment can observe
- public static final String ACTION_DATA_RECEIVED = "com.yourapp.SERIAL_DATA";
- public static final String EXTRA_DATA = "data";
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- startForegroundNotification(); // required for foreground service
- connectAndListen();
- return START_STICKY; // restarts if killed
- }
-
- private void connectAndListen() {
- DeviceEngine deviceEngine = APIProxy.getDeviceEngine(this);
- port = deviceEngine.getSerialPortDriver(0);
-
- SerialCfgEntity entity = new SerialCfgEntity();
- entity.setBaudRate(115200);
- entity.setDataBits(8);
- entity.setParity('n');
- entity.setStopBits(1);
-
- int result = port.connect(entity);
-
- if (result == SdkResult.Success) {
- LogUtil.d(TAG, "Connected");
- isRunning = true;
- startReceiveThread();
- } else {
- LogUtil.e(TAG, "Connection failed");
- }
- }
-
- private void startReceiveThread() {
- receiveThread = new Thread(() -> {
- byte[] buffer = new byte[2048];
- while (isRunning) {
- int len = port.recv(buffer, buffer.length, 3000);
- if (len > 0) {
- String msg = new String(buffer, 0, len, StandardCharsets.UTF_8);
- LogUtil.d(TAG, "Received: " + msg);
- broadcastData(msg); // notify UI
- }
- }
- });
- receiveThread.start();
- }
-
- private void broadcastData(String msg) {
- Intent intent = new Intent(ACTION_DATA_RECEIVED);
- intent.putExtra(EXTRA_DATA, msg);
- LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
- }
-
- @Override
- public void onDestroy() {
- isRunning = false;
- if (port != null) port.disconnect();
- super.onDestroy();
- }
-
- private void startForegroundNotification() {
- NotificationChannel channel = null;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- channel = new NotificationChannel(
- "serial_channel", "Serial Port", NotificationManager.IMPORTANCE_LOW);
-
- getSystemService(NotificationManager.class).createNotificationChannel(channel);
-
- Notification notification = new Notification.Builder(this, "serial_channel")
- .setContentTitle("Serial Port Active")
- .setSmallIcon(R.drawable.ic_launcher_foreground)
- .build();
-
- startForeground(1, notification);
- }
-
- }
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) { return null; }
-}
diff --git a/app/src/main/java/com/utsmm/kbz/util/tms/TMSSetupsImpl.java b/app/src/main/java/com/utsmm/kbz/util/tms/TMSSetupsImpl.java
index a789467..05cf591 100644
--- a/app/src/main/java/com/utsmm/kbz/util/tms/TMSSetupsImpl.java
+++ b/app/src/main/java/com/utsmm/kbz/util/tms/TMSSetupsImpl.java
@@ -606,9 +606,9 @@ public class TMSSetupsImpl implements TMSSetups{
} else if (TextUtils.equals(name, "upi_tc_enabled")) {
SystemParamsOperation.getInstance().setUpiTCEnabled(parseBoolean(data));
} else if (TextUtils.equals(name,"master_terminal_capability")) {
- if(!data.isEmpty()) {
- emvParamOperation.updatePayPassTerminalCapability(data);
- }
+// if(!data.isEmpty()) {
+// emvParamOperation.updatePayPassTerminalCapability(data);
+// }
} else if (TextUtils.equals(name,"speedup_contactless_enable")) {
SystemParamsOperation.getInstance().setSpeedUpContactless(parseBoolean(data));
} else if (TextUtils.equals(name,"manual_entry_pin_enable")) {
diff --git a/cmhl/.gitignore b/cmhl/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/cmhl/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/cmhl/build.gradle b/cmhl/build.gradle
new file mode 100644
index 0000000..f186836
--- /dev/null
+++ b/cmhl/build.gradle
@@ -0,0 +1,35 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ namespace 'com.kizzy.cmhl'
+ compileSdk 34
+
+ defaultConfig {
+ minSdk 23 // Lowered to 23 for broader device compatibility
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.appcompat:appcompat:1.7.1'
+ implementation 'com.google.android.material:material:1.13.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
+}
\ No newline at end of file
diff --git a/cmhl/consumer-rules.pro b/cmhl/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/cmhl/proguard-rules.pro b/cmhl/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/cmhl/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/cmhl/src/androidTest/java/com/kizzy/cmhl/ExampleInstrumentedTest.java b/cmhl/src/androidTest/java/com/kizzy/cmhl/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..8300228
--- /dev/null
+++ b/cmhl/src/androidTest/java/com/kizzy/cmhl/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.kizzy.cmhl;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.kizzy.cmhl.test", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/cmhl/src/main/AndroidManifest.xml b/cmhl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/cmhl/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/EcrManager.java b/cmhl/src/main/java/com/kizzy/cmhl/EcrManager.java
new file mode 100644
index 0000000..af69197
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/EcrManager.java
@@ -0,0 +1,148 @@
+package com.kizzy.cmhl;
+
+
+
+import com.kizzy.cmhl.models.EcrCommand;
+import com.kizzy.cmhl.models.EcrPacket;
+import com.kizzy.cmhl.utils.ByteUtils;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+public class EcrManager {
+
+ /**
+ * Builds the full packet bytes from an EcrCommand (Request or Response).
+ * Structure: STX LLL(2) VERSION COMMAND DATA... ETX LRC
+ * LLL is BCD encoded length of (VERSION + COMMAND + DATA).
+ * LRC = XOR of (LLL + VERSION + DATA + ETX).
+ */
+ public static byte[] buildPacket(EcrCommand command) {
+ byte[] tlvData = command.toTlvBytes();
+
+ int bodyLength = 1 + 1 + tlvData.length;
+
+ byte[] lll = ByteUtils.intToBcd(bodyLength);
+
+ ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + bodyLength + 1 + 1);
+
+ buffer.put(EcrProtocol.STX);
+ buffer.put(lll);
+ buffer.put(EcrProtocol.VERSION);
+ buffer.put(command.getCommandCode());
+ buffer.put(tlvData);
+ buffer.put(EcrProtocol.ETX);
+
+ byte[] packet = buffer.array();
+
+
+
+ byte lrc = 0;
+ int etxIndex = packet.length - 2;
+
+ for (int i = 1; i < etxIndex; i++) { // added <= instead of <
+ lrc ^= packet[i];
+ }
+
+ packet[packet.length - 1] = lrc;
+
+ return packet;
+ }
+
+// public static byte[] buildPacket(EcrCommand command) {
+// byte[] tlvData = command.toTlvBytes();
+// int bodyLength = 1 + 1 + tlvData.length; // Version(1) + Command(1) + Data
+//
+// // Convert bodyLength to BCD
+// byte[] lll = ByteUtils.intToBcd(bodyLength);
+//
+// ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + bodyLength + 1 + 1);
+//
+// buffer.put(EcrProtocol.STX);
+// buffer.put(lll);
+// buffer.put(EcrProtocol.VERSION);
+// buffer.put(command.getCommandCode());
+// buffer.put(tlvData);
+// buffer.put(EcrProtocol.ETX);
+//
+// byte[] packet = buffer.array();
+//
+// // Calculate LRC
+// // From LLL (index 1) to ETX (index packet.length - 2), inclusive
+// byte lrc = 0;
+// for (int i = 1; i < packet.length - 1; i++) {
+// lrc ^= packet[i];
+// }
+// packet[packet.length - 1] = lrc;
+//
+// return packet;
+// }
+
+ /**
+ * Parses raw bytes into an EcrPacket.
+ * Validates STX and LRC.
+ */
+ public static EcrPacket parsePacket(byte[] packet) throws Exception {
+ if (packet == null || packet.length < 5) {
+ throw new Exception("Invalid packet length");
+ }
+
+ if (packet[0] != EcrProtocol.STX) {
+ throw new Exception("Invalid STX");
+ }
+
+ byte receivedLrc = packet[packet.length - 1];
+ byte calculatedLrc = 0;
+ for (int i = 1; i < packet.length - 1; i++) {
+ if (i == 4) continue;
+ calculatedLrc ^= packet[i];
+ }
+
+ if (receivedLrc != calculatedLrc) {
+ System.out.println("LRC Mismatch: Recv=" + receivedLrc + " Calc=" + calculatedLrc);
+ }
+
+
+ int lll = bcdToInt(packet[1], packet[2]);
+
+ byte commandCode = packet[4];
+
+ int dataEnd = packet.length - 2;
+ byte[] data = new byte[dataEnd - 5];
+ System.arraycopy(packet, 5, data, 0, data.length);
+
+ Map tlvMap = parseTlv(data);
+
+ return new EcrPacket(commandCode, tlvMap);
+ }
+
+ private static int bcdToInt(byte b1, byte b2) {
+ int thousands = (b1 >> 4) & 0x0F;
+ int hundreds = b1 & 0x0F;
+ int tens = (b2 >> 4) & 0x0F;
+ int ones = b2 & 0x0F;
+ return thousands * 1000 + hundreds * 100 + tens * 10 + ones;
+ }
+
+ private static Map parseTlv(byte[] data) {
+ Map map = new HashMap<>();
+ int i = 0;
+ while (i < data.length) {
+ if (i + 1 >= data.length) break;
+ byte tag = data[i++];
+ int len = data[i++] & 0xFF;
+
+ if (i + len > data.length) {
+ break;
+ }
+
+ byte[] value = new byte[len];
+ System.arraycopy(data, i, value, 0, len);
+ map.put(tag, value);
+
+ i += len;
+ }
+ return map;
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/EcrProtocol.java b/cmhl/src/main/java/com/kizzy/cmhl/EcrProtocol.java
new file mode 100644
index 0000000..b848508
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/EcrProtocol.java
@@ -0,0 +1,40 @@
+package com.kizzy.cmhl;
+
+public class EcrProtocol {
+ public static final byte STX = 0x02;
+ public static final byte ETX = 0x03;
+ public static final byte VERSION = 0x01;
+ public static final byte ACK = 0x06;
+
+ // Commands
+ public static final byte CMD_PING = 0x01;
+ public static final byte CMD_SALE = 0x03;
+ public static final byte CMD_VOID = 0x04;
+ public static final byte CMD_SETTLEMENT = 0x05;
+ public static final byte CMD_PRE_AUTH = 0x06;
+ public static final byte CMD_PRE_AUTH_COMPLETION = 0x07;
+ public static final byte CMD_PRE_AUTH_CANCELLATION = 0x08;
+ public static final byte CMD_QR_PAYMENT = 0x0B;
+ public static final byte CMD_VOID_QR_PAYMENT = 0x0C;
+
+ // Tags
+ public static final byte TAG_PAN = 0x02;
+ public static final byte TAG_AMOUNT = 0x04;
+ public static final byte TAG_STAN = 0x11;
+ public static final byte TAG_TIME = 0x12;
+ public static final byte TAG_DATE = 0x13;
+ public static final byte TAG_EXP_DATE = 0x14;
+ public static final byte TAG_CARD_TYPE = 0x15;
+ public static final byte TAG_PAYMENT_TYPE = 0x16;
+ public static final byte TAG_POS_ENTRY = 0x22;
+ public static final byte TAG_RRN = 0x37;
+ public static final byte TAG_APPROVAL_CODE = 0x38;
+ public static final byte TAG_RESPONSE_CODE = 0x39;
+ public static final byte TAG_TID = 0x41;
+ public static final byte TAG_MID = 0x42;
+ public static final byte TAG_CURRENCY_CODE = 0x49;
+ public static final byte TAG_INVOICE_NUMBER = 0x62;
+
+ public static final String CARD_TYPE_ALL = "00"; //
+
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/EcrRequestDispatcher.java b/cmhl/src/main/java/com/kizzy/cmhl/EcrRequestDispatcher.java
new file mode 100644
index 0000000..19085b1
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/EcrRequestDispatcher.java
@@ -0,0 +1,49 @@
+package com.kizzy.cmhl;
+
+import com.kizzy.cmhl.models.EcrPacket;
+import com.kizzy.cmhl.models.*;
+
+public class EcrRequestDispatcher {
+
+ public static void dispatch(EcrPacket packet, EcrRequestListener listener) {
+ if (packet == null || listener == null) return;
+
+ try {
+ switch (packet.getCommandCode()) {
+ case EcrProtocol.CMD_PING:
+ listener.onPingRequest(new PingRequest(packet));
+ break;
+ case EcrProtocol.CMD_SALE:
+ listener.onSaleRequest(new SaleRequest(packet));
+ break;
+ case EcrProtocol.CMD_VOID:
+ listener.onVoidRequest(new VoidRequest(packet));
+ break;
+ case EcrProtocol.CMD_SETTLEMENT:
+ listener.onSettlementRequest(new SettlementRequest(packet));
+ break;
+ case EcrProtocol.CMD_PRE_AUTH:
+ listener.onPreAuthRequest(new PreAuthRequest(packet));
+ break;
+ case EcrProtocol.CMD_PRE_AUTH_COMPLETION:
+ listener.onPreAuthCompletionRequest(new PreAuthCompletionRequest(packet));
+ break;
+ case EcrProtocol.CMD_PRE_AUTH_CANCELLATION:
+ listener.onPreAuthCancellationRequest(new PreAuthCancellationRequest(packet));
+ break;
+ case EcrProtocol.CMD_QR_PAYMENT:
+ listener.onQrPaymentRequest(new QrPaymentRequest(packet));
+ break;
+ case EcrProtocol.CMD_VOID_QR_PAYMENT:
+ listener.onVoidQrPaymentRequest(new VoidQrPaymentRequest(packet));
+ break;
+ default:
+ listener.onUnknownRequest(packet);
+ break;
+ }
+ } catch (Exception e) {
+
+ listener.onUnknownRequest(packet);
+ }
+ }
+}
\ No newline at end of file
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/EcrRequestListener.java b/cmhl/src/main/java/com/kizzy/cmhl/EcrRequestListener.java
new file mode 100644
index 0000000..043715c
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/EcrRequestListener.java
@@ -0,0 +1,17 @@
+package com.kizzy.cmhl;
+
+
+import com.kizzy.cmhl.models.*;
+
+public interface EcrRequestListener {
+ void onPingRequest(PingRequest request);
+ void onSaleRequest(SaleRequest request);
+ void onVoidRequest(VoidRequest request);
+ void onSettlementRequest(SettlementRequest request);
+ void onPreAuthRequest(PreAuthRequest request);
+ void onPreAuthCompletionRequest(PreAuthCompletionRequest request);
+ void onPreAuthCancellationRequest(PreAuthCancellationRequest request);
+ void onQrPaymentRequest(QrPaymentRequest request);
+ void onVoidQrPaymentRequest(VoidQrPaymentRequest request);
+ void onUnknownRequest(EcrPacket packet);
+}
\ No newline at end of file
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/BaseEcrResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/BaseEcrResponse.java
new file mode 100644
index 0000000..878e28c
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/BaseEcrResponse.java
@@ -0,0 +1,63 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+import com.kizzy.cmhl.utils.ByteUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public abstract class BaseEcrResponse implements EcrCommand {
+ protected String responseCode;
+ protected String tid;
+ protected String mid;
+ protected String time;
+ protected String date;
+
+ // Getters
+ public String getResponseCode() { return responseCode; }
+ public String getTid() { return tid; }
+ public String getMid() { return mid; }
+ public String getTime() { return time; }
+ public String getDate() { return date; }
+
+ public boolean isApproved() {
+ return "00".equals(responseCode);
+ }
+
+ protected void writeCommonTags(ByteArrayOutputStream baos) throws IOException {
+ writeTag(baos, EcrProtocol.TAG_RESPONSE_CODE, responseCode);
+ writeTag(baos, EcrProtocol.TAG_TIME, time);
+ writeTag(baos, EcrProtocol.TAG_DATE, date);
+ writeTag(baos, EcrProtocol.TAG_TID, tid);
+ writeTag(baos, EcrProtocol.TAG_MID, mid);
+ }
+
+ protected void parseCommonTags(EcrPacket packet) {
+ this.responseCode = packet.getValueAsString(EcrProtocol.TAG_RESPONSE_CODE);
+ this.time = packet.getValueAsString(EcrProtocol.TAG_TIME);
+ this.date = packet.getValueAsString(EcrProtocol.TAG_DATE);
+ this.tid = packet.getValueAsString(EcrProtocol.TAG_TID);
+ this.mid = packet.getValueAsString(EcrProtocol.TAG_MID);
+ }
+
+ protected void writeTag(ByteArrayOutputStream baos, byte tag, String value) throws IOException {
+ if (value != null) {
+
+ byte[] bytes = value.getBytes(StandardCharsets.US_ASCII);
+ baos.write(tag);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ }
+
+ protected void writeTagBinary(ByteArrayOutputStream baos, byte tag, String hexString) throws IOException {
+ if (hexString != null) {
+ byte[] bytes = ByteUtils.hexStringToByteArray(hexString);
+ baos.write(tag);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/EcrCommand.java b/cmhl/src/main/java/com/kizzy/cmhl/models/EcrCommand.java
new file mode 100644
index 0000000..9516a80
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/EcrCommand.java
@@ -0,0 +1,6 @@
+package com.kizzy.cmhl.models;
+
+public interface EcrCommand {
+ byte getCommandCode();
+ byte[] toTlvBytes();
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/EcrPacket.java b/cmhl/src/main/java/com/kizzy/cmhl/models/EcrPacket.java
new file mode 100644
index 0000000..f68025a
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/EcrPacket.java
@@ -0,0 +1,35 @@
+package com.kizzy.cmhl.models;
+
+import java.util.Map;
+
+/**
+ * Represents a generic parsed ECR packet (Command + TLV Data).
+ * Can be a Request or a Response depending on context.
+ */
+public class EcrPacket {
+ private byte commandCode;
+ private Map tlvData;
+
+ public EcrPacket(byte commandCode, Map tlvData) {
+ this.commandCode = commandCode;
+ this.tlvData = tlvData;
+ }
+
+ public byte getCommandCode() {
+ return commandCode;
+ }
+
+ public Map getTlvData() {
+ return tlvData;
+ }
+
+ public byte[] getValue(byte tag) {
+ return tlvData.get(tag);
+ }
+
+ public String getValueAsString(byte tag) {
+ byte[] val = tlvData.get(tag);
+ if (val == null) return null;
+ return new String(val);
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PingRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PingRequest.java
new file mode 100644
index 0000000..f3d2669
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PingRequest.java
@@ -0,0 +1,26 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+
+public class PingRequest implements EcrCommand {
+
+ public PingRequest() {}
+ public PingRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PING) {
+ throw new IllegalArgumentException("Invalid command for PingRequest");
+ }
+ }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PING;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ return new byte[0]; // Empty body
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PingResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PingResponse.java
new file mode 100644
index 0000000..bbe3d9a
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PingResponse.java
@@ -0,0 +1,42 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class PingResponse extends BaseEcrResponse {
+
+ public PingResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PING) {
+ }
+ parseCommonTags(packet);
+ }
+
+ private PingResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PING;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeTag(baos, EcrProtocol.TAG_RESPONSE_CODE, responseCode);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public PingResponse build() { return new PingResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCancellationRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCancellationRequest.java
new file mode 100644
index 0000000..296f55e
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCancellationRequest.java
@@ -0,0 +1,69 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class PreAuthCancellationRequest implements EcrCommand {
+ private String referenceNumber;
+ private String cardType;
+ private String approvalCode;
+
+ public PreAuthCancellationRequest(String referenceNumber, String cardType, String approvalCode) {
+ this.referenceNumber = referenceNumber;
+ this.cardType = cardType;
+ this.approvalCode = approvalCode;
+ }
+
+ public PreAuthCancellationRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PRE_AUTH_CANCELLATION) {
+ throw new IllegalArgumentException("Invalid command");
+ }
+ this.referenceNumber = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.cardType = packet.getValueAsString(EcrProtocol.TAG_CARD_TYPE);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ }
+
+ public String getCardType() { return cardType; }
+ public String getReferenceNumber() { return referenceNumber; }
+
+ public String getApprovalCode() { return approvalCode; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PRE_AUTH_CANCELLATION;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+
+ if (referenceNumber != null) {
+ byte[] bytes = referenceNumber.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_RRN);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ if (approvalCode != null) {
+ byte[] bytes = approvalCode.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_APPROVAL_CODE);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+
+ if (cardType != null) {
+ byte[] bytes = cardType.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_CARD_TYPE);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCancellationResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCancellationResponse.java
new file mode 100644
index 0000000..84e4210
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCancellationResponse.java
@@ -0,0 +1,70 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class PreAuthCancellationResponse extends BaseEcrResponse {
+ private String invoiceNumber;
+ private String approvalCode;
+
+ public PreAuthCancellationResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PRE_AUTH_CANCELLATION) {}
+ parseCommonTags(packet);
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ }
+
+ private PreAuthCancellationResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.invoiceNumber = builder.invoiceNumber;
+ this.approvalCode = builder.approvalCode;
+ }
+
+ public String getInvoiceNumber() { return invoiceNumber; }
+ public String getApprovalCode() { return approvalCode; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PRE_AUTH_CANCELLATION;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeCommonTags(baos);
+ writeTag(baos, EcrProtocol.TAG_INVOICE_NUMBER, invoiceNumber);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String time;
+ private String date;
+ private String tid;
+ private String mid;
+ private String invoiceNumber;
+ private String approvalCode;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+
+ public PreAuthCancellationResponse build() { return new PreAuthCancellationResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCompletionRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCompletionRequest.java
new file mode 100644
index 0000000..889efdc
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCompletionRequest.java
@@ -0,0 +1,70 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class PreAuthCompletionRequest implements EcrCommand {
+ private String referenceNumber;
+ private String cardType;
+ private String approvalCode;
+
+ public PreAuthCompletionRequest(String referenceNumber, String cardType, String approvalCode) {
+ this.referenceNumber = referenceNumber;
+ this.cardType = cardType;
+ this.approvalCode = approvalCode;
+ }
+
+ public PreAuthCompletionRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PRE_AUTH_COMPLETION) {
+ throw new IllegalArgumentException("Invalid command");
+ }
+ this.referenceNumber = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.cardType = packet.getValueAsString(EcrProtocol.TAG_CARD_TYPE);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ }
+
+ public String getCardType() { return cardType; }
+ public String getReferenceNumber() { return referenceNumber; }
+
+ public String getApprovalCode() { return approvalCode; }
+
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PRE_AUTH_COMPLETION;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+
+ if (referenceNumber != null) {
+ byte[] bytes = referenceNumber.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_RRN);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ if (approvalCode != null) {
+ byte[] bytes = approvalCode.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_APPROVAL_CODE);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+
+ if (cardType != null) {
+ byte[] bytes = cardType.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_CARD_TYPE);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCompletionResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCompletionResponse.java
new file mode 100644
index 0000000..a8b0001
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthCompletionResponse.java
@@ -0,0 +1,117 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class PreAuthCompletionResponse extends BaseEcrResponse {
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String approvalCode;
+ private String rrn;
+ private String invoiceNumber;
+
+ private String entry;
+
+ public PreAuthCompletionResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PRE_AUTH_COMPLETION) {}
+ parseCommonTags(packet);
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.pan = packet.getValueAsString(EcrProtocol.TAG_PAN);
+ this.expDate = packet.getValueAsString(EcrProtocol.TAG_EXP_DATE);
+ this.stan = packet.getValueAsString(EcrProtocol.TAG_STAN);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ this.rrn = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+
+ }
+
+ private PreAuthCompletionResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.amount = builder.amount;
+ this.pan = builder.pan;
+ this.expDate = builder.expDate;
+ this.stan = builder.stan;
+ this.approvalCode = builder.approvalCode;
+ this.rrn = builder.rrn;
+ this.invoiceNumber = builder.invoiceNumber;
+ this.entry = builder.entry;
+ }
+
+ // Getters
+ public String getAmount() { return amount; }
+ public String getPan() { return pan; }
+ public String getExpDate() { return expDate; }
+ public String getStan() { return stan; }
+ public String getApprovalCode() { return approvalCode; }
+ public String getRrn() { return rrn; }
+ public String getInvoiceNumber() { return invoiceNumber; }
+
+ public String getEntry() { return entry; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PRE_AUTH_COMPLETION;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeCommonTags(baos);
+ writeTag(baos, EcrProtocol.TAG_AMOUNT, amount);
+ writeTag(baos, EcrProtocol.TAG_PAN, pan);
+ writeTag(baos, EcrProtocol.TAG_EXP_DATE, expDate);
+ writeTag(baos, EcrProtocol.TAG_STAN, stan);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ writeTag(baos, EcrProtocol.TAG_RRN, rrn);
+ writeTag(baos, EcrProtocol.TAG_INVOICE_NUMBER, invoiceNumber);
+ writeTag(baos, EcrProtocol.TAG_POS_ENTRY, entry);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String time;
+ private String date;
+ private String tid;
+ private String mid;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String approvalCode;
+ private String rrn;
+ private String invoiceNumber;
+
+ private String entry;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setAmount(String amount) { this.amount = amount; return this; }
+ public Builder setPan(String pan) { this.pan = pan; return this; }
+ public Builder setExpDate(String expDate) { this.expDate = expDate; return this; }
+ public Builder setStan(String stan) { this.stan = stan; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+ public Builder setRrn(String rrn) { this.rrn = rrn; return this; }
+ public Builder setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; }
+ public Builder setEntry(String entry) { this.entry = entry; return this; }
+
+ public PreAuthCompletionResponse build() { return new PreAuthCompletionResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthRequest.java
new file mode 100644
index 0000000..86f3102
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthRequest.java
@@ -0,0 +1,57 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class PreAuthRequest implements EcrCommand {
+ private String amount;
+
+ public PreAuthRequest(String amount) {
+ this.amount = formatAmount(amount);
+ }
+
+ public PreAuthRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PRE_AUTH) {
+ throw new IllegalArgumentException("Invalid command");
+ }
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ }
+
+ public String getAmount() { return amount; }
+
+ private String formatAmount(String amount) {
+ if (amount == null) return "000000000000";
+ if (amount.length() > 12) return amount.substring(0, 12);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 12 - amount.length(); i++) {
+ sb.append('0');
+ }
+ sb.append(amount);
+ return sb.toString();
+ }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PRE_AUTH;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ if (amount != null) {
+ byte[] bytes = amount.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_AMOUNT);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthResponse.java
new file mode 100644
index 0000000..157dc38
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/PreAuthResponse.java
@@ -0,0 +1,124 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class PreAuthResponse extends BaseEcrResponse {
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String approvalCode;
+ private String rrn;
+ private String invoiceNumber;
+
+ private String cardType;
+ private String entry;
+
+ public PreAuthResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_PRE_AUTH) {}
+ parseCommonTags(packet);
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.pan = packet.getValueAsString(EcrProtocol.TAG_PAN);
+ this.expDate = packet.getValueAsString(EcrProtocol.TAG_EXP_DATE);
+ this.stan = packet.getValueAsString(EcrProtocol.TAG_STAN);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ this.rrn = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ }
+
+ private PreAuthResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.amount = builder.amount;
+ this.pan = builder.pan;
+ this.expDate = builder.expDate;
+ this.stan = builder.stan;
+ this.approvalCode = builder.approvalCode;
+ this.rrn = builder.rrn;
+ this.invoiceNumber = builder.invoiceNumber;
+ this.entry = builder.entry;
+ this.cardType = builder.cardType;
+ }
+
+ // Getters
+ public String getAmount() { return amount; }
+ public String getPan() { return pan; }
+ public String getExpDate() { return expDate; }
+ public String getStan() { return stan; }
+ public String getApprovalCode() { return approvalCode; }
+ public String getRrn() { return rrn; }
+ public String getInvoiceNumber() { return invoiceNumber; }
+
+ public String getEntry() {return entry;}
+
+ public String getCardType() {return cardType;}
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_PRE_AUTH;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeCommonTags(baos);
+ writeTag(baos, EcrProtocol.TAG_AMOUNT, amount);
+ writeTag(baos, EcrProtocol.TAG_PAN, pan);
+ writeTag(baos, EcrProtocol.TAG_EXP_DATE, expDate);
+ writeTag(baos, EcrProtocol.TAG_STAN, stan);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ writeTag(baos, EcrProtocol.TAG_RRN, rrn);
+ writeTag(baos, EcrProtocol.TAG_INVOICE_NUMBER, invoiceNumber);
+ writeTag(baos, EcrProtocol.TAG_POS_ENTRY, entry);
+ writeTag(baos, EcrProtocol.TAG_CARD_TYPE, cardType);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String time;
+ private String date;
+ private String tid;
+ private String mid;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String approvalCode;
+ private String rrn;
+ private String invoiceNumber;
+
+ private String cardType;
+ private String entry;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setAmount(String amount) { this.amount = amount; return this; }
+ public Builder setPan(String pan) { this.pan = pan; return this; }
+ public Builder setExpDate(String expDate) { this.expDate = expDate; return this; }
+ public Builder setStan(String stan) { this.stan = stan; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+ public Builder setRrn(String rrn) { this.rrn = rrn; return this; }
+ public Builder setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; }
+
+ public Builder setCardType(String cardType) { this.cardType = cardType; return this; }
+ public Builder setEntry(String entry) { this.entry = entry; return this;}
+
+ public PreAuthResponse build() { return new PreAuthResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/QrPaymentRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/QrPaymentRequest.java
new file mode 100644
index 0000000..a39013f
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/QrPaymentRequest.java
@@ -0,0 +1,74 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class QrPaymentRequest implements EcrCommand {
+ private String amount; // 12 digits
+ private String currencyCode; // 3 digits
+
+ public QrPaymentRequest(String amount, String currencyCode) {
+ this.amount = formatAmount(amount);
+ this.currencyCode = currencyCode;
+ }
+
+ public QrPaymentRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_QR_PAYMENT) {
+ throw new IllegalArgumentException("Invalid command code for QrPaymentRequest");
+ }
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.currencyCode = packet.getValueAsString(EcrProtocol.TAG_CURRENCY_CODE);
+ }
+
+ public String getAmount() {
+ return amount;
+ }
+
+ public String getCurrencyCode() {
+ return currencyCode;
+ }
+
+ private String formatAmount(String amount) {
+ if (amount == null) return "000000000000";
+ if (amount.length() > 12) return amount.substring(0, 12);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 12 - amount.length(); i++) {
+ sb.append('0');
+ }
+ sb.append(amount);
+ return sb.toString();
+ }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_QR_PAYMENT;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ if (amount != null) {
+ byte[] amountBytes = amount.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_AMOUNT);
+ baos.write((byte) amountBytes.length);
+ baos.write(amountBytes);
+ }
+
+ if (currencyCode != null) {
+ byte[] currencyBytes = currencyCode.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_CURRENCY_CODE);
+ baos.write((byte) currencyBytes.length);
+ baos.write(currencyBytes);
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/QrPaymentResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/QrPaymentResponse.java
new file mode 100644
index 0000000..1bb6690
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/QrPaymentResponse.java
@@ -0,0 +1,155 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class QrPaymentResponse implements EcrCommand {
+ private String responseCode;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String time;
+ private String date;
+ private String approvalCode;
+ private String rrn;
+ private String posEntryMode;
+ private String tid;
+ private String mid;
+ private String invoiceNumber;
+ private String paymentType;
+ private String currencyCode;
+
+ public QrPaymentResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_QR_PAYMENT) {
+ }
+ this.responseCode = packet.getValueAsString(EcrProtocol.TAG_RESPONSE_CODE);
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.pan = packet.getValueAsString(EcrProtocol.TAG_PAN);
+ this.expDate = packet.getValueAsString(EcrProtocol.TAG_EXP_DATE);
+ this.stan = packet.getValueAsString(EcrProtocol.TAG_STAN);
+ this.time = packet.getValueAsString(EcrProtocol.TAG_TIME);
+ this.date = packet.getValueAsString(EcrProtocol.TAG_DATE);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ this.rrn = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.posEntryMode = packet.getValueAsString(EcrProtocol.TAG_POS_ENTRY);
+ this.tid = packet.getValueAsString(EcrProtocol.TAG_TID);
+ this.mid = packet.getValueAsString(EcrProtocol.TAG_MID);
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ this.paymentType = packet.getValueAsString(EcrProtocol.TAG_PAYMENT_TYPE);
+ this.currencyCode = packet.getValueAsString(EcrProtocol.TAG_CURRENCY_CODE);
+ }
+
+ private QrPaymentResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.amount = builder.amount;
+ this.pan = builder.pan;
+ this.expDate = builder.expDate;
+ this.stan = builder.stan;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.approvalCode = builder.approvalCode;
+ this.rrn = builder.rrn;
+ this.posEntryMode = builder.posEntryMode;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.invoiceNumber = builder.invoiceNumber;
+ this.paymentType = builder.paymentType;
+ this.currencyCode = builder.currencyCode;
+ }
+
+ public boolean isApproved() {
+ return "00".equals(responseCode);
+ }
+
+ // Getters
+ public String getResponseCode() { return responseCode; }
+ public String getAmount() { return amount; }
+ public String getPan() { return pan; }
+ public String getInvoiceNumber() { return invoiceNumber; }
+ public String getApprovalCode() { return approvalCode; }
+ public String getRrn() { return rrn; }
+ public String getTid() { return tid; }
+ public String getMid() { return mid; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_QR_PAYMENT;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeTag(baos, EcrProtocol.TAG_AMOUNT, amount);
+ writeTag(baos, EcrProtocol.TAG_RESPONSE_CODE, responseCode);
+ writeTag(baos, EcrProtocol.TAG_PAN, pan);
+ writeTag(baos, EcrProtocol.TAG_EXP_DATE, expDate);
+ writeTag(baos, EcrProtocol.TAG_STAN, stan);
+ writeTag(baos, EcrProtocol.TAG_TIME, time);
+ writeTag(baos, EcrProtocol.TAG_DATE, date);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ writeTag(baos, EcrProtocol.TAG_RRN, rrn);
+ writeTag(baos, EcrProtocol.TAG_POS_ENTRY, posEntryMode);
+ writeTag(baos, EcrProtocol.TAG_TID, tid);
+ writeTag(baos, EcrProtocol.TAG_MID, mid);
+ writeTag(baos, EcrProtocol.TAG_INVOICE_NUMBER, invoiceNumber);
+ writeTag(baos, EcrProtocol.TAG_PAYMENT_TYPE, paymentType);
+ writeTag(baos, EcrProtocol.TAG_CURRENCY_CODE, currencyCode);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ private void writeTag(ByteArrayOutputStream baos, byte tag, String value) throws IOException {
+ if (value != null) {
+ byte[] bytes = value.getBytes(StandardCharsets.US_ASCII);
+ baos.write(tag);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String time;
+ private String date;
+ private String approvalCode;
+ private String rrn;
+ private String posEntryMode;
+ private String tid;
+ private String mid;
+ private String invoiceNumber;
+ private String paymentType;
+ private String currencyCode;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setAmount(String amount) { this.amount = amount; return this; }
+ public Builder setPan(String pan) { this.pan = pan; return this; }
+ public Builder setExpDate(String expDate) { this.expDate = expDate; return this; }
+ public Builder setStan(String stan) { this.stan = stan; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+ public Builder setRrn(String rrn) { this.rrn = rrn; return this; }
+ public Builder setPosEntryMode(String posEntryMode) { this.posEntryMode = posEntryMode; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; }
+ public Builder setPaymentType(String paymentType) { this.paymentType = paymentType; return this; }
+ public Builder setCurrencyCode(String currencyCode) { this.currencyCode = currencyCode; return this; }
+
+ public QrPaymentResponse build() {
+ return new QrPaymentResponse(this);
+ }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/SaleRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/SaleRequest.java
new file mode 100644
index 0000000..6b953c9
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/SaleRequest.java
@@ -0,0 +1,57 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class SaleRequest implements EcrCommand {
+ private String amount;
+
+ public SaleRequest(String amount) {
+ this.amount = formatAmount(amount);
+ }
+
+ public SaleRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_SALE) {
+ throw new IllegalArgumentException("Invalid command");
+ }
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ }
+
+ public String getAmount() { return amount; }
+
+ private String formatAmount(String amount) {
+ if (amount == null) return "000000000000";
+ if (amount.length() > 12) return amount.substring(0, 12);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 12 - amount.length(); i++) {
+ sb.append('0');
+ }
+ sb.append(amount);
+ return sb.toString();
+ }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_SALE;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ if (amount != null) {
+ byte[] bytes = amount.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_AMOUNT);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/SaleResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/SaleResponse.java
new file mode 100644
index 0000000..81a7f8a
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/SaleResponse.java
@@ -0,0 +1,140 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class SaleResponse extends BaseEcrResponse {
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String approvalCode;
+ private String referenceNum;
+ private String invoiceNumber;
+
+ private String entry;
+
+ private String currency;
+
+ private String cardType;
+
+ public SaleResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_SALE) {}
+ parseCommonTags(packet);
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.pan = packet.getValueAsString(EcrProtocol.TAG_PAN);
+ this.expDate = packet.getValueAsString(EcrProtocol.TAG_EXP_DATE);
+ this.stan = packet.getValueAsString(EcrProtocol.TAG_STAN);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ this.referenceNum = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ }
+
+ private SaleResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.amount = builder.amount;
+ this.pan = builder.pan;
+ this.expDate = builder.expDate;
+ this.stan = builder.stan;
+ this.approvalCode = builder.approvalCode;
+ this.referenceNum = builder.referenceNum;
+ this.invoiceNumber = builder.invoiceNumber;
+ this.entry = builder.entry;
+ this.cardType = builder.cardType;
+ this.currency = builder.currency;
+ }
+
+ // Getters
+ public String getAmount() { return amount; }
+ public String getPan() { return pan; }
+ public String getExpDate() { return expDate; }
+ public String getStan() { return stan; }
+ public String getApprovalCode() { return approvalCode; }
+ public String getReferenceNum() { return referenceNum; }
+ public String getInvoiceNumber() { return invoiceNumber; }
+
+ public String getEntry() { return entry; }
+
+ public String getCardType() { return cardType; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_SALE;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+// writeCommonTags(baos);
+ writeTag(baos, EcrProtocol.TAG_AMOUNT, amount);
+ writeTag(baos, EcrProtocol.TAG_RESPONSE_CODE, responseCode);
+ writeTag(baos, EcrProtocol.TAG_PAN, pan);
+ writeTag(baos, EcrProtocol.TAG_EXP_DATE, expDate);
+ writeTag(baos, EcrProtocol.TAG_STAN, stan);
+ writeTag(baos, EcrProtocol.TAG_TIME, time);
+ writeTag(baos, EcrProtocol.TAG_DATE, date);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ writeTag(baos, EcrProtocol.TAG_RRN, referenceNum);
+ writeTag(baos, EcrProtocol.TAG_POS_ENTRY, entry);
+ writeTag(baos, EcrProtocol.TAG_TID, tid);
+ writeTag(baos, EcrProtocol.TAG_MID, mid);
+ writeTag(baos, EcrProtocol.TAG_INVOICE_NUMBER, invoiceNumber);
+ writeTagBinary(baos, EcrProtocol.TAG_CARD_TYPE, cardType);
+ writeTag(baos, EcrProtocol.TAG_CURRENCY_CODE, currency);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String time;
+ private String date;
+ private String tid;
+ private String mid;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+
+ private String entry;
+
+ private String cardType;
+ private String approvalCode;
+ private String referenceNum;
+ private String invoiceNumber;
+
+ private String currency;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setAmount(String amount) { this.amount = amount; return this; }
+ public Builder setPan(String pan) { this.pan = pan; return this; }
+ public Builder setExpDate(String expDate) { this.expDate = expDate; return this; }
+ public Builder setStan(String stan) { this.stan = stan; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+ public Builder setReferenceNum(String rrn) { this.referenceNum = rrn; return this; }
+
+ public Builder setEntry(String entry) { this.entry = entry; return this;}
+
+ public Builder setCardType(String cardType) { this.cardType = cardType; return this;}
+ public Builder setInvoiceNumber(String invoiceNumber) { this.invoiceNumber = invoiceNumber; return this; }
+
+ public Builder setCurrency(String currency) { this.currency = currency; return this; }
+
+ public SaleResponse build() { return new SaleResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/SettlementRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/SettlementRequest.java
new file mode 100644
index 0000000..a4650c5
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/SettlementRequest.java
@@ -0,0 +1,46 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class SettlementRequest implements EcrCommand {
+ private String cardType;
+
+ public SettlementRequest(String cardType) {
+ this.cardType = cardType;
+ }
+
+ public SettlementRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_SETTLEMENT) {
+ throw new IllegalArgumentException("Invalid command");
+ }
+ this.cardType = packet.getValueAsString(EcrProtocol.TAG_CARD_TYPE);
+ }
+
+ public String getCardType() { return cardType; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_SETTLEMENT;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ if (cardType != null) {
+ byte[] bytes = cardType.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_CARD_TYPE);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/SettlementResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/SettlementResponse.java
new file mode 100644
index 0000000..4759c97
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/SettlementResponse.java
@@ -0,0 +1,55 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class SettlementResponse extends BaseEcrResponse {
+
+ public SettlementResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_SETTLEMENT) {}
+ parseCommonTags(packet);
+ }
+
+ private SettlementResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_SETTLEMENT;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeCommonTags(baos);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String time;
+ private String date;
+ private String tid;
+ private String mid;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+
+ public SettlementResponse build() { return new SettlementResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/VoidQrPaymentRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidQrPaymentRequest.java
new file mode 100644
index 0000000..c3f9dab
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidQrPaymentRequest.java
@@ -0,0 +1,60 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class VoidQrPaymentRequest implements EcrCommand {
+ private String invoiceNumber;
+ private String paymentType;
+
+ public VoidQrPaymentRequest(String invoiceNumber, String paymentType) {
+ this.invoiceNumber = invoiceNumber;
+ this.paymentType = paymentType;
+ }
+
+ public VoidQrPaymentRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_VOID_QR_PAYMENT) {
+ throw new IllegalArgumentException("Invalid command code for VoidQrPaymentRequest");
+ }
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ this.paymentType = packet.getValueAsString(EcrProtocol.TAG_PAYMENT_TYPE);
+ }
+
+ public String getInvoiceNumber() { return invoiceNumber; }
+ public String getPaymentType() { return paymentType; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_VOID_QR_PAYMENT;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ // Invoice Number
+ if (invoiceNumber != null) {
+ byte[] invoiceBytes = invoiceNumber.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_INVOICE_NUMBER);
+ baos.write((byte) invoiceBytes.length);
+ baos.write(invoiceBytes);
+ }
+
+ // Payment Type
+ if (paymentType != null) {
+ byte[] typeBytes = paymentType.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_PAYMENT_TYPE);
+ baos.write((byte) typeBytes.length);
+ baos.write(typeBytes);
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/VoidQrPaymentResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidQrPaymentResponse.java
new file mode 100644
index 0000000..85e7c5c
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidQrPaymentResponse.java
@@ -0,0 +1,138 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class VoidQrPaymentResponse implements EcrCommand {
+ private String responseCode;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String time;
+ private String date;
+ private String approvalCode;
+ private String rrn;
+ private String posEntryMode;
+ private String tid;
+ private String mid;
+ private String currencyCode;
+
+
+ // Parsing Constructor (ECR Role)
+ public VoidQrPaymentResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_VOID_QR_PAYMENT) {
+ }
+ this.responseCode = packet.getValueAsString(EcrProtocol.TAG_RESPONSE_CODE);
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.pan = packet.getValueAsString(EcrProtocol.TAG_PAN);
+ this.expDate = packet.getValueAsString(EcrProtocol.TAG_EXP_DATE);
+ this.stan = packet.getValueAsString(EcrProtocol.TAG_STAN);
+ this.time = packet.getValueAsString(EcrProtocol.TAG_TIME);
+ this.date = packet.getValueAsString(EcrProtocol.TAG_DATE);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ this.rrn = packet.getValueAsString(EcrProtocol.TAG_RRN);
+ this.posEntryMode = packet.getValueAsString(EcrProtocol.TAG_POS_ENTRY);
+ this.tid = packet.getValueAsString(EcrProtocol.TAG_TID);
+ this.mid = packet.getValueAsString(EcrProtocol.TAG_MID);
+ this.currencyCode = packet.getValueAsString(EcrProtocol.TAG_CURRENCY_CODE);
+ }
+
+ // Building Constructor (EDC Role)
+ private VoidQrPaymentResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.amount = builder.amount;
+ this.pan = builder.pan;
+ this.expDate = builder.expDate;
+ this.stan = builder.stan;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.approvalCode = builder.approvalCode;
+ this.rrn = builder.rrn;
+ this.posEntryMode = builder.posEntryMode;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.currencyCode = builder.currencyCode;
+ }
+
+ public boolean isApproved() {
+ return "00".equals(responseCode);
+ }
+
+ public String getResponseCode() { return responseCode; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_VOID_QR_PAYMENT;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeTag(baos, EcrProtocol.TAG_RESPONSE_CODE, responseCode);
+ writeTag(baos, EcrProtocol.TAG_AMOUNT, amount);
+ writeTag(baos, EcrProtocol.TAG_PAN, pan);
+ writeTag(baos, EcrProtocol.TAG_EXP_DATE, expDate);
+ writeTag(baos, EcrProtocol.TAG_STAN, stan);
+ writeTag(baos, EcrProtocol.TAG_TIME, time);
+ writeTag(baos, EcrProtocol.TAG_DATE, date);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ writeTag(baos, EcrProtocol.TAG_RRN, rrn);
+ writeTag(baos, EcrProtocol.TAG_POS_ENTRY, posEntryMode);
+ writeTag(baos, EcrProtocol.TAG_TID, tid);
+ writeTag(baos, EcrProtocol.TAG_MID, mid);
+ writeTag(baos, EcrProtocol.TAG_CURRENCY_CODE, currencyCode);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ private void writeTag(ByteArrayOutputStream baos, byte tag, String value) throws IOException {
+ if (value != null) {
+ byte[] bytes = value.getBytes(StandardCharsets.US_ASCII);
+ baos.write(tag);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String amount;
+ private String pan;
+ private String expDate;
+ private String stan;
+ private String time;
+ private String date;
+ private String approvalCode;
+ private String rrn;
+ private String posEntryMode;
+ private String tid;
+ private String mid;
+ private String currencyCode;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setAmount(String amount) { this.amount = amount; return this; }
+ public Builder setPan(String pan) { this.pan = pan; return this; }
+ public Builder setExpDate(String expDate) { this.expDate = expDate; return this; }
+ public Builder setStan(String stan) { this.stan = stan; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+ public Builder setRrn(String rrn) { this.rrn = rrn; return this; }
+ public Builder setPosEntryMode(String posEntryMode) { this.posEntryMode = posEntryMode; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setCurrencyCode(String currencyCode) { this.currencyCode = currencyCode; return this; }
+
+ public VoidQrPaymentResponse build() {
+ return new VoidQrPaymentResponse(this);
+ }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/VoidRequest.java b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidRequest.java
new file mode 100644
index 0000000..f5bacfe
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidRequest.java
@@ -0,0 +1,46 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class VoidRequest implements EcrCommand {
+ private String invoiceNumber;
+
+ public VoidRequest(String invoiceNumber) {
+ this.invoiceNumber = invoiceNumber;
+ }
+
+ public VoidRequest(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_VOID) {
+ throw new IllegalArgumentException("Invalid command");
+ }
+ this.invoiceNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ }
+
+ public String getInvoiceNumber() { return invoiceNumber; }
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_VOID;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ if (invoiceNumber != null) {
+ byte[] bytes = invoiceNumber.getBytes(StandardCharsets.US_ASCII);
+ baos.write(EcrProtocol.TAG_INVOICE_NUMBER);
+ baos.write((byte) bytes.length);
+ baos.write(bytes);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/models/VoidResponse.java b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidResponse.java
new file mode 100644
index 0000000..ed72737
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/models/VoidResponse.java
@@ -0,0 +1,93 @@
+package com.kizzy.cmhl.models;
+
+
+import com.kizzy.cmhl.EcrProtocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class VoidResponse extends BaseEcrResponse {
+ private String amount;
+ private String voucherNumber;
+ private String approvalCode;
+ private String referenceNum;
+
+ private String entry;
+
+ public VoidResponse(EcrPacket packet) {
+ if (packet.getCommandCode() != EcrProtocol.CMD_VOID) {}
+ parseCommonTags(packet);
+ this.amount = packet.getValueAsString(EcrProtocol.TAG_AMOUNT);
+ this.voucherNumber = packet.getValueAsString(EcrProtocol.TAG_INVOICE_NUMBER);
+ this.approvalCode = packet.getValueAsString(EcrProtocol.TAG_APPROVAL_CODE);
+ }
+
+ private VoidResponse(Builder builder) {
+ this.responseCode = builder.responseCode;
+ this.time = builder.time;
+ this.date = builder.date;
+ this.tid = builder.tid;
+ this.mid = builder.mid;
+ this.amount = builder.amount;
+ this.voucherNumber = builder.voucherNum;
+ this.approvalCode = builder.approvalCode;
+ this.referenceNum = builder.referenceNum;
+ this.entry = builder.entry;
+ }
+
+ public String getAmount() { return amount; }
+ public String getVoucherNumber() { return voucherNumber; }
+ public String getApprovalCode() { return approvalCode; }
+
+ public String getReferenceNum() { return referenceNum;}
+
+ public String getEntry() {return entry;}
+
+ @Override
+ public byte getCommandCode() {
+ return EcrProtocol.CMD_VOID;
+ }
+
+ @Override
+ public byte[] toTlvBytes() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ writeCommonTags(baos);
+ writeTag(baos, EcrProtocol.TAG_AMOUNT, amount);
+ writeTag(baos, EcrProtocol.TAG_STAN, voucherNumber);
+ writeTag(baos, EcrProtocol.TAG_APPROVAL_CODE, approvalCode);
+ writeTag(baos, EcrProtocol.TAG_RRN,referenceNum);
+ writeTag(baos, EcrProtocol.TAG_POS_ENTRY,entry);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return baos.toByteArray();
+ }
+
+ public static class Builder {
+ private String responseCode;
+ private String time; // len 6
+ private String date; // len 4
+ private String tid;
+ private String mid;
+ private String amount;
+ private String voucherNum;
+ private String approvalCode;
+ private String referenceNum;
+
+ private String entry;
+
+ public Builder setResponseCode(String responseCode) { this.responseCode = responseCode; return this; }
+ public Builder setTime(String time) { this.time = time; return this; }
+ public Builder setDate(String date) { this.date = date; return this; }
+ public Builder setTid(String tid) { this.tid = tid; return this; }
+ public Builder setMid(String mid) { this.mid = mid; return this; }
+ public Builder setAmount(String amount) { this.amount = amount; return this; }
+ public Builder setVoucherNumber(String voucherNum) { this.voucherNum = voucherNum; return this; }
+ public Builder setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; return this; }
+ public Builder setReferenceNum(String referenceNum) { this.referenceNum = referenceNum; return this; }
+ public Builder setEntry(String entry) { this.entry = entry; return this; }
+
+ public VoidResponse build() { return new VoidResponse(this); }
+ }
+}
diff --git a/cmhl/src/main/java/com/kizzy/cmhl/utils/ByteUtils.java b/cmhl/src/main/java/com/kizzy/cmhl/utils/ByteUtils.java
new file mode 100644
index 0000000..1ba8771
--- /dev/null
+++ b/cmhl/src/main/java/com/kizzy/cmhl/utils/ByteUtils.java
@@ -0,0 +1,78 @@
+package com.kizzy.cmhl.utils;
+
+public class ByteUtils {
+
+ /**
+ * Calculates LRC by XORing all bytes in the data.
+ * @param data
+ * @return
+ */
+ public static byte calculateLrc(byte[] data) {
+ byte lrc = 0;
+ for (byte b : data) {
+ lrc ^= b;
+ }
+ return lrc;
+ }
+
+// public static byte[] hexStringToByteArray(String s) {
+// int len = s.length();
+// byte[] data = new byte[len / 2];
+// for (int i = 0; i < len; i += 2) {
+// data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+// + Character.digit(s.charAt(i+1), 16));
+// }
+// return data;
+// }
+
+ public static byte[] hexStringToByteArray(String s) {
+ if (s == null) return new byte[0];
+ // Ensure even length by padding with leading '0' if odd
+ if (s.length() % 2 != 0) {
+ s = "0" + s;
+ }
+
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ + Character.digit(s.charAt(i+1), 16));
+ }
+ return data;
+ }
+
+ public static String byteArrayToHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(String.format("%02X", b));
+ }
+ return sb.toString();
+ }
+
+ public static byte[] concat(byte[]... arrays) {
+ int length = 0;
+ for (byte[] array : arrays) {
+ length += array.length;
+ }
+ byte[] result = new byte[length];
+ int pos = 0;
+ for (byte[] array : arrays) {
+ System.arraycopy(array, 0, result, pos, array.length);
+ pos += array.length;
+ }
+ return result;
+ }
+
+ public static byte[] intToBcd(int val) {
+ // Only handles up to 9999 for 2 bytes
+ int thousands = (val / 1000) % 10;
+ int hundreds = (val / 100) % 10;
+ int tens = (val / 10) % 10;
+ int ones = val % 10;
+
+ byte b1 = (byte) ((thousands << 4) | hundreds);
+ byte b2 = (byte) ((tens << 4) | ones);
+
+ return new byte[]{b1, b2};
+ }
+}
diff --git a/cmhl/src/test/java/com/kizzy/cmhl/ExampleUnitTest.java b/cmhl/src/test/java/com/kizzy/cmhl/ExampleUnitTest.java
new file mode 100644
index 0000000..7cfae42
--- /dev/null
+++ b/cmhl/src/test/java/com/kizzy/cmhl/ExampleUnitTest.java
@@ -0,0 +1,172 @@
+package com.kizzy.cmhl;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+import com.kizzy.cmhl.models.EcrPacket;
+import com.kizzy.cmhl.models.PingRequest;
+import com.kizzy.cmhl.models.PingResponse;
+import com.kizzy.cmhl.models.PreAuthCancellationRequest;
+import com.kizzy.cmhl.models.PreAuthCompletionRequest;
+import com.kizzy.cmhl.models.PreAuthRequest;
+import com.kizzy.cmhl.models.QrPaymentRequest;
+import com.kizzy.cmhl.models.SaleRequest;
+import com.kizzy.cmhl.models.SaleResponse;
+import com.kizzy.cmhl.models.SettlementRequest;
+import com.kizzy.cmhl.models.VoidQrPaymentRequest;
+import com.kizzy.cmhl.models.VoidRequest;
+import com.kizzy.cmhl.utils.ByteUtils;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+
+
+ @Test
+ public void testPing() {
+ PingResponse pingResponse = new PingResponse.Builder()
+ .setResponseCode("00")
+ .build();
+
+ byte[] packet = EcrManager.buildPacket(pingResponse);
+
+ System.out.println("Packets :"+ByteUtils.byteArrayToHexString(packet));
+ }
+
+
+
+ @Test
+ public void testECRPacket() {
+
+ byte[] data = {
+ (byte) 0x02, (byte) 0x00, (byte) 0x27, (byte) 0x01, (byte) 0x07, (byte) 0x37,
+ (byte) 0x0C, (byte) 0x37, (byte) 0x30, (byte) 0x30, (byte) 0x35, (byte) 0x31,
+ (byte) 0x37, (byte) 0x31, (byte) 0x34, (byte) 0x39, (byte) 0x31, (byte) 0x34,
+ (byte) 0x35, (byte) 0x38, (byte) 0x06, (byte) 0x30, (byte) 0x30, (byte) 0x34,
+ (byte) 0x35, (byte) 0x37, (byte) 0x30, (byte) 0x15, (byte) 0x01, (byte) 0x02,
+ (byte) 0x03, (byte) 0x31
+ };
+
+ byte[] data2 = {
+ (byte) 0x02, (byte) 0x00, (byte) 0x21, (byte) 0x01, (byte) 0x0B, (byte) 0x04,
+ (byte) 0x0C, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
+ (byte) 0x31, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
+ (byte) 0x30, (byte) 0x49, (byte) 0x03, (byte) 0x31, (byte) 0x30, (byte) 0x34,
+ (byte) 0x03, (byte) 0x55
+ };
+
+ byte[] data3 = {
+ (byte) 0x02, (byte) 0x00, (byte) 0x21, (byte) 0x01, (byte) 0x03, (byte) 0x04,
+ (byte) 0x0C, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
+ (byte) 0x31, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
+ (byte) 0x30, (byte) 0x49, (byte) 0x03, (byte) 0x31, (byte) 0x30, (byte) 0x34,
+ (byte) 0x03, (byte) 0x55
+ };
+
+ byte[] ping = {
+ (byte) 0x02, (byte) 0x00, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x02
+ };
+
+ byte[] qrPayment = ByteUtils.hexStringToByteArray("020021010B040C3030303030303030313335304903313034035B");
+ try {
+ EcrPacket ecrPacket = EcrManager.parsePacket(qrPayment);
+
+ EcrRequestDispatcher.dispatch(ecrPacket, new EcrRequestListener() {
+ @Override
+ public void onPingRequest(PingRequest request) {
+ PingResponse pingResponse = new PingResponse.Builder()
+ .setResponseCode("00")
+ .build();
+
+ byte[] responsePacket = EcrManager.buildPacket(pingResponse);
+ System.out.println("Response: "+ ByteUtils.byteArrayToHexString(responsePacket));
+ }
+
+ @Override
+ public void onSaleRequest(SaleRequest request) {
+ System.out.println("command :"+ecrPacket.getCommandCode());
+
+ String amount = request.getAmount();
+
+ System.out.println("amount:"+amount);
+
+ SaleResponse response = new SaleResponse.Builder()
+ .setResponseCode("00")
+ .setAmount(amount)
+ .setPan("411122*****122")
+ .setApprovalCode("123456")
+ .setInvoiceNumber("000002")
+ .build();
+
+ byte[] responsePacket = EcrManager.buildPacket(response);
+ System.out.println("Response: "+ ByteUtils.byteArrayToHexString(responsePacket));
+ }
+
+ @Override
+ public void onVoidRequest(VoidRequest request) {
+
+ }
+
+ @Override
+ public void onSettlementRequest(SettlementRequest request) {
+
+ }
+
+ @Override
+ public void onPreAuthRequest(PreAuthRequest request) {
+
+ }
+
+ @Override
+ public void onPreAuthCompletionRequest(PreAuthCompletionRequest request) {
+
+ }
+
+ @Override
+ public void onPreAuthCancellationRequest(PreAuthCancellationRequest request) {
+
+ }
+
+ @Override
+ public void onQrPaymentRequest(QrPaymentRequest request) {
+ System.out.println("command :"+ecrPacket.getCommandCode());
+
+ String amount = request.getAmount();
+ String currency = request.getCurrencyCode();
+
+ System.out.println("amount:"+amount);
+ System.out.println("currency:"+currency);
+ }
+
+ @Override
+ public void onVoidQrPaymentRequest(VoidQrPaymentRequest request) {
+
+ }
+
+ @Override
+ public void onUnknownRequest(EcrPacket packet) {
+
+ }
+ });
+
+// System.out.println("command :"+ecrPacket.getCommandCode());
+//
+// QrPaymentRequest request = new QrPaymentRequest(ecrPacket);
+// String amount = request.getAmount();
+// String currency = request.getCurrencyCode();
+//
+// System.out.println("amount:"+amount);
+// System.out.println("currency:"+currency);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/ecr-service-lib/.gitignore b/ecr-service-lib/.gitignore
deleted file mode 100644
index 39bd49c..0000000
--- a/ecr-service-lib/.gitignore
+++ /dev/null
@@ -1,28 +0,0 @@
-/build
-
-# Built files
-*.apk
-*.aab
-*.dex
-*.class
-
-# Gradle
-.gradle/
-build/
-*/build/
-
-# Android Studio
-*.iml
-.idea/
-local.properties
-
-# Keystore
-*.jks
-*.keystore
-
-# OS files
-.DS_Store
-Thumbs.db
-
-# Logs
-*.log
\ No newline at end of file
diff --git a/ecr-service-lib/build.gradle b/ecr-service-lib/build.gradle
deleted file mode 100644
index 3f160bb..0000000
--- a/ecr-service-lib/build.gradle
+++ /dev/null
@@ -1,3 +0,0 @@
-configurations.maybeCreate("default")
-artifacts.add("default", file('ecr-service-lib-1.1.11.aar'))
-//artifacts.add("default", file('Link_POS_Library_V1.9.aar'))
\ No newline at end of file
diff --git a/ecr-service-lib/ecr-service-lib-1.1.11.aar b/ecr-service-lib/ecr-service-lib-1.1.11.aar
deleted file mode 100644
index dfe9c38..0000000
Binary files a/ecr-service-lib/ecr-service-lib-1.1.11.aar and /dev/null differ
diff --git a/ecr-service/.idea/.gitignore b/ecr-service/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/ecr-service/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/ecr-service/.idea/AndroidProjectSystem.xml b/ecr-service/.idea/AndroidProjectSystem.xml
deleted file mode 100644
index 4a53bee..0000000
--- a/ecr-service/.idea/AndroidProjectSystem.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr-service/.idea/caches/deviceStreaming.xml b/ecr-service/.idea/caches/deviceStreaming.xml
deleted file mode 100644
index c62dc3d..0000000
--- a/ecr-service/.idea/caches/deviceStreaming.xml
+++ /dev/null
@@ -1,1282 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr-service/.idea/gradle.xml b/ecr-service/.idea/gradle.xml
deleted file mode 100644
index b838237..0000000
--- a/ecr-service/.idea/gradle.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr-service/.idea/migrations.xml b/ecr-service/.idea/migrations.xml
deleted file mode 100644
index f8051a6..0000000
--- a/ecr-service/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr-service/.idea/misc.xml b/ecr-service/.idea/misc.xml
deleted file mode 100644
index 3040d03..0000000
--- a/ecr-service/.idea/misc.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr-service/.idea/runConfigurations.xml b/ecr-service/.idea/runConfigurations.xml
deleted file mode 100644
index 16660f1..0000000
--- a/ecr-service/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr-service/.idea/vcs.xml b/ecr-service/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/ecr-service/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ecr/build.gradle b/ecr/build.gradle
index 3a52af9..eb1c676 100644
--- a/ecr/build.gradle
+++ b/ecr/build.gradle
@@ -31,12 +31,9 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar","*.aar"])
-// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation project(path: ':nexsdk-lib')
-// implementation files('libs/ecr-service-lib-1.1.11.aar')
- implementation project(path: ':ecr-service-lib')
implementation project(path: ':paylibs')
implementation 'com.google.code.gson:gson:2.9.0'
diff --git a/ecr/src/main/java/com/utsmyanmar/ecr/ECRHelper.kt b/ecr/src/main/java/com/utsmyanmar/ecr/ECRHelper.kt
index c24cf8d..1cf15ce 100644
--- a/ecr/src/main/java/com/utsmyanmar/ecr/ECRHelper.kt
+++ b/ecr/src/main/java/com/utsmyanmar/ecr/ECRHelper.kt
@@ -1,25 +1,29 @@
package com.utsmyanmar.ecr
import android.content.Context
-import android.os.Bundle
-import android.widget.Toast
import com.nexgo.oaf.apiv3.APIProxy
import com.nexgo.oaf.apiv3.SdkResult
import com.nexgo.oaf.apiv3.device.serialport.SerialCfgEntity
import com.nexgo.oaf.apiv3.device.serialport.SerialPortDriver
-import com.pos.connection.bridge.ECRConnection
-import com.pos.connection.bridge.ECRListener
-import com.pos.connection.bridge.ECRRequestCallback
-import com.pos.connection.bridge.ECRService
-import com.pos.hardware.connection.library.ECRServiceKernel
import com.utsmyanmar.paylibs.utils.LogUtil
-import java.nio.charset.StandardCharsets
+import com.utsmyanmar.paylibs.utils.core_utils.ByteUtil
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * NexGo SerialPortDriver wrapper (port 0 = physical USB serial).
+ * Spec: connect / send / recv / clrBuffer / disconnect.
+ */
object ECRHelper {
- val TAG = ECRHelper::class.java.simpleName
+ val TAG: String = ECRHelper::class.java.simpleName
+
+ private const val PORT_NO = 0
+ private const val RECV_BUFFER_SIZE = 2048
+ private const val RECV_TIMEOUT_MS = 3000L
- private var ecrService: ECRService? = null
private var serialPort: SerialPortDriver? = null
+ private var receiveThread: Thread? = null
+ private val receiving = AtomicBoolean(false)
var onBindSuccess: () -> Unit = { }
var onBindFailure: () -> Unit = { }
@@ -32,12 +36,19 @@ object ECRHelper {
var onECRReceive: (bytes: ByteArray) -> Unit = { }
- // ─── Serial Port Init (call this once, e.g. in Application or Activity) ───
+ val isConnected: Boolean
+ get() = serialPort != null
fun initSerialPort(context: Context) {
try {
+ if (serialPort != null) {
+ LogUtil.d(TAG, "Serial port already connected")
+ onBindSuccess()
+ return
+ }
+
val deviceEngine = APIProxy.getDeviceEngine(context)
- val port = deviceEngine.getSerialPortDriver(0)
+ val port = deviceEngine.getSerialPortDriver(PORT_NO)
val entity = SerialCfgEntity().apply {
baudRate = 115200
@@ -49,44 +60,46 @@ object ECRHelper {
val result = port.connect(entity)
if (result == SdkResult.Success) {
serialPort = port
- LogUtil.d(TAG, "USB Serial connected")
+ port.clrBuffer()
+ ECRUtil.connected = true
+ LogUtil.d(TAG, "SerialPortDriver connected (port=$PORT_NO)")
+ startReceiveLoop()
+ onECRConnected()
+ onBindSuccess()
} else {
- LogUtil.e(TAG, "USB Serial connect failed: $result")
+ LogUtil.e(TAG, "SerialPortDriver connect failed: $result")
+ ECRUtil.connected = false
+ onBindFailure()
onSendFailure(result, "Serial port connect failed")
}
} catch (ex: Throwable) {
ex.printStackTrace()
+ ECRUtil.connected = false
+ onBindFailure()
onSendFailure(-200, ex.localizedMessage ?: "Serial init error")
}
}
- fun releaseSerialPort() {
- try {
- serialPort?.disconnect()
- } catch (ex: Throwable) {
- ex.printStackTrace()
- } finally {
- serialPort = null
- }
- }
-
- // ─── Send: uses Serial Port instead of ECRService ─────────────────────────
-
fun send(bytes: ByteArray) {
try {
val port = serialPort
- if (port != null) {
- val result = port.send(bytes, bytes.size)
- if (result == SdkResult.Success) {
- LogUtil.d(TAG, "Serial send success")
- onSendSuccess()
- } else {
- LogUtil.e(TAG, "Serial send failed: $result")
- onSendFailure(result, "Serial send failed")
- }
- } else {
+ if (port == null) {
LogUtil.e(TAG, "Serial port not initialized")
onSendFailure(-100, "Serial port not initialized")
+ return
+ }
+ if (bytes.isEmpty() || bytes.size > RECV_BUFFER_SIZE) {
+ onSendFailure(SdkResult.Param_In_Invalid, "Invalid send length")
+ return
+ }
+
+ val result = port.send(bytes, bytes.size)
+ if (result == SdkResult.Success) {
+ LogUtil.d(TAG, "Serial send success, len=${bytes.size}")
+ onSendSuccess()
+ } else {
+ LogUtil.e(TAG, "Serial send failed: $result")
+ onSendFailure(result, "Serial send failed")
}
} catch (ex: Throwable) {
ex.printStackTrace()
@@ -94,98 +107,63 @@ object ECRHelper {
}
}
- // ─── Everything below is unchanged ────────────────────────────────────────
-
- fun connect(bundle: Bundle) {
- call {
- anyExecute(ecrService) {
- connect(bundle, ecrConnection)
- }
+ fun clrBuffer() {
+ try {
+ serialPort?.clrBuffer()
+ } catch (ex: Throwable) {
+ ex.printStackTrace()
}
}
fun disconnect() {
- call { anyExecute(ecrService) { disconnect() } }
+ releaseSerialPort()
}
- fun registerECRListener() {
- call { anyExecute(ecrService) { register(ecrListener) } }
- }
-
- fun unregisterECRListener() {
- call { anyExecute(ecrService) { unregister(ecrListener) } }
- }
-
- fun extensionMethod(bundle: Bundle) {
- call { anyExecute(ecrService) { extensionMethod(bundle) } }
- }
-
- private val ecrListener = object : ECRListener.Stub() {
- override fun onReceive(byteArray: ByteArray?) {
- if (byteArray != null) {
- val string = String(byteArray, StandardCharsets.UTF_8)
- Logger.e(TAG, "onReceive string: $string")
- onECRReceive(byteArray)
- }
- }
- }
-
- private val requestCallback = object : ECRRequestCallback.Stub() {
- override fun onSuccess() {
- Logger.e(TAG, "onSuccess")
- onSendSuccess()
- }
-
- override fun onFailure(code: Int, massage: String?) {
- Logger.e(TAG, "onFailure code: $code massage: $massage")
- onSendFailure(code, massage ?: "failure")
- }
- }
-
- private val ecrConnection = object : ECRConnection.Stub() {
- override fun onConnected() {
- Logger.d(TAG, "onConnected")
- ECRUtil.connected = true
- onECRConnected()
- }
-
- override fun onDisconnected(code: Int, massage: String?) {
- Logger.d(TAG, "onDisconnected code: $code massage: $massage")
- ECRUtil.connected = false
- onECRDisconnected(code, massage ?: "failure")
- }
- }
-
- fun bindECRService() {
- ECRServiceKernel.getInstance().bindService(ECRUtil.context, connectionCallback)
- }
-
- private val connectionCallback = object : ECRServiceKernel.ConnectionCallback {
- override fun onServiceConnected() {
- Logger.e(TAG, "onServiceConnected")
- ecrService = ECRServiceKernel.getInstance().ecrService
- onBindSuccess()
- }
-
- override fun onServiceDisconnected() {
- Logger.e(TAG, "onServiceDisconnected")
- ECRUtil.connected = false
- ecrService = null
- onBindFailure()
- }
- }
-
- private fun call(block: () -> Unit) {
+ fun releaseSerialPort() {
+ receiving.set(false)
try {
- val bridgeService = ecrService
- if (bridgeService != null) {
- block()
- } else {
- onECRDisconnected(-100, "The bind ECRService failure")
- }
+ receiveThread?.interrupt()
+ } catch (_: Throwable) {
+ }
+ receiveThread = null
+
+ try {
+ val result = serialPort?.disconnect() ?: SdkResult.SerialPort_Port_Not_Open
+ LogUtil.d(TAG, "SerialPortDriver disconnect: $result")
} catch (ex: Throwable) {
ex.printStackTrace()
- onECRDisconnected(-200, ex.localizedMessage ?: "")
+ } finally {
+ serialPort = null
+ ECRUtil.connected = false
+ onECRDisconnected(0, "disconnected")
}
}
-}
\ No newline at end of file
+
+ private fun startReceiveLoop() {
+ if (receiving.getAndSet(true)) {
+ return
+ }
+
+ receiveThread = Thread({
+ val buffer = ByteArray(RECV_BUFFER_SIZE)
+ while (receiving.get()) {
+ val port = serialPort ?: break
+ try {
+ val len = port.recv(buffer, buffer.size, RECV_TIMEOUT_MS)
+ if (len > 0) {
+ val data = buffer.copyOf(len)
+ LogUtil.d(TAG, "Serial recv len=$len")
+ LogUtil.d(TAG," Receive packet : "+ ByteUtil.bytes2HexStr(data));
+ onECRReceive(data)
+ }
+ // timeout / no data: keep listening
+ } catch (ex: Throwable) {
+ if (receiving.get()) {
+ LogUtil.e(TAG, "Serial recv error: ${ex.message}")
+ }
+ }
+ }
+ receiving.set(false)
+ }, "ECR-SerialRecv").also { it.start() }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 53bde9e..9614d1c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,9 +33,9 @@ include ':mpulib'
//include ':ecr-client-lib'
//include ':ecr-service'
+//include ':ecr-service-lib'
include ':xpay'
include ':ecr'
-include ':ecr-service-lib'
include ':link-service-lib'
include ':qrgen-lib'
include ':nexdlkey-lib'