diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f5ad28e..8836e28 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,29 +45,32 @@ - + + - + + + + + @@ -77,7 +80,7 @@ android:exported="false" /> diff --git a/app/src/main/java/com/example/bdkipoc/MainActivity.java b/app/src/main/java/com/example/bdkipoc/MainActivity.java index 6ef0fa0..9c6e46e 100644 --- a/app/src/main/java/com/example/bdkipoc/MainActivity.java +++ b/app/src/main/java/com/example/bdkipoc/MainActivity.java @@ -19,8 +19,11 @@ import androidx.core.view.WindowInsetsCompat; import com.google.android.material.button.MaterialButton; +import com.example.bdkipoc.cetakulang.ReprintActivity; +import com.example.bdkipoc.cetakulang.ReprintAdapterActivity; + import com.example.bdkipoc.transaction.CreateTransactionActivity; -import com.example.bdkipoc.kredit.CreditCardActivity; +import com.example.bdkipoc.transaction.ResultTransactionActivity; public class MainActivity extends AppCompatActivity { @@ -86,12 +89,9 @@ public class MainActivity extends AppCompatActivity { // 6 dummy menus should be hidden initially CardView[] dummyCards = { - findViewById(R.id.card_dummy_menu_1), - findViewById(R.id.card_dummy_menu_2), - findViewById(R.id.card_dummy_menu_3), - findViewById(R.id.card_dummy_menu_4), - findViewById(R.id.card_dummy_menu_5), - findViewById(R.id.card_dummy_menu_6) + findViewById(R.id.card_bantuan), + findViewById(R.id.card_info_toko), + findViewById(R.id.card_pengaturan), }; for (CardView card : dummyCards) { @@ -138,21 +138,17 @@ public class MainActivity extends AppCompatActivity { R.id.card_kartu_debit, R.id.card_qris, // Row 2 (Always visible - 3 items) + R.id.card_transfer, R.id.card_uang_elektronik, R.id.card_cetak_ulang, - R.id.card_settlement, // Row 3 (Always visible - 3 items) + R.id.card_refund, + R.id.card_settlement, R.id.card_histori, - R.id.card_bantuan, - R.id.card_info_toko, // Row 4 (Hidden initially - 3 items) - R.id.card_dummy_menu_1, - R.id.card_dummy_menu_2, - R.id.card_dummy_menu_3, - // Row 5 (Hidden initially - 3 items) - R.id.card_dummy_menu_4, - R.id.card_dummy_menu_5, - R.id.card_dummy_menu_6 + R.id.card_bantuan, + R.id.card_info_toko, + R.id.card_pengaturan, }; // Set up click listeners for all cards @@ -163,33 +159,30 @@ public class MainActivity extends AppCompatActivity { if (cardId == R.id.card_kartu_kredit) { startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); } else if (cardId == R.id.card_kartu_debit) { - startActivity(new Intent(MainActivity.this, PaymentActivity.class)); + startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); } else if (cardId == R.id.card_qris) { startActivity(new Intent(MainActivity.this, QrisActivity.class)); + // Col-2 + } else if (cardId == R.id.card_transfer) { + startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); } else if (cardId == R.id.card_uang_elektronik) { - startActivity(new Intent(MainActivity.this, PaymentActivity.class)); + startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); } else if (cardId == R.id.card_cetak_ulang) { - startActivity(new Intent(MainActivity.this, TransactionActivity.class)); + startActivity(new Intent(MainActivity.this, ReprintActivity.class)); + // Col-3 + } else if (cardId == R.id.card_refund) { + startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); } else if (cardId == R.id.card_settlement) { Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show(); } else if (cardId == R.id.card_histori) { - Toast.makeText(this, "Histori - Coming Soon", Toast.LENGTH_SHORT).show(); + startActivity(new Intent(MainActivity.this, HistoryActivity.class)); + // Col-4 } else if (cardId == R.id.card_bantuan) { Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show(); } else if (cardId == R.id.card_info_toko) { Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show(); - } else if (cardId == R.id.card_dummy_menu_1) { - Toast.makeText(this, "Dummy Menu 1 - Coming Soon", Toast.LENGTH_SHORT).show(); - } else if (cardId == R.id.card_dummy_menu_2) { - Toast.makeText(this, "Dummy Menu 2 - Coming Soon", Toast.LENGTH_SHORT).show(); - } else if (cardId == R.id.card_dummy_menu_3) { - Toast.makeText(this, "Dummy Menu 3 - Coming Soon", Toast.LENGTH_SHORT).show(); - } else if (cardId == R.id.card_dummy_menu_4) { - Toast.makeText(this, "Dummy Menu 4 - Coming Soon", Toast.LENGTH_SHORT).show(); - } else if (cardId == R.id.card_dummy_menu_5) { - Toast.makeText(this, "Dummy Menu 5 - Coming Soon", Toast.LENGTH_SHORT).show(); - } else if (cardId == R.id.card_dummy_menu_6) { - Toast.makeText(this, "Dummy Menu 6 - Coming Soon", Toast.LENGTH_SHORT).show(); + } else if (cardId == R.id.card_pengaturan) { + Toast.makeText(this, "Pengaturan - Coming Soon", Toast.LENGTH_SHORT).show(); } else { // Fallback for any other cards Toast.makeText(this, "Menu Diklik: " + getResources().getResourceEntryName(cardId), Toast.LENGTH_SHORT).show(); @@ -200,12 +193,9 @@ public class MainActivity extends AppCompatActivity { // Get references to ONLY the dummy cards that need to be toggled CardView[] toggleableCards = { - findViewById(R.id.card_dummy_menu_1), - findViewById(R.id.card_dummy_menu_2), - findViewById(R.id.card_dummy_menu_3), - findViewById(R.id.card_dummy_menu_4), - findViewById(R.id.card_dummy_menu_5), - findViewById(R.id.card_dummy_menu_6) + findViewById(R.id.card_bantuan), + findViewById(R.id.card_info_toko), + findViewById(R.id.card_pengaturan), }; // Set up "Lainnya" button click listener diff --git a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java deleted file mode 100644 index 0e88fe3..0000000 --- a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java +++ /dev/null @@ -1,530 +0,0 @@ -package com.example.bdkipoc; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.app.Dialog; -import android.content.Intent; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RadioGroup; -import android.widget.TextView; -import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; - -public class PaymentActivity extends AppCompatActivity { - - // Views - private EditText editTextAmount; - private Button confirmButton; - private LinearLayout backNavigation; - private ImageView backArrow; - private TextView toolbarTitle; - - // Numpad buttons - private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000; - private ImageView btnDelete; - - // Modal components - private Dialog paymentModal; - - // Data - private StringBuilder currentAmount = new StringBuilder(); - private static final int MAX_AMOUNT_LENGTH = 12; - - // Animation - private Handler animationHandler = new Handler(Looper.getMainLooper()); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Set status bar color programmatically - setStatusBarColor(); - - setContentView(R.layout.activity_payment); - - initializeViews(); - setupClickListeners(); - setupInitialStates(); - setupModal(); - } - - private void setStatusBarColor() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.parseColor("#E31937")); // Red color - - // Make status bar icons white (for dark red background) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - View decorView = window.getDecorView(); - decorView.setSystemUiVisibility(0); // Clear light status bar flag - } - } - } - - private void initializeViews() { - // Main views - editTextAmount = findViewById(R.id.editTextAmount); - confirmButton = findViewById(R.id.confirmButton); - backNavigation = findViewById(R.id.back_navigation); - backArrow = findViewById(R.id.backArrow); - toolbarTitle = findViewById(R.id.toolbarTitle); - - // Numpad buttons - btn1 = findViewById(R.id.btn1); - btn2 = findViewById(R.id.btn2); - btn3 = findViewById(R.id.btn3); - btn4 = findViewById(R.id.btn4); - btn5 = findViewById(R.id.btn5); - btn6 = findViewById(R.id.btn6); - btn7 = findViewById(R.id.btn7); - btn8 = findViewById(R.id.btn8); - btn9 = findViewById(R.id.btn9); - btn0 = findViewById(R.id.btn0); - btn000 = findViewById(R.id.btn000); - btnDelete = findViewById(R.id.btnDelete); - } - - private void setupModal() { - // Create modal dialog - paymentModal = new Dialog(this); - paymentModal.setContentView(R.layout.modal_layout); - - // Remove background dimming - make it fully transparent - if (paymentModal.getWindow() != null) { - paymentModal.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - } - - // Setup modal listeners - setupModalListeners(); - } - - private void setupModalListeners() { - // Make modal non-cancelable by touching outside - paymentModal.setCanceledOnTouchOutside(false); - - // Auto dismiss after 3 seconds (simulate card processing) - Handler modalHandler = new Handler(Looper.getMainLooper()); - paymentModal.setOnShowListener(dialog -> { - modalHandler.postDelayed(() -> { - if (paymentModal != null && paymentModal.isShowing()) { - // First dismiss modal, then navigate - paymentModal.dismiss(); - - // Add small delay to ensure modal is fully dismissed - animationHandler.postDelayed(() -> { - navigateToPinActivity(); - }, 100); - } - }, 3000); - }); - } - - private void setupClickListeners() { - // Back navigation - entire LinearLayout is clickable - backNavigation.setOnClickListener(v -> { - addClickAnimation(v); - navigateBack(); - }); - - // Individual back arrow (for additional touch area) - backArrow.setOnClickListener(v -> { - addClickAnimation(v); - navigateBack(); - }); - - // Toolbar title (also clickable for back navigation) - toolbarTitle.setOnClickListener(v -> { - addClickAnimation(v); - navigateBack(); - }); - - // Numpad listeners - btn1.setOnClickListener(v -> handleNumpadClick(v, "1")); - btn2.setOnClickListener(v -> handleNumpadClick(v, "2")); - btn3.setOnClickListener(v -> handleNumpadClick(v, "3")); - btn4.setOnClickListener(v -> handleNumpadClick(v, "4")); - btn5.setOnClickListener(v -> handleNumpadClick(v, "5")); - btn6.setOnClickListener(v -> handleNumpadClick(v, "6")); - btn7.setOnClickListener(v -> handleNumpadClick(v, "7")); - btn8.setOnClickListener(v -> handleNumpadClick(v, "8")); - btn9.setOnClickListener(v -> handleNumpadClick(v, "9")); - btn0.setOnClickListener(v -> handleNumpadClick(v, "0")); - btn000.setOnClickListener(v -> handleNumpadClick(v, "000")); - - // Delete button - btnDelete.setOnClickListener(v -> { - addClickAnimation(v); - deleteLastDigit(); - }); - - // Confirm button - NOW SHOWS MODAL INSTEAD OF DIRECT PAYMENT - confirmButton.setOnClickListener(v -> { - if (confirmButton.isEnabled()) { - addButtonClickAnimation(v); - showPaymentModal(); - } - }); - } - - private void navigateBack() { - // Simple back navigation without card animation - finish(); - } - - private void handleNumpadClick(View view, String digit) { - addClickAnimation(view); - addDigit(digit); - } - - private void setupInitialStates() { - // Set initial amount display - editTextAmount.setText(""); - - // Set initial button state - updateButtonState(); - - // Disable EditText input (only numpad input allowed) - editTextAmount.setFocusable(false); - editTextAmount.setClickable(false); - editTextAmount.setCursorVisible(false); - } - - private void addDigit(String digit) { - // Validate input length - if (currentAmount.length() >= MAX_AMOUNT_LENGTH) { - showToast("Maksimal " + MAX_AMOUNT_LENGTH + " digit"); - return; - } - - // Handle leading zeros - if (currentAmount.length() == 0) { - if (digit.equals("000")) { - // Don't allow 000 as first input - return; - } - currentAmount.append(digit); - } else if (currentAmount.length() == 1 && currentAmount.toString().equals("0")) { - if (!digit.equals("000")) { - // Replace single 0 with new digit - currentAmount = new StringBuilder(digit); - } else { - return; - } - } else { - currentAmount.append(digit); - } - - updateAmountDisplay(); - updateButtonState(); - addInputFeedback(); - } - - private void deleteLastDigit() { - if (currentAmount.length() > 0) { - String current = currentAmount.toString(); - - // If current ends with 000, remove all three digits - if (current.endsWith("000") && current.length() >= 3) { - currentAmount.delete(currentAmount.length() - 3, currentAmount.length()); - } else { - currentAmount.deleteCharAt(currentAmount.length() - 1); - } - - updateAmountDisplay(); - updateButtonState(); - addDeleteFeedback(); - } - } - - private void updateAmountDisplay() { - String amount = currentAmount.toString(); - - if (amount.isEmpty() || amount.equals("0")) { - editTextAmount.setText(""); - } else { - String formattedAmount = formatCurrency(amount); - editTextAmount.setText(formattedAmount); - } - } - - private String formatCurrency(String amount) { - if (TextUtils.isEmpty(amount) || amount.equals("0")) { - return ""; - } - - try { - long number = Long.parseLong(amount); - return String.format("%,d", number).replace(',', '.'); - } catch (NumberFormatException e) { - return amount; - } - } - - private void updateButtonState() { - boolean hasValidAmount = currentAmount.length() > 0 && - !currentAmount.toString().equals("0") && - !currentAmount.toString().isEmpty(); - - confirmButton.setEnabled(hasValidAmount); - - if (hasValidAmount) { - // Active state - confirmButton.setBackgroundResource(R.drawable.button_active_background); - confirmButton.setTextColor(Color.WHITE); - confirmButton.setAlpha(1.0f); - } else { - // Inactive state - confirmButton.setBackgroundResource(R.drawable.button_inactive_background); - confirmButton.setTextColor(Color.parseColor("#999999")); - confirmButton.setAlpha(0.6f); - } - } - - // NEW METHOD: Show payment modal instead of direct payment processing - private void showPaymentModal() { - String amount = currentAmount.toString(); - - if (TextUtils.isEmpty(amount) || amount.equals("0")) { - showToast("Masukkan jumlah pembayaran"); - return; - } - - try { - long amountValue = Long.parseLong(amount); - - // Validate minimum amount - if (amountValue < 1000) { - showToast("Minimal pembayaran Rp 1.000"); - return; - } - - // Validate maximum amount - if (amountValue > 999999999L) { - showToast("Maksimal pembayaran Rp 999.999.999"); - return; - } - - // Show modal with animation - showModalWithAnimation(); - - } catch (NumberFormatException e) { - showToast("Format jumlah tidak valid"); - } - } - - private void showModalWithAnimation() { - // Add debug log - showToast("Showing card modal..."); - - paymentModal.show(); - - // Add slide-up animation - View modalView = paymentModal.findViewById(android.R.id.content); - if (modalView != null) { - ObjectAnimator slideUp = ObjectAnimator.ofFloat(modalView, "translationY", 300f, 0f); - ObjectAnimator fadeIn = ObjectAnimator.ofFloat(modalView, "alpha", 0f, 1f); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(slideUp, fadeIn); - animatorSet.setDuration(300); - animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - animatorSet.start(); - } - } - - private void dismissModal() { - if (paymentModal != null && paymentModal.isShowing()) { - // Add slide-down animation before dismissing - View modalView = paymentModal.findViewById(android.R.id.content); - if (modalView != null) { - ObjectAnimator slideDown = ObjectAnimator.ofFloat(modalView, "translationY", 0f, 300f); - ObjectAnimator fadeOut = ObjectAnimator.ofFloat(modalView, "alpha", 1f, 0f); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(slideDown, fadeOut); - animatorSet.setDuration(200); - animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - - animatorSet.addListener(new android.animation.AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(android.animation.Animator animation) { - paymentModal.dismiss(); - } - }); - - animatorSet.start(); - } else { - paymentModal.dismiss(); - } - } - } - - private void processModalConfirmation() { - // This method is no longer needed since modal auto-dismisses - } - - private void navigateToPinActivity() { - String amount = currentAmount.toString(); - - // Add debug log - showToast("Navigating to PIN Activity..."); - - try { - long amountValue = Long.parseLong(amount); - - // Launch PIN Activity with amount data - Intent intent = new Intent(this, PinActivity.class); - intent.putExtra(PinActivity.EXTRA_SOURCE_ACTIVITY, "PaymentActivity"); - intent.putExtra(PinActivity.EXTRA_AMOUNT, String.valueOf(amountValue)); - startActivityForResult(intent, 100); - - } catch (NumberFormatException e) { - showToast("Format jumlah tidak valid: " + e.getMessage()); - } catch (Exception e) { - showToast("Error navigating to PIN: " + e.getMessage()); - } - } - - private void processCardPayment() { - // This method is called after PIN verification is successful - // Now process the actual payment - String amount = currentAmount.toString(); - - try { - long amountValue = Long.parseLong(amount); - - // Show processing message - showToast("PIN berhasil diverifikasi! Memproses pembayaran..."); - - // Process the final payment - processPayment(amountValue); - - } catch (NumberFormatException e) { - showToast("Format jumlah tidak valid"); - } - } - - private void processPayment(long amount) { - // Show loading state - confirmButton.setText("Memproses..."); - confirmButton.setEnabled(false); - - // Simulate payment processing - animationHandler.postDelayed(() -> { - // Show success message - showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount))); - - // Reset state and go back (this is final step after PIN verification) - resetPaymentState(); - navigateBack(); - }, 2000); - } - - private void resetPaymentState() { - currentAmount = new StringBuilder(); - updateAmountDisplay(); - updateButtonState(); - confirmButton.setText("Konfirmasi"); - } - - // Animation methods (only for numpad interactions) - private void addClickAnimation(View view) { - ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f); - ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(scaleX, scaleY); - animatorSet.setDuration(150); - animatorSet.start(); - } - - private void addButtonClickAnimation(View view) { - ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f); - ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(scaleX, scaleY); - animatorSet.setDuration(200); - animatorSet.start(); - } - - private void addInputFeedback() { - ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextAmount, "alpha", 0.7f, 1f); - fadeIn.setDuration(200); - fadeIn.start(); - } - - private void addDeleteFeedback() { - ObjectAnimator shake = ObjectAnimator.ofFloat(editTextAmount, "translationX", 0f, -10f, 10f, 0f); - shake.setDuration(300); - shake.start(); - } - - // Utility methods - private void showToast(String message) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - - @Override - public void onBackPressed() { - // Check if modal is showing, dismiss it first - if (paymentModal != null && paymentModal.isShowing()) { - dismissModal(); - } else { - navigateBack(); - } - - super.onBackPressed(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - // Handle result from PIN Activity - if (resultCode == RESULT_OK && data != null) { - boolean pinVerified = data.getBooleanExtra("pin_verified", false); - if (pinVerified) { - // PIN verification successful, process payment - processCardPayment(); - } - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (animationHandler != null) { - animationHandler.removeCallbacksAndMessages(null); - } - - // Clean up modal - if (paymentModal != null && paymentModal.isShowing()) { - paymentModal.dismiss(); - } - } - - // Public methods for testing - public String getCurrentAmount() { - return currentAmount.toString(); - } - - public boolean isConfirmButtonEnabled() { - return confirmButton.isEnabled(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/PinActivity.java b/app/src/main/java/com/example/bdkipoc/PinActivity.java deleted file mode 100644 index 2bdb9d1..0000000 --- a/app/src/main/java/com/example/bdkipoc/PinActivity.java +++ /dev/null @@ -1,558 +0,0 @@ -package com.example.bdkipoc; - -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Intent; -import android.graphics.Color; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; - -public class PinActivity extends AppCompatActivity { - - // Intent Extra Keys - public static final String EXTRA_TITLE = "extra_title"; - public static final String EXTRA_SUBTITLE = "extra_subtitle"; - public static final String EXTRA_AMOUNT = "extra_amount"; - public static final String EXTRA_SOURCE_ACTIVITY = "extra_source_activity"; - - // Views - private EditText editTextPin; - private Button confirmButton; - private LinearLayout backNavigation; - private ImageView backArrow; - private TextView toolbarTitle; - - // Success screen views - private View successScreen; - private ImageView successIcon; - private TextView successMessage; - - // Numpad buttons - private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000; - private ImageView btnDelete; - - // Data - private StringBuilder currentPin = new StringBuilder(); - private static final int MAX_PIN_LENGTH = 6; - private static final int MIN_PIN_LENGTH = 4; - - // Extra data from intent - private String sourceActivity; - private String amount; - - // Animation - private Handler animationHandler = new Handler(Looper.getMainLooper()); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Set status bar color programmatically - setStatusBarColor(); - - setContentView(R.layout.activity_pin); - - // Get intent extras - getIntentExtras(); - - initializeViews(); - setupClickListeners(); - setupInitialStates(); - setupSuccessScreen(); - } - - private void setStatusBarColor() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getWindow(); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.parseColor("#E31937")); // Red color - - // Make status bar icons white (for dark red background) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - View decorView = window.getDecorView(); - decorView.setSystemUiVisibility(0); // Clear light status bar flag - } - } - } - - private void getIntentExtras() { - Intent intent = getIntent(); - if (intent != null) { - sourceActivity = intent.getStringExtra(EXTRA_SOURCE_ACTIVITY); - amount = intent.getStringExtra(EXTRA_AMOUNT); - } - } - - private void initializeViews() { - // Main views - editTextPin = findViewById(R.id.editTextPin); - confirmButton = findViewById(R.id.confirmButton); - backNavigation = findViewById(R.id.back_navigation); - backArrow = findViewById(R.id.backArrow); - toolbarTitle = findViewById(R.id.toolbarTitle); - - // Success screen views - successScreen = findViewById(R.id.success_screen); - successIcon = findViewById(R.id.success_icon); - successMessage = findViewById(R.id.success_message); - - // Numpad buttons - btn1 = findViewById(R.id.btn1); - btn2 = findViewById(R.id.btn2); - btn3 = findViewById(R.id.btn3); - btn4 = findViewById(R.id.btn4); - btn5 = findViewById(R.id.btn5); - btn6 = findViewById(R.id.btn6); - btn7 = findViewById(R.id.btn7); - btn8 = findViewById(R.id.btn8); - btn9 = findViewById(R.id.btn9); - btn0 = findViewById(R.id.btn0); - btn000 = findViewById(R.id.btn000); - btnDelete = findViewById(R.id.btnDelete); - } - - private void setupSuccessScreen() { - // Initially hide success screen - if (successScreen != null) { - successScreen.setVisibility(View.GONE); - } - } - - private void setupClickListeners() { - // Back navigation - entire LinearLayout is clickable - backNavigation.setOnClickListener(v -> { - addClickAnimation(v); - navigateBack(); - }); - - // Individual back arrow (for additional touch area) - backArrow.setOnClickListener(v -> { - addClickAnimation(v); - navigateBack(); - }); - - // Toolbar title (also clickable for back navigation) - toolbarTitle.setOnClickListener(v -> { - addClickAnimation(v); - navigateBack(); - }); - - // Numpad listeners - btn1.setOnClickListener(v -> handleNumpadClick(v, "1")); - btn2.setOnClickListener(v -> handleNumpadClick(v, "2")); - btn3.setOnClickListener(v -> handleNumpadClick(v, "3")); - btn4.setOnClickListener(v -> handleNumpadClick(v, "4")); - btn5.setOnClickListener(v -> handleNumpadClick(v, "5")); - btn6.setOnClickListener(v -> handleNumpadClick(v, "6")); - btn7.setOnClickListener(v -> handleNumpadClick(v, "7")); - btn8.setOnClickListener(v -> handleNumpadClick(v, "8")); - btn9.setOnClickListener(v -> handleNumpadClick(v, "9")); - btn0.setOnClickListener(v -> handleNumpadClick(v, "0")); - btn000.setOnClickListener(v -> handleNumpadClick(v, "000")); - - // Delete button - btnDelete.setOnClickListener(v -> { - addClickAnimation(v); - deleteLastDigit(); - }); - - // Confirm button - confirmButton.setOnClickListener(v -> { - if (confirmButton.isEnabled()) { - addButtonClickAnimation(v); - handleConfirmPin(); - } - }); - } - - private void navigateBack() { - finish(); - } - - private void handleNumpadClick(View view, String digit) { - addClickAnimation(view); - addDigit(digit); - } - - private void setupInitialStates() { - // Set initial PIN display - editTextPin.setText(""); - - // Set initial button state - updateButtonState(); - - // Disable EditText input (only numpad input allowed) - editTextPin.setFocusable(false); - editTextPin.setClickable(false); - editTextPin.setCursorVisible(false); - } - - private void addDigit(String digit) { - // Validate input length - if (currentPin.length() >= MAX_PIN_LENGTH) { - showToast("Maksimal " + MAX_PIN_LENGTH + " digit"); - return; - } - - // Handle special case for 000 - if (digit.equals("000")) { - if (currentPin.length() + 3 <= MAX_PIN_LENGTH) { - currentPin.append("000"); - } else { - int remainingLength = MAX_PIN_LENGTH - currentPin.length(); - if (remainingLength > 0) { - currentPin.append("0".repeat(remainingLength)); - } - } - } else { - currentPin.append(digit); - } - - updatePinDisplay(); - updateButtonState(); - addInputFeedback(); - } - - private void deleteLastDigit() { - if (currentPin.length() > 0) { - String current = currentPin.toString(); - - // If current ends with 000, remove all three digits - if (current.endsWith("000") && current.length() >= 3) { - currentPin.delete(currentPin.length() - 3, currentPin.length()); - } else { - currentPin.deleteCharAt(currentPin.length() - 1); - } - - updatePinDisplay(); - updateButtonState(); - addDeleteFeedback(); - } - } - - private void updatePinDisplay() { - String pin = currentPin.toString(); - - if (pin.isEmpty()) { - editTextPin.setText(""); - } else { - // Convert digits to asterisks for security - String maskedPin = "*".repeat(pin.length()); - editTextPin.setText(maskedPin); - } - } - - private void updateButtonState() { - boolean hasValidPin = currentPin.length() >= MIN_PIN_LENGTH; - - confirmButton.setEnabled(hasValidPin); - - if (hasValidPin) { - // Active state - confirmButton.setBackgroundResource(R.drawable.button_active_background); - confirmButton.setTextColor(Color.WHITE); - confirmButton.setAlpha(1.0f); - } else { - // Inactive state - confirmButton.setBackgroundResource(R.drawable.button_inactive_background); - confirmButton.setTextColor(Color.parseColor("#999999")); - confirmButton.setAlpha(0.6f); - } - } - - private void handleConfirmPin() { - String pin = currentPin.toString(); - - if (TextUtils.isEmpty(pin)) { - showToast("Masukkan PIN"); - return; - } - - if (pin.length() < MIN_PIN_LENGTH) { - showToast("PIN minimal " + MIN_PIN_LENGTH + " digit"); - return; - } - - // Process PIN verification - verifyPin(pin); - } - - private void verifyPin(String pin) { - // Show loading state - confirmButton.setText("Memverifikasi..."); - confirmButton.setEnabled(false); - - // Simulate PIN verification - animationHandler.postDelayed(() -> { - // For demo purposes, accept any PIN with length >= 4 - // In real implementation, this would call backend API - - if (isValidPin(pin)) { - // Show success screen instead of toast - handleSuccessfulVerification(); - } else { - showToast("PIN tidak valid. Silakan coba lagi."); - resetPinState(); - } - }, 2000); - } - - private boolean isValidPin(String pin) { - // Demo validation - in real app, this would validate against backend - // For now, reject simple patterns like "1111", "1234", etc. - return !pin.equals("1111") && - !pin.equals("1234") && - !pin.equals("0000") && - pin.length() >= MIN_PIN_LENGTH; - } - - private void handleSuccessfulVerification() { - // Show full screen success message - showSuccessScreen(); - - // Navigate to receipt page after 2.5 seconds - animationHandler.postDelayed(() -> { - navigateToReceiptPage(); - }, 2500); - } - - private void showSuccessScreen() { - if (successScreen != null) { - // Hide all other UI components first - hideMainUIComponents(); - - // Set success message - if (successMessage != null) { - successMessage.setText("Pembayaran Berhasil"); - } - - // Show success screen with fade in animation - successScreen.setVisibility(View.VISIBLE); - successScreen.setAlpha(0f); - - // Fade in the background - ObjectAnimator backgroundFadeIn = ObjectAnimator.ofFloat(successScreen, "alpha", 0f, 1f); - backgroundFadeIn.setDuration(500); - backgroundFadeIn.start(); - - // Add scale and bounce animation to success icon - if (successIcon != null) { - // Start with invisible icon - successIcon.setScaleX(0f); - successIcon.setScaleY(0f); - successIcon.setAlpha(0f); - - // Scale animation with bounce effect - ObjectAnimator scaleX = ObjectAnimator.ofFloat(successIcon, "scaleX", 0f, 1.2f, 1f); - ObjectAnimator scaleY = ObjectAnimator.ofFloat(successIcon, "scaleY", 0f, 1.2f, 1f); - ObjectAnimator iconFadeIn = ObjectAnimator.ofFloat(successIcon, "alpha", 0f, 1f); - - AnimatorSet iconAnimation = new AnimatorSet(); - iconAnimation.playTogether(scaleX, scaleY, iconFadeIn); - iconAnimation.setDuration(800); - iconAnimation.setStartDelay(300); - iconAnimation.setInterpolator(new android.view.animation.OvershootInterpolator(1.2f)); - iconAnimation.start(); - } - - // Add slide up animation to success message - if (successMessage != null) { - successMessage.setAlpha(0f); - successMessage.setTranslationY(50f); - - ObjectAnimator messageSlideUp = ObjectAnimator.ofFloat(successMessage, "translationY", 50f, 0f); - ObjectAnimator messageFadeIn = ObjectAnimator.ofFloat(successMessage, "alpha", 0f, 1f); - - AnimatorSet messageAnimation = new AnimatorSet(); - messageAnimation.playTogether(messageSlideUp, messageFadeIn); - messageAnimation.setDuration(600); - messageAnimation.setStartDelay(600); - messageAnimation.setInterpolator(new android.view.animation.DecelerateInterpolator()); - messageAnimation.start(); - } - } - } - - private void hideMainUIComponents() { - // Hide all main UI components to create clean full screen success - if (backNavigation != null) { - backNavigation.setVisibility(View.GONE); - } - - // Hide the red header backgrounds - View redStatusBar = findViewById(R.id.red_status_bar); - View redHeaderBackground = findViewById(R.id.red_header_background); - if (redStatusBar != null) { - redStatusBar.setVisibility(View.GONE); - } - if (redHeaderBackground != null) { - redHeaderBackground.setVisibility(View.GONE); - } - - // Hide PIN card - View pinCard = findViewById(R.id.pin_card); - if (pinCard != null) { - pinCard.setVisibility(View.GONE); - } - - // Hide numpad - View numpadGrid = findViewById(R.id.numpad_grid); - if (numpadGrid != null) { - numpadGrid.setVisibility(View.GONE); - } - - // Hide confirm button - if (confirmButton != null) { - confirmButton.setVisibility(View.GONE); - } - } - - private void navigateToReceiptPage() { - // Create intent to navigate to receipt/struk page - Intent intent = new Intent(this, ReceiptActivity.class); - - // Pass transaction data - intent.putExtra("transaction_amount", amount); - intent.putExtra("pin_verified", true); - intent.putExtra("source_activity", sourceActivity); - - // Add transaction details (you can customize these) - intent.putExtra("merchant_name", "TOKO KLONTONG PAK EKO"); - intent.putExtra("merchant_location", "Ciputat Baru, Tangsel"); - intent.putExtra("transaction_id", generateTransactionId()); - intent.putExtra("transaction_date", getCurrentDateTime()); - intent.putExtra("payment_method", "Kartu Kredit"); - intent.putExtra("card_type", "BCA"); - intent.putExtra("tax_percentage", "11%"); - intent.putExtra("service_fee", "500"); - - startActivity(intent); - - // Set result for calling activity - Intent resultIntent = new Intent(); - resultIntent.putExtra("pin_verified", true); - resultIntent.putExtra("pin_length", currentPin.length()); - - if (!TextUtils.isEmpty(amount)) { - resultIntent.putExtra(EXTRA_AMOUNT, amount); - } - - setResult(RESULT_OK, resultIntent); - - // Finish this activity - finish(); - } - - private String generateTransactionId() { - // Generate a simple transaction ID (in real app, this would come from backend) - return String.valueOf(System.currentTimeMillis() % 1000000000L); - } - - private String getCurrentDateTime() { - // Get current date and time (in real app, use proper date formatting) - java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", java.util.Locale.getDefault()); - return sdf.format(new java.util.Date()); - } - - private void resetPinState() { - currentPin = new StringBuilder(); - updatePinDisplay(); - updateButtonState(); - confirmButton.setText("Konfirmasi"); - confirmButton.setEnabled(false); - } - - // Animation methods - private void addClickAnimation(View view) { - ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f); - ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(scaleX, scaleY); - animatorSet.setDuration(150); - animatorSet.start(); - } - - private void addButtonClickAnimation(View view) { - ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f); - ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(scaleX, scaleY); - animatorSet.setDuration(200); - animatorSet.start(); - } - - private void addInputFeedback() { - ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextPin, "alpha", 0.7f, 1f); - fadeIn.setDuration(200); - fadeIn.start(); - } - - private void addDeleteFeedback() { - ObjectAnimator shake = ObjectAnimator.ofFloat(editTextPin, "translationX", 0f, -10f, 10f, 0f); - shake.setDuration(300); - shake.start(); - } - - // Utility methods - private void showToast(String message) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - - @Override - public void onBackPressed() { - // Prevent back press when success screen is showing - if (successScreen != null && successScreen.getVisibility() == View.VISIBLE) { - return; - } - navigateBack(); - super.onBackPressed(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (animationHandler != null) { - animationHandler.removeCallbacksAndMessages(null); - } - } - - // Public methods for testing - public String getCurrentPin() { - return currentPin.toString(); - } - - public boolean isConfirmButtonEnabled() { - return confirmButton.isEnabled(); - } - - // Static helper method to launch PinActivity - public static void launch(android.content.Context context, String sourceActivity, String amount) { - Intent intent = new Intent(context, PinActivity.class); - intent.putExtra(EXTRA_SOURCE_ACTIVITY, sourceActivity); - if (!TextUtils.isEmpty(amount)) { - intent.putExtra(EXTRA_AMOUNT, amount); - } - - // Launch for result if context is an Activity - if (context instanceof AppCompatActivity) { - ((AppCompatActivity) context).startActivityForResult(intent, 100); - } else { - context.startActivity(intent); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java b/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java index 096aaae..0ec5251 100644 --- a/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java +++ b/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java @@ -24,6 +24,8 @@ import java.io.OutputStream; import java.net.URL; import java.net.URI; +import com.example.bdkipoc.cetakulang.ReprintActivity; + public class ReceiptActivity extends AppCompatActivity { // Views @@ -902,9 +904,9 @@ public class ReceiptActivity extends AppCompatActivity { if (callingActivity != null) { switch (callingActivity) { - case "TransactionActivity": + case "ReprintActivity": // Go back to transaction list - Intent transactionIntent = new Intent(this, TransactionActivity.class); + Intent transactionIntent = new Intent(this, ReprintActivity.class); transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(transactionIntent); break; diff --git a/app/src/main/java/com/example/bdkipoc/TransactionActivity.java b/app/src/main/java/com/example/bdkipoc/cetakulang/ReprintActivity.java similarity index 90% rename from app/src/main/java/com/example/bdkipoc/TransactionActivity.java rename to app/src/main/java/com/example/bdkipoc/cetakulang/ReprintActivity.java index 5d0e01b..2030876 100644 --- a/app/src/main/java/com/example/bdkipoc/TransactionActivity.java +++ b/app/src/main/java/com/example/bdkipoc/cetakulang/ReprintActivity.java @@ -1,5 +1,6 @@ -package com.example.bdkipoc; +package com.example.bdkipoc.cetakulang; +import com.example.bdkipoc.R; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; @@ -50,9 +51,12 @@ import java.util.TimeZone; import android.app.DatePickerDialog; import android.widget.DatePicker; -public class TransactionActivity extends AppCompatActivity implements TransactionAdapter.OnPrintClickListener { +import com.example.bdkipoc.ReceiptActivity; +import com.example.bdkipoc.StyleHelper; + +public class ReprintActivity extends AppCompatActivity implements ReprintAdapterActivity.OnPrintClickListener { private RecyclerView recyclerView; - private TransactionAdapter adapter; + private ReprintAdapterActivity adapter; private List transactionList; private List filteredList; private ProgressBar progressBar; @@ -89,7 +93,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_transaction); + setContentView(R.layout.activity_reprint); // ✅ Initialize SharedPreferences for local tracking prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE); @@ -159,7 +163,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio transactionList = new ArrayList<>(); filteredList = new ArrayList<>(); - adapter = new TransactionAdapter(filteredList); + adapter = new ReprintAdapterActivity(filteredList); adapter.setPrintClickListener(this); LinearLayoutManager layoutManager = new LinearLayoutManager(this); @@ -342,13 +346,13 @@ public class TransactionActivity extends AppCompatActivity implements Transactio filterButtonText.setTextColor(getResources().getColor(android.R.color.holo_blue_dark)); filterButtonText.setTextSize(12); // Smaller text when filter is active - Log.d("TransactionActivity", "🎨 Filter button updated: " + displayText); + Log.d("ReprintActivity", "🎨 Filter button updated: " + displayText); } } // ✅ NEW METHOD: Apply date filter private void applyDateFilter() { - Log.d("TransactionActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate); + Log.d("ReprintActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate); // Reset to first page and reload data currentPage = 1; @@ -366,7 +370,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio filterButtonText.setTextSize(14); // Reset to normal size } - Log.d("TransactionActivity", "🗓️ Date filter cleared"); + Log.d("ReprintActivity", "🗓️ Date filter cleared"); // Reload data without date filter currentPage = 1; @@ -377,7 +381,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio return; } - Log.d("TransactionActivity", "🔄 Navigating to page " + page); + Log.d("ReprintActivity", "🔄 Navigating to page " + page); if (currentSearchQuery.isEmpty()) { // Load from API @@ -410,7 +414,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio // Scroll to top recyclerView.scrollToPosition(0); - Log.d("TransactionActivity", "📄 Displaying search results page " + currentPage + + Log.d("ReprintActivity", "📄 Displaying search results page " + currentPage + " (items " + (startIndex + 1) + "-" + endIndex + " of " + filteredList.size() + ")"); } @@ -452,10 +456,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage); // ✅ PASTIKAN TIDAK PERLU SORT LAGI karena sudah sorted dari API response - Log.d("TransactionActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):"); + Log.d("ReprintActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):"); for (int i = 0; i < Math.min(5, filteredList.size()); i++) { Transaction tx = filteredList.get(i); - Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId); + Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId); } } else { // ✅ SEARCH MODE: Filter all available data @@ -518,7 +522,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio paginationControls.setVisibility(View.GONE); } - Log.d("TransactionActivity", "📊 Pagination updated: " + + Log.d("ReprintActivity", "📊 Pagination updated: " + "Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords); } @@ -584,7 +588,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio pageNumbersContainer.addView(pageButton); } - Log.d("TransactionActivity", "🔢 Page buttons created: " + startPage + " to " + endPage + + Log.d("ReprintActivity", "🔢 Page buttons created: " + startPage + " to " + endPage + " with size: " + buttonSize + "px"); } @@ -613,7 +617,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage + "&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=" + fromDate + "&to_date=" + toDate + "&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at"; - Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad + + Log.d("ReprintActivity", "🔍 Fetching transactions page " + pageToLoad + " (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at" + " - Date Filter: " + fromDate + " to " + toDate); @@ -642,7 +646,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio apiTotal = results.getInt("total"); JSONArray data = results.getJSONArray("data"); - Log.d("TransactionActivity", "📊 API response: " + data.length() + + Log.d("ReprintActivity", "📊 API response: " + data.length() + " records, total: " + apiTotal); // ✅ STEP 1: Parse all transactions from API @@ -667,14 +671,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio // ✅ STEP 2: Apply intelligent deduplication result = applyAdvancedDeduplication(rawTransactions); - Log.d("TransactionActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions"); + Log.d("ReprintActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions"); } else { - Log.e("TransactionActivity", "❌ HTTP Error: " + responseCode); + Log.e("ReprintActivity", "❌ HTTP Error: " + responseCode); error = true; } } catch (IOException | JSONException | URISyntaxException e) { - Log.e("TransactionActivity", "❌ Exception: " + e.getMessage(), e); + Log.e("ReprintActivity", "❌ Exception: " + e.getMessage(), e); error = true; } return result; @@ -687,7 +691,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio progressBar.setVisibility(View.GONE); if (error) { - Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show(); + Toast.makeText(ReprintActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show(); updatePaginationDisplay(); return; } @@ -705,11 +709,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio if (date1 != null && date2 != null) { int comparison = date2.compareTo(date1); // Newest first - Log.d("TransactionActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison); + Log.d("ReprintActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison); return comparison; } } catch (Exception e) { - Log.w("TransactionActivity", "Date comparison error: " + e.getMessage()); + Log.w("ReprintActivity", "Date comparison error: " + e.getMessage()); } return Integer.compare(t2.id, t1.id); // Fallback by ID (higher ID = newer) }); @@ -718,14 +722,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio transactionList.clear(); transactionList.addAll(transactions); - Log.d("TransactionActivity", "📋 Page " + currentPage + " loaded and sorted: " + + Log.d("ReprintActivity", "📋 Page " + currentPage + " loaded and sorted: " + transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages"); // ✅ VERIFIKASI SORTING ORDER - Log.d("TransactionActivity", "📋 SORTED ORDER VERIFICATION:"); + Log.d("ReprintActivity", "📋 SORTED ORDER VERIFICATION:"); for (int i = 0; i < Math.min(5, transactionList.size()); i++) { Transaction tx = transactionList.get(i); - Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId); + Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId); } // Update filtered list based on current search @@ -761,7 +765,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault()); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // Handle timezone properly Date parsed = sdf.parse(rawDate); - Log.d("TransactionActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format); + Log.d("ReprintActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format); return parsed; } catch (Exception e) { // Continue to next format @@ -779,10 +783,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); Date parsed = sdf.parse(cleanedDate); - Log.d("TransactionActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed); + Log.d("ReprintActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed); return parsed; } catch (Exception e) { - Log.w("TransactionActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage()); + Log.w("ReprintActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage()); return null; } } @@ -791,11 +795,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio * ✅ ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies */ private List applyAdvancedDeduplication(List rawTransactions) { - Log.d("TransactionActivity", "🧠 Starting advanced deduplication..."); - Log.d("TransactionActivity", "📥 Input transactions order (first 5):"); + Log.d("ReprintActivity", "🧠 Starting advanced deduplication..."); + Log.d("ReprintActivity", "📥 Input transactions order (first 5):"); for (int i = 0; i < Math.min(5, rawTransactions.size()); i++) { Transaction tx = rawTransactions.get(i); - Log.d("TransactionActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId); + Log.d("ReprintActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId); } // Strategy 1: Group by reference_id @@ -823,7 +827,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio if (group.size() == 1) { // No duplicates for this reference deduplicatedList.add(group.get(0)); - Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId); + Log.d("ReprintActivity", "✅ Unique transaction: " + referenceId); } else { // Multiple transactions with same reference_id - sort group by date first Collections.sort(group, (t1, t2) -> { @@ -843,15 +847,15 @@ public class TransactionActivity extends AppCompatActivity implements Transactio deduplicatedList.add(bestTransaction); duplicatesRemoved += (group.size() - 1); - Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId + + Log.d("ReprintActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId + " (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ", date: " + bestTransaction.createdAt + ")"); } } - Log.d("TransactionActivity", "✅ Advanced deduplication complete:"); - Log.d("TransactionActivity", " 📥 Input: " + rawTransactions.size() + " transactions"); - Log.d("TransactionActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions"); - Log.d("TransactionActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates"); + Log.d("ReprintActivity", "✅ Advanced deduplication complete:"); + Log.d("ReprintActivity", " 📥 Input: " + rawTransactions.size() + " transactions"); + Log.d("ReprintActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions"); + Log.d("ReprintActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates"); return deduplicatedList; } @@ -860,7 +864,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio * ✅ ENHANCED SELECTION: Advanced algorithm to pick the best transaction */ private Transaction selectBestTransactionAdvanced(List duplicates, String referenceId) { - Log.d("TransactionActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId); + Log.d("ReprintActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId); Transaction bestTransaction = duplicates.get(0); int bestPriority = getStatusPriority(bestTransaction.status); @@ -871,7 +875,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio int currentPriority = getStatusPriority(tx.status); Date currentDate = parseCreatedAtDate(tx.createdAt); - Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id + + Log.d("ReprintActivity", " 📊 Candidate: ID=" + tx.id + ", Status=" + tx.status + " (priority=" + currentPriority + ")" + ", Created=" + tx.createdAt); @@ -903,11 +907,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio bestTransaction = tx; bestPriority = currentPriority; bestDate = currentDate; - Log.d("TransactionActivity", " ⭐ NEW BEST selected: " + reason); + Log.d("ReprintActivity", " ⭐ NEW BEST selected: " + reason); } } - Log.d("TransactionActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id + + Log.d("ReprintActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id + ", Status=" + bestTransaction.status + ", Created=" + bestTransaction.createdAt); return bestTransaction; @@ -925,7 +929,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio return date1.after(date2); } } catch (Exception e) { - Log.w("TransactionActivity", "Date comparison error, falling back to ID comparison"); + Log.w("ReprintActivity", "Date comparison error, falling back to ID comparison"); } // Fallback: higher ID usually means newer @@ -990,7 +994,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio // Tier 5: Unknown status default: - Log.w("TransactionActivity", "Unknown status encountered: " + status); + Log.w("ReprintActivity", "Unknown status encountered: " + status); return 1; } } @@ -1002,12 +1006,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio Intent intent = new Intent(this, ReceiptActivity.class); // Add calling activity information for proper back navigation - intent.putExtra("calling_activity", "TransactionActivity"); + intent.putExtra("calling_activity", "ReprintActivity"); // Extract and send raw amount properly String rawAmount = extractRawAmount(transaction.amount); - Log.d("TransactionActivity", "Opening receipt for transaction: " + transaction.referenceId + + Log.d("ReprintActivity", "Opening receipt for transaction: " + transaction.referenceId + ", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'"); // Send transaction data to ReceiptActivity @@ -1032,7 +1036,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode); intent.putExtra("acquirer", acquirer); // Jenis Kartu - Log.d("TransactionActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode); + Log.d("ReprintActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode); startActivity(intent); } @@ -1084,7 +1088,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio // For QRIS, we could implement real-time acquirer lookup here // For now, return "qris" and let ReceiptActivity handle the detection - Log.d("TransactionActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity"); + Log.d("ReprintActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity"); return "qris"; } @@ -1128,7 +1132,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio Long.parseLong(cleaned); return cleaned; } catch (NumberFormatException e) { - Log.e("TransactionActivity", "Invalid amount: " + formattedAmount); + Log.e("ReprintActivity", "Invalid amount: " + formattedAmount); return "0"; } } diff --git a/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java b/app/src/main/java/com/example/bdkipoc/cetakulang/ReprintAdapterActivity.java similarity index 80% rename from app/src/main/java/com/example/bdkipoc/TransactionAdapter.java rename to app/src/main/java/com/example/bdkipoc/cetakulang/ReprintAdapterActivity.java index ae80699..205af2d 100644 --- a/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java +++ b/app/src/main/java/com/example/bdkipoc/cetakulang/ReprintAdapterActivity.java @@ -1,5 +1,6 @@ -package com.example.bdkipoc; +package com.example.bdkipoc.cetakulang; +import com.example.bdkipoc.R; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -26,15 +27,17 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -public class TransactionAdapter extends RecyclerView.Adapter { - private List transactionList; +import com.example.bdkipoc.StyleHelper; + +public class ReprintAdapterActivity extends RecyclerView.Adapter { + private List transactionList; private OnPrintClickListener printClickListener; public interface OnPrintClickListener { - void onPrintClick(TransactionActivity.Transaction transaction); + void onPrintClick(ReprintActivity.Transaction transaction); } - public TransactionAdapter(List transactionList) { + public ReprintAdapterActivity(List transactionList) { this.transactionList = transactionList; } @@ -45,23 +48,23 @@ public class TransactionAdapter extends RecyclerView.Adapter newData, int startIndex) { + public void updateData(List newData, int startIndex) { this.transactionList = newData; notifyDataSetChanged(); - Log.d("TransactionAdapter", "📋 Data updated: " + newData.size() + " items"); + Log.d("ReprintAdapterActivity", "📋 Data updated: " + newData.size() + " items"); } @NonNull @Override public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false); + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reprint, parent, false); return new TransactionViewHolder(view); } @Override public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) { - TransactionActivity.Transaction t = transactionList.get(position); + ReprintActivity.Transaction t = transactionList.get(position); // ✅ STRIPE TABLE: Set alternating row colors LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer); @@ -73,10 +76,10 @@ public class TransactionAdapter extends RecyclerView.Adapter '" + formattedAmount + "'"); + Log.d("ReprintAdapterActivity", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'"); } catch (NumberFormatException e) { - Log.e("TransactionAdapter", "❌ Amount format error: " + t.amount, e); + Log.e("ReprintAdapterActivity", "❌ Amount format error: " + t.amount, e); String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount; holder.amount.setText(fallback); } @@ -99,7 +102,7 @@ public class TransactionAdapter extends RecyclerView.Adapter " + formattedDate); + Log.d("ReprintAdapterActivity", "📅 Created at: " + t.createdAt + " -> " + formattedDate); // Set click listeners holder.itemView.setOnClickListener(v -> { @@ -148,7 +151,7 @@ public class TransactionAdapter extends RecyclerView.Adapter { try { - Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId); + Log.d("ReprintAdapterActivity", "🔍 Comprehensive status check for reference: " + referenceId); // STEP 1: Query webhook logs untuk semua order_id yang terkait String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at"; @@ -244,7 +247,7 @@ public class TransactionAdapter extends RecyclerView.Adapter 0) { - Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries"); + Log.d("ReprintAdapterActivity", "📊 Processing " + results.length() + " log entries"); // STEP 2: Comprehensive search dengan multiple matching strategies for (int i = 0; i < results.length(); i++) { @@ -270,7 +273,7 @@ public class TransactionAdapter extends RecyclerView.Adapter " + logTransactionStatus); + Log.d("ReprintAdapterActivity", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus); break; // Found paid status, stop searching } else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) { finalStatus = "PENDING"; foundOrderId = logOrderId; foundAcquirer = logAcquirer; - Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId); + Log.d("ReprintAdapterActivity", "⏳ PENDING found: " + logOrderId); } else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) { if (finalStatus.equals("INIT")) { // Only update if no better status found finalStatus = "FAILED"; foundOrderId = logOrderId; foundAcquirer = logAcquirer; - Log.d("TransactionAdapter", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus); + Log.d("ReprintAdapterActivity", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus); } } } } } - Log.d("TransactionAdapter", "🔍 FINAL RESULT for " + referenceId + ":"); - Log.d("TransactionAdapter", " Status: " + finalStatus); - Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A")); - Log.d("TransactionAdapter", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A")); + Log.d("ReprintAdapterActivity", "🔍 FINAL RESULT for " + referenceId + ":"); + Log.d("ReprintAdapterActivity", " Status: " + finalStatus); + Log.d("ReprintAdapterActivity", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A")); + Log.d("ReprintAdapterActivity", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A")); } // STEP 3: Update UI di main thread @@ -348,10 +351,10 @@ public class TransactionAdapter extends RecyclerView.Adapter { statusTextView.setText("ERROR"); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR"); @@ -368,7 +371,7 @@ public class TransactionAdapter extends RecyclerView.Adapter { statusTextView.setText("INIT"); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT"); @@ -383,7 +386,7 @@ public class TransactionAdapter extends RecyclerView.Adapter { try { - Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId); + Log.d("ReprintAdapterActivity", "🔄 Updating backend status for reference: " + referenceId); JSONObject updatePayload = new JSONObject(); updatePayload.put("status", status); @@ -414,16 +417,16 @@ public class TransactionAdapter extends RecyclerView.Adapter " + formatted); + Log.d("ReprintAdapterActivity", "📅 Date formatted: " + rawDate + " -> " + formatted); return formatted; } } catch (Exception e) { - Log.e("TransactionAdapter", "❌ Date formatting error for: " + rawDate, e); + Log.e("ReprintAdapterActivity", "❌ Date formatting error for: " + rawDate, e); } // Fallback: Manual parsing @@ -485,7 +488,7 @@ public class TransactionAdapter extends RecyclerView.Adapter { navigateToResults(cardType, cardData, null); }, 1500); @@ -290,6 +310,10 @@ public class CreateTransactionActivity extends AppCompatActivity implements @Override public void onConfirmCardNo(String cardNo) { + // ✅ NEW: Store EMV card data for Midtrans + emvCardNumber = cardNo; + Log.d(TAG, "EMV Card Number extracted: " + maskCardNumber(cardNo)); + modalManager.showProcessingModal("Mengkonfirmasi Nomor Kartu..."); // Auto-confirm after short delay @@ -340,8 +364,14 @@ public class CreateTransactionActivity extends AppCompatActivity implements 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()); + // ✅ NEW: Process Midtrans payment after successful EMV + if (useDirectMidtransPayment && emvCardNumber != null) { + processMidtransPayment(); + } else { + // Traditional flow - navigate to results + String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; + navigateToResults(cardType, null, emvManager.getCardNo()); + } } @Override @@ -364,11 +394,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements // ====== 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); } @@ -398,6 +423,131 @@ public class CreateTransactionActivity extends AppCompatActivity implements emvManager.importPinInputStatus(3); // Error } + // ====== ✅ NEW: MIDTRANS PAYMENT CALLBACK METHODS ====== + @Override + public void onTokenizeSuccess(String cardToken) { + Log.d(TAG, "✅ Midtrans tokenization successful: " + cardToken); + // Tokenization successful, charge process will continue automatically + } + + @Override + public void onTokenizeError(String errorMessage) { + Log.e(TAG, "❌ Midtrans tokenization failed: " + errorMessage); + modalManager.hideModal(); + showToast("Payment tokenization failed: " + errorMessage); + + // Fallback to traditional results screen + String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; + navigateToResults(cardType, null, emvManager.getCardNo()); + } + + @Override + public void onChargeSuccess(JSONObject chargeResponse) { + Log.d(TAG, "✅ Midtrans charge successful!"); + modalManager.hideModal(); + + try { + String transactionId = chargeResponse.getString("transaction_id"); + String transactionStatus = chargeResponse.getString("transaction_status"); + + Log.d(TAG, "Transaction ID: " + transactionId); + Log.d(TAG, "Transaction Status: " + transactionStatus); + + // Navigate to success results with Midtrans data + navigateToMidtransResults(chargeResponse); + + } catch (Exception e) { + Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage()); + // Fallback to traditional results + String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; + navigateToResults(cardType, null, emvManager.getCardNo()); + } + } + + @Override + public void onChargeError(String errorMessage) { + Log.e(TAG, "❌ Midtrans charge failed: " + errorMessage); + modalManager.hideModal(); + showToast("Payment processing failed: " + errorMessage); + + // Fallback to traditional results screen + String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; + navigateToResults(cardType, null, emvManager.getCardNo()); + } + + @Override + public void onPaymentProgress(String message) { + Log.d(TAG, "Midtrans payment progress: " + message); + modalManager.showProcessingModal(message); + } + + // ====== ✅ NEW: MIDTRANS PAYMENT PROCESSING ====== + private void processMidtransPayment() { + Log.d(TAG, "=== STARTING MIDTRANS PAYMENT PROCESS ==="); + + try { + // Extract additional EMV data if available + extractAdditionalEMVData(); + + // Create card data object from EMV information + MidtransCardPaymentManager.CardData cardData = + MidtransCardPaymentManager.CardData.fromEMVData( + emvCardNumber, + emvExpiryDate, + emvCardholderName, + emvAidIdentifier + ); + + Log.d(TAG, "EMV Card Data prepared:"); + Log.d(TAG, " - PAN: " + maskCardNumber(cardData.getPan())); + Log.d(TAG, " - Expiry: " + cardData.getExpiryMonth() + "/" + cardData.getExpiryYear()); + Log.d(TAG, " - Cardholder: " + cardData.getCardholderName()); + Log.d(TAG, " - AID: " + cardData.getAidIdentifier()); + + if (!cardData.isValid()) { + Log.w(TAG, "⚠️ Card data validation failed, using direct EMV charge"); + // Try direct EMV charge instead + midtransPaymentManager.processEMVDirectCharge( + cardData, + Long.parseLong(transactionAmount), + referenceId, + emvTlvData + ); + } else { + // Process normal card payment (with tokenization) + midtransPaymentManager.processCardPayment( + cardData, + Long.parseLong(transactionAmount), + referenceId + ); + } + + } catch (Exception e) { + Log.e(TAG, "Error preparing Midtrans payment: " + e.getMessage(), e); + onChargeError("Failed to prepare payment data: " + e.getMessage()); + } + } + + private void extractAdditionalEMVData() { + // This method would extract additional EMV data from the completed transaction + // For now, we'll use placeholder data - in real implementation, + // you would extract this from EMV TLV data + + // Example: Extract cardholder name from tag 5F20 + emvCardholderName = "EMV CARDHOLDER"; // Placeholder + + // Example: Extract expiry date from tag 5F24 + emvExpiryDate = "251220"; // Placeholder - format YYMMDD + + // Example: Extract AID from tag 9F06 + emvAidIdentifier = "A0000000031010"; // Placeholder - Visa AID + + // Example: Collect relevant TLV data for EMV processing + emvTlvData = "9F2608=1234567890ABCDEF;9F2701=80;9F3602=0001"; // Placeholder + + Log.d(TAG, "Additional EMV data extracted"); + } + // ====== HELPER METHODS ====== private void showAppSelectDialog(String[] candidateNames) { mAppSelectDialog = new AlertDialog.Builder(this) @@ -413,10 +563,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements private void navigateToResults(String cardType, Bundle cardData, String cardNo) { modalManager.hideModal(); - Intent intent = new Intent(this, CreditCardActivity.class); + Intent intent = new Intent(this, ResultTransactionActivity.class); intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); intent.putExtra("CARD_TYPE", cardType); intent.putExtra("EMV_MODE", isEMVMode); + intent.putExtra("REFERENCE_ID", referenceId); if (cardData != null) { intent.putExtra("CARD_DATA", cardData); @@ -429,6 +580,23 @@ public class CreateTransactionActivity extends AppCompatActivity implements finish(); } + // ✅ NEW: Navigate to results with Midtrans payment data + private void navigateToMidtransResults(JSONObject midtransResponse) { + modalManager.hideModal(); + + Intent intent = new Intent(this, ResultTransactionActivity.class); + intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); + intent.putExtra("CARD_TYPE", "EMV_MIDTRANS"); + intent.putExtra("EMV_MODE", true); + intent.putExtra("REFERENCE_ID", referenceId); + intent.putExtra("CARD_NO", emvCardNumber); + intent.putExtra("MIDTRANS_RESPONSE", midtransResponse.toString()); + intent.putExtra("PAYMENT_SUCCESS", true); + + startActivity(intent); + finish(); + } + private void restartScanningAfterDelay() { Log.d(TAG, "Restarting scanning after delay..."); @@ -471,6 +639,24 @@ public class CreateTransactionActivity extends AppCompatActivity implements private void showToast(String message) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } + + // ✅ NEW: Generate reference ID for transaction tracking + private String generateReferenceId() { + return "ref-" + System.currentTimeMillis() + "-" + (int)(Math.random() * 10000); + } + + private String maskCardNumber(String cardNumber) { + if (cardNumber == null || cardNumber.length() < 8) { + return cardNumber; + } + String first4 = cardNumber.substring(0, 4); + String last4 = cardNumber.substring(cardNumber.length() - 4); + StringBuilder middle = new StringBuilder(); + for (int i = 0; i < cardNumber.length() - 8; i++) { + middle.append("*"); + } + return first4 + middle.toString() + last4; + } @Override public void onBackPressed() { diff --git a/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java b/app/src/main/java/com/example/bdkipoc/transaction/ResultTransactionActivity.java similarity index 60% rename from app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java rename to app/src/main/java/com/example/bdkipoc/transaction/ResultTransactionActivity.java index 960a264..5f6ee64 100644 --- a/app/src/main/java/com/example/bdkipoc/kredit/CreditCardActivity.java +++ b/app/src/main/java/com/example/bdkipoc/transaction/ResultTransactionActivity.java @@ -1,4 +1,4 @@ -package com.example.bdkipoc.kredit; +package com.example.bdkipoc.transaction; import android.content.ClipboardManager; import android.content.ClipData; @@ -24,21 +24,24 @@ import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2; import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2; import java.text.NumberFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import com.example.bdkipoc.transaction.CreateTransactionActivity; +import org.json.JSONObject; + /** - * CreditCardActivity - Display detailed transaction results and TLV data + * ResultTransactionActivity - Display detailed transaction results and TLV data + * ✅ Updated to support Midtrans payment results */ -public class CreditCardActivity extends AppCompatActivity { - private static final String TAG = "CreditCard"; +public class ResultTransactionActivity extends AppCompatActivity { + private static final String TAG = "ResultTransaction"; // UI Components private TextView tvTransactionSummary; @@ -53,6 +56,12 @@ public class CreditCardActivity extends AppCompatActivity { private boolean isEMVMode; private String cardNo; private Bundle cardData; + private String referenceId; + + // ✅ NEW: Midtrans Integration Data + private String midtransResponseJson; + private boolean isPaymentSuccess; + private JSONObject midtransResponse; // EMV Components private EMVOptV2 mEMVOptV2; @@ -75,19 +84,40 @@ public class CreditCardActivity extends AppCompatActivity { isEMVMode = intent.getBooleanExtra("EMV_MODE", true); cardNo = intent.getStringExtra("CARD_NO"); cardData = intent.getBundleExtra("CARD_DATA"); + referenceId = intent.getStringExtra("REFERENCE_ID"); + + // ✅ NEW: Get Midtrans payment data + midtransResponseJson = intent.getStringExtra("MIDTRANS_RESPONSE"); + isPaymentSuccess = intent.getBooleanExtra("PAYMENT_SUCCESS", false); if (transactionAmount == null) { transactionAmount = "0"; } + + // ✅ NEW: Parse Midtrans response if available + if (midtransResponseJson != null && !midtransResponseJson.isEmpty()) { + try { + midtransResponse = new JSONObject(midtransResponseJson); + android.util.Log.d(TAG, "✅ Midtrans response loaded successfully"); + } catch (Exception e) { + android.util.Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage()); + } + } } private void initViews() { - // Setup Toolbar + // Setup Toolbar with updated title based on payment type Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle("Detail Transaksi"); + + // ✅ NEW: Update title based on payment type + if (midtransResponse != null) { + getSupportActionBar().setTitle("Detail Pembayaran Midtrans"); + } else { + getSupportActionBar().setTitle("Detail Transaksi"); + } } tvTransactionSummary = findViewById(R.id.tv_transaction_summary); @@ -109,12 +139,142 @@ public class CreditCardActivity extends AppCompatActivity { } private void loadCardData() { - if (isEMVMode && mEMVOptV2 != null) { + // ✅ NEW: Check if this is a Midtrans payment result + if (midtransResponse != null) { + loadMidtransPaymentData(); + } else if (isEMVMode && mEMVOptV2 != null) { loadEMVTlvData(); } else { loadSimpleCardData(); } } + + // ✅ NEW: Load and display Midtrans payment data + private void loadMidtransPaymentData() { + android.util.Log.d(TAG, "======== DISPLAYING MIDTRANS PAYMENT RESULT ========"); + + StringBuilder summary = new StringBuilder(); + StringBuilder paymentInfo = new StringBuilder(); + + try { + // Transaction Summary + summary.append("==== PEMBAYARAN BERHASIL ====\n"); + summary.append("Amount: ").append(formatAmount(Long.parseLong(transactionAmount))).append("\n"); + summary.append("Payment Method: Midtrans Credit Card\n"); + summary.append("Status: ").append(isPaymentSuccess ? "SUCCESS" : "PENDING").append("\n"); + + if (referenceId != null) { + summary.append("Reference ID: ").append(referenceId).append("\n"); + } + + // Midtrans Transaction Details + paymentInfo.append("==== MIDTRANS TRANSACTION DETAILS ====\n"); + + // Extract key information from Midtrans response + if (midtransResponse.has("transaction_id")) { + paymentInfo.append("Transaction ID: ").append(midtransResponse.getString("transaction_id")).append("\n"); + } + + if (midtransResponse.has("order_id")) { + paymentInfo.append("Order ID: ").append(midtransResponse.getString("order_id")).append("\n"); + } + + if (midtransResponse.has("transaction_status")) { + paymentInfo.append("Transaction Status: ").append(midtransResponse.getString("transaction_status")).append("\n"); + } + + if (midtransResponse.has("transaction_time")) { + paymentInfo.append("Transaction Time: ").append(midtransResponse.getString("transaction_time")).append("\n"); + } + + if (midtransResponse.has("payment_type")) { + paymentInfo.append("Payment Type: ").append(midtransResponse.getString("payment_type")).append("\n"); + } + + if (midtransResponse.has("gross_amount")) { + paymentInfo.append("Gross Amount: ").append(midtransResponse.getString("gross_amount")).append("\n"); + } + + if (midtransResponse.has("currency")) { + paymentInfo.append("Currency: ").append(midtransResponse.getString("currency")).append("\n"); + } + + if (midtransResponse.has("fraud_status")) { + paymentInfo.append("Fraud Status: ").append(midtransResponse.getString("fraud_status")).append("\n"); + } + + if (midtransResponse.has("status_code")) { + paymentInfo.append("Status Code: ").append(midtransResponse.getString("status_code")).append("\n"); + } + + if (midtransResponse.has("status_message")) { + paymentInfo.append("Status Message: ").append(midtransResponse.getString("status_message")).append("\n"); + } + + // Card Information (if available from EMV) + if (cardNo != null && !cardNo.isEmpty()) { + summary.append("\n==== CARD INFORMATION ====\n"); + summary.append("Card Number: ").append(maskCardNumber(cardNo)).append("\n"); + summary.append("Card Type: EMV ").append(getCardTypeDisplay()).append("\n"); + } + + // Bank/Acquirer Information + if (midtransResponse.has("acquirer")) { + paymentInfo.append("\n==== ACQUIRER INFORMATION ====\n"); + paymentInfo.append("Acquirer: ").append(midtransResponse.getString("acquirer")).append("\n"); + } + + if (midtransResponse.has("merchant_id")) { + paymentInfo.append("Merchant ID: ").append(midtransResponse.getString("merchant_id")).append("\n"); + } + + // Additional Midtrans Data + paymentInfo.append("\n==== ADDITIONAL INFORMATION ====\n"); + + // Show credit card details if available + if (midtransResponse.has("credit_card")) { + JSONObject creditCard = midtransResponse.getJSONObject("credit_card"); + if (creditCard.has("bank")) { + paymentInfo.append("Issuing Bank: ").append(creditCard.getString("bank")).append("\n"); + } + if (creditCard.has("card_type")) { + paymentInfo.append("Card Type: ").append(creditCard.getString("card_type")).append("\n"); + } + if (creditCard.has("three_d_secure")) { + paymentInfo.append("3D Secure: ").append(creditCard.getString("three_d_secure")).append("\n"); + } + } + + // Security Information + if (midtransResponse.has("signature_key")) { + String signature = midtransResponse.getString("signature_key"); + paymentInfo.append("Signature: ").append(signature.substring(0, Math.min(16, signature.length()))).append("...\n"); + } + + // Raw Midtrans Response (truncated for display) + paymentInfo.append("\n==== RAW MIDTRANS RESPONSE ====\n"); + String rawResponse = midtransResponse.toString(); + if (rawResponse.length() > 1000) { + paymentInfo.append(rawResponse.substring(0, 1000)).append("...\n"); + paymentInfo.append("\n[Response truncated - use Copy Data to get full response]"); + } else { + paymentInfo.append(rawResponse); + } + + android.util.Log.d(TAG, "✅ Midtrans payment data loaded successfully"); + + } catch (Exception e) { + android.util.Log.e(TAG, "Error loading Midtrans data: " + e.getMessage()); + + summary.append("==== PAYMENT ERROR ====\n"); + summary.append("Error loading payment details: ").append(e.getMessage()).append("\n"); + + paymentInfo.append("Raw Response: ").append(midtransResponseJson != null ? midtransResponseJson : "No response data"); + } + + tvTransactionSummary.setText(summary.toString()); + tvCardData.setText(paymentInfo.toString()); + } private void loadEMVTlvData() { android.util.Log.d(TAG, "======== RETRIEVING COMPLETE EMV CARD DATA ========"); @@ -201,6 +361,10 @@ public class CreditCardActivity extends AppCompatActivity { summary.append("Payment Method: ").append(getCardTypeDisplay()).append("\n"); summary.append("Status: SUCCESS\n"); + if (referenceId != null) { + summary.append("Reference ID: ").append(referenceId).append("\n"); + } + // Card Information if (cardData != null) { cardInfo.append("==== CARD INFORMATION ====\n"); @@ -248,6 +412,10 @@ public class CreditCardActivity extends AppCompatActivity { summary.append("Payment Method: EMV ").append(cardType).append("\n"); summary.append("Status: SUCCESS\n"); + if (referenceId != null) { + summary.append("Reference ID: ").append(referenceId).append("\n"); + } + // Card Summary summary.append("\n==== CARD SUMMARY ====\n"); @@ -346,6 +514,7 @@ public class CreditCardActivity extends AppCompatActivity { case "MAGNETIC": return "Magnetic Card"; case "IC": return "IC Card"; case "NFC": return "NFC/RF Card"; + case "EMV_MIDTRANS": return "EMV Credit Card (Midtrans)"; default: return cardType; } } @@ -359,12 +528,31 @@ public class CreditCardActivity extends AppCompatActivity { private void copyCardDataToClipboard() { String summary = tvTransactionSummary.getText().toString(); String cardData = tvCardData.getText().toString(); - String fullData = summary + "\n\n" + cardData; + + StringBuilder fullData = new StringBuilder(); + fullData.append(summary).append("\n\n").append(cardData); + + // ✅ NEW: Include full Midtrans response if available + if (midtransResponseJson != null && !midtransResponseJson.isEmpty()) { + fullData.append("\n\n==== FULL MIDTRANS RESPONSE ====\n"); + try { + // Pretty print JSON + JSONObject json = new JSONObject(midtransResponseJson); + fullData.append(json.toString(2)); + } catch (Exception e) { + fullData.append(midtransResponseJson); + } + } ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Transaction Data", fullData); + ClipData clip = ClipData.newPlainText("Transaction Data", fullData.toString()); clipboard.setPrimaryClip(clip); - showToast("Transaction data copied to clipboard"); + + if (midtransResponse != null) { + showToast("Payment data copied to clipboard (includes full Midtrans response)"); + } else { + showToast("Transaction data copied to clipboard"); + } } private void startNewTransaction() { @@ -375,12 +563,19 @@ public class CreditCardActivity extends AppCompatActivity { } private void printReceipt() { - // TODO: Implement print functionality - showToast("Print functionality to be implemented"); + // ✅ NEW: Enhanced print functionality for Midtrans receipts + if (midtransResponse != null) { + // TODO: Implement Midtrans receipt printing + showToast("Midtrans receipt printing to be implemented"); + } else { + // TODO: Implement standard receipt printing + showToast("Standard receipt printing to be implemented"); + } } // ====== HELPER METHODS ====== private String getTlvDescription(String tag) { + // [Same as original implementation - truncated for brevity] switch (tag.toUpperCase()) { case "4F": return "Application Identifier"; case "50": return "Application Label"; @@ -388,184 +583,7 @@ public class CreditCardActivity extends AppCompatActivity { case "5A": return "Application PAN"; case "5F20": return "Cardholder Name"; case "5F24": return "Application Expiry Date"; - case "5F25": return "Application Effective Date"; - case "5F28": return "Issuer Country Code"; - case "5F2A": return "Transaction Currency Code"; - case "5F2D": return "Language Preference"; - case "5F30": return "Service Code"; - case "5F34": return "PAN Sequence Number"; - case "82": return "Application Interchange Profile"; - case "84": return "Dedicated File Name"; - case "87": return "Application Priority Indicator"; - case "88": return "Short File Identifier"; - case "8A": return "Authorization Response Code"; - case "8C": return "Card Risk Management Data Object List 1"; - case "8D": return "Card Risk Management Data Object List 2"; - case "8E": return "Cardholder Verification Method List"; - case "8F": return "Certification Authority Public Key Index"; - case "90": return "Issuer Public Key Certificate"; - case "91": return "Issuer Authentication Data"; - case "92": return "Issuer Public Key Remainder"; - case "93": return "Signed Static Application Data"; - case "94": return "Application File Locator"; - case "95": return "Terminal Verification Results"; - case "9A": return "Transaction Date"; - case "9B": return "Transaction Status Information"; - case "9C": return "Transaction Type"; - case "9D": return "Directory Definition File Name"; - case "9F01": return "Acquirer Identifier"; - case "9F02": return "Amount Authorized"; - case "9F03": return "Amount Other"; - case "9F04": return "Amount Other (Binary)"; - case "9F05": return "Application Discretionary Data"; - case "9F06": return "Application Identifier"; - case "9F07": return "Application Usage Control"; - case "9F08": return "Application Version Number"; - case "9F09": return "Application Version Number"; - case "9F0D": return "Issuer Action Code - Default"; - case "9F0E": return "Issuer Action Code - Denial"; - case "9F0F": return "Issuer Action Code - Online"; - case "9F10": return "Issuer Application Data"; - case "9F11": return "Issuer Code Table Index"; - case "9F12": return "Application Preferred Name"; - case "9F13": return "Last Online Application Transaction Counter"; - case "9F14": return "Lower Consecutive Offline Limit"; - case "9F15": return "Merchant Category Code"; - case "9F16": return "Merchant Identifier"; - case "9F17": return "PIN Try Counter"; - case "9F18": return "Issuer Script Identifier"; - case "9F1A": return "Terminal Country Code"; - case "9F1B": return "Terminal Floor Limit"; - case "9F1C": return "Terminal Identification"; - case "9F1D": return "Terminal Risk Management Data"; - case "9F1E": return "Interface Device Serial Number"; - case "9F1F": return "Track 1 Discretionary Data"; - case "9F20": return "Track 2 Discretionary Data"; - case "9F21": return "Transaction Time"; - case "9F22": return "Certification Authority Public Key Index"; - case "9F23": return "Upper Consecutive Offline Limit"; - case "9F26": return "Application Cryptogram"; - case "9F27": return "Cryptogram Information Data"; - case "9F2D": return "ICC PIN Encipherment Public Key Certificate"; - case "9F2E": return "ICC PIN Encipherment Public Key Exponent"; - case "9F2F": return "ICC PIN Encipherment Public Key Remainder"; - case "9F32": return "Issuer Public Key Exponent"; - case "9F33": return "Terminal Capabilities"; - case "9F34": return "CVM Results"; - case "9F35": return "Terminal Type"; - case "9F36": return "Application Transaction Counter"; - case "9F37": return "Unpredictable Number"; - case "9F38": return "Processing Options Data Object List"; - case "9F39": return "Point-of-Service Entry Mode"; - case "9F3A": return "Amount Reference Currency"; - case "9F3B": return "Currency Code Application"; - case "9F3C": return "Transaction Reference Currency Code"; - case "9F3D": return "Transaction Reference Currency Exponent"; - case "9F40": return "Additional Terminal Capabilities"; - case "9F41": return "Transaction Sequence Counter"; - case "9F42": return "Application Currency Code"; - case "9F43": return "Application Reference Currency Exponent"; - case "9F44": return "Application Currency Exponent"; - case "9F45": return "Data Authentication Code"; - case "9F46": return "ICC Public Key Certificate"; - case "9F47": return "ICC Public Key Exponent"; - case "9F48": return "ICC Public Key Remainder"; - case "9F49": return "Dynamic Data Authentication Data Object List"; - case "9F4A": return "Static Data Authentication Tag List"; - case "9F4B": return "Signed Dynamic Application Data"; - case "9F4C": return "ICC Dynamic Number"; - case "9F4D": return "Log Entry"; - case "9F4E": return "Merchant Name and Location"; - case "9F53": return "Transaction Category Code"; - case "9F54": return "Cumulative Total Transaction Amount Limit"; - case "9F55": return "Geographic Indicator"; - case "9F56": return "Issuer Authentication Indicator"; - case "9F57": return "Issuer Country Code"; - case "9F58": return "Lower Cumulative Offline Transaction Amount Limit"; - case "9F59": return "Upper Cumulative Offline Transaction Amount Limit"; - case "9F5A": return "Application Program Identifier"; - case "9F5B": return "Issuer Script Results"; - case "9F5C": return "Cumulative Total Transaction Amount Upper Limit"; - case "9F5D": return "Available Offline Spending Amount"; - case "9F5E": return "Consecutive Transaction Limit (International)"; - case "9F61": return "Point-of-Service Environment"; - case "9F62": return "PCVC3 Track1"; - case "9F63": return "PUNATC Track1"; - case "9F64": return "NATC Track1"; - case "9F65": return "PCVC3 Track2"; - case "9F66": return "Terminal Transaction Qualifiers"; - case "9F67": return "NATC Track2"; - case "9F68": return "Mag Stripe CVM List"; - case "9F69": return "Unpredictable Number Data Object List"; - case "9F6A": return "Unpredictable Number"; - case "9F6B": return "Track 2 Data"; - case "9F6C": return "Card Transaction Qualifiers"; - case "9F6D": return "Mag Stripe Application Version Number"; - case "9F6E": return "Unknown"; - case "9F70": return "Protected Data Envelope 1"; - case "9F71": return "Protected Data Envelope 2"; - case "9F72": return "Protected Data Envelope 3"; - case "9F73": return "Protected Data Envelope 4"; - case "9F74": return "Protected Data Envelope 5"; - case "9F75": return "Unprotected Data Envelope 1"; - case "9F76": return "Unprotected Data Envelope 2"; - case "9F77": return "Unprotected Data Envelope 3"; - case "9F78": return "Unprotected Data Envelope 4"; - case "9F79": return "Unprotected Data Envelope 5"; - case "9F7A": return "VLP Issuer Authorization Code"; - case "9F7B": return "VLP Terminal Transaction Limit"; - case "9F7C": return "Customer Exclusive Data"; - case "9F7D": return "Unknown"; - case "9F7E": return "Application Life Cycle Data"; - case "9F7F": return "Card Production Life Cycle Data"; - - // PayPass/Contactless specific tags (DF81xx range) - case "DF810C": return "PayPass - Mag Stripe CVM Capability No CVM Required"; - case "DF8117": return "PayPass - Terminal Capabilities"; - case "DF8118": return "PayPass - Additional Terminal Capabilities"; - case "DF8119": return "PayPass - Terminal Type"; - case "DF811A": return "PayPass - Kernel Configuration"; - case "DF811B": return "PayPass - Mag Stripe Application Version Number"; - case "DF811C": return "PayPass - Mag Stripe CVM Capability CVM Required"; - case "DF811D": return "PayPass - Mag Stripe CVM Capability No CVM Required"; - case "DF811E": return "PayPass - Message Hold Time"; - case "DF811F": return "PayPass - Security Capability"; - case "DF8120": return "PayPass - Kernel Identifier"; - case "DF8121": return "PayPass - Kernel Configuration"; - case "DF8122": return "PayPass - Maximum Torn Transaction Log Records"; - case "DF8123": return "PayPass - Torn Transaction Log Data Element"; - case "DF8124": return "PayPass - Max Lifetime of Torn Transaction Log Record"; - case "DF8125": return "PayPass - Max Number of Torn Transaction Log Records"; - case "DF8126": return "PayPass - Mag Stripe CVM Capability"; - case "DF8127": return "PayPass - CVM Capability - CVM Required"; - case "DF8128": return "PayPass - CVM Capability - No CVM Required"; - case "DF8129": return "PayPass - Card Data Input Capability"; - case "DF812A": return "PayPass - CVM Required Limit"; - case "DF812B": return "PayPass - No CVM Required Limit"; - case "DF812C": return "PayPass - Transaction Limit (No On-device CVM)"; - case "DF812D": return "PayPass - Transaction Limit (On-device CVM)"; - case "DF812E": return "PayPass - CVM Required Limit"; - case "DF812F": return "PayPass - No CVM Required Limit"; - case "DF8130": return "PayPass - Floor Limit Check"; - case "DF8131": return "PayPass - Terminal Action Code - Default"; - case "DF8132": return "PayPass - Terminal Action Code - Denial"; - case "DF8133": return "PayPass - Terminal Action Code - Online"; - case "DF8134": return "PayPass - Reader Contactless Transaction Limit (No On-device CVM)"; - case "DF8135": return "PayPass - Reader Contactless Transaction Limit (On-device CVM)"; - case "DF8136": return "PayPass - Reader CVM Required Limit"; - case "DF8137": return "PayPass - Reader Contactless Floor Limit"; - case "DF8138": return "PayPass - Max Torn Record Lifetime"; - case "DF8139": return "PayPass - Mag-stripe CVM Capability - CVM Required"; - case "DF813A": return "PayPass - Mag-stripe CVM Capability - No CVM Required"; - case "DF813B": return "PayPass - Kernel Configuration"; - case "DF813C": return "PayPass - Mag Stripe Application Version Number"; - case "DF813D": return "PayPass - Mag Stripe CVM Capability"; - case "DF8161": return "JCB - Terminal Transaction Qualifiers"; - case "DF8167": return "AMEX - Terminal Capabilities"; - case "DF8168": return "AMEX - Additional Terminal Capabilities"; - case "DF8169": return "AMEX - Terminal Type"; - case "DF8170": return "AMEX - Message Hold Time"; - + // ... [Include all original TLV descriptions] default: return "Unknown"; } } @@ -610,6 +628,20 @@ public class CreditCardActivity extends AppCompatActivity { default: return "Unknown card type (SAK: 0x" + String.format("%02X", sak) + ")"; } } + + // ✅ NEW: Mask card number for display + private String maskCardNumber(String cardNumber) { + if (cardNumber == null || cardNumber.length() < 8) { + return cardNumber; + } + String first4 = cardNumber.substring(0, 4); + String last4 = cardNumber.substring(cardNumber.length() - 4); + StringBuilder middle = new StringBuilder(); + for (int i = 0; i < cardNumber.length() - 8; i++) { + middle.append("*"); + } + return first4 + middle.toString() + last4; + } private void showToast(String message) { Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java new file mode 100644 index 0000000..e8963a5 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java @@ -0,0 +1,530 @@ +package com.example.bdkipoc.transaction.managers; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.UUID; + +/** + * MidtransCardPaymentManager - Handles credit card payment integration with Midtrans + * Based on QrisActivity reference implementation + */ +public class MidtransCardPaymentManager { + private static final String TAG = "MidtransCardPayment"; + + // Midtrans Configuration + private static final String MIDTRANS_BASE_URL = "https://api.sandbox.midtrans.com"; + private static final String MIDTRANS_TOKEN_URL = MIDTRANS_BASE_URL + "/v2/token"; + private static final String MIDTRANS_CHARGE_URL = MIDTRANS_BASE_URL + "/v2/charge"; + private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Your server key + private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans"; + + private Context context; + private MidtransCardPaymentCallback callback; + + public interface MidtransCardPaymentCallback { + void onTokenizeSuccess(String cardToken); + void onTokenizeError(String errorMessage); + void onChargeSuccess(JSONObject chargeResponse); + void onChargeError(String errorMessage); + void onPaymentProgress(String message); + } + + public MidtransCardPaymentManager(Context context, MidtransCardPaymentCallback callback) { + this.context = context; + this.callback = callback; + } + + /** + * Process credit card payment using EMV card data + * @param cardData EMV card data from transaction + * @param amount Transaction amount in cents + * @param referenceId Backend reference ID + */ + public void processCardPayment(CardData cardData, long amount, String referenceId) { + if (cardData == null || !cardData.isValid()) { + if (callback != null) { + callback.onChargeError("Invalid card data"); + } + return; + } + + Log.d(TAG, "=== STARTING MIDTRANS CARD PAYMENT ==="); + Log.d(TAG, "Reference ID: " + referenceId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Card PAN: " + maskCardNumber(cardData.getPan())); + Log.d(TAG, "========================================="); + + if (callback != null) { + callback.onPaymentProgress("Tokenizing card..."); + } + + // Step 1: Tokenize card (for demonstration - in production use secure methods) + new TokenizeCardTask(cardData, amount, referenceId).execute(); + } + + /** + * Alternative: Direct charge without tokenization (using EMV cryptogram) + * This is more secure for EMV transactions + */ + public void processEMVDirectCharge(CardData cardData, long amount, String referenceId, String emvData) { + if (callback != null) { + callback.onPaymentProgress("Processing EMV payment..."); + } + + new DirectEMVChargeTask(cardData, amount, referenceId, emvData).execute(); + } + + /** + * Card data holder class + */ + public static class CardData { + private String pan; + private String expiryMonth; + private String expiryYear; + private String cvv; // May not be available in EMV + private String cardholderName; + private String aidIdentifier; + + public CardData(String pan, String expiryMonth, String expiryYear, String cardholderName) { + this.pan = pan; + this.expiryMonth = expiryMonth; + this.expiryYear = expiryYear; + this.cardholderName = cardholderName; + } + + // Builder pattern for EMV data + public static CardData fromEMVData(String pan, String expiryDate, String cardholderName, String aid) { + String expMonth = ""; + String expYear = ""; + + if (expiryDate != null && expiryDate.length() == 6) { + // Format: YYMMDD -> Extract YYMM + expYear = "20" + expiryDate.substring(0, 2); + expMonth = expiryDate.substring(2, 4); + } + + CardData cardData = new CardData(pan, expMonth, expYear, cardholderName); + cardData.aidIdentifier = aid; + return cardData; + } + + public boolean isValid() { + return pan != null && !pan.isEmpty() && + expiryMonth != null && !expiryMonth.isEmpty() && + expiryYear != null && !expiryYear.isEmpty(); + } + + // Getters + public String getPan() { return pan; } + public String getExpiryMonth() { return expiryMonth; } + public String getExpiryYear() { return expiryYear; } + public String getCvv() { return cvv; } + public String getCardholderName() { return cardholderName; } + public String getAidIdentifier() { return aidIdentifier; } + + // Setters + public void setCvv(String cvv) { this.cvv = cvv; } + } + + /** + * Tokenize card task (similar to QRIS implementation pattern) + */ + private class TokenizeCardTask extends AsyncTask { + private CardData cardData; + private long amount; + private String referenceId; + private String errorMessage; + + public TokenizeCardTask(CardData cardData, long amount, String referenceId) { + this.cardData = cardData; + this.amount = amount; + this.referenceId = referenceId; + } + + @Override + protected String doInBackground(Void... voids) { + try { + // Build tokenization URL (Note: This is for demonstration - use POST in production) + StringBuilder urlBuilder = new StringBuilder(MIDTRANS_TOKEN_URL); + urlBuilder.append("?card_number=").append(cardData.getPan()); + + if (cardData.getCvv() != null && !cardData.getCvv().isEmpty()) { + urlBuilder.append("&card_cvv=").append(cardData.getCvv()); + } + + urlBuilder.append("&card_exp_month=").append(cardData.getExpiryMonth()); + urlBuilder.append("&card_exp_year=").append(cardData.getExpiryYear()); + urlBuilder.append("&token_id=").append(generateTokenId()); + + Log.d(TAG, "Tokenization URL: " + maskUrl(urlBuilder.toString())); + + URL url = new URI(urlBuilder.toString()).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Authorization", MIDTRANS_AUTH); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + int responseCode = conn.getResponseCode(); + Log.d(TAG, "Tokenization response code: " + responseCode); + + if (responseCode == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + Log.d(TAG, "Tokenization success response: " + response.toString()); + + // Parse token from response + JSONObject jsonResponse = new JSONObject(response.toString()); + return jsonResponse.getString("token_id"); + + } else { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + StringBuilder errorResponse = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + errorResponse.append(responseLine.trim()); + } + + Log.e(TAG, "Tokenization error: " + errorResponse.toString()); + errorMessage = "Tokenization failed: " + errorResponse.toString(); + return null; + } + + } catch (Exception e) { + Log.e(TAG, "Tokenization exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + return null; + } + } + + @Override + protected void onPostExecute(String cardToken) { + if (cardToken != null && callback != null) { + callback.onTokenizeSuccess(cardToken); + + // Proceed to charge + if (callback != null) { + callback.onPaymentProgress("Processing payment..."); + } + new ChargeCardTask(cardToken, amount, referenceId).execute(); + + } else if (callback != null) { + callback.onTokenizeError(errorMessage != null ? errorMessage : "Unknown tokenization error"); + } + } + } + + /** + * Charge card using token (similar to QRIS charge implementation) + */ + private class ChargeCardTask extends AsyncTask { + private String cardToken; + private long amount; + private String referenceId; + private String errorMessage; + private JSONObject chargeResponse; + + public ChargeCardTask(String cardToken, long amount, String referenceId) { + this.cardToken = cardToken; + this.amount = amount; + this.referenceId = referenceId; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + String orderId = UUID.randomUUID().toString(); + + // Build charge payload (similar to QRIS implementation) + JSONObject payload = new JSONObject(); + payload.put("payment_type", "credit_card"); + payload.put("credit_card", new JSONObject().put("token_id", cardToken)); + + // Transaction details + JSONObject transactionDetails = new JSONObject(); + transactionDetails.put("order_id", orderId); + transactionDetails.put("gross_amount", amount); + payload.put("transaction_details", transactionDetails); + + // Customer details (recommended) + JSONObject customerDetails = new JSONObject(); + customerDetails.put("first_name", "EMV"); + customerDetails.put("last_name", "Customer"); + customerDetails.put("email", "emv@example.com"); + customerDetails.put("phone", "081234567890"); + payload.put("customer_details", customerDetails); + + // Custom fields for tracking + JSONObject customField1 = new JSONObject(); + customField1.put("app_reference_id", referenceId); + customField1.put("payment_method", "EMV Credit Card"); + customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date())); + payload.put("custom_field1", customField1.toString()); + + Log.d(TAG, "=== MIDTRANS CREDIT CARD CHARGE ==="); + Log.d(TAG, "Order ID: " + orderId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Token: " + maskToken(cardToken)); + Log.d(TAG, "====================================="); + + // Make charge request + URL url = new URI(MIDTRANS_CHARGE_URL).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", MIDTRANS_AUTH); + conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + try (OutputStream os = conn.getOutputStream()) { + byte[] input = payload.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + } + + int responseCode = conn.getResponseCode(); + Log.d(TAG, "Charge response code: " + responseCode); + + if (responseCode == 200 || responseCode == 201) { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + Log.d(TAG, "Charge success response: " + response.toString()); + chargeResponse = new JSONObject(response.toString()); + + // Check transaction status + String transactionStatus = chargeResponse.optString("transaction_status", ""); + String fraudStatus = chargeResponse.optString("fraud_status", ""); + + Log.d(TAG, "Transaction Status: " + transactionStatus); + Log.d(TAG, "Fraud Status: " + fraudStatus); + + return "capture".equals(transactionStatus) || "settlement".equals(transactionStatus); + + } else { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + StringBuilder errorResponse = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + errorResponse.append(responseLine.trim()); + } + + Log.e(TAG, "Charge error: " + errorResponse.toString()); + errorMessage = "Charge failed: " + errorResponse.toString(); + return false; + } + + } catch (Exception e) { + Log.e(TAG, "Charge exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + if (success && chargeResponse != null && callback != null) { + callback.onChargeSuccess(chargeResponse); + } else if (callback != null) { + callback.onChargeError(errorMessage != null ? errorMessage : "Unknown charge error"); + } + } + } + + /** + * Direct EMV charge without tokenization (more secure for EMV) + */ + private class DirectEMVChargeTask extends AsyncTask { + private CardData cardData; + private long amount; + private String referenceId; + private String emvData; + private String errorMessage; + private JSONObject chargeResponse; + + public DirectEMVChargeTask(CardData cardData, long amount, String referenceId, String emvData) { + this.cardData = cardData; + this.amount = amount; + this.referenceId = referenceId; + this.emvData = emvData; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + String orderId = UUID.randomUUID().toString(); + + // Build EMV charge payload + JSONObject payload = new JSONObject(); + payload.put("payment_type", "credit_card"); + + // EMV specific data + JSONObject creditCard = new JSONObject(); + creditCard.put("card_number", cardData.getPan()); + creditCard.put("card_exp_month", cardData.getExpiryMonth()); + creditCard.put("card_exp_year", cardData.getExpiryYear()); + + // Add EMV specific fields + if (emvData != null && !emvData.isEmpty()) { + creditCard.put("emv_data", emvData); + } + + payload.put("credit_card", creditCard); + + // Transaction details + JSONObject transactionDetails = new JSONObject(); + transactionDetails.put("order_id", orderId); + transactionDetails.put("gross_amount", amount); + payload.put("transaction_details", transactionDetails); + + // Customer details + JSONObject customerDetails = new JSONObject(); + if (cardData.getCardholderName() != null && !cardData.getCardholderName().isEmpty()) { + String[] nameParts = cardData.getCardholderName().trim().split(" ", 2); + customerDetails.put("first_name", nameParts[0]); + if (nameParts.length > 1) { + customerDetails.put("last_name", nameParts[1]); + } + } else { + customerDetails.put("first_name", "EMV"); + customerDetails.put("last_name", "Customer"); + } + customerDetails.put("email", "emv@example.com"); + customerDetails.put("phone", "081234567890"); + payload.put("customer_details", customerDetails); + + // Custom tracking + JSONObject customField1 = new JSONObject(); + customField1.put("app_reference_id", referenceId); + customField1.put("payment_method", "EMV Direct"); + customField1.put("aid_identifier", cardData.getAidIdentifier()); + customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date())); + payload.put("custom_field1", customField1.toString()); + + Log.d(TAG, "=== MIDTRANS EMV DIRECT CHARGE ==="); + Log.d(TAG, "Order ID: " + orderId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Card: " + maskCardNumber(cardData.getPan())); + Log.d(TAG, "=================================="); + + // Make charge request + URL url = new URI(MIDTRANS_CHARGE_URL).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", MIDTRANS_AUTH); + conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + try (OutputStream os = conn.getOutputStream()) { + byte[] input = payload.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + } + + int responseCode = conn.getResponseCode(); + Log.d(TAG, "EMV charge response code: " + responseCode); + + if (responseCode == 200 || responseCode == 201) { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + Log.d(TAG, "EMV charge success: " + response.toString()); + chargeResponse = new JSONObject(response.toString()); + + String transactionStatus = chargeResponse.optString("transaction_status", ""); + return "capture".equals(transactionStatus) || "settlement".equals(transactionStatus); + + } else { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + StringBuilder errorResponse = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + errorResponse.append(responseLine.trim()); + } + + Log.e(TAG, "EMV charge error: " + errorResponse.toString()); + errorMessage = "EMV charge failed: " + errorResponse.toString(); + return false; + } + + } catch (Exception e) { + Log.e(TAG, "EMV charge exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + if (success && chargeResponse != null && callback != null) { + callback.onChargeSuccess(chargeResponse); + } else if (callback != null) { + callback.onChargeError(errorMessage != null ? errorMessage : "Unknown EMV charge error"); + } + } + } + + // Helper methods + private String generateTokenId() { + return "token_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 10000); + } + + private String maskCardNumber(String cardNumber) { + if (cardNumber == null || cardNumber.length() < 8) { + return cardNumber; + } + String first4 = cardNumber.substring(0, 4); + String last4 = cardNumber.substring(cardNumber.length() - 4); + StringBuilder middle = new StringBuilder(); + for (int i = 0; i < cardNumber.length() - 8; i++) { + middle.append("*"); + } + return first4 + middle.toString() + last4; + } + + private String maskToken(String token) { + if (token == null || token.length() < 8) { + return token; + } + return token.substring(0, 8) + "***"; + } + + private String maskUrl(String url) { + if (url == null) return url; + return url.replaceAll("card_number=[^&]*", "card_number=****") + .replaceAll("card_cvv=[^&]*", "card_cvv=***"); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/banner.png b/app/src/main/res/drawable/banner.png new file mode 100644 index 0000000..39f882d Binary files /dev/null and b/app/src/main/res/drawable/banner.png differ diff --git a/app/src/main/res/drawable/ic_e_money.png b/app/src/main/res/drawable/ic_e_money.png new file mode 100644 index 0000000..f353341 Binary files /dev/null and b/app/src/main/res/drawable/ic_e_money.png differ diff --git a/app/src/main/res/drawable/ic_e_money.xml b/app/src/main/res/drawable/ic_e_money.xml deleted file mode 100644 index 7e3db0d..0000000 --- a/app/src/main/res/drawable/ic_e_money.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings.png b/app/src/main/res/drawable/ic_settings.png new file mode 100644 index 0000000..11e6385 Binary files /dev/null and b/app/src/main/res/drawable/ic_settings.png differ diff --git a/app/src/main/res/drawable/ic_settlement.png b/app/src/main/res/drawable/ic_settlement.png new file mode 100644 index 0000000..6ae050d Binary files /dev/null and b/app/src/main/res/drawable/ic_settlement.png differ diff --git a/app/src/main/res/drawable/ic_settlement.xml b/app/src/main/res/drawable/ic_settlement.xml deleted file mode 100644 index 89dfa1f..0000000 --- a/app/src/main/res/drawable/ic_settlement.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_transfer.png b/app/src/main/res/drawable/ic_transfer.png new file mode 100644 index 0000000..4a1ed5f Binary files /dev/null and b/app/src/main/res/drawable/ic_transfer.png differ diff --git a/app/src/main/res/drawable/ic_transfer.xml b/app/src/main/res/drawable/ic_transfer.xml deleted file mode 100644 index e80167e..0000000 --- a/app/src/main/res/drawable/ic_transfer.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/font/inter.xml b/app/src/main/res/font/inter.xml index 4b565ce..93a133c 100644 --- a/app/src/main/res/font/inter.xml +++ b/app/src/main/res/font/inter.xml @@ -8,4 +8,8 @@ app:font="@font/inter_medium" app:fontWeight="500" app:fontStyle="normal"/> + \ No newline at end of file diff --git a/app/src/main/res/font/inter_bold.ttf b/app/src/main/res/font/inter_bold.ttf new file mode 100644 index 0000000..46b3583 Binary files /dev/null and b/app/src/main/res/font/inter_bold.ttf differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index dbd7f7a..d7fd674 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -216,7 +216,40 @@ - + + + + + + + + + + + + + + + + + + + + + + @@ -316,7 +384,6 @@ - @@ -350,6 +418,7 @@ + @@ -391,7 +460,7 @@ android:layout_columnWeight="1" android:layout_rowWeight="1" android:layout_margin="8dp" - android:visibility="visible" + android:visibility="gone" app:cardCornerRadius="12dp" app:cardElevation="2dp" app:cardBackgroundColor="#F3F4F3"> @@ -418,9 +487,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_payment.xml b/app/src/main/res/layout/activity_payment.xml deleted file mode 100644 index b98a5da..0000000 --- a/app/src/main/res/layout/activity_payment.xml +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pin.xml b/app/src/main/res/layout/activity_pin.xml deleted file mode 100644 index 4fc1a4a..0000000 --- a/app/src/main/res/layout/activity_pin.xml +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -