refactor transaction

This commit is contained in:
riz081 2025-06-23 11:38:31 +07:00
parent 0af0e836b1
commit ece79942c1
12 changed files with 1672 additions and 1806 deletions

View File

@ -73,12 +73,8 @@
android:exported="false" />
<activity
android:name=".kredit.CreateTransactionActivity"
android:exported="false" />
<activity
android:name=".kredit.EmvTransactionActivity"
android:exported="false" />
android:name=".transaction.CreateTransactionActivity"
android:exported="false" />
<activity
android:name=".kredit.CreditCardActivity"

View File

@ -19,8 +19,7 @@ import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.button.MaterialButton;
import com.example.bdkipoc.kredit.CreateTransactionActivity;
import com.example.bdkipoc.kredit.EmvTransactionActivity;
import com.example.bdkipoc.transaction.CreateTransactionActivity;
import com.example.bdkipoc.kredit.CreditCardActivity;
public class MainActivity extends AppCompatActivity {

View File

@ -1,179 +0,0 @@
package com.example.bdkipoc.kredit;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.example.bdkipoc.R;
import java.text.NumberFormat;
import java.util.Locale;
/**
* CreateTransactionActivity - Handle amount input and transaction creation
*/
public class CreateTransactionActivity extends AppCompatActivity {
private static final String TAG = "CreateTransaction";
// UI Components
private TextView tvAmountDisplay;
private TextView tvModeIndicator;
private Button btnConfirm;
private Button btnToggleMode;
// Amount Input Keypad
private Button btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn00, btnClear;
// State Management
private String transactionAmount = "0";
private boolean isEMVMode = true; // Default to EMV mode
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_transaction);
initViews();
setupListeners();
updateAmountDisplay();
updateModeDisplay();
}
private void initViews() {
// Setup Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("Input Nominal Transaksi");
}
// Initialize UI components
tvAmountDisplay = findViewById(R.id.tv_amount_display);
tvModeIndicator = findViewById(R.id.tv_mode_indicator);
btnConfirm = findViewById(R.id.btn_confirm);
btnToggleMode = findViewById(R.id.btn_toggle_mode);
// Initialize keypad buttons
btn1 = findViewById(R.id.btn_1);
btn2 = findViewById(R.id.btn_2);
btn3 = findViewById(R.id.btn_3);
btn4 = findViewById(R.id.btn_4);
btn5 = findViewById(R.id.btn_5);
btn6 = findViewById(R.id.btn_6);
btn7 = findViewById(R.id.btn_7);
btn8 = findViewById(R.id.btn_8);
btn9 = findViewById(R.id.btn_9);
btn0 = findViewById(R.id.btn_0);
btn00 = findViewById(R.id.btn_00);
btnClear = findViewById(R.id.btn_clear);
}
private void setupListeners() {
// Keypad number listeners
View.OnClickListener numberClickListener = v -> {
Button btn = (Button) v;
String number = btn.getText().toString();
appendToAmount(number);
};
btn1.setOnClickListener(numberClickListener);
btn2.setOnClickListener(numberClickListener);
btn3.setOnClickListener(numberClickListener);
btn4.setOnClickListener(numberClickListener);
btn5.setOnClickListener(numberClickListener);
btn6.setOnClickListener(numberClickListener);
btn7.setOnClickListener(numberClickListener);
btn8.setOnClickListener(numberClickListener);
btn9.setOnClickListener(numberClickListener);
btn0.setOnClickListener(numberClickListener);
btn00.setOnClickListener(numberClickListener);
// Clear button
btnClear.setOnClickListener(v -> clearAmount());
// Confirm button
btnConfirm.setOnClickListener(v -> handleConfirmAmount());
// Toggle mode button
btnToggleMode.setOnClickListener(v -> toggleEMVMode());
}
private void appendToAmount(String number) {
// Remove leading zeros and handle maximum amount
String currentAmount = transactionAmount.equals("0") ? "" : transactionAmount;
String newAmount = currentAmount + number;
// Limit to reasonable amount (max 999999999 = 9,999,999.99)
if (newAmount.length() <= 9) {
transactionAmount = newAmount;
updateAmountDisplay();
}
}
private void clearAmount() {
if (transactionAmount.length() > 1) {
transactionAmount = transactionAmount.substring(0, transactionAmount.length() - 1);
} else {
transactionAmount = "0";
}
updateAmountDisplay();
}
private void updateAmountDisplay() {
if (tvAmountDisplay != null) {
long amountCents = Long.parseLong(transactionAmount);
double amountRupiah = amountCents / 100.0;
NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
String formattedAmount = formatter.format(amountRupiah);
tvAmountDisplay.setText(formattedAmount);
}
}
private void handleConfirmAmount() {
long amountCents = Long.parseLong(transactionAmount);
if (amountCents < 100) { // Minimum Rp 1.00
showToast("Minimum amount is Rp 1.00");
return;
}
// Start EMV Transaction Activity
Intent intent = new Intent(this, EmvTransactionActivity.class);
intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
intent.putExtra("EMV_MODE", isEMVMode);
startActivity(intent);
finish(); // Remove this activity from stack
}
private void toggleEMVMode() {
isEMVMode = !isEMVMode;
updateModeDisplay();
showToast("Mode switched to: " + (isEMVMode ? "EMV Mode" : "Simple Mode"));
}
private void updateModeDisplay() {
String mode = isEMVMode ? "EMV Mode (Full Card Data)" : "Simple Mode (Basic Detection)";
if (tvModeIndicator != null) {
tvModeIndicator.setText("Current Mode: " + mode);
}
if (btnToggleMode != null) {
btnToggleMode.setText(isEMVMode ? "Switch to Simple" : "Switch to EMV");
}
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}

View File

@ -32,6 +32,8 @@ import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.example.bdkipoc.transaction.CreateTransactionActivity;
/**
* CreditCardActivity - Display detailed transaction results and TLV data
*/

View File

