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'