package com.example.bdkipoc; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.cardview.widget.CardView; import com.sunmi.peripheral.printer.InnerResultCallback; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.io.OutputStream; import java.net.URL; import java.net.URI; import java.util.HashMap; import java.util.Map; import com.example.bdkipoc.cetakulang.ReprintActivity; public class ReceiptActivity extends AppCompatActivity { private static final Map ISSUER_DISPLAY_MAP = new HashMap() {{ put("airpay shopee", "ShopeePay"); put("shopeepay", "ShopeePay"); put("shopee", "ShopeePay"); put("linkaja", "LinkAja"); put("link aja", "LinkAja"); put("dana", "DANA"); put("ovo", "OVO"); put("gopay", "GoPay"); put("jenius", "Jenius"); put("sakuku", "Sakuku"); put("bni", "BNI"); put("bca", "BCA"); put("mandiri", "Mandiri"); put("bri", "BRI"); put("cimb", "CIMB Niaga"); put("permata", "Permata"); put("maybank", "Maybank"); put("qris", "QRIS"); }}; // Views private LinearLayout backNavigation; private ImageView backArrow; private TextView toolbarTitle; private CardView receiptCard; // Receipt details private TextView merchantName; private TextView merchantLocation; private TextView midText; private TextView tidText; private TextView transactionNumber; private TextView transactionDate; private TextView paymentMethod; private TextView cardType; private TextView transactionTotal; private TextView taxPercentage; private TextView serviceFee; private TextView finalTotal; // Action buttons private LinearLayout printButton; private LinearLayout emailButton; private Button finishButton; // Printer callback private final InnerResultCallback printCallback = new InnerResultCallback() { @Override public void onRunResult(boolean isSuccess) throws RemoteException { runOnUiThread(() -> { if (isSuccess) { showToast("Struk berhasil dicetak"); } else { showToast("Gagal mencetak struk"); } }); } @Override public void onReturnString(String result) throws RemoteException { Log.d("ReceiptActivity", "Print result: " + result); } @Override public void onRaiseException(int code, String msg) throws RemoteException { runOnUiThread(() -> showToast("Printer error: " + msg)); Log.e("ReceiptActivity", "Printer exception: " + code + " - " + msg); } @Override public void onPrintResult(int code, String msg) throws RemoteException { Log.d("ReceiptActivity", "Print result code: " + code + ", msg: " + msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set status bar color setStatusBarColor(); setContentView(R.layout.activity_receipt); initializeViews(); setupClickListeners(); loadTransactionData(); } private void initializeViews() { // Navigation backNavigation = findViewById(R.id.back_navigation); backArrow = findViewById(R.id.backArrow); toolbarTitle = findViewById(R.id.toolbarTitle); // Receipt card view that will be printed receiptCard = findViewById(R.id.receipt_card); // Receipt details merchantName = findViewById(R.id.merchant_name); merchantLocation = findViewById(R.id.merchant_location); midText = findViewById(R.id.mid_text); tidText = findViewById(R.id.tid_text); transactionNumber = findViewById(R.id.transaction_number); transactionDate = findViewById(R.id.transaction_date); paymentMethod = findViewById(R.id.payment_method); cardType = findViewById(R.id.card_type); transactionTotal = findViewById(R.id.transaction_total); taxPercentage = findViewById(R.id.tax_percentage); serviceFee = findViewById(R.id.service_fee); finalTotal = findViewById(R.id.final_total); // Action buttons printButton = findViewById(R.id.print_button); emailButton = findViewById(R.id.email_button); finishButton = findViewById(R.id.finish_button); } private void handlePrintReceipt() { try { // Check if printer service is available if (MyApplication.app.sunmiPrinterService == null) { showToast("Printer tidak tersedia"); return; } // Get all the data from the views String merchantNameText = merchantName.getText().toString(); String merchantLocationText = merchantLocation.getText().toString(); String midTextValue = midText.getText().toString(); String tidTextValue = tidText.getText().toString(); String transactionNumberText = transactionNumber.getText().toString(); String transactionDateText = transactionDate.getText().toString(); String paymentMethodText = paymentMethod.getText().toString(); String cardTypeText = cardType.getText().toString(); String transactionTotalText = transactionTotal.getText().toString(); String taxPercentageText = taxPercentage.getText().toString(); String serviceFeeText = serviceFee.getText().toString(); String finalTotalText = finalTotal.getText().toString(); showToast("Mencetak struk..."); try { // Start printing MyApplication.app.sunmiPrinterService.enterPrinterBuffer(true); // Set alignment to center MyApplication.app.sunmiPrinterService.setAlignment(1, null); // Print header MyApplication.app.sunmiPrinterService.printText("# Payvora PRO\n\n", null); // Set alignment to left MyApplication.app.sunmiPrinterService.setAlignment(0, null); // Print merchant info MyApplication.app.sunmiPrinterService.printText(merchantNameText + "\n", null); MyApplication.app.sunmiPrinterService.printText(merchantLocationText + "\n\n", null); // Print MID/TID MyApplication.app.sunmiPrinterService.printText(midTextValue + " | " + tidTextValue + "\n\n", null); // Print transaction details MyApplication.app.sunmiPrinterService.printText("Nomor transaksi " + transactionNumberText + "\n", null); MyApplication.app.sunmiPrinterService.printText("Tanggal transaksi " + transactionDateText + "\n", null); MyApplication.app.sunmiPrinterService.printText("Metode pembayaran " + paymentMethodText + "\n", null); MyApplication.app.sunmiPrinterService.printText("Jenis Kartu " + cardTypeText + "\n\n", null); // Print amounts MyApplication.app.sunmiPrinterService.printText("Total transaksi " + transactionTotalText + "\n", null); MyApplication.app.sunmiPrinterService.printText("Pajak (%) " + taxPercentageText + "\n", null); MyApplication.app.sunmiPrinterService.printText("Biaya Layanan " + serviceFeeText + "\n\n", null); // Print total in bold MyApplication.app.sunmiPrinterService.printText("**TOTAL** **" + finalTotalText + "**\n", null); // Add some line feeds MyApplication.app.sunmiPrinterService.lineWrap(4, null); // Exit buffer mode MyApplication.app.sunmiPrinterService.exitPrinterBuffer(true); } catch (RemoteException e) { e.printStackTrace(); showToast("Error printer: " + e.getMessage()); } } catch (Exception e) { Log.e("ReceiptActivity", "Print error", e); showToast("Error saat mencetak: " + e.getMessage()); } } 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 } } } /** Create Bitmap from View */ private Bitmap createBitmapFromView(View view) { try { // Enable drawing cache view.setDrawingCacheEnabled(true); view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); // Measure and layout the view to ensure it has correct dimensions view.measure( View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); // Create bitmap Bitmap bitmap = Bitmap.createBitmap( view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888 ); Canvas canvas = new Canvas(bitmap); view.draw(canvas); return bitmap; } catch (Exception e) { Log.e("ReceiptActivity", "Error creating bitmap", e); return null; } finally { view.setDrawingCacheEnabled(false); } } private void setupClickListeners() { // Back navigation - Goes back to previous activity backNavigation.setOnClickListener(v -> handleBackNavigation()); backArrow.setOnClickListener(v -> handleBackNavigation()); toolbarTitle.setOnClickListener(v -> handleBackNavigation()); // Action buttons printButton.setOnClickListener(v -> handlePrintReceipt()); emailButton.setOnClickListener(v -> handleEmailReceipt()); finishButton.setOnClickListener(v -> handleFinish()); } /** * ✅ ENHANCED: Load transaction data with support for both EMV/Card and QRIS */ private void loadTransactionData() { Intent intent = getIntent(); if (intent != null) { Log.d("ReceiptActivity", "=== LOADING ENHANCED TRANSACTION DATA ==="); // ✅ DETECT TRANSACTION TYPE String callingActivity = intent.getStringExtra("calling_activity"); String channelCategory = intent.getStringExtra("channel_category"); String channelCode = intent.getStringExtra("channel_code"); boolean isEmvTransaction = "EMV_CARD".equals(channelCategory) || "ResultTransactionActivity".equals(callingActivity); boolean isQrisTransaction = "QRIS".equalsIgnoreCase(channelCode) || "QrisResultActivity".equals(callingActivity); Log.d("ReceiptActivity", "🔍 TRANSACTION TYPE DETECTION:"); Log.d("ReceiptActivity", " Calling Activity: " + callingActivity); Log.d("ReceiptActivity", " Channel Category: " + channelCategory); Log.d("ReceiptActivity", " Channel Code: " + channelCode); Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction); Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction); // Get all available data from intent String amount = intent.getStringExtra("transaction_amount"); String grossAmount = intent.getStringExtra("gross_amount"); String merchantNameStr = intent.getStringExtra("merchant_name"); String merchantLocationStr = intent.getStringExtra("merchant_location"); String transactionId = intent.getStringExtra("transaction_id"); String referenceId = intent.getStringExtra("reference_id"); String orderId = intent.getStringExtra("order_id"); String transactionDateStr = intent.getStringExtra("transaction_date"); String createdAt = intent.getStringExtra("created_at"); String paymentMethodStr = intent.getStringExtra("payment_method"); String cardTypeStr = intent.getStringExtra("card_type"); String acquirer = intent.getStringExtra("acquirer"); String mid = intent.getStringExtra("mid"); String tid = intent.getStringExtra("tid"); // ✅ EMV SPECIFIC DATA String emvCardholderName = intent.getStringExtra("emv_cardholder_name"); String emvAid = intent.getStringExtra("emv_aid"); String emvExpiry = intent.getStringExtra("emv_expiry"); String cardNumber = intent.getStringExtra("card_number"); String midtransResponse = intent.getStringExtra("midtrans_response"); boolean emvMode = intent.getBooleanExtra("emv_mode", false); // ✅ QRIS SPECIFIC DATA String qrString = intent.getStringExtra("qr_string"); // Log received data for debugging Log.d("ReceiptActivity", "🔍 RECEIVED DATA:"); Log.d("ReceiptActivity", " amount: " + amount); Log.d("ReceiptActivity", " referenceId: " + referenceId); Log.d("ReceiptActivity", " orderId: " + orderId); Log.d("ReceiptActivity", " acquirer (from intent): " + acquirer); Log.d("ReceiptActivity", " createdAt: " + createdAt); Log.d("ReceiptActivity", " EMV Mode: " + emvMode); Log.d("ReceiptActivity", " EMV Cardholder: " + emvCardholderName); Log.d("ReceiptActivity", " Card Number: " + (cardNumber != null ? cardNumber : "N/A")); Log.d("ReceiptActivity", " QR String Available: " + (qrString != null && !qrString.isEmpty())); // 1. Set merchant data with defaults merchantName.setText(merchantNameStr != null ? merchantNameStr : "TOKO KLONTONG PAK EKO"); merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Ciputat Baru, Tangsel"); // 2. Set MID and TID midText.setText("MID: " + (mid != null ? mid : "123456789901")); tidText.setText("TID: " + (tid != null ? tid : "123456789901")); // 3. Set transaction number String displayTransactionNumber = getDisplayTransactionNumber(referenceId, transactionId, orderId); transactionNumber.setText(displayTransactionNumber); // 4. Set transaction date String displayDate = getDisplayTransactionDate(createdAt, transactionDateStr, isEmvTransaction); transactionDate.setText(displayDate); // 5. ✅ ENHANCED: Set payment method based on transaction type String displayPaymentMethod = getDisplayPaymentMethod(channelCode, paymentMethodStr, isEmvTransaction, emvMode); paymentMethod.setText(displayPaymentMethod); // 6. ✅ ENHANCED: Set card type with EMV and QRIS priority String displayCardType = getDisplayCardType(cardTypeStr, acquirer, channelCode, isEmvTransaction, isQrisTransaction, midtransResponse, referenceId); cardType.setText(displayCardType); // 7. ✅ Format and set amounts with proper calculation setAmountDataEnhanced(amount, grossAmount, isEmvTransaction, isQrisTransaction); Log.d("ReceiptActivity", "💳 FINAL DISPLAY VALUES:"); Log.d("ReceiptActivity", " Payment Method: " + displayPaymentMethod); Log.d("ReceiptActivity", " Card Type: " + displayCardType); Log.d("ReceiptActivity", " Transaction Number: " + displayTransactionNumber); Log.d("ReceiptActivity", "=== ENHANCED TRANSACTION DATA LOADED ==="); } } /** * Get display transaction number with priority */ private String getDisplayTransactionNumber(String referenceId, String transactionId, String orderId) { if (referenceId != null && !referenceId.isEmpty()) { return referenceId; } else if (transactionId != null && !transactionId.isEmpty()) { // For long transaction IDs, show last 10 characters return transactionId.length() > 10 ? transactionId.substring(transactionId.length() - 10) : transactionId; } else if (orderId != null && !orderId.isEmpty()) { return orderId.length() > 10 ? orderId.substring(orderId.length() - 10) : orderId; } return String.valueOf(System.currentTimeMillis() % 10000000000L); } /** * Get display transaction date with proper formatting */ private String getDisplayTransactionDate(String createdAt, String transactionDateStr, boolean isEmvTransaction) { String dateToFormat = null; if (createdAt != null && !createdAt.isEmpty()) { dateToFormat = createdAt; } else if (transactionDateStr != null && !transactionDateStr.isEmpty()) { dateToFormat = transactionDateStr; } if (dateToFormat != null) { if (isEmvTransaction) { return formatDateForEmvTransaction(dateToFormat); } else { return formatDateFromCreatedAt(dateToFormat); } } return getCurrentDateTime(); } private String formatDateForEmvTransaction(String dateString) { try { // EMV transactions might use different date formats String[] inputFormats = { "dd MMMM yyyy HH:mm", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "dd/MM/yyyy HH:mm" }; SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss", new Locale("id", "ID")); for (String format : inputFormats) { try { SimpleDateFormat inputFormat = new SimpleDateFormat(format, format.contains("MMMM") ? new Locale("id", "ID") : Locale.getDefault()); Date date = inputFormat.parse(dateString); String formatted = outputFormat.format(date); Log.d("ReceiptActivity", "EMV Date formatting: '" + dateString + "' -> '" + formatted + "'"); return formatted; } catch (Exception ignored) { // Try next format } } // If all formats fail, return as-is Log.w("ReceiptActivity", "Could not format EMV date: " + dateString); return dateString; } catch (Exception e) { Log.e("ReceiptActivity", "Error formatting EMV date: " + dateString, e); return dateString; } } private String formatDateFromCreatedAt(String createdAt) { try { // Input format from database: "yyyy-MM-dd HH:mm:ss" atau "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" SimpleDateFormat inputFormat; if (createdAt.contains("T")) { inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()); } else { inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); } // Output format for receipt: "dd/MM/yy HH:mm:ss" SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss", new Locale("id", "ID")); Date date = inputFormat.parse(createdAt); String formatted = outputFormat.format(date); Log.d("ReceiptActivity", "Date formatting: '" + createdAt + "' -> '" + formatted + "'"); return formatted; } catch (Exception e) { Log.e("ReceiptActivity", "Error formatting date: " + createdAt, e); // Fallback: return original string if parsing fails return createdAt; } } /** * Get display payment method with EMV support */ private String getDisplayPaymentMethod(String channelCode, String fallbackPaymentMethod, boolean isEmvTransaction, boolean emvMode) { // ✅ NEW: Check if this is from ReprintActivity String callingActivity = getIntent().getStringExtra("calling_activity"); boolean isFromReprintActivity = "ReprintActivity".equals(callingActivity); if (isFromReprintActivity) { // For ReprintActivity, use the payment_method that was already processed if (fallbackPaymentMethod != null && !fallbackPaymentMethod.isEmpty()) { Log.d("ReceiptActivity", "✅ REPRINT: Using processed payment method: " + fallbackPaymentMethod); return fallbackPaymentMethod; } } // Continue with existing logic for other sources if (isEmvTransaction) { // For EMV transactions, be more specific if (emvMode) { if (channelCode != null) { switch (channelCode.toUpperCase()) { case "CREDIT": return "Kartu Kredit (EMV)"; case "DEBIT": return "Kartu Debit (EMV)"; default: return "Kartu Kredit (EMV)"; } } return "Kartu Kredit (EMV)"; } else { // Magnetic stripe if (channelCode != null) { switch (channelCode.toUpperCase()) { case "CREDIT": return "Kartu Kredit"; case "DEBIT": return "Kartu Debit"; default: return "Kartu Kredit"; } } return "Kartu Kredit"; } } else { // For QRIS and other transactions, use existing logic return getPaymentMethodFromChannelCode(channelCode, fallbackPaymentMethod); } } /** * Get payment method name from channel_code with comprehensive mapping */ private String getPaymentMethodFromChannelCode(String channelCode, String fallbackPaymentMethod) { if (channelCode != null && !channelCode.isEmpty()) { String code = channelCode.toUpperCase(); switch (code) { case "QRIS": return "QRIS"; case "DEBIT": return "Kartu Debit"; case "CREDIT": return "Kartu Kredit"; case "BCA": return "BCA"; case "MANDIRI": return "Bank Mandiri"; case "BNI": return "Bank BNI"; case "BRI": return "Bank BRI"; case "PERMATA": return "Bank Permata"; case "CIMB": return "CIMB Niaga"; case "DANAMON": return "Bank Danamon"; case "BSI": return "Bank Syariah Indonesia"; case "CASH": return "Tunai"; case "EDC": return "EDC"; case "ALFAMART": return "Alfamart"; case "INDOMARET": return "Indomaret"; case "AKULAKU": return "Akulaku"; default: return code; // Return the code as-is if not mapped } } // Fallback to provided payment method or default return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS"; } /** * ✅ ENHANCED: Card type detection with EMV and QRIS priority */ private String getDisplayCardType(String cardTypeStr, String acquirer, String channelCode, boolean isEmvTransaction, boolean isQrisTransaction, String midtransResponse, String referenceId) { Log.d("ReceiptActivity", "🔍 ENHANCED CARD TYPE DETECTION:"); Log.d("ReceiptActivity", " Input Card Type: " + cardTypeStr); Log.d("ReceiptActivity", " Input Acquirer: " + acquirer); Log.d("ReceiptActivity", " Channel Code: " + channelCode); Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction); Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction); // ✅ NEW: Check if this is from ReprintActivity String callingActivity = getIntent().getStringExtra("calling_activity"); boolean isFromReprintActivity = "ReprintActivity".equals(callingActivity); Log.d("ReceiptActivity", " Is from ReprintActivity: " + isFromReprintActivity); if (isFromReprintActivity) { return handleReprintActivityCardType(cardTypeStr, acquirer, channelCode, referenceId); } // Continue with existing logic for other sources if (isEmvTransaction) { // ✅ FOR EMV TRANSACTIONS: Priority to cardTypeStr from ResultTransactionActivity if (cardTypeStr != null && !cardTypeStr.isEmpty() && !cardTypeStr.equalsIgnoreCase("unknown")) { Log.d("ReceiptActivity", "✅ EMV: Using provided card type: " + cardTypeStr); return cardTypeStr; } // ✅ FALLBACK: Try to extract from Midtrans response if (midtransResponse != null && !midtransResponse.isEmpty()) { String bankFromMidtrans = extractBankFromMidtransResponse(midtransResponse); if (bankFromMidtrans != null && !bankFromMidtrans.isEmpty()) { Log.d("ReceiptActivity", "✅ EMV: Using bank from Midtrans response: " + bankFromMidtrans); return bankFromMidtrans; } } // ✅ FALLBACK: Use acquirer if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) { String mappedAcquirer = getCardTypeFromAcquirer(acquirer, channelCode, null); Log.d("ReceiptActivity", "✅ EMV: Using mapped acquirer: " + mappedAcquirer); return mappedAcquirer; } Log.d("ReceiptActivity", "⚠️ EMV: Using default fallback: BCA"); return "BCA"; // Default for EMV } else if (isQrisTransaction) { // ✅ FOR QRIS TRANSACTIONS: Enhanced detection Log.d("ReceiptActivity", "🔍 QRIS transaction detected - searching for real acquirer"); // Priority 1: Use provided cardTypeStr if it's not generic if (cardTypeStr != null && !cardTypeStr.isEmpty() && !cardTypeStr.equalsIgnoreCase("qris") && !cardTypeStr.equalsIgnoreCase("unknown")) { Log.d("ReceiptActivity", "✅ QRIS: Using provided specific card type: " + cardTypeStr); return cardTypeStr; } // Priority 2: Search webhook logs for real acquirer if (referenceId != null && !referenceId.isEmpty()) { String realAcquirer = fetchRealAcquirerSync(referenceId); if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) { String mappedQrisAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null); Log.d("ReceiptActivity", "✅ QRIS real acquirer found: " + realAcquirer + " -> " + mappedQrisAcquirer); return mappedQrisAcquirer; } else { Log.w("ReceiptActivity", "⚠️ QRIS real acquirer not found, using generic QRIS"); // Start async search for better results fetchRealAcquirerFromWebhook(referenceId); return "QRIS"; } } else { return "QRIS"; } } else { // ✅ FOR OTHER TRANSACTIONS: Use standard logic return getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr); } } private String handleReprintActivityCardType(String cardTypeStr, String acquirer, String channelCode, String referenceId) { Log.d("ReceiptActivity", "🔍 HANDLING REPRINT ACTIVITY CARD TYPE:"); Log.d("ReceiptActivity", " Provided Card Type: " + cardTypeStr); Log.d("ReceiptActivity", " Channel Code: " + channelCode); Log.d("ReceiptActivity", " Acquirer: " + acquirer); // Priority 1: If channelCode indicates QRIS, search for real acquirer if ("QRIS".equalsIgnoreCase(channelCode) || "RETAIL_OUTLET".equalsIgnoreCase(channelCode)) { Log.d("ReceiptActivity", "🔍 QRIS detected from ReprintActivity"); if (referenceId != null && !referenceId.isEmpty()) { String realAcquirer = fetchRealAcquirerSync(referenceId); if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) { String mappedAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null); Log.d("ReceiptActivity", "✅ REPRINT QRIS: Found real acquirer: " + realAcquirer + " -> " + mappedAcquirer); return mappedAcquirer; } else { Log.w("ReceiptActivity", "⚠️ REPRINT QRIS: No real acquirer found, using generic QRIS"); // Start async search for better results fetchRealAcquirerFromWebhook(referenceId); return "QRIS"; } } else { return "QRIS"; } } // Priority 2: Use cardTypeStr if it's meaningful if (cardTypeStr != null && !cardTypeStr.isEmpty() && !cardTypeStr.equalsIgnoreCase("unknown") && !cardTypeStr.equalsIgnoreCase("retail_outlet")) { Log.d("ReceiptActivity", "✅ REPRINT: Using provided card type: " + cardTypeStr); return cardTypeStr; } // Priority 3: Map from channelCode if (channelCode != null && !channelCode.isEmpty()) { String mappedFromChannel = mapChannelCodeToCardType(channelCode); Log.d("ReceiptActivity", "✅ REPRINT: Mapped from channel code: " + channelCode + " -> " + mappedFromChannel); return mappedFromChannel; } // Priority 4: Use acquirer if available if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) { String mappedAcquirer = getCardTypeFromAcquirer(acquirer, channelCode, null); Log.d("ReceiptActivity", "✅ REPRINT: Using mapped acquirer: " + mappedAcquirer); return mappedAcquirer; } // Final fallback Log.w("ReceiptActivity", "⚠️ REPRINT: Using final fallback: Unknown"); return "Unknown"; } private String mapChannelCodeToCardType(String channelCode) { if (channelCode == null || channelCode.isEmpty()) { return "Unknown"; } String code = channelCode.toUpperCase().trim(); switch (code) { case "QRIS": case "RETAIL_OUTLET": return "QRIS"; case "DEBIT": case "DEBIT_CARD": return "Visa"; // Default for debit case "CREDIT": case "CREDIT_CARD": return "Mastercard"; // Default for credit case "BCA": return "BCA"; case "MANDIRI": return "Mandiri"; case "BNI": return "BNI"; case "BRI": return "BRI"; case "PERMATA": return "Permata"; case "CIMB": return "CIMB Niaga"; case "DANAMON": return "Danamon"; case "BSI": return "BSI"; default: return capitalizeFirstLetter(channelCode.replace("_", " ")); } } private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) { // STEP 1: If we have a valid acquirer that's not generic "qris", use it if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) { String acq = acquirer.toLowerCase().trim(); Log.d("ReceiptActivity", "🔍 Mapping acquirer: '" + acquirer + "' -> '" + acq + "'"); // ✅ COMPREHENSIVE acquirer mapping (case-insensitive) String mappedName = ISSUER_DISPLAY_MAP.get(acq); if (mappedName != null) { Log.d("ReceiptActivity", "✅ Mapped acquirer: " + acquirer + " -> " + mappedName); return mappedName; } // Additional mapping for variations not in the map switch (acq) { // Credit card acquirers case "visa": return "Visa"; case "mastercard": case "master_card": return "Mastercard"; case "jcb": return "JCB"; case "amex": case "american_express": return "American Express"; case "discover": return "Discover"; case "unionpay": case "union_pay": return "UnionPay"; // Over-the-counter case "alfamart": case "alfa_mart": return "Alfamart"; case "indomaret": case "indo_maret": return "Indomaret"; case "pos_indonesia": return "Pos Indonesia"; // Buy now pay later case "akulaku": return "Akulaku"; case "kredivo": return "Kredivo"; case "indodana": case "indo_dana": return "Indodana"; case "traveloka_paylater": case "traveloka": return "Traveloka PayLater"; case "atome": return "Atome"; default: // Return capitalized version of acquirer if not in mapping String capitalized = capitalizeFirstLetter(acquirer); Log.d("ReceiptActivity", "🔤 Using capitalized acquirer: " + capitalized); return capitalized; } } // STEP 2: Fallback based on channel code if (channelCode != null && !channelCode.isEmpty()) { String code = channelCode.toUpperCase(); Log.d("ReceiptActivity", "🔍 Using channel code fallback: " + code); switch (code) { case "QRIS": return "QRIS"; // Generic QRIS case "DEBIT": return "Debit"; case "CREDIT": return "Credit"; case "BCA": return "BCA"; case "MANDIRI": return "Mandiri"; case "BNI": return "BNI"; case "BRI": return "BRI"; default: return code; } } // STEP 3: Final fallback String result = fallbackCardType != null ? fallbackCardType : "Unknown"; Log.d("ReceiptActivity", "🔍 Using final fallback: " + result); return result; } private String fetchRealAcquirerSync(String referenceId) { try { Log.d("ReceiptActivity", "🔍 Sync search for acquirer: " + referenceId); // ✅ IMPROVED: Search with broader scope for better matches String queryUrl = "https://be-edc.msvc.app/api-logs?limit=100&sortOrder=DESC&sortColumn=created_at"; URL url = new URL(queryUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "application/json"); conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); conn.setConnectTimeout(8000); // Slightly longer timeout for better results conn.setReadTimeout(8000); if (conn.getResponseCode() == 200) { BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) { response.append(line); } org.json.JSONObject json = new org.json.JSONObject(response.toString()); org.json.JSONArray results = json.optJSONArray("results"); if (results != null && results.length() > 0) { Log.d("ReceiptActivity", "📊 Sync analyzing " + results.length() + " webhook logs"); // ✅ PRIORITY SEARCH: Look for settlement first, then pending, then any String[] searchPriority = {"settlement", "capture", "success", "pending", ""}; for (String status : searchPriority) { String[] targetStatuses = status.isEmpty() ? new String[]{} : new String[]{status}; String foundAcquirer = searchLogsByStatus(results, referenceId, targetStatuses); if (foundAcquirer != null && !foundAcquirer.isEmpty() && !foundAcquirer.equalsIgnoreCase("qris")) { Log.d("ReceiptActivity", "🎯 Sync found acquirer: " + foundAcquirer + " (priority: " + (status.isEmpty() ? "any" : status) + ")"); return foundAcquirer; } } Log.w("ReceiptActivity", "⚠️ Sync search completed but no valid acquirer found"); } } else { Log.w("ReceiptActivity", "⚠️ Sync API call failed with code: " + conn.getResponseCode()); } } catch (Exception e) { Log.e("ReceiptActivity", "❌ Sync acquirer fetch error: " + e.getMessage()); } return null; // No acquirer found } private void fetchRealAcquirerFromWebhook(String referenceId) { new Thread(() -> { try { Log.d("ReceiptActivity", "🔍 Async search for real acquirer: " + referenceId); String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at"; URL url = new URL(queryUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept", "application/json"); conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); conn.setConnectTimeout(15000); conn.setReadTimeout(15000); if (conn.getResponseCode() == 200) { BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder response = new StringBuilder(); String line; while ((line = br.readLine()) != null) { response.append(line); } org.json.JSONObject json = new org.json.JSONObject(response.toString()); org.json.JSONArray results = json.optJSONArray("results"); if (results != null && results.length() > 0) { Log.d("ReceiptActivity", "📊 Async analyzing " + results.length() + " webhook logs"); String realAcquirer = searchForRealAcquirer(results, referenceId); if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) { Log.d("ReceiptActivity", "✅ Async found real acquirer: " + realAcquirer); // Update UI on main thread final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null); runOnUiThread(() -> { if (cardType != null) { cardType.setText(displayAcquirer); Log.d("ReceiptActivity", "🎨 UI UPDATED: QRIS -> " + displayAcquirer); // Show a subtle indication that the data was updated showToast("Card type updated: " + displayAcquirer); } }); } else { Log.w("ReceiptActivity", "⚠️ Async search found no valid acquirer for: " + referenceId); } } else { Log.w("ReceiptActivity", "⚠️ Async search returned no results"); } } else { Log.w("ReceiptActivity", "⚠️ Async API call failed with code: " + conn.getResponseCode()); } } catch (Exception e) { Log.e("ReceiptActivity", "❌ Async acquirer fetch error: " + e.getMessage(), e); } }).start(); } private String searchForRealAcquirer(org.json.JSONArray results, String referenceId) { try { Log.d("ReceiptActivity", "🔍 Analyzing " + results.length() + " webhook logs for acquirer"); Log.d("ReceiptActivity", "🎯 Target reference ID: " + referenceId); // Get order_id from intent for additional matching String orderId = getIntent().getStringExtra("order_id"); Log.d("ReceiptActivity", "🎯 Target order ID: " + orderId); // Strategy 1: Look for settlement/success transactions first (highest priority) Log.d("ReceiptActivity", "🔍 Strategy 1: Searching for settlement/success status"); String acquirerFromSettlement = searchLogsByStatus(results, referenceId, new String[]{"settlement", "capture", "success"}); if (acquirerFromSettlement != null) { Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from settlement: " + acquirerFromSettlement); return acquirerFromSettlement; } // Strategy 2: Look for pending transactions Log.d("ReceiptActivity", "🔍 Strategy 2: Searching for pending status"); String acquirerFromPending = searchLogsByStatus(results, referenceId, new String[]{"pending"}); if (acquirerFromPending != null) { Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from pending: " + acquirerFromPending); return acquirerFromPending; } // Strategy 3: Look for any transaction with this reference (broadest search) Log.d("ReceiptActivity", "🔍 Strategy 3: Searching for any status"); String acquirerFromAny = searchLogsByStatus(results, referenceId, new String[]{}); if (acquirerFromAny != null) { Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from any status: " + acquirerFromAny); return acquirerFromAny; } Log.w("ReceiptActivity", "❌ FAILED: No acquirer found in webhook logs for reference: " + referenceId); // Strategy 4: Debug logging - show what we actually found Log.d("ReceiptActivity", "🔍 DEBUG: Showing first 5 log entries for analysis:"); for (int i = 0; i < Math.min(5, results.length()); i++) { try { org.json.JSONObject log = results.getJSONObject(i); org.json.JSONObject reqBody = log.optJSONObject("request_body"); if (reqBody != null) { String logOrderId = reqBody.optString("order_id", ""); String logRefId = reqBody.optString("reference_id", ""); String logStatus = reqBody.optString("transaction_status", ""); String logAcquirer = reqBody.optString("acquirer", ""); String logIssuer = reqBody.optString("issuer", ""); Log.d("ReceiptActivity", " Log " + (i+1) + ": " + "order_id=" + logOrderId + ", ref_id=" + logRefId + ", status=" + logStatus + ", acquirer=" + logAcquirer + ", issuer=" + logIssuer); } } catch (Exception e) { Log.w("ReceiptActivity", "Error parsing debug log " + i + ": " + e.getMessage()); } } return null; } catch (Exception e) { Log.e("ReceiptActivity", "❌ Error analyzing webhook logs: " + e.getMessage(), e); return null; } } private String searchLogsByStatus(org.json.JSONArray results, String referenceId, String[] targetStatuses) { try { for (int i = 0; i < results.length(); i++) { org.json.JSONObject log = results.getJSONObject(i); org.json.JSONObject reqBody = log.optJSONObject("request_body"); if (reqBody != null) { String logReferenceId = reqBody.optString("reference_id", ""); String logTransactionStatus = reqBody.optString("transaction_status", ""); String logAcquirer = reqBody.optString("acquirer", ""); String logIssuer = reqBody.optString("issuer", ""); String logOrderId = reqBody.optString("order_id", ""); Log.d("ReceiptActivity", "🔍 Checking log: order_id=" + logOrderId + ", status=" + logTransactionStatus + ", acquirer=" + logAcquirer + ", issuer=" + logIssuer); // Check for direct reference match boolean isDirectMatch = referenceId.equals(logReferenceId); // ✅ IMPROVED: Also check order_id match from QrisResultActivity boolean isOrderMatch = false; String orderId = getIntent().getStringExtra("order_id"); if (orderId != null && !orderId.isEmpty() && orderId.equals(logOrderId)) { isOrderMatch = true; Log.d("ReceiptActivity", "✅ Found order_id match: " + orderId); } // Check custom_field1 for refresh tracking boolean isRefreshMatch = false; String customField1 = reqBody.optString("custom_field1", ""); if (!customField1.isEmpty()) { try { org.json.JSONObject customData = new org.json.JSONObject(customField1); String originalReference = customData.optString("original_reference", ""); String appReferenceId = customData.optString("app_reference_id", ""); if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) { isRefreshMatch = true; } } catch (org.json.JSONException e) { // Ignore parsing errors } } // Check if this log matches our reference if (isDirectMatch || isRefreshMatch || isOrderMatch) { Log.d("ReceiptActivity", "🎯 TRANSACTION MATCH FOUND!"); Log.d("ReceiptActivity", " Match type: " + (isDirectMatch ? "DIRECT " : "") + (isRefreshMatch ? "REFRESH " : "") + (isOrderMatch ? "ORDER" : "")); // If target statuses specified, check status if (targetStatuses.length > 0) { boolean statusMatches = false; for (String targetStatus : targetStatuses) { if (logTransactionStatus.equalsIgnoreCase(targetStatus)) { statusMatches = true; break; } } if (!statusMatches) { Log.d("ReceiptActivity", " Status doesn't match target: " + logTransactionStatus); continue; // Skip if status doesn't match } } // ✅ CRITICAL FIX: For QRIS transactions, prioritize issuer over acquirer String foundAcquirer = null; // Check if this is a QRIS transaction by payment_type String paymentType = reqBody.optString("payment_type", ""); boolean isQrisTransaction = "qris".equalsIgnoreCase(paymentType); if (isQrisTransaction) { // For QRIS: issuer contains the actual payment provider (LinkAja, DANA, etc.) // acquirer is usually just "gopay" (the QRIS aggregator) if (!logIssuer.isEmpty() && !logIssuer.equalsIgnoreCase("qris")) { foundAcquirer = logIssuer; Log.d("ReceiptActivity", "📱 QRIS: Using issuer as acquirer: " + foundAcquirer); } else if (!logAcquirer.isEmpty() && !logAcquirer.equalsIgnoreCase("qris")) { foundAcquirer = logAcquirer; Log.d("ReceiptActivity", "📱 QRIS: Fallback to acquirer: " + foundAcquirer); } } else { // For non-QRIS: prefer acquirer over issuer (traditional behavior) if (!logAcquirer.isEmpty() && !logAcquirer.equalsIgnoreCase("qris")) { foundAcquirer = logAcquirer; Log.d("ReceiptActivity", "💳 Non-QRIS: Using acquirer: " + foundAcquirer); } else if (!logIssuer.isEmpty() && !logIssuer.equalsIgnoreCase("qris")) { foundAcquirer = logIssuer; Log.d("ReceiptActivity", "💳 Non-QRIS: Fallback to issuer: " + foundAcquirer); } } if (foundAcquirer != null && !foundAcquirer.isEmpty()) { Log.d("ReceiptActivity", "✅ FINAL ACQUIRER FOUND: " + foundAcquirer + " (status: " + logTransactionStatus + ", payment_type: " + paymentType + ")"); return foundAcquirer; } else { Log.w("ReceiptActivity", "⚠️ Transaction matched but no valid acquirer found"); } } } } } catch (Exception e) { Log.e("ReceiptActivity", "❌ Error searching logs by status: " + e.getMessage(), e); } return null; // No acquirer found for specified criteria } /** * Extract bank from Midtrans response JSON string */ private String extractBankFromMidtransResponse(String midtransResponse) { try { org.json.JSONObject response = new org.json.JSONObject(midtransResponse); // Try different possible bank fields String[] bankFields = {"bank", "issuer", "acquiring_bank", "issuer_bank"}; for (String field : bankFields) { if (response.has(field)) { String bankValue = response.getString(field); if (bankValue != null && !bankValue.trim().isEmpty() && !bankValue.equalsIgnoreCase("qris")) { Log.d("ReceiptActivity", "Found bank in Midtrans response (" + field + "): " + bankValue); return formatBankNameForReceipt(bankValue); } } } Log.w("ReceiptActivity", "No valid bank found in Midtrans response"); return null; } catch (Exception e) { Log.e("ReceiptActivity", "Error extracting bank from Midtrans response: " + e.getMessage()); return null; } } /** * Format bank name specifically for receipt display */ private String formatBankNameForReceipt(String bankName) { if (bankName == null || bankName.trim().isEmpty()) { return "BCA"; // Default } String formatted = bankName.trim(); // Common bank name mappings for receipt display switch (formatted.toUpperCase()) { case "BCA": case "BANK BCA": case "BANK CENTRAL ASIA": return "BCA"; case "MANDIRI": case "BANK MANDIRI": return "Mandiri"; case "BNI": case "BANK BNI": case "BANK NEGARA INDONESIA": return "BNI"; case "BRI": case "BANK BRI": case "BANK RAKYAT INDONESIA": return "BRI"; case "CIMB": case "CIMB NIAGA": case "BANK CIMB NIAGA": return "CIMB Niaga"; case "DANAMON": case "BANK DANAMON": return "Danamon"; case "PERMATA": case "BANK PERMATA": return "Permata"; default: // Return capitalized version return capitalizeFirstLetter(formatted); } } private String capitalizeFirstLetter(String input) { if (input == null || input.isEmpty()) { return input; } // Handle special cases String cleaned = input.trim(); // Special handling for common patterns if (cleaned.toLowerCase().contains("pay")) { // Keep "Pay" capitalized in payment methods return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase() .replace("pay", "Pay"); } return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase(); } /** * ✅ ENHANCED: Set amount data with EMV and QRIS support */ private void setAmountDataEnhanced(String amount, String grossAmount, boolean isEmvTransaction, boolean isQrisTransaction) { String amountToUse = amount != null ? amount : grossAmount; Log.d("ReceiptActivity", "Setting enhanced amount data - amount: " + amount + ", grossAmount: " + grossAmount + ", using: " + amountToUse + ", isEMV: " + isEmvTransaction + ", isQRIS: " + isQrisTransaction); if (amountToUse != null) { try { String cleanAmount = cleanAmountString(amountToUse); Log.d("ReceiptActivity", "Cleaned amount: " + cleanAmount); long amountLong = Long.parseLong(cleanAmount); // Set transaction total transactionTotal.setText(formatCurrency(amountLong)); // ✅ CALCULATE FEES BASED ON TRANSACTION TYPE long tax = 0; long serviceFeeValue = 0; long total = amountLong; if (isEmvTransaction) { // For EMV transactions, check if gross amount includes additional fees if (grossAmount != null && !grossAmount.equals(amount)) { try { long grossAmountLong = Long.parseLong(cleanAmountString(grossAmount)); long difference = grossAmountLong - amountLong; if (difference > 0) { // Assume 11% tax and 500 service fee (adjust based on your business logic) tax = Math.round(amountLong * 0.11); serviceFeeValue = 500; total = grossAmountLong; Log.d("ReceiptActivity", "EMV: Calculated tax=" + tax + ", service=" + serviceFeeValue + ", total=" + total); } } catch (Exception e) { Log.w("ReceiptActivity", "Could not parse gross amount for EMV: " + grossAmount); } } } else if (isQrisTransaction) { // For QRIS, typically no additional fees (tax=0, service=0) tax = 0; serviceFeeValue = 0; total = amountLong; Log.d("ReceiptActivity", "QRIS: No additional fees - total=" + total); } // Set calculated values taxPercentage.setText(tax > 0 ? "11%" : "0%"); serviceFee.setText(formatCurrency(serviceFeeValue)); finalTotal.setText(formatCurrency(total)); Log.d("ReceiptActivity", "Enhanced amount formatting successful: " + amountLong + " -> " + formatCurrency(total)); } catch (NumberFormatException e) { Log.e("ReceiptActivity", "Error parsing enhanced amount: " + amountToUse, e); // Fallback if parsing fails transactionTotal.setText(amountToUse); taxPercentage.setText("0%"); serviceFee.setText("0"); finalTotal.setText(amountToUse); } } else { // Default values if no amount provided transactionTotal.setText("0"); taxPercentage.setText("0%"); serviceFee.setText("0"); finalTotal.setText("0"); } } /** * Clean amount string to extract raw number correctly * Input examples: "1000", "1000.00", "Rp 1.000", "1.000" * Output: "1000" */ private String cleanAmountString(String amount) { if (amount == null || amount.isEmpty()) { return "0"; } // Remove currency symbols and spaces first String cleaned = amount .replace("Rp. ", "") .replace("Rp ", "") .replace("IDR ", "") .replace(" ", "") .trim(); Log.d("ReceiptActivity", "After currency removal: '" + cleaned + "'"); // Handle decimal cases properly if (cleaned.contains(".")) { // Check if it contains decimal cents (like "1000.00") or thousand separator (like "1.000") String[] parts = cleaned.split("\\."); if (parts.length == 2) { String beforeDot = parts[0]; String afterDot = parts[1]; // If after dot is "00" or "0", it's decimal cents - remove it if (afterDot.equals("00") || afterDot.equals("0")) { cleaned = beforeDot; Log.d("ReceiptActivity", "Removed decimal cents: '" + cleaned + "'"); } // If after dot has 3 digits, it's thousand separator - combine them else if (afterDot.length() == 3) { cleaned = beforeDot + afterDot; Log.d("ReceiptActivity", "Combined thousand separator: '" + cleaned + "'"); } // For other cases, try to determine based on length else { // If beforeDot is short (1-3 digits) and afterDot is 3 digits, it's thousand separator if (beforeDot.length() <= 3 && afterDot.length() == 3) { cleaned = beforeDot + afterDot; } else { // Otherwise, it's likely decimal - remove the decimal part cleaned = beforeDot; } Log.d("ReceiptActivity", "Processed mixed format: '" + cleaned + "'"); } } else { // Multiple dots - remove all dots (treat as thousand separators) cleaned = cleaned.replace(".", ""); Log.d("ReceiptActivity", "Removed multiple dots: '" + cleaned + "'"); } } // Remove any remaining commas (some locales use comma as thousand separator) cleaned = cleaned.replace(",", ""); Log.d("ReceiptActivity", "Final cleaned amount: '" + amount + "' -> '" + cleaned + "'"); return cleaned; } private String formatCurrency(long amount) { // Use Indonesian locale formatting with dots as thousand separators return String.format("%,d", amount).replace(',', '.'); } private String getCurrentDateTime() { SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID")); return sdf.format(new Date()); } private void handleEmailReceipt() { // Handle email receipt action // In real app, this would open email intent showToast("Mengirim email..."); } private void handleFinish() { // Navigate to MainActivity/Home Page when "Selesai" button is pressed navigateToHomePage(); } private void handleBackNavigation() { // Smart back navigation - go to the actual previous activity // Check if we have a calling activity in the intent String callingActivity = getIntent().getStringExtra("calling_activity"); if (callingActivity != null) { switch (callingActivity) { case "ReprintActivity": // Go back to transaction list Intent transactionIntent = new Intent(this, ReprintActivity.class); transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(transactionIntent); break; case "QrisResultActivity": // Go back to main menu since QrisResultActivity is typically finished navigateToHomePage(); break; case "ResultTransactionActivity": // ✅ NEW: Handle back from ResultTransactionActivity navigateToHomePage(); break; case "PaymentActivity": case "QrisActivity": // Go back to payment/qris activity Intent paymentIntent = new Intent(this, QrisActivity.class); paymentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(paymentIntent); break; default: // Default: use system back navigation super.onBackPressed(); break; } } else { // No calling activity specified, use system back navigation super.onBackPressed(); } finish(); } private void navigateToHomePage() { // Navigate to MainActivity/Home Page Intent intent = new Intent(this, MainActivity.class); // Clear all previous activities from the stack and start fresh intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); // Optional: Add success message to show in MainActivity intent.putExtra("transaction_completed", true); intent.putExtra("transaction_amount", getIntent().getStringExtra("transaction_amount")); startActivity(intent); finish(); // Show success message showToast("Transaksi berhasil diselesaikan!"); } private void showToast(String message) { android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show(); } @Override public void onBackPressed() { // Use the smart back navigation handleBackNavigation(); super.onBackPressed(); } }