@ -0,0 +1,504 @@
package com.example.bdkipoc.transaction;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.example.bdkipoc.R;
import com.example.bdkipoc.transaction.managers.CardScannerManager;
import com.example.bdkipoc.transaction.managers.EMVManager;
import com.example.bdkipoc.transaction.managers.ModalManager;
import com.example.bdkipoc.transaction.managers.PinPadManager;
import java.text.NumberFormat;
import java.util.Locale;
import com.example.bdkipoc.kredit.CreditCardActivity;
/**
* CreateTransactionActivity - Refactored with Manager Classes
* Handles amount input and card scanning in one screen
* Located in transaction package
*/
public class CreateTransactionActivity extends AppCompatActivity implements
EMVManager.EMVManagerCallback,
CardScannerManager.CardScannerCallback,
PinPadManager.PinPadManagerCallback {
private static final String TAG = "CreateTransaction";
// UI Components - Amount Input
private TextView tvAmountDisplay;
private TextView tvModeIndicator;
private Button btnConfirm;
private Button btnToggleMode;
private ProgressBar progressBar;
// Amount Input Keypad
private Button btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn00, btnClear;
// State Management
private String transactionAmount = "0";
private boolean isEMVMode = true;
// Manager Classes
private EMVManager emvManager;
private CardScannerManager cardScannerManager;
private PinPadManager pinPadManager;
private ModalManager modalManager;
// EMV Dialog
private AlertDialog mAppSelectDialog;
private int mSelectIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_transaction);
initViews();
initManagers();
setupListeners();
updateAmountDisplay();
updateModeDisplay();
}
private void initViews() {
// Setup Toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("Input Nominal Transaksi");
}
// Initialize amount input UI components
tvAmountDisplay = findViewById(R.id.tv_amount_display);
tvModeIndicator = findViewById(R.id.tv_mode_indicator);
btnConfirm = findViewById(R.id.btn_confirm);
btnToggleMode = findViewById(R.id.btn_toggle_mode);
progressBar = findViewById(R.id.progress_bar);
// Initialize keypad buttons
btn1 = findViewById(R.id.btn_1);
btn2 = findViewById(R.id.btn_2);
btn3 = findViewById(R.id.btn_3);
btn4 = findViewById(R.id.btn_4);
btn5 = findViewById(R.id.btn_5);
btn6 = findViewById(R.id.btn_6);
btn7 = findViewById(R.id.btn_7);
btn8 = findViewById(R.id.btn_8);
btn9 = findViewById(R.id.btn_9);
btn0 = findViewById(R.id.btn_0);
btn00 = findViewById(R.id.btn_00);
btnClear = findViewById(R.id.btn_clear);
}
private void initManagers() {
// Initialize Modal Manager
FrameLayout modalOverlay = findViewById(R.id.modal_overlay);
TextView modalText = findViewById(R.id.modal_text);
ImageView modalIcon = findViewById(R.id.modal_icon);
modalManager = new ModalManager(modalOverlay, modalText, modalIcon);
// Initialize other managers
emvManager = new EMVManager(this);
cardScannerManager = new CardScannerManager(this);
pinPadManager = new PinPadManager(this);
// Initialize EMV data
emvManager.initEMVData();
Log.d(TAG, "All managers initialized");
}
private void setupListeners() {
// Keypad number listeners
View.OnClickListener numberClickListener = v -> {
Button btn = (Button) v;
String number = btn.getText().toString();
appendToAmount(number);
};
btn1.setOnClickListener(numberClickListener);
btn2.setOnClickListener(numberClickListener);
btn3.setOnClickListener(numberClickListener);
btn4.setOnClickListener(numberClickListener);
btn5.setOnClickListener(numberClickListener);
btn6.setOnClickListener(numberClickListener);
btn7.setOnClickListener(numberClickListener);
btn8.setOnClickListener(numberClickListener);
btn9.setOnClickListener(numberClickListener);
btn0.setOnClickListener(numberClickListener);
btn00.setOnClickListener(numberClickListener);
// Clear button
btnClear.setOnClickListener(v -> clearAmount());
// Confirm button - shows modal and starts scanning
btnConfirm.setOnClickListener(v -> handleConfirmAmount());
// Toggle mode button
btnToggleMode.setOnClickListener(v -> toggleEMVMode());
// Modal overlay click to close (only if not processing)
findViewById(R.id.modal_overlay).setOnClickListener(v -> {
if (modalManager.isShowing() && !cardScannerManager.isScanning()) {
modalManager.hideModal();
}
});
}
// ====== AMOUNT INPUT METHODS ======
private void appendToAmount(String number) {
String currentAmount = transactionAmount.equals("0") ? "" : transactionAmount;
String newAmount = currentAmount + number;
if (newAmount.length() <= 9) {
transactionAmount = newAmount;
updateAmountDisplay();
}
}
private void clearAmount() {
if (transactionAmount.length() > 1) {
transactionAmount = transactionAmount.substring(0, transactionAmount.length() - 1);
} else {
transactionAmount = "0";
}
updateAmountDisplay();
}
private void updateAmountDisplay() {
if (tvAmountDisplay != null) {
long amountCents = Long.parseLong(transactionAmount);
double amountRupiah = amountCents / 100.0;
NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
String formattedAmount = formatter.format(amountRupiah);
tvAmountDisplay.setText(formattedAmount);
}
}
private void handleConfirmAmount() {
long amountCents = Long.parseLong(transactionAmount);
if (amountCents < 100) { // Minimum Rp 1.00
showToast("Minimum amount is Rp 1.00");
return;
}
// Show modal and start card scanning
showModalAndStartScanning();
}
private void showModalAndStartScanning() {
Log.d(TAG, "Starting card scanning with modal...");
// Show modal first
modalManager.showScanCardModal();
// Start scanning after short delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
cardScannerManager.startScanning(isEMVMode);
}, 1000);
}
private void toggleEMVMode() {
isEMVMode = !isEMVMode;
updateModeDisplay();
showToast("Mode switched to: " + (isEMVMode ? "EMV Mode" : "Simple Mode"));
}
private void updateModeDisplay() {
String mode = isEMVMode ? "EMV Mode (Full Card Data)" : "Simple Mode (Basic Detection)";
if (tvModeIndicator != null) {
tvModeIndicator.setText("Current Mode: " + mode);
}
if (btnToggleMode != null) {
btnToggleMode.setText(isEMVMode ? "Switch to Simple" : "Switch to EMV");
}
}
// ====== CARD SCANNER CALLBACK METHODS ======
@Override
public void onCardDetected(String cardType, Bundle cardData) {
Log.d(TAG, "Simple card detected: " + cardType);
modalManager.showProcessingModal("Kartu " + cardType + " Ditemukan - Memproses...");
// Navigate to results after short delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
navigateToResults(cardType, cardData, null);
}, 1500);
}
@Override
public void onEMVCardDetected(int cardType) {
Log.d(TAG, "EMV card detected: " + cardType);
modalManager.showProcessingModal("Kartu Ditemukan - Memulai EMV...");
// Start EMV transaction process
emvManager.startEMVTransaction(transactionAmount, cardType);
}
@Override
public void onScanError(String errorMessage) {
Log.e(TAG, "Scan error: " + errorMessage);
showToast(errorMessage);
modalManager.hideModal();
// Auto-restart scanning after error
restartScanningAfterDelay();
}
@Override
public void onScanProgress(String message) {
Log.d(TAG, "Scan progress: " + message);
// Can update UI with progress if needed
}
// ====== EMV MANAGER CALLBACK METHODS ======
@Override
public void onAppSelect(String[] candidateNames) {
modalManager.hideModal();
showAppSelectDialog(candidateNames);
}
@Override
public void onFinalAppSelect() {
emvManager.importFinalAppSelectStatus(0);
}
@Override
public void onConfirmCardNo(String cardNo) {
modalManager.showProcessingModal("Mengkonfirmasi Nomor Kartu...");
// Auto-confirm after short delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Log.d(TAG, "Auto-confirming card number");
emvManager.importCardNoStatus(0);
}, 1500);
}
@Override
public void onCertVerify(String certInfo) {
modalManager.showProcessingModal("Memverifikasi Sertifikat...");
// Auto-confirm after short delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Log.d(TAG, "Auto-confirming certificate");
emvManager.importCertStatus(0);
}, 1500);
}
@Override
public void onShowPinPad(int pinType) {
Log.d(TAG, "Initializing PIN pad...");
modalManager.hideModal();
String cardNo = emvManager.getCardNo();
if (cardNo != null && cardNo.length() >= 13) {
pinPadManager.initPinPad(cardNo, pinType);
} else {
showToast("Invalid card number for PIN");
emvManager.importPinInputStatus(3); // Error
}
}
@Override
public void onOnlineProcess() {
modalManager.showProcessingModal("Memproses Otorisasi Online...");
emvManager.mockOnlineProcess();
}
@Override
public void onSignature() {
emvManager.importSignatureStatus(0);
}
@Override
public void onTransactionSuccess(int code, String desc) {
Log.d(TAG, "EMV Transaction successful");
modalManager.hideModal();
String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
navigateToResults(cardType, null, emvManager.getCardNo());
}
@Override
public void onTransactionFailed(int code, String desc) {
Log.e(TAG, "EMV Transaction failed: " + desc + " (Code: " + code + ")");
modalManager.hideModal();
if (code == -50009) {
// EMV process conflict - reset and retry
Log.d(TAG, "EMV process conflict detected - resetting...");
showToast("EMV busy, resetting...");
resetEMVAndRetry();
} else {
// Other errors - show message and restart scanning
showToast("Transaction failed: " + desc);
restartScanningAfterDelay();
}
}
// ====== PIN PAD CALLBACK METHODS ======
@Override
public void onPinInputLength(int length) {
String dots = "";
for (int i = 0; i < length; i++) {
dots += "";
}
// Can show PIN input feedback on UI if needed
Log.d(TAG, "PIN input length: " + length);
}
@Override
public void onPinInputConfirmed(byte[] pinBlock) {
modalManager.showProcessingModal("PIN Dikonfirmasi - Memproses...");
if (pinBlock != null) {
emvManager.importPinInputStatus(0); // PIN entered
} else {
emvManager.importPinInputStatus(2); // PIN bypassed
}
}
@Override
public void onPinInputCancelled() {
showToast("PIN input cancelled by user");
modalManager.hideModal();
emvManager.importPinInputStatus(1); // Cancelled
}
@Override
public void onPinInputError(int code, String message) {
Log.e(TAG, "PIN Error: " + message);
showToast("PIN Error: " + message);
modalManager.hideModal();
emvManager.importPinInputStatus(3); // Error
}
// ====== HELPER METHODS ======
private void showAppSelectDialog(String[] candidateNames) {
mAppSelectDialog = new AlertDialog.Builder(this)
.setTitle("Please select application")
.setNegativeButton("Cancel", (dialog, which) -> emvManager.importAppSelect(-1))
.setPositiveButton("OK", (dialog, which) -> emvManager.importAppSelect(mSelectIndex))
.setSingleChoiceItems(candidateNames, 0, (dialog, which) -> mSelectIndex = which)
.create();
mSelectIndex = 0;
mAppSelectDialog.show();
}
private void navigateToResults(String cardType, Bundle cardData, String cardNo) {
modalManager.hideModal();
Intent intent = new Intent(this, CreditCardActivity.class);
intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
intent.putExtra("CARD_TYPE", cardType);
intent.putExtra("EMV_MODE", isEMVMode);
if (cardData != null) {
intent.putExtra("CARD_DATA", cardData);
}
if (cardNo != null) {
intent.putExtra("CARD_NO", cardNo);
}
startActivity(intent);
finish();
}
private void restartScanningAfterDelay() {
Log.d(TAG, "Restarting scanning after delay...");
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isFinishing()) {
showModalAndStartScanning();
}
}, 2000);
}
private void resetEMVAndRetry() {
new Thread(() -> {
try {
Log.d(TAG, "Resetting EMV process...");
// Cancel any existing operations
cardScannerManager.stopScanning();
Thread.sleep(500);
// Reset EMV process
emvManager.resetEMVProcess();
Thread.sleep(1500);
runOnUiThread(() -> {
Log.d(TAG, "EMV reset complete - auto-restarting scan");
showToast("EMV reset - restarting scan");
restartScanningAfterDelay();
});
} catch (Exception e) {
Log.e(TAG, "Error resetting EMV: " + e.getMessage(), e);
runOnUiThread(() -> {
showToast("Reset failed - " + e.getMessage());
restartScanningAfterDelay();
});
}
}).start();
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onSupportNavigateUp() {
cleanup();
finish();
return true;
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy - cleaning up resources");
cleanup();
super.onDestroy();
}
private void cleanup() {
try {
// Stop all operations
if (cardScannerManager != null) {
cardScannerManager.stopScanning();
}
if (emvManager != null) {
emvManager.resetEMVProcess();
}
if (pinPadManager != null) {
pinPadManager.cancelPinInput();
}
if (modalManager != null) {
modalManager.hideModal();
}
} catch (Exception e) {
Log.e(TAG, "Error during cleanup: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,258 @@
package com.example.bdkipoc.transaction.managers;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.sunmi.pay.hardware.aidl.AidlConstants.CardType;
import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2;
import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2;
/**
* CardScannerManager - Handles card detection for both EMV and Simple modes
*/
public class CardScannerManager {
private static final String TAG = "CardScannerManager";
private CardScannerCallback callback;
private boolean isProcessing = false;
public interface CardScannerCallback {
void onCardDetected(String cardType, Bundle cardData);
void onEMVCardDetected(int cardType);
void onScanError(String errorMessage);
void onScanProgress(String message);
}
public CardScannerManager(CardScannerCallback callback) {
this.callback = callback;
}
public void startScanning(boolean isEMVMode) {
if (isProcessing) {
Log.d(TAG, "Card check already in progress - ignoring call");
return;
}
Log.d(TAG, "Starting card check - setting isProcessing = true");
isProcessing = true;
try {
// Small delay to ensure everything is ready
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (isProcessing) {
if (isEMVMode) {
startEMVCardCheck();
} else {
startSimpleCardCheck();
}
}
}, 500);
} catch (Exception e) {
Log.e(TAG, "Error in startScanning: " + e.getMessage(), e);
handleScanError("Error: " + e.getMessage());
}
}
public void stopScanning() {
try {
if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) {
MyApplication.app.readCardOptV2.cancelCheckCard();
}
isProcessing = false;
Log.d(TAG, "Card scanning stopped");
} catch (Exception e) {
Log.e(TAG, "Error stopping card check: " + e.getMessage());
}
}
public boolean isScanning() {
return isProcessing;
}
private void startEMVCardCheck() {
try {
if (callback != null) {
callback.onScanProgress("EMV Mode: Starting card scan...");
}
int cardType = AidlConstantsV2.CardType.NFC.getValue() | AidlConstantsV2.CardType.IC.getValue();
Log.d(TAG, "Starting EMV checkCard with cardType: " + cardType);
MyApplication.app.readCardOptV2.checkCard(cardType, mEMVCheckCardCallback, 60);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Error in startEMVCardCheck: " + e.getMessage());
handleScanError("Error starting EMV card scan: " + e.getMessage());
}
}
private void startSimpleCardCheck() {
try {
if (!MyApplication.app.isConnectPaySDK()) {
if (callback != null) {
callback.onScanProgress("Connecting to PaySDK...");
}
MyApplication.app.bindPaySDKService();
return;
}
if (callback != null) {
callback.onScanProgress("Simple Mode: Starting card scan...");
}
int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue();
MyApplication.app.readCardOptV2.checkCard(cardType, mSimpleCheckCardCallback, 60);
} catch (Exception e) {
e.printStackTrace();
handleScanError("Error starting card scan: " + e.getMessage());
}
}
private void handleScanError(String errorMessage) {
Log.e(TAG, "Scan error: " + errorMessage);
isProcessing = false;
if (callback != null) {
callback.onScanError(errorMessage);
}
}
public void resetScanning() {
Log.d(TAG, "Resetting scanning state");
isProcessing = false;
}
// Simple Card Detection Callback
private final CheckCardCallbackV2 mSimpleCheckCardCallback = new CheckCardCallbackV2.Stub() {
@Override
public void findMagCard(Bundle info) throws RemoteException {
Log.d(TAG, "Simple Mode: findMagCard callback triggered");
isProcessing = false;
if (callback != null) {
callback.onCardDetected("MAGNETIC", info);
}
}
@Override
public void findICCard(String atr) throws RemoteException {
Bundle info = new Bundle();
info.putString("atr", atr);
isProcessing = false;
if (callback != null) {
callback.onCardDetected("IC", info);
}
}
@Override
public void findRFCard(String uuid) throws RemoteException {
Bundle info = new Bundle();
info.putString("uuid", uuid);
isProcessing = false;
if (callback != null) {
callback.onCardDetected("NFC", info);
}
}
@Override
public void onError(int code, String message) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onScanError("Card error: " + message);
}
}
@Override
public void findICCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onCardDetected("IC", info);
}
}
@Override
public void findRFCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onCardDetected("NFC", info);
}
}
@Override
public void onErrorEx(Bundle info) throws RemoteException {
isProcessing = false;
String msg = info.getString("message", "Unknown error");
if (callback != null) {
callback.onScanError("Card error: " + msg);
}
}
};
// EMV Card Detection Callback
private final CheckCardCallbackV2 mEMVCheckCardCallback = new CheckCardCallbackV2.Stub() {
@Override
public void findMagCard(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onCardDetected("MAGNETIC", info);
}
}
@Override
public void findICCard(String atr) throws RemoteException {
MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0);
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.IC.getValue());
}
}
@Override
public void findRFCard(String uuid) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.NFC.getValue());
}
}
@Override
public void onError(int code, String message) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onScanError("EMV Error: " + message);
}
}
@Override
public void findICCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.IC.getValue());
}
}
@Override
public void findRFCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.NFC.getValue());
}
}
@Override
public void onErrorEx(Bundle info) throws RemoteException {
isProcessing = false;
String msg = info.getString("message", "Unknown error");
if (callback != null) {
callback.onScanError("EMV Error: " + msg);
}
}
};
}

View File

@ -0,0 +1,417 @@
package com.example.bdkipoc.transaction.managers;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2;
import com.sunmi.pay.hardware.aidlv2.bean.EMVCandidateV2;
import com.sunmi.pay.hardware.aidlv2.emv.EMVListenerV2;
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2;
import java.util.List;
/**
* EMVManager - Handles all EMV related operations
*/
public class EMVManager {
private static final String TAG = "EMVManager";
private EMVOptV2 mEMVOptV2;
private EMVManagerCallback callback;
// EMV Process Variables
private int mCardType;
private String mCardNo;
private int mPinType;
private String mCertInfo;
private int mProcessStep;
public interface EMVManagerCallback {
void onAppSelect(String[] candidateNames);
void onFinalAppSelect();
void onConfirmCardNo(String cardNo);
void onCertVerify(String certInfo);
void onShowPinPad(int pinType);
void onOnlineProcess();
void onSignature();
void onTransactionSuccess(int code, String desc);
void onTransactionFailed(int code, String desc);
}
public EMVManager(EMVManagerCallback callback) {
this.callback = callback;
initEMVComponents();
}
private void initEMVComponents() {
if (MyApplication.app != null) {
mEMVOptV2 = MyApplication.app.emvOptV2;
Log.d(TAG, "EMV components initialized");
} else {
Log.e(TAG, "MyApplication.app is null");
}
}
public void initEMVData() {
try {
if (mEMVOptV2 != null) {
mEMVOptV2.initEmvProcess();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
try {
initEmvTlvData();
Log.d(TAG, "EMV data initialized successfully");
} catch (Exception e) {
Log.e(TAG, "Error in delayed EMV init: " + e.getMessage());
}
}, 500);
}
} catch (Exception e) {
Log.e(TAG, "Error initializing EMV data: " + e.getMessage());
}
}
private void initEmvTlvData() {
try {
// Set PayPass (MasterCard) TLV data
String[] tagsPayPass = {"DF8117", "DF8118", "DF8119", "DF811F", "DF811E", "DF812C",
"DF8123", "DF8124", "DF8125", "DF8126", "DF811B", "DF811D", "DF8122", "DF8120", "DF8121"};
String[] valuesPayPass = {"E0", "F8", "F8", "E8", "00", "00",
"000000000000", "000000100000", "999999999999", "000000100000",
"30", "02", "0000000000", "000000000000", "000000000000"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS, tagsPayPass, valuesPayPass);
// Set AMEX TLV data
String[] tagsAE = {"9F6D", "9F6E", "9F33", "9F35", "DF8168", "DF8167", "DF8169", "DF8170"};
String[] valuesAE = {"C0", "D8E00000", "E0E888", "22", "00", "00", "00", "60"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_AE, tagsAE, valuesAE);
// Set JCB TLV data
String[] tagsJCB = {"9F53", "DF8161"};
String[] valuesJCB = {"708000", "7F00"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_JCB, tagsJCB, valuesJCB);
// Set DPAS TLV data
String[] tagsDPAS = {"9F66"};
String[] valuesDPAS = {"B600C000"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_DPAS, tagsDPAS, valuesDPAS);
// Set Flash TLV data
String[] tagsFLASH = {"9F58", "9F59", "9F5A", "9F5D", "9F5E"};
String[] valuesFLASH = {"03", "D88700", "00", "000000000000", "E000"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_FLASH, tagsFLASH, valuesFLASH);
// Set Pure TLV data
String[] tagsPURE = {"DF7F", "DF8134", "DF8133"};
String[] valuesPURE = {"A0000007271010", "DF", "36006043F9"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PURE, tagsPURE, valuesPURE);
Bundle bundle = new Bundle();
bundle.putBoolean("optOnlineRes", true);
mEMVOptV2.setTermParamEx(bundle);
} catch (RemoteException e) {
Log.e(TAG, "Error setting EMV TLV data: " + e.getMessage());
}
}
public void startEMVTransaction(String transactionAmount, int cardType) {
if (mProcessStep != 0) {
Log.d(TAG, "EMV transaction already in progress (step: " + mProcessStep + ") - ignoring call");
return;
}
Log.d(TAG, "Starting EMV transaction process");
mProcessStep = 1;
mCardType = cardType;
try {
mEMVOptV2.initEmvProcess();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
try {
if (mProcessStep <= 0) {
Log.d(TAG, "EMV process was cancelled - not starting");
return;
}
Bundle bundle = new Bundle();
bundle.putString("amount", transactionAmount);
bundle.putString("transType", "00");
bundle.putInt("flowType", AidlConstantsV2.EMV.FlowType.TYPE_EMV_STANDARD);
bundle.putInt("cardType", mCardType);
Log.d(TAG, "Starting transactProcessEx with reset EMV");
mEMVOptV2.transactProcessEx(bundle, mEMVListener);
} catch (Exception e) {
Log.e(TAG, "Error in delayed EMV start: " + e.getMessage(), e);
if (callback != null) {
callback.onTransactionFailed(-1, "Error starting EMV: " + e.getMessage());
}
}
}, 300);
} catch (Exception e) {
Log.e(TAG, "Error starting EMV transaction: " + e.getMessage());
if (callback != null) {
callback.onTransactionFailed(-1, "Error starting EMV transaction: " + e.getMessage());
}
}
}
public void resetEMVProcess() {
try {
if (mEMVOptV2 != null) {
mEMVOptV2.initEmvProcess();
}
mProcessStep = 0;
mCardNo = null;
Log.d(TAG, "EMV process reset");
} catch (Exception e) {
Log.e(TAG, "Error resetting EMV process: " + e.getMessage());
}
}
// EMV Import Methods
public void importAppSelect(int selectIndex) {
try {
mEMVOptV2.importAppSelect(selectIndex);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importFinalAppSelectStatus(int status) {
try {
mEMVOptV2.importAppFinalSelectStatus(status);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void importCardNoStatus(int status) {
try {
mEMVOptV2.importCardNoStatus(status);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importCertStatus(int status) {
try {
mEMVOptV2.importCertStatus(status);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importPinInputStatus(int inputResult) {
try {
mEMVOptV2.importPinInputStatus(mPinType, inputResult);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importSignatureStatus(int status) {
try {
mEMVOptV2.importSignatureStatus(status);
} catch (Exception e) {
e.printStackTrace();
}
}
public void mockOnlineProcess() {
new Thread(() -> {
try {
Thread.sleep(2000);
try {
String[] tags = {"71", "72", "91", "8A", "89"};
String[] values = {"", "", "", "", ""};
byte[] out = new byte[1024];
int len = mEMVOptV2.importOnlineProcStatus(0, tags, values, out);
if (len < 0) {
Log.e(TAG, "importOnlineProcessStatus error,code:" + len);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
// Getters
public String getCardNo() {
return mCardNo;
}
public int getCardType() {
return mCardType;
}
public int getPinType() {
return mPinType;
}
// Helper Methods
public String maskCardNumber(String cardNo) {
if (cardNo == null || cardNo.length() < 8) {
return cardNo;
}
String first4 = cardNo.substring(0, 4);
String last4 = cardNo.substring(cardNo.length() - 4);
StringBuilder middle = new StringBuilder();
for (int i = 0; i < cardNo.length() - 8; i++) {
middle.append("*");
}
return first4 + middle.toString() + last4;
}
public String[] getCandidateNames(List<EMVCandidateV2> candiList) {
if (candiList == null || candiList.size() == 0) return new String[0];
String[] result = new String[candiList.size()];
for (int i = 0; i < candiList.size(); i++) {
EMVCandidateV2 candi = candiList.get(i);
String name = candi.appPreName;
name = TextUtils.isEmpty(name) ? candi.appLabel : name;
name = TextUtils.isEmpty(name) ? candi.appName : name;
name = TextUtils.isEmpty(name) ? "" : name;
result[i] = name;
}
return result;
}
// EMV Listener
private final EMVListenerV2 mEMVListener = new EMVListenerV2.Stub() {
@Override
public void onWaitAppSelect(List<EMVCandidateV2> appNameList, boolean isFirstSelect) throws RemoteException {
Log.d(TAG, "onWaitAppSelect isFirstSelect:" + isFirstSelect);
mProcessStep = 1; // EMV_APP_SELECT
String[] candidateNames = getCandidateNames(appNameList);
if (callback != null) {
callback.onAppSelect(candidateNames);
}
}
@Override
public void onAppFinalSelect(String tag9F06Value) throws RemoteException {
Log.d(TAG, "onAppFinalSelect tag9F06Value:" + tag9F06Value);
mProcessStep = 2; // EMV_FINAL_APP_SELECT
if (callback != null) {
callback.onFinalAppSelect();
}
}
@Override
public void onConfirmCardNo(String cardNo) throws RemoteException {
Log.d(TAG, "onConfirmCardNo cardNo:" + maskCardNumber(cardNo));
mCardNo = cardNo;
mProcessStep = 3; // EMV_CONFIRM_CARD_NO
if (callback != null) {
callback.onConfirmCardNo(cardNo);
}
}
@Override
public void onRequestShowPinPad(int pinType, int remainTime) throws RemoteException {
Log.d(TAG, "onRequestShowPinPad pinType:" + pinType + " remainTime:" + remainTime);
mPinType = pinType;
mProcessStep = 5; // EMV_SHOW_PIN_PAD
if (callback != null) {
callback.onShowPinPad(pinType);
}
}
@Override
public void onRequestSignature() throws RemoteException {
Log.d(TAG, "onRequestSignature");
mProcessStep = 7; // EMV_SIGNATURE
if (callback != null) {
callback.onSignature();
}
}
@Override
public void onCertVerify(int certType, String certInfo) throws RemoteException {
Log.d(TAG, "onCertVerify certType:" + certType + " certInfo:" + certInfo);
mCertInfo = certInfo;
mProcessStep = 4; // EMV_CERT_VERIFY
if (callback != null) {
callback.onCertVerify(certInfo);
}
}
@Override
public void onOnlineProc() throws RemoteException {
Log.d(TAG, "onOnlineProcess");
mProcessStep = 6; // EMV_ONLINE_PROCESS
if (callback != null) {
callback.onOnlineProcess();
}
}
@Override
public void onCardDataExchangeComplete() throws RemoteException {
Log.d(TAG, "onCardDataExchangeComplete");
if (mCardType == AidlConstantsV2.CardType.NFC.getValue()) {
MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0);
}
}
@Override
public void onTransResult(int code, String desc) throws RemoteException {
Log.d(TAG, "onTransResult code:" + code + " desc:" + desc);
if (code == 1 || code == 2 || code == 5 || code == 6) {
if (callback != null) {
callback.onTransactionSuccess(code, desc);
}
} else {
if (callback != null) {
callback.onTransactionFailed(code, desc);
}
}
}
@Override
public void onConfirmationCodeVerified() throws RemoteException {
Log.d(TAG, "onConfirmationCodeVerified");
}
@Override
public void onRequestDataExchange(String cardNo) throws RemoteException {
Log.d(TAG, "onRequestDataExchange,cardNo:" + cardNo);
mEMVOptV2.importDataExchangeStatus(0);
}
@Override
public void onTermRiskManagement() throws RemoteException {
Log.d(TAG, "onTermRiskManagement");
mEMVOptV2.importTermRiskManagementStatus(0);
}
@Override
public void onPreFirstGenAC() throws RemoteException {
Log.d(TAG, "onPreFirstGenAC");
mEMVOptV2.importPreFirstGenACStatus(0);
}
@Override
public void onDataStorageProc(String[] containerID, String[] containerContent) throws RemoteException {
Log.d(TAG, "onDataStorageProc");
String[] tags = new String[0];
String[] values = new String[0];
mEMVOptV2.importDataStorage(tags, values);
}
};
}

View File

@ -0,0 +1,121 @@
package com.example.bdkipoc.transaction.managers;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.util.Log;
import com.example.bdkipoc.R;
/**
* ModalManager - Handles modal UI operations
*/
public class ModalManager {
private static final String TAG = "ModalManager";
private FrameLayout modalOverlay;
private TextView modalText;
private ImageView modalIcon;
private Animation fadeIn;
private Animation fadeOut;
private boolean isModalShowing = false;
public ModalManager(FrameLayout modalOverlay, TextView modalText, ImageView modalIcon) {
this.modalOverlay = modalOverlay;
this.modalText = modalText;
this.modalIcon = modalIcon;
initAnimations();
}
private void initAnimations() {
fadeIn = AnimationUtils.loadAnimation(modalOverlay.getContext(), android.R.anim.fade_in);
fadeOut = AnimationUtils.loadAnimation(modalOverlay.getContext(), android.R.anim.fade_out);
fadeIn.setDuration(300);
fadeOut.setDuration(300);
}
public void showScanCardModal() {
if (isModalShowing) return;
modalOverlay.post(() -> {
modalText.setText("Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat");
modalIcon.setImageResource(R.drawable.ic_card_insert);
modalOverlay.setVisibility(View.VISIBLE);
modalOverlay.startAnimation(fadeIn);
isModalShowing = true;
Log.d(TAG, "Modal scan card shown");
});
}
public void showProcessingModal(String message) {
if (!isModalShowing) {
modalOverlay.post(() -> {
modalText.setText(message);
modalIcon.setImageResource(R.drawable.ic_card_insert);
modalOverlay.setVisibility(View.VISIBLE);
modalOverlay.startAnimation(fadeIn);
isModalShowing = true;
Log.d(TAG, "Modal processing shown: " + message);
});
} else {
// Just update text if modal already showing
modalOverlay.post(() -> {
modalText.setText(message);
Log.d(TAG, "Modal text updated: " + message);
});
}
}
public void hideModal() {
if (!isModalShowing) return;
modalOverlay.post(() -> {
fadeOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
modalOverlay.setVisibility(View.GONE);
isModalShowing = false;
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
modalOverlay.startAnimation(fadeOut);
Log.d(TAG, "Modal hidden");
});
}
public boolean isShowing() {
return isModalShowing;
}
public void updateText(String text) {
if (isModalShowing) {
modalOverlay.post(() -> {
modalText.setText(text);
Log.d(TAG, "Modal text updated: " + text);
});
}
}
public void updateIcon(int iconResource) {
if (isModalShowing) {
modalOverlay.post(() -> {
modalIcon.setImageResource(iconResource);
Log.d(TAG, "Modal icon updated");
});
}
}
}

View File

@ -0,0 +1,142 @@
package com.example.bdkipoc.transaction.managers;
import android.os.RemoteException;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.example.bdkipoc.utils.ByteUtil;
import com.sunmi.pay.hardware.aidlv2.AidlErrorCodeV2;
import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2;
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2;
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2;
/**
* PinPadManager - Handles PIN pad operations
*/
public class PinPadManager {
private static final String TAG = "PinPadManager";
private PinPadOptV2 mPinPadOptV2;
private PinPadManagerCallback callback;
public interface PinPadManagerCallback {
void onPinInputLength(int length);
void onPinInputConfirmed(byte[] pinBlock);
void onPinInputCancelled();
void onPinInputError(int code, String message);
}
public PinPadManager(PinPadManagerCallback callback) {
this.callback = callback;
initPinPadComponents();
}
private void initPinPadComponents() {
if (MyApplication.app != null) {
mPinPadOptV2 = MyApplication.app.pinPadOptV2;
Log.d(TAG, "PIN Pad components initialized");
} else {
Log.e(TAG, "MyApplication.app is null");
}
}
public void initPinPad(String cardNo, int pinType) {
Log.d(TAG, "========== PIN PAD INITIALIZATION ==========");
try {
if (mPinPadOptV2 == null) {
throw new IllegalStateException("PIN Pad service not available");
}
if (cardNo == null || cardNo.length() < 13) {
throw new IllegalArgumentException("Invalid card number for PIN");
}
PinPadConfigV2 pinPadConfig = new PinPadConfigV2();
pinPadConfig.setPinPadType(0);
pinPadConfig.setPinType(pinType);
pinPadConfig.setOrderNumKey(true); // Set to true for normal order, false for random
String panForPin = cardNo.substring(cardNo.length() - 13, cardNo.length() - 1);
byte[] panBytes = panForPin.getBytes("US-ASCII");
pinPadConfig.setPan(panBytes);
pinPadConfig.setTimeout(60 * 1000);
pinPadConfig.setPinKeyIndex(12);
pinPadConfig.setMaxInput(12);
pinPadConfig.setMinInput(0);
pinPadConfig.setKeySystem(0);
pinPadConfig.setAlgorithmType(0);
Log.d(TAG, "Initializing PIN pad with config");
mPinPadOptV2.initPinPad(pinPadConfig, mPinPadListener);
} catch (Exception e) {
Log.e(TAG, "PIN pad initialization failed: " + e.getMessage());
if (callback != null) {
callback.onPinInputError(-1, "PIN Error: " + e.getMessage());
}
}
}
public void cancelPinInput() {
try {
if (mPinPadOptV2 != null) {
// Cancel PIN input if needed
Log.d(TAG, "PIN input cancelled");
}
} catch (Exception e) {
Log.e(TAG, "Error cancelling PIN input: " + e.getMessage());
}
}
// PIN Pad Listener
private final PinPadListenerV2 mPinPadListener = new PinPadListenerV2.Stub() {
@Override
public void onPinLength(int len) throws RemoteException {
Log.d(TAG, "PIN input length: " + len);
if (callback != null) {
callback.onPinInputLength(len);
}
}
@Override
public void onConfirm(int i, byte[] pinBlock) throws RemoteException {
Log.d(TAG, "PIN input confirmed");
if (pinBlock != null) {
String hexStr = ByteUtil.bytes2HexStr(pinBlock);
Log.d(TAG, "PIN block received: " + hexStr);
if (callback != null) {
callback.onPinInputConfirmed(pinBlock);
}
} else {
Log.d(TAG, "PIN bypass confirmed");
if (callback != null) {
callback.onPinInputConfirmed(null);
}
}
}
@Override
public void onCancel() throws RemoteException {
Log.d(TAG, "PIN input cancelled by user");
if (callback != null) {
callback.onPinInputCancelled();
}
}
@Override
public void onError(int code) throws RemoteException {
Log.e(TAG, "PIN pad error: " + code);
String msg = AidlErrorCodeV2.valueOf(code).getMsg();
if (callback != null) {
callback.onPinInputError(code, msg);
}
}
@Override
public void onHover(int event, byte[] data) throws RemoteException {
Log.d(TAG, "PIN pad hover event: " + event);
}
};
}

View File

@ -1,203 +1,276 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:background="@color/colorBackground"
tools:context=".kredit.CreateTransactionActivity">
<LinearLayout
<!-- Main Content -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="match_parent"
android:fillViewport="true">
<!-- Toolbar -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary_blue"
android:theme="@style/CustomToolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<!-- Content Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
android:orientation="vertical">
<!-- Header Section -->
<androidx.cardview.widget.CardView
<!-- Toolbar -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary_blue"
android:theme="@style/CustomToolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<!-- Content Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
<!-- Header Section -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="MASUKKAN NOMINAL"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#333333"
android:gravity="center"
android:layout_marginBottom="16dp" />
android:orientation="vertical"
android:padding="20dp">
<!-- Amount Display -->
<TextView
android:id="@+id/tv_amount_display"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Rp 0,00"
style="@style/AmountDisplayStyle"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="MASUKKAN NOMINAL"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#333333"
android:gravity="center"
android:layout_marginBottom="16dp" />
<!-- Mode Indicator -->
<TextView
android:id="@+id/tv_mode_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Current Mode: EMV Mode (Full Card Data)"
style="@style/ModeIndicatorStyle" />
<!-- Amount Display -->
<TextView
android:id="@+id/tv_amount_display"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Rp 0,00"
style="@style/AmountDisplayStyle"
android:layout_marginBottom="8dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Mode Indicator -->
<TextView
android:id="@+id/tv_mode_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Current Mode: EMV Mode (Full Card Data)"
style="@style/ModeIndicatorStyle" />
<!-- Mode Toggle Button -->
<Button
android:id="@+id/btn_toggle_mode"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="24dp"
android:text="Switch to Simple"
style="@style/OutlineButton" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Keypad Section -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<!-- Mode Toggle Button -->
<Button
android:id="@+id/btn_toggle_mode"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="24dp"
android:text="Switch to Simple"
style="@style/OutlineButton" />
<LinearLayout
<!-- Keypad Section -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Keypad Input"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#333333"
android:gravity="center"
android:layout_marginBottom="16dp" />
android:orientation="vertical"
android:padding="16dp">
<!-- Keypad Grid -->
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Keypad Input"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#333333"
android:gravity="center"
android:layout_marginBottom="16dp" />
<!-- Row 1: 1, 2, 3 -->
<Button
android:id="@+id/btn_1"
style="@style/KeypadButton"
android:text="1" />
<!-- Keypad Grid -->
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_gravity="center">
<Button
android:id="@+id/btn_2"
style="@style/KeypadButton"
android:text="2" />
<!-- Row 1: 1, 2, 3 -->
<Button
android:id="@+id/btn_1"
style="@style/KeypadButton"
android:text="1" />
<Button
android:id="@+id/btn_3"
style="@style/KeypadButton"
android:text="3" />
<Button
android:id="@+id/btn_2"
style="@style/KeypadButton"
android:text="2" />
<!-- Row 2: 4, 5, 6 -->
<Button
android:id="@+id/btn_4"
style="@style/KeypadButton"
android:text="4" />
<Button
android:id="@+id/btn_3"
style="@style/KeypadButton"
android:text="3" />
<Button
android:id="@+id/btn_5"
style="@style/KeypadButton"
android:text="5" />
<!-- Row 2: 4, 5, 6 -->
<Button
android:id="@+id/btn_4"
style="@style/KeypadButton"
android:text="4" />
<Button
android:id="@+id/btn_6"
style="@style/KeypadButton"
android:text="6" />
<Button
android:id="@+id/btn_5"
style="@style/KeypadButton"
android:text="5" />
<!-- Row 3: 7, 8, 9 -->
<Button
android:id="@+id/btn_7"
style="@style/KeypadButton"
android:text="7" />
<Button
android:id="@+id/btn_6"
style="@style/KeypadButton"
android:text="6" />
<Button
android:id="@+id/btn_8"
style="@style/KeypadButton"
android:text="8" />
<!-- Row 3: 7, 8, 9 -->
<Button
android:id="@+id/btn_7"
style="@style/KeypadButton"
android:text="7" />
<Button
android:id="@+id/btn_9"
style="@style/KeypadButton"
android:text="9" />
<Button
android:id="@+id/btn_8"
style="@style/KeypadButton"
android:text="8" />
<!-- Row 4: 00, 0, Clear -->
<Button
android:id="@+id/btn_00"
style="@style/KeypadButton"
android:text="00" />
<Button
android:id="@+id/btn_9"
style="@style/KeypadButton"
android:text="9" />
<Button
android:id="@+id/btn_0"
style="@style/KeypadButton"
android:text="0" />
<!-- Row 4: 00, 0, Clear -->
<Button
android:id="@+id/btn_00"
style="@style/KeypadButton"
android:text="00" />
<Button
android:id="@+id/btn_clear"
style="@style/KeypadButtonSecondary"
android:text="⌫" />
<Button
android:id="@+id/btn_0"
style="@style/KeypadButton"
android:text="0" />
</GridLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/btn_clear"
style="@style/KeypadButtonSecondary"
android:text="⌫" />
<!-- Confirm Button -->
<Button
android:id="@+id/btn_confirm"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="KONFIRMASI NOMINAL"
style="@style/PrimaryButton"
android:layout_marginBottom="16dp" />
</GridLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Instructions -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="• Gunakan keypad untuk memasukkan nominal\n• Minimal transaksi Rp 1,00\n• Pilih mode EMV untuk data lengkap kartu"
style="@style/HintTextStyle" />
<!-- Confirm Button -->
<Button
android:id="@+id/btn_confirm"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="KONFIRMASI NOMINAL"
style="@style/PrimaryButton"
android:layout_marginBottom="16dp" />
<!-- Instructions -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="• Gunakan keypad untuk memasukkan nominal\n• Minimal transaksi Rp 1,00\n• Pilih mode EMV untuk data lengkap kartu"
style="@style/HintTextStyle" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</ScrollView>
<!-- Hidden Progress Bar for Card Scanning -->
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:visibility="gone"
android:indeterminateTint="@color/primary_blue" />
<!-- Modal Overlay with Blur Effect for Card Scanning -->
<FrameLayout
android:id="@+id/modal_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:visibility="gone"
android:clickable="true"
android:focusable="true">
<!-- Modal Content -->
<androidx.cardview.widget.CardView
android:id="@+id/modal_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="32dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<!-- Card Icon -->
<ImageView
android:id="@+id/modal_icon"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_card_insert"
android:layout_marginBottom="24dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
app:tint="@color/primary_blue" />
<!-- Main Text -->
<TextView
android:id="@+id/modal_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"
style="@style/StatusTextStyle"
android:textAlignment="center"
android:gravity="center"
android:lineSpacingExtra="4dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
</FrameLayout>

View File

@ -1,160 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".kredit.EmvTransactionActivity">
<!-- Main Content -->
<LinearLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Toolbar -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary_blue"
android:theme="@style/CustomToolbarTheme"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<!-- Main Content Container with Center Alignment -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp">
<!-- Status Modal Card - Centered and Compact -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<!-- Header Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Card Scanner"
style="@style/HeaderTextStyle"
android:layout_marginBottom="24dp" />
<!-- Card Reader Animation/Icon -->
<ImageView
android:id="@+id/iv_card_reader"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
android:layout_marginBottom="24dp"
android:src="@drawable/ic_card_insert"
android:scaleType="centerInside"
app:tint="@color/primary_blue" />
<!-- Status Text -->
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ready for card scanning..."
style="@style/StatusTextStyle"
android:layout_marginBottom="24dp" />
<!-- Progress Indicator -->
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:visibility="visible"
android:indeterminateTint="@color/primary_blue"
android:layout_marginBottom="16dp" />
<!-- Instructions Text -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Please insert, tap, or swipe your card\nScanning will start automatically"
style="@style/HintTextStyle"
android:gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</LinearLayout>
<!-- Modal Overlay with Blur Effect -->
<FrameLayout
android:id="@+id/modal_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:visibility="gone"
android:clickable="true"
android:focusable="true">
<!-- Modal Content -->
<androidx.cardview.widget.CardView
android:id="@+id/modal_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="32dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<!-- Card Icon -->
<ImageView
android:id="@+id/modal_icon"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_card_insert"
android:layout_marginBottom="24dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
app:tint="@color/primary_blue" />
<!-- Main Text -->
<TextView
android:id="@+id/modal_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"
style="@style/StatusTextStyle"
android:textAlignment="center"
android:gravity="center"
android:lineSpacingExtra="4dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
</FrameLayout>