solved duplicate data
This commit is contained in:
		
							parent
							
								
									99fab68e71
								
							
						
					
					
						commit
						4aaa9957e7
					
				@ -2,6 +2,7 @@ package com.example.bdkipoc;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context;
 | 
					import android.content.Context;
 | 
				
			||||||
import android.content.Intent;
 | 
					import android.content.Intent;
 | 
				
			||||||
 | 
					import android.content.SharedPreferences;
 | 
				
			||||||
import android.graphics.Bitmap;
 | 
					import android.graphics.Bitmap;
 | 
				
			||||||
import android.os.AsyncTask;
 | 
					import android.os.AsyncTask;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
@ -21,6 +22,7 @@ import androidx.annotation.Nullable;
 | 
				
			|||||||
import androidx.appcompat.app.AppCompatActivity;
 | 
					import androidx.appcompat.app.AppCompatActivity;
 | 
				
			||||||
import androidx.appcompat.widget.Toolbar;
 | 
					import androidx.appcompat.widget.Toolbar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.json.JSONArray;
 | 
				
			||||||
import org.json.JSONException;
 | 
					import org.json.JSONException;
 | 
				
			||||||
import org.json.JSONObject;
 | 
					import org.json.JSONObject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -57,6 +59,15 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    private StringBuilder currentAmount = new StringBuilder();
 | 
					    private StringBuilder currentAmount = new StringBuilder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ✅ FRONTEND DEDUPLICATION: Add SharedPreferences for tracking
 | 
				
			||||||
 | 
					    private SharedPreferences transactionPrefs;
 | 
				
			||||||
 | 
					    private static final String PREF_RECENT_REFERENCES = "recent_references";
 | 
				
			||||||
 | 
					    private static final String PREF_LAST_TRANSACTION_TIME = "last_transaction_time";
 | 
				
			||||||
 | 
					    private static final String PREF_CURRENT_REFERENCE = "current_reference";
 | 
				
			||||||
 | 
					    private static final String PREF_LAST_SUCCESSFUL_TX = "last_successful_tx";
 | 
				
			||||||
 | 
					    private static final long REFERENCE_COOLDOWN_MS = 60000; // 1 minute cooldown
 | 
				
			||||||
 | 
					    private static final long TRANSACTION_COOLDOWN_MS = 5000; // 5 second cooldown
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String BACKEND_BASE = "https://be-edc.msvc.app";
 | 
					    private static final String BACKEND_BASE = "https://be-edc.msvc.app";
 | 
				
			||||||
    private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
 | 
					    private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
 | 
				
			||||||
    private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key
 | 
					    private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key
 | 
				
			||||||
@ -67,6 +78,9 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
        super.onCreate(savedInstanceState);
 | 
					        super.onCreate(savedInstanceState);
 | 
				
			||||||
        setContentView(R.layout.activity_qris);
 | 
					        setContentView(R.layout.activity_qris);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ✅ Initialize SharedPreferences for duplicate prevention
 | 
				
			||||||
 | 
					        transactionPrefs = getSharedPreferences("qris_transactions", MODE_PRIVATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Initialize views
 | 
					        // Initialize views
 | 
				
			||||||
        progressBar = findViewById(R.id.progressBar);
 | 
					        progressBar = findViewById(R.id.progressBar);
 | 
				
			||||||
        initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
 | 
					        initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
 | 
				
			||||||
@ -90,8 +104,8 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
        btn000 = findViewById(R.id.btn000);
 | 
					        btn000 = findViewById(R.id.btn000);
 | 
				
			||||||
        btnDelete = findViewById(R.id.btnDelete);
 | 
					        btnDelete = findViewById(R.id.btnDelete);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Generate reference ID
 | 
					        // ✅ Generate unique reference ID with duplicate prevention
 | 
				
			||||||
        referenceId = "ref-" + generateRandomString(8);
 | 
					        referenceId = generateUniqueReferenceId();
 | 
				
			||||||
        referenceIdTextView.setText(referenceId);
 | 
					        referenceIdTextView.setText(referenceId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Set up click listeners
 | 
					        // Set up click listeners
 | 
				
			||||||
@ -105,6 +119,144 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
        initiatePaymentButton.setEnabled(false);
 | 
					        initiatePaymentButton.setEnabled(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ FRONTEND DEDUPLICATION: Generate unique reference ID with local tracking
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String generateUniqueReferenceId() {
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "🔄 Generating unique reference ID...");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        String baseRef = "ref-" + generateRandomString(8);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Check if this reference was recently created
 | 
				
			||||||
 | 
					        String recentRefs = transactionPrefs.getString(PREF_RECENT_REFERENCES, "");
 | 
				
			||||||
 | 
					        long currentTime = System.currentTimeMillis();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Clean up old references (older than cooldown period)
 | 
				
			||||||
 | 
					        StringBuilder validRefs = new StringBuilder();
 | 
				
			||||||
 | 
					        if (!recentRefs.isEmpty()) {
 | 
				
			||||||
 | 
					            String[] refs = recentRefs.split(",");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            for (String refEntry : refs) {
 | 
				
			||||||
 | 
					                if (refEntry.contains(":")) {
 | 
				
			||||||
 | 
					                    String[] parts = refEntry.split(":");
 | 
				
			||||||
 | 
					                    if (parts.length == 2) {
 | 
				
			||||||
 | 
					                        try {
 | 
				
			||||||
 | 
					                            long timestamp = Long.parseLong(parts[1]);
 | 
				
			||||||
 | 
					                            if (currentTime - timestamp < REFERENCE_COOLDOWN_MS) {
 | 
				
			||||||
 | 
					                                // Reference is still in cooldown period
 | 
				
			||||||
 | 
					                                if (validRefs.length() > 0) validRefs.append(",");
 | 
				
			||||||
 | 
					                                validRefs.append(refEntry);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        } catch (NumberFormatException e) {
 | 
				
			||||||
 | 
					                            // Skip invalid entries
 | 
				
			||||||
 | 
					                            Log.w("QrisActivity", "Invalid reference entry: " + refEntry);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Check if baseRef already exists in recent references
 | 
				
			||||||
 | 
					        if (validRefs.length() > 0) {
 | 
				
			||||||
 | 
					            String[] validRefArray = validRefs.toString().split(",");
 | 
				
			||||||
 | 
					            for (String refEntry : validRefArray) {
 | 
				
			||||||
 | 
					                if (refEntry.startsWith(baseRef + ":")) {
 | 
				
			||||||
 | 
					                    // Reference already exists, generate a new one
 | 
				
			||||||
 | 
					                    Log.w("QrisActivity", "⚠️ Reference " + baseRef + " recently used, generating new one");
 | 
				
			||||||
 | 
					                    return generateUniqueReferenceId(); // Recursive call with new random string
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Add this reference to recent references
 | 
				
			||||||
 | 
					        if (validRefs.length() > 0) validRefs.append(",");
 | 
				
			||||||
 | 
					        validRefs.append(baseRef).append(":").append(currentTime);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Save updated references
 | 
				
			||||||
 | 
					        transactionPrefs.edit()
 | 
				
			||||||
 | 
					            .putString(PREF_RECENT_REFERENCES, validRefs.toString())
 | 
				
			||||||
 | 
					            .apply();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "✅ Generated unique reference: " + baseRef);
 | 
				
			||||||
 | 
					        return baseRef;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ FRONTEND DEDUPLICATION: Check if transaction is currently being processed
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private boolean isTransactionInProgress() {
 | 
				
			||||||
 | 
					        long lastTransactionTime = transactionPrefs.getLong(PREF_LAST_TRANSACTION_TIME, 0);
 | 
				
			||||||
 | 
					        long currentTime = System.currentTimeMillis();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // If last transaction was less than cooldown period, consider it in progress
 | 
				
			||||||
 | 
					        boolean inProgress = (currentTime - lastTransactionTime) < TRANSACTION_COOLDOWN_MS;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (inProgress) {
 | 
				
			||||||
 | 
					            Log.w("QrisActivity", "⏸️ Transaction in progress, cooldown active");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return inProgress;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ FRONTEND DEDUPLICATION: Mark transaction processing status
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void markTransactionInProgress(boolean inProgress) {
 | 
				
			||||||
 | 
					        SharedPreferences.Editor editor = transactionPrefs.edit();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (inProgress) {
 | 
				
			||||||
 | 
					            editor.putLong(PREF_LAST_TRANSACTION_TIME, System.currentTimeMillis())
 | 
				
			||||||
 | 
					                  .putString(PREF_CURRENT_REFERENCE, referenceId);
 | 
				
			||||||
 | 
					            Log.d("QrisActivity", "🔒 Marked transaction in progress: " + referenceId);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            editor.remove(PREF_CURRENT_REFERENCE);
 | 
				
			||||||
 | 
					            Log.d("QrisActivity", "🔓 Cleared transaction progress status");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        editor.apply();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ FRONTEND DEDUPLICATION: Save successful transaction for future reference
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void saveSuccessfulTransaction() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            JSONObject txData = new JSONObject();
 | 
				
			||||||
 | 
					            txData.put("reference_id", referenceId);
 | 
				
			||||||
 | 
					            txData.put("transaction_uuid", transactionUuid);
 | 
				
			||||||
 | 
					            txData.put("amount", amount);
 | 
				
			||||||
 | 
					            txData.put("created_at", System.currentTimeMillis());
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Save to SharedPreferences
 | 
				
			||||||
 | 
					            transactionPrefs.edit()
 | 
				
			||||||
 | 
					                .putString(PREF_LAST_SUCCESSFUL_TX, txData.toString())
 | 
				
			||||||
 | 
					                .putLong("last_success_time", System.currentTimeMillis())
 | 
				
			||||||
 | 
					                .apply();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            Log.d("QrisActivity", "💾 Saved successful transaction: " + referenceId);
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.w("QrisActivity", "Failed to save transaction data: " + e.getMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ FRONTEND DEDUPLICATION: Create client info for better backend tracking
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private JSONObject createClientInfo() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            JSONObject clientInfo = new JSONObject();
 | 
				
			||||||
 | 
					            clientInfo.put("app_version", "1.0.0");
 | 
				
			||||||
 | 
					            clientInfo.put("platform", "android");
 | 
				
			||||||
 | 
					            clientInfo.put("timestamp", System.currentTimeMillis());
 | 
				
			||||||
 | 
					            clientInfo.put("session_id", generateRandomString(16));
 | 
				
			||||||
 | 
					            clientInfo.put("reference_generation_time", System.currentTimeMillis());
 | 
				
			||||||
 | 
					            return clientInfo;
 | 
				
			||||||
 | 
					        } catch (JSONException e) {
 | 
				
			||||||
 | 
					            Log.w("QrisActivity", "Failed to create client info: " + e.getMessage());
 | 
				
			||||||
 | 
					            return new JSONObject();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    private void setupNumpadListeners() {
 | 
					    private void setupNumpadListeners() {
 | 
				
			||||||
        View.OnClickListener numberClickListener = v -> {
 | 
					        View.OnClickListener numberClickListener = v -> {
 | 
				
			||||||
            TextView button = (TextView) v;
 | 
					            TextView button = (TextView) v;
 | 
				
			||||||
@ -172,17 +324,33 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ ENHANCED: Modified createTransaction with comprehensive duplicate prevention
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    private void createTransaction() {
 | 
					    private void createTransaction() {
 | 
				
			||||||
        if (currentAmount.length() == 0) {
 | 
					        if (currentAmount.length() == 0) {
 | 
				
			||||||
            Toast.makeText(this, "Masukkan jumlah pembayaran", Toast.LENGTH_SHORT).show();
 | 
					            Toast.makeText(this, "Masukkan jumlah pembayaran", Toast.LENGTH_SHORT).show();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // ✅ FRONTEND CHECK: Prevent rapid duplicate submissions
 | 
				
			||||||
 | 
					        if (isTransactionInProgress()) {
 | 
				
			||||||
 | 
					            Toast.makeText(this, "Transaksi sedang diproses, harap tunggu...", Toast.LENGTH_SHORT).show();
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "🚀 Starting transaction creation process");
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "   Reference ID: " + referenceId);
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "   Amount: " + currentAmount.toString());
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        progressBar.setVisibility(View.VISIBLE);
 | 
					        progressBar.setVisibility(View.VISIBLE);
 | 
				
			||||||
        initiatePaymentButton.setEnabled(false);
 | 
					        initiatePaymentButton.setEnabled(false);
 | 
				
			||||||
        statusTextView.setVisibility(View.VISIBLE);
 | 
					        statusTextView.setVisibility(View.VISIBLE);
 | 
				
			||||||
        statusTextView.setText("Creating transaction...");
 | 
					        statusTextView.setText("Creating transaction...");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Mark transaction as in progress
 | 
				
			||||||
 | 
					        markTransactionInProgress(true);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        new CreateTransactionTask().execute();
 | 
					        new CreateTransactionTask().execute();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -253,6 +421,12 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                // Generate a UUID for the transaction
 | 
					                // Generate a UUID for the transaction
 | 
				
			||||||
                transactionUuid = UUID.randomUUID().toString();
 | 
					                transactionUuid = UUID.randomUUID().toString();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                // ✅ ENHANCED LOGGING: Better tracking for debugging
 | 
				
			||||||
 | 
					                Log.d("MidtransCharge", "=== TRANSACTION CREATION START ===");
 | 
				
			||||||
 | 
					                Log.d("MidtransCharge", "Reference ID: " + referenceId);
 | 
				
			||||||
 | 
					                Log.d("MidtransCharge", "Transaction UUID: " + transactionUuid);
 | 
				
			||||||
 | 
					                Log.d("MidtransCharge", "Timestamp: " + System.currentTimeMillis());
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                // Create transaction JSON payload
 | 
					                // Create transaction JSON payload
 | 
				
			||||||
                JSONObject payload = new JSONObject();
 | 
					                JSONObject payload = new JSONObject();
 | 
				
			||||||
                payload.put("type", "PAYMENT");
 | 
					                payload.put("type", "PAYMENT");
 | 
				
			||||||
@ -260,6 +434,10 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                payload.put("channel_code", "QRIS");
 | 
					                payload.put("channel_code", "QRIS");
 | 
				
			||||||
                payload.put("reference_id", referenceId);
 | 
					                payload.put("reference_id", referenceId);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                // ✅ FRONTEND ENHANCEMENT: Add client-side metadata for better tracking
 | 
				
			||||||
 | 
					                payload.put("client_info", createClientInfo());
 | 
				
			||||||
 | 
					                payload.put("is_initial_creation", true); // Mark as initial creation
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                // Get amount from current input
 | 
					                // Get amount from current input
 | 
				
			||||||
                String amountText = currentAmount.toString();
 | 
					                String amountText = currentAmount.toString();
 | 
				
			||||||
                Log.d("MidtransCharge", "Raw amount text: " + amountText);
 | 
					                Log.d("MidtransCharge", "Raw amount text: " + amountText);
 | 
				
			||||||
@ -307,6 +485,12 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                conn.setRequestProperty("Content-Type", "application/json");
 | 
					                conn.setRequestProperty("Content-Type", "application/json");
 | 
				
			||||||
                conn.setRequestProperty("Accept", "application/json");
 | 
					                conn.setRequestProperty("Accept", "application/json");
 | 
				
			||||||
                conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
 | 
					                conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // ✅ FRONTEND ENHANCEMENT: Add client headers for better backend tracking
 | 
				
			||||||
 | 
					                conn.setRequestProperty("X-Client-Reference", referenceId);
 | 
				
			||||||
 | 
					                conn.setRequestProperty("X-Client-Timestamp", String.valueOf(System.currentTimeMillis()));
 | 
				
			||||||
 | 
					                conn.setRequestProperty("X-Client-Version", "1.0.0");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                conn.setDoOutput(true);
 | 
					                conn.setDoOutput(true);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                try (OutputStream os = conn.getOutputStream()) {
 | 
					                try (OutputStream os = conn.getOutputStream()) {
 | 
				
			||||||
@ -318,7 +502,7 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                Log.d("MidtransCharge", "Backend response code: " + responseCode);
 | 
					                Log.d("MidtransCharge", "Backend response code: " + responseCode);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                if (responseCode == 200 || responseCode == 201) {
 | 
					                if (responseCode == 200 || responseCode == 201) {
 | 
				
			||||||
                    // Read the response
 | 
					                    // Success - process response
 | 
				
			||||||
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
 | 
					                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
 | 
				
			||||||
                    StringBuilder response = new StringBuilder();
 | 
					                    StringBuilder response = new StringBuilder();
 | 
				
			||||||
                    String responseLine;
 | 
					                    String responseLine;
 | 
				
			||||||
@ -326,32 +510,73 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                        response.append(responseLine.trim());
 | 
					                        response.append(responseLine.trim());
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    Log.d("MidtransCharge", "Backend response: " + response.toString());
 | 
					                    Log.d("MidtransCharge", "Backend success response: " + response.toString());
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    // Parse the response to get transaction ID
 | 
					                    // Parse the response to get transaction ID
 | 
				
			||||||
                    JSONObject jsonResponse = new JSONObject(response.toString());
 | 
					                    JSONObject jsonResponse = new JSONObject(response.toString());
 | 
				
			||||||
                    JSONObject data = jsonResponse.getJSONObject("data");
 | 
					                    JSONObject data = jsonResponse.getJSONObject("data");
 | 
				
			||||||
                    transactionId = String.valueOf(data.getInt("id"));
 | 
					                    transactionId = String.valueOf(data.getInt("id"));
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    Log.d("MidtransCharge", "Created transaction ID: " + transactionId);
 | 
					                    Log.d("MidtransCharge", "✅ Created transaction ID: " + transactionId);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // ✅ FRONTEND SUCCESS: Save successful transaction info
 | 
				
			||||||
 | 
					                    saveSuccessfulTransaction();
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    // Now generate QRIS via Midtrans
 | 
					                    // Now generate QRIS via Midtrans
 | 
				
			||||||
                    return generateQris(amount);
 | 
					                    return generateQris(amount);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                } else if (responseCode == 409 || responseCode == 400) {
 | 
				
			||||||
 | 
					                    // ✅ ENHANCED DUPLICATE HANDLING: Handle gracefully
 | 
				
			||||||
 | 
					                    Log.w("MidtransCharge", "⚠️ Potential duplicate detected (HTTP " + responseCode + ")");
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Try to read and parse error response
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        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());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        String errorResponseStr = errorResponse.toString();
 | 
				
			||||||
 | 
					                        Log.d("MidtransCharge", "Error response: " + errorResponseStr);
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        // Check if it's actually a duplicate reference error
 | 
				
			||||||
 | 
					                        if (errorResponseStr.toLowerCase().contains("duplicate") || 
 | 
				
			||||||
 | 
					                            errorResponseStr.toLowerCase().contains("already exists") ||
 | 
				
			||||||
 | 
					                            errorResponseStr.toLowerCase().contains("reference") ||
 | 
				
			||||||
 | 
					                            responseCode == 409) {
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                            Log.i("MidtransCharge", "✅ Confirmed duplicate reference - proceeding with QRIS generation");
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                            // For duplicates, we can still generate QRIS with existing reference
 | 
				
			||||||
 | 
					                            return generateQris(amount);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } catch (Exception e) {
 | 
				
			||||||
 | 
					                        Log.w("MidtransCharge", "Could not parse error response: " + e.getMessage());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // If we can't determine the exact error, try QRIS generation anyway
 | 
				
			||||||
 | 
					                    Log.i("MidtransCharge", "🔄 Proceeding with QRIS generation despite backend error");
 | 
				
			||||||
 | 
					                    return generateQris(amount);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    // Read error response
 | 
					                    // Other HTTP errors
 | 
				
			||||||
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
 | 
					                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
 | 
				
			||||||
                    StringBuilder response = new StringBuilder();
 | 
					                    StringBuilder response = new StringBuilder();
 | 
				
			||||||
                    String responseLine;
 | 
					                    String responseLine;
 | 
				
			||||||
                    while ((responseLine = br.readLine()) != null) {
 | 
					                    while ((responseLine = br.readLine()) != null) {
 | 
				
			||||||
                        response.append(responseLine.trim());
 | 
					                        response.append(responseLine.trim());
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    Log.e("MidtransCharge", "Backend error response: " + response.toString());
 | 
					                    
 | 
				
			||||||
                    errorMessage = "Error creating backend transaction: " + response.toString();
 | 
					                    String errorResponse = response.toString();
 | 
				
			||||||
 | 
					                    Log.e("MidtransCharge", "❌ Backend error (HTTP " + responseCode + "): " + errorResponse);
 | 
				
			||||||
 | 
					                    errorMessage = "Backend error (" + responseCode + "): " + errorResponse;
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
                Log.e("MidtransCharge", "Backend transaction exception: " + e.getMessage(), e);
 | 
					                Log.e("MidtransCharge", "❌ Backend transaction exception: " + e.getMessage(), e);
 | 
				
			||||||
                errorMessage = "Backend transaction error: " + e.getMessage();
 | 
					                errorMessage = "Network error: " + e.getMessage();
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -386,21 +611,30 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                payload.put("customer_details", customerDetails);
 | 
					                payload.put("customer_details", customerDetails);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Add item details (optional but recommended)
 | 
					                // Add item details (optional but recommended)
 | 
				
			||||||
                org.json.JSONArray itemDetails = new org.json.JSONArray();
 | 
					                JSONArray itemDetails = new JSONArray();
 | 
				
			||||||
                JSONObject item = new JSONObject();
 | 
					                JSONObject item = new JSONObject();
 | 
				
			||||||
                item.put("id", "item1");
 | 
					                item.put("id", "item1");
 | 
				
			||||||
                item.put("price", amount);
 | 
					                item.put("price", amount);
 | 
				
			||||||
                item.put("quantity", 1);
 | 
					                item.put("quantity", 1);
 | 
				
			||||||
                item.put("name", "QRIS Payment");
 | 
					                item.put("name", "QRIS Payment - " + referenceId);
 | 
				
			||||||
                itemDetails.put(item);
 | 
					                itemDetails.put(item);
 | 
				
			||||||
                payload.put("item_details", itemDetails);
 | 
					                payload.put("item_details", itemDetails);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                // ✅ FRONTEND ENHANCEMENT: Add tracking info for reference linkage
 | 
				
			||||||
 | 
					                JSONObject customField1 = new JSONObject();
 | 
				
			||||||
 | 
					                customField1.put("app_reference_id", referenceId);
 | 
				
			||||||
 | 
					                customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
 | 
				
			||||||
 | 
					                customField1.put("client_version", "1.0.0");
 | 
				
			||||||
 | 
					                payload.put("custom_field1", customField1.toString());
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                // Log the request details
 | 
					                // Log the request details
 | 
				
			||||||
                Log.d("MidtransCharge", "=== MIDTRANS QRIS REQUEST ===");
 | 
					                Log.d("MidtransCharge", "=== MIDTRANS QRIS REQUEST ===");
 | 
				
			||||||
                Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
 | 
					                Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
 | 
				
			||||||
                Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
 | 
					                Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
 | 
				
			||||||
                Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
 | 
					                Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
 | 
				
			||||||
                Log.d("MidtransCharge", "Payload: " + payload.toString());
 | 
					                Log.d("MidtransCharge", "Reference ID: " + referenceId);
 | 
				
			||||||
 | 
					                Log.d("MidtransCharge", "Order ID: " + transactionUuid);
 | 
				
			||||||
 | 
					                Log.d("MidtransCharge", "Amount: " + amount);
 | 
				
			||||||
                Log.d("MidtransCharge", "================================");
 | 
					                Log.d("MidtransCharge", "================================");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Make the API call to Midtrans
 | 
					                // Make the API call to Midtrans
 | 
				
			||||||
@ -459,7 +693,7 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                            return false;
 | 
					                            return false;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
                        Log.d("MidtransCharge", "QRIS generation successful!");
 | 
					                        Log.d("MidtransCharge", "✅ QRIS generation successful!");
 | 
				
			||||||
                        return true;
 | 
					                        return true;
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available");
 | 
					                        Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available");
 | 
				
			||||||
@ -513,10 +747,13 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        protected void onPostExecute(Boolean success) {
 | 
					        protected void onPostExecute(Boolean success) {
 | 
				
			||||||
 | 
					            // ✅ FRONTEND CLEANUP: Always clear in-progress status
 | 
				
			||||||
 | 
					            markTransactionInProgress(false);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            if (success && midtransResponse != null) {
 | 
					            if (success && midtransResponse != null) {
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    // Extract needed values from midtransResponse
 | 
					                    // Extract needed values from midtransResponse
 | 
				
			||||||
                    org.json.JSONArray actionsArray = midtransResponse.getJSONArray("actions");
 | 
					                    JSONArray actionsArray = midtransResponse.getJSONArray("actions");
 | 
				
			||||||
                    if (actionsArray.length() == 0) {
 | 
					                    if (actionsArray.length() == 0) {
 | 
				
			||||||
                        Log.e("MidtransCharge", "No actions found in Midtrans response");
 | 
					                        Log.e("MidtransCharge", "No actions found in Midtrans response");
 | 
				
			||||||
                        Toast.makeText(QrisActivity.this, "Error: No QR code URL found in response", Toast.LENGTH_LONG).show();
 | 
					                        Toast.makeText(QrisActivity.this, "Error: No QR code URL found in response", Toast.LENGTH_LONG).show();
 | 
				
			||||||
@ -535,11 +772,12 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                    String acquirer = midtransResponse.getString("acquirer");
 | 
					                    String acquirer = midtransResponse.getString("acquirer");
 | 
				
			||||||
                    String merchantId = midtransResponse.getString("merchant_id");
 | 
					                    String merchantId = midtransResponse.getString("merchant_id");
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    // FIXED: Send raw amount as string without decimal conversion
 | 
					                    // Send raw amount as string without decimal conversion
 | 
				
			||||||
                    String rawAmountString = String.valueOf(amount); // Keep original integer amount
 | 
					                    String rawAmountString = String.valueOf(amount); // Keep original integer amount
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    // Log everything before launching activity
 | 
					                    // Log everything before launching activity
 | 
				
			||||||
                    Log.d("MidtransCharge", "=== LAUNCHING QRIS RESULT ACTIVITY ===");
 | 
					                    Log.d("MidtransCharge", "=== LAUNCHING QRIS RESULT ACTIVITY ===");
 | 
				
			||||||
 | 
					                    Log.d("MidtransCharge", "✅ Transaction created successfully!");
 | 
				
			||||||
                    Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl);
 | 
					                    Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl);
 | 
				
			||||||
                    Log.d("MidtransCharge", "amount (raw): " + amount);
 | 
					                    Log.d("MidtransCharge", "amount (raw): " + amount);
 | 
				
			||||||
                    Log.d("MidtransCharge", "rawAmountString: " + rawAmountString);
 | 
					                    Log.d("MidtransCharge", "rawAmountString: " + rawAmountString);
 | 
				
			||||||
@ -551,6 +789,13 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                    Log.d("MidtransCharge", "merchantId: " + merchantId);
 | 
					                    Log.d("MidtransCharge", "merchantId: " + merchantId);
 | 
				
			||||||
                    Log.d("MidtransCharge", "========================================");
 | 
					                    Log.d("MidtransCharge", "========================================");
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
 | 
					                    // ✅ FINAL SUCCESS: Update transaction status in preferences
 | 
				
			||||||
 | 
					                    transactionPrefs.edit()
 | 
				
			||||||
 | 
					                        .putString("last_qris_url", qrImageUrl)
 | 
				
			||||||
 | 
					                        .putString("last_qris_reference", referenceId)
 | 
				
			||||||
 | 
					                        .putLong("last_qris_time", System.currentTimeMillis())
 | 
				
			||||||
 | 
					                        .apply();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                    // Launch QrisResultActivity
 | 
					                    // Launch QrisResultActivity
 | 
				
			||||||
                    Intent intent = new Intent(QrisActivity.this, QrisResultActivity.class);
 | 
					                    Intent intent = new Intent(QrisActivity.this, QrisResultActivity.class);
 | 
				
			||||||
                    intent.putExtra("qrImageUrl", qrImageUrl);
 | 
					                    intent.putExtra("qrImageUrl", qrImageUrl);
 | 
				
			||||||
@ -558,7 +803,7 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                    intent.putExtra("referenceId", referenceId);
 | 
					                    intent.putExtra("referenceId", referenceId);
 | 
				
			||||||
                    intent.putExtra("orderId", transactionUuid); // Order ID
 | 
					                    intent.putExtra("orderId", transactionUuid); // Order ID
 | 
				
			||||||
                    intent.putExtra("transactionId", transactionId); // Actual Midtrans transaction_id
 | 
					                    intent.putExtra("transactionId", transactionId); // Actual Midtrans transaction_id
 | 
				
			||||||
                    intent.putExtra("grossAmount", rawAmountString); // FIXED: Raw amount as string (no decimals)
 | 
					                    intent.putExtra("grossAmount", rawAmountString); // Raw amount as string (no decimals)
 | 
				
			||||||
                    intent.putExtra("transactionTime", transactionTime); // For timestamp
 | 
					                    intent.putExtra("transactionTime", transactionTime); // For timestamp
 | 
				
			||||||
                    intent.putExtra("acquirer", acquirer);
 | 
					                    intent.putExtra("acquirer", acquirer);
 | 
				
			||||||
                    intent.putExtra("merchantId", merchantId);
 | 
					                    intent.putExtra("merchantId", merchantId);
 | 
				
			||||||
@ -566,23 +811,84 @@ public class QrisActivity extends AppCompatActivity {
 | 
				
			|||||||
                    try {
 | 
					                    try {
 | 
				
			||||||
                        startActivity(intent);
 | 
					                        startActivity(intent);
 | 
				
			||||||
                        finish(); // Close QrisActivity
 | 
					                        finish(); // Close QrisActivity
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        Log.d("MidtransCharge", "🎉 Successfully launched QrisResultActivity");
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
                    } catch (Exception e) {
 | 
					                    } catch (Exception e) {
 | 
				
			||||||
                        Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e);
 | 
					                        Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e);
 | 
				
			||||||
                        Toast.makeText(QrisActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show();
 | 
					                        Toast.makeText(QrisActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show();
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        // Re-enable button on error
 | 
				
			||||||
 | 
					                        initiatePaymentButton.setEnabled(true);
 | 
				
			||||||
 | 
					                        progressBar.setVisibility(View.GONE);
 | 
				
			||||||
 | 
					                        statusTextView.setVisibility(View.GONE);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                } catch (JSONException e) {
 | 
					                } catch (JSONException e) {
 | 
				
			||||||
                    Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
 | 
					                    Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
 | 
				
			||||||
                    Toast.makeText(QrisActivity.this, "Error processing QRIS response: " + e.getMessage(), Toast.LENGTH_LONG).show();
 | 
					                    Toast.makeText(QrisActivity.this, "Error processing QRIS response: " + e.getMessage(), Toast.LENGTH_LONG).show();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Handle error case
 | 
				
			||||||
                String message = (errorMessage != null && !errorMessage.isEmpty()) ? 
 | 
					                String message = (errorMessage != null && !errorMessage.isEmpty()) ? 
 | 
				
			||||||
                    errorMessage : "Unknown error occurred. Please check Logcat for details.";
 | 
					                    errorMessage : "Unknown error occurred. Please check your connection and try again.";
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                Log.e("MidtransCharge", "❌ Transaction failed: " + message);
 | 
				
			||||||
                Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
 | 
					                Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Re-enable button for retry
 | 
				
			||||||
                initiatePaymentButton.setEnabled(true);
 | 
					                initiatePaymentButton.setEnabled(true);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Always hide progress indicators
 | 
				
			||||||
            progressBar.setVisibility(View.GONE);
 | 
					            progressBar.setVisibility(View.GONE);
 | 
				
			||||||
            statusTextView.setVisibility(View.GONE);
 | 
					            statusTextView.setVisibility(View.GONE);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected void onDestroy() {
 | 
				
			||||||
 | 
					        super.onDestroy();
 | 
				
			||||||
 | 
					        // ✅ CLEANUP: Clear any in-progress status when activity is destroyed
 | 
				
			||||||
 | 
					        markTransactionInProgress(false);
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "🧹 QrisActivity destroyed, cleared progress status");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected void onPause() {
 | 
				
			||||||
 | 
					        super.onPause();
 | 
				
			||||||
 | 
					        // Keep progress status when paused (user might come back)
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "⏸️ QrisActivity paused");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected void onResume() {
 | 
				
			||||||
 | 
					        super.onResume();
 | 
				
			||||||
 | 
					        Log.d("QrisActivity", "▶️ QrisActivity resumed");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Check if there's a recent successful transaction
 | 
				
			||||||
 | 
					        String lastSuccessfulTx = transactionPrefs.getString(PREF_LAST_SUCCESSFUL_TX, "");
 | 
				
			||||||
 | 
					        if (!lastSuccessfulTx.isEmpty()) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                JSONObject txData = new JSONObject(lastSuccessfulTx);
 | 
				
			||||||
 | 
					                String lastRef = txData.getString("reference_id");
 | 
				
			||||||
 | 
					                long lastTime = txData.getLong("created_at");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // If last successful transaction was recent (within 5 minutes) and same reference
 | 
				
			||||||
 | 
					                if (System.currentTimeMillis() - lastTime < 300000 && lastRef.equals(referenceId)) {
 | 
				
			||||||
 | 
					                    Log.d("QrisActivity", "🔄 Recent successful transaction detected for same reference");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (Exception e) {
 | 
				
			||||||
 | 
					                Log.w("QrisActivity", "Could not parse last successful transaction: " + e.getMessage());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void onBackPressed() {
 | 
				
			||||||
 | 
					        // ✅ CLEANUP: Clear progress status when user goes back
 | 
				
			||||||
 | 
					        markTransactionInProgress(false);
 | 
				
			||||||
 | 
					        super.onBackPressed();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -859,6 +859,17 @@ public class QrisResultActivity extends AppCompatActivity {
 | 
				
			|||||||
        
 | 
					        
 | 
				
			||||||
        returnMainButton.setVisibility(View.VISIBLE);
 | 
					        returnMainButton.setVisibility(View.VISIBLE);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Add receipt button or automatically launch receipt
 | 
				
			||||||
 | 
					        Button viewReceiptButton = new Button(this);
 | 
				
			||||||
 | 
					        viewReceiptButton.setText("Lihat Struk");
 | 
				
			||||||
 | 
					        viewReceiptButton.setOnClickListener(v -> launchReceiptActivity());
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // You can add this button to your layout or automatically launch receipt
 | 
				
			||||||
 | 
					        // For better UX, let's automatically launch receipt after a short delay
 | 
				
			||||||
 | 
					        new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
 | 
				
			||||||
 | 
					            launchReceiptActivity();
 | 
				
			||||||
 | 
					        }, 2000); // Launch receipt after 2 seconds
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        Toast.makeText(this, "Payment simulation completed successfully!", Toast.LENGTH_LONG).show();
 | 
					        Toast.makeText(this, "Payment simulation completed successfully!", Toast.LENGTH_LONG).show();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1232,20 +1243,33 @@ public class QrisResultActivity extends AppCompatActivity {
 | 
				
			|||||||
    private void launchReceiptActivity() {
 | 
					    private void launchReceiptActivity() {
 | 
				
			||||||
        Intent intent = new Intent(this, ReceiptActivity.class);
 | 
					        Intent intent = new Intent(this, ReceiptActivity.class);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Add calling activity information for proper back navigation
 | 
				
			||||||
        intent.putExtra("calling_activity", "QrisResultActivity");
 | 
					        intent.putExtra("calling_activity", "QrisResultActivity");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Log.d("QrisResultFlow", "Launching receipt with data:");
 | 
				
			||||||
 | 
					        Log.d("QrisResultFlow", "  Reference ID: " + referenceId);
 | 
				
			||||||
 | 
					        Log.d("QrisResultFlow", "  Transaction ID: " + transactionId);
 | 
				
			||||||
 | 
					        Log.d("QrisResultFlow", "  Amount: " + originalAmount);
 | 
				
			||||||
 | 
					        Log.d("QrisResultFlow", "  Acquirer: " + acquirer);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        intent.putExtra("transaction_id", transactionId);
 | 
					        intent.putExtra("transaction_id", transactionId);
 | 
				
			||||||
        intent.putExtra("reference_id", referenceId);
 | 
					        intent.putExtra("reference_id", referenceId); // Nomor Transaksi
 | 
				
			||||||
        intent.putExtra("order_id", orderId);
 | 
					        intent.putExtra("order_id", orderId);
 | 
				
			||||||
        intent.putExtra("transaction_amount", grossAmount != null ? grossAmount : "0");
 | 
					        intent.putExtra("transaction_amount", String.valueOf(originalAmount)); // Total transaksi
 | 
				
			||||||
        intent.putExtra("gross_amount", grossAmount);
 | 
					        intent.putExtra("gross_amount", grossAmount != null ? grossAmount : String.valueOf(originalAmount));
 | 
				
			||||||
        intent.putExtra("transaction_date", getCurrentDateTime());
 | 
					        intent.putExtra("created_at", getCurrentISOTime()); // Tanggal transaksi (current time for QRIS)
 | 
				
			||||||
 | 
					        intent.putExtra("transaction_date", getCurrentDateTime()); // Backup formatted date
 | 
				
			||||||
        intent.putExtra("payment_method", "QRIS");
 | 
					        intent.putExtra("payment_method", "QRIS");
 | 
				
			||||||
        intent.putExtra("card_type", "QRIS");
 | 
					        intent.putExtra("channel_code", "QRIS"); // Metode Pembayaran
 | 
				
			||||||
        intent.putExtra("channel_code", "QRIS");
 | 
					 | 
				
			||||||
        intent.putExtra("channel_category", "RETAIL_OUTLET");
 | 
					        intent.putExtra("channel_category", "RETAIL_OUTLET");
 | 
				
			||||||
 | 
					        intent.putExtra("card_type", "QRIS");
 | 
				
			||||||
        intent.putExtra("merchant_name", "Marcel Panjaitan");
 | 
					        intent.putExtra("merchant_name", "Marcel Panjaitan");
 | 
				
			||||||
        intent.putExtra("merchant_location", "Jakarta, Indonesia");
 | 
					        intent.putExtra("merchant_location", "Jakarta, Indonesia");
 | 
				
			||||||
        intent.putExtra("acquirer", acquirer);
 | 
					        intent.putExtra("acquirer", acquirer != null ? acquirer : "qris"); // Jenis Kartu
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Add MID and TID with default values
 | 
				
			||||||
 | 
					        intent.putExtra("mid", "71000026521"); // MID
 | 
				
			||||||
 | 
					        intent.putExtra("tid", "73001500");   // TID
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        startActivity(intent);
 | 
					        startActivity(intent);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import android.graphics.Color;
 | 
				
			|||||||
import android.os.Build;
 | 
					import android.os.Build;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.text.TextUtils;
 | 
				
			||||||
import android.util.Log; // ADD THIS IMPORT
 | 
					import android.util.Log;
 | 
				
			||||||
import android.view.View;
 | 
					import android.view.View;
 | 
				
			||||||
import android.view.Window;
 | 
					import android.view.Window;
 | 
				
			||||||
import android.view.WindowManager;
 | 
					import android.view.WindowManager;
 | 
				
			||||||
@ -14,6 +14,13 @@ import android.widget.ImageView;
 | 
				
			|||||||
import android.widget.LinearLayout;
 | 
					import android.widget.LinearLayout;
 | 
				
			||||||
import android.widget.TextView;
 | 
					import android.widget.TextView;
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity;
 | 
					import androidx.appcompat.app.AppCompatActivity;
 | 
				
			||||||
 | 
					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.net.URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ReceiptActivity extends AppCompatActivity {
 | 
					public class ReceiptActivity extends AppCompatActivity {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -110,74 +117,134 @@ public class ReceiptActivity extends AppCompatActivity {
 | 
				
			|||||||
    private void loadTransactionData() {
 | 
					    private void loadTransactionData() {
 | 
				
			||||||
        Intent intent = getIntent();
 | 
					        Intent intent = getIntent();
 | 
				
			||||||
        if (intent != null) {
 | 
					        if (intent != null) {
 | 
				
			||||||
            // Data dari TransactionActivity atau QrisResultActivity
 | 
					            Log.d("ReceiptActivity", "=== LOADING TRANSACTION DATA ===");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Get all available data from intent
 | 
				
			||||||
            String amount = intent.getStringExtra("transaction_amount");
 | 
					            String amount = intent.getStringExtra("transaction_amount");
 | 
				
			||||||
 | 
					            String grossAmount = intent.getStringExtra("gross_amount");
 | 
				
			||||||
            String merchantNameStr = intent.getStringExtra("merchant_name");
 | 
					            String merchantNameStr = intent.getStringExtra("merchant_name");
 | 
				
			||||||
            String merchantLocationStr = intent.getStringExtra("merchant_location");
 | 
					            String merchantLocationStr = intent.getStringExtra("merchant_location");
 | 
				
			||||||
            String transactionId = intent.getStringExtra("transaction_id");
 | 
					            String transactionId = intent.getStringExtra("transaction_id");
 | 
				
			||||||
            String transactionDateStr = intent.getStringExtra("transaction_date");
 | 
					 | 
				
			||||||
            String paymentMethodStr = intent.getStringExtra("payment_method");
 | 
					 | 
				
			||||||
            String cardTypeStr = intent.getStringExtra("card_type");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Additional data that might come from different sources
 | 
					 | 
				
			||||||
            String referenceId = intent.getStringExtra("reference_id");
 | 
					            String referenceId = intent.getStringExtra("reference_id");
 | 
				
			||||||
            String orderId = intent.getStringExtra("order_id");
 | 
					            String orderId = intent.getStringExtra("order_id");
 | 
				
			||||||
            String grossAmount = intent.getStringExtra("gross_amount");
 | 
					            String transactionDateStr = intent.getStringExtra("transaction_date");
 | 
				
			||||||
            String acquirer = intent.getStringExtra("acquirer");
 | 
					            String createdAt = intent.getStringExtra("created_at");
 | 
				
			||||||
 | 
					            String paymentMethodStr = intent.getStringExtra("payment_method");
 | 
				
			||||||
 | 
					            String cardTypeStr = intent.getStringExtra("card_type");
 | 
				
			||||||
            String channelCode = intent.getStringExtra("channel_code");
 | 
					            String channelCode = intent.getStringExtra("channel_code");
 | 
				
			||||||
            String channelCategory = intent.getStringExtra("channel_category");
 | 
					            String channelCategory = intent.getStringExtra("channel_category");
 | 
				
			||||||
 | 
					            String acquirer = intent.getStringExtra("acquirer");
 | 
				
			||||||
 | 
					            String mid = intent.getStringExtra("mid");
 | 
				
			||||||
 | 
					            String tid = intent.getStringExtra("tid");
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Set merchant data with defaults
 | 
					            // Log received data for debugging
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "amount: " + amount);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "grossAmount: " + grossAmount);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "merchantName: " + merchantNameStr);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "referenceId: " + referenceId);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "createdAt: " + createdAt);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "channelCode: " + channelCode);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "acquirer: " + acquirer);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "mid: " + mid);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "tid: " + tid);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 1. Set merchant data with defaults
 | 
				
			||||||
            merchantName.setText(merchantNameStr != null ? merchantNameStr : "Marcel Panjaitan");
 | 
					            merchantName.setText(merchantNameStr != null ? merchantNameStr : "Marcel Panjaitan");
 | 
				
			||||||
            merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Jakarta, Indonesia");
 | 
					            merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Jakarta, Indonesia");
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Set MID and TID with defaults
 | 
					            // 2. Set MID and TID - prioritize from intent, then defaults
 | 
				
			||||||
            midText.setText("71000026521"); // Default MID from your transaction code
 | 
					            midText.setText(mid != null ? mid : "71000026521");
 | 
				
			||||||
            tidText.setText("73001500"); // Default TID from your transaction code
 | 
					            tidText.setText(tid != null ? tid : "73001500");
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Set transaction number - prefer reference_id, then transaction_id, then order_id
 | 
					            // 3. Set transaction number - prioritize reference_id
 | 
				
			||||||
            String displayTransactionNumber = null;
 | 
					            String displayTransactionNumber = null;
 | 
				
			||||||
            if (referenceId != null && !referenceId.isEmpty()) {
 | 
					            if (referenceId != null && !referenceId.isEmpty()) {
 | 
				
			||||||
                displayTransactionNumber = referenceId;
 | 
					                displayTransactionNumber = referenceId;
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Using reference_id as transaction number: " + referenceId);
 | 
				
			||||||
            } else if (transactionId != null && !transactionId.isEmpty()) {
 | 
					            } else if (transactionId != null && !transactionId.isEmpty()) {
 | 
				
			||||||
                displayTransactionNumber = transactionId;
 | 
					                displayTransactionNumber = transactionId;
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Using transaction_id as transaction number: " + transactionId);
 | 
				
			||||||
            } else if (orderId != null && !orderId.isEmpty()) {
 | 
					            } else if (orderId != null && !orderId.isEmpty()) {
 | 
				
			||||||
                displayTransactionNumber = orderId;
 | 
					                displayTransactionNumber = orderId;
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Using order_id as transaction number: " + orderId);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            transactionNumber.setText(displayTransactionNumber != null ? displayTransactionNumber : "3429483635");
 | 
					            transactionNumber.setText(displayTransactionNumber != null ? displayTransactionNumber : "N/A");
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Set transaction date
 | 
					            // 4. Set transaction date - prioritize createdAt, format as dd/MM/yyyy HH:mm
 | 
				
			||||||
            transactionDate.setText(transactionDateStr != null ? transactionDateStr : getCurrentDateTime());
 | 
					            String displayDate = null;
 | 
				
			||||||
 | 
					            if (createdAt != null && !createdAt.isEmpty()) {
 | 
				
			||||||
 | 
					                displayDate = formatDateFromCreatedAt(createdAt);
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Formatted createdAt: " + createdAt + " -> " + displayDate);
 | 
				
			||||||
 | 
					            } else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
 | 
				
			||||||
 | 
					                displayDate = transactionDateStr;
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Using provided transaction_date: " + transactionDateStr);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                displayDate = getCurrentDateTime();
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Using current datetime: " + displayDate);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            transactionDate.setText(displayDate);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Set payment method - determine from various sources
 | 
					            // 5. Set payment method - use channel_code mapping
 | 
				
			||||||
            String displayPaymentMethod = determinePaymentMethod(paymentMethodStr, channelCode, channelCategory);
 | 
					            String displayPaymentMethod = getPaymentMethodFromChannelCode(channelCode, paymentMethodStr);
 | 
				
			||||||
            paymentMethod.setText(displayPaymentMethod);
 | 
					            paymentMethod.setText(displayPaymentMethod);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "Payment method: " + displayPaymentMethod + " (from channel: " + channelCode + ")");
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Set card type - prefer acquirer, then cardType, then channelCode
 | 
					            // 6. Set card type - use acquirer with comprehensive mapping
 | 
				
			||||||
            String displayCardType = null;
 | 
					            // For QRIS, we need to get the actual acquirer (GoPay, OVO, DANA, etc.)
 | 
				
			||||||
            if (acquirer != null && !acquirer.isEmpty()) {
 | 
					            String displayCardType = getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr);
 | 
				
			||||||
                displayCardType = acquirer.toUpperCase();
 | 
					            
 | 
				
			||||||
            } else if (cardTypeStr != null && !cardTypeStr.isEmpty()) {
 | 
					            // Special handling for QRIS - if we only have "QRIS" or "qris", try to get real acquirer
 | 
				
			||||||
                displayCardType = cardTypeStr;
 | 
					            if (displayCardType.equalsIgnoreCase("QRIS") && channelCode != null && channelCode.equalsIgnoreCase("QRIS")) {
 | 
				
			||||||
            } else if (channelCode != null && !channelCode.isEmpty()) {
 | 
					                // For QRIS transactions, we should try to determine the actual acquirer
 | 
				
			||||||
                displayCardType = channelCode.toUpperCase();
 | 
					                // This might require additional API call or webhook data parsing
 | 
				
			||||||
 | 
					                Log.w("ReceiptActivity", "QRIS transaction detected but no specific acquirer found. Using default.");
 | 
				
			||||||
 | 
					                // In production, this should fetch the real acquirer from transaction status API
 | 
				
			||||||
 | 
					                displayCardType = acquirer != null && !acquirer.equalsIgnoreCase("qris") ? 
 | 
				
			||||||
 | 
					                    getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr) : "GoPay"; // Default QRIS acquirer
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            cardType.setText(displayCardType != null ? displayCardType : "QRIS");
 | 
					 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Format and set amounts
 | 
					            cardType.setText(displayCardType);
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "Card type: " + displayCardType + " (from acquirer: " + acquirer + ")");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 7. Format and set amounts - prioritize amount over grossAmount
 | 
				
			||||||
            setAmountData(amount, grossAmount);
 | 
					            setAmountData(amount, grossAmount);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "=== TRANSACTION DATA LOADED ===");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String determinePaymentMethod(String paymentMethodStr, String channelCode, String channelCategory) {
 | 
					    /**
 | 
				
			||||||
        // If payment method is already provided and formatted, use it
 | 
					     * Format date from createdAt field (yyyy-MM-dd HH:mm:ss) to Indonesian format (dd/MM/yyyy HH:mm)
 | 
				
			||||||
        if (paymentMethodStr != null && !paymentMethodStr.isEmpty()) {
 | 
					     */
 | 
				
			||||||
            return paymentMethodStr;
 | 
					    private String formatDateFromCreatedAt(String createdAt) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Input format from database: "yyyy-MM-dd HH:mm:ss"
 | 
				
			||||||
 | 
					            SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Output format for receipt: "dd/MM/yyyy HH:mm"
 | 
				
			||||||
 | 
					            SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm", 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: try alternative formats or return as-is
 | 
				
			||||||
 | 
					            return createdAt;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Determine from channel code
 | 
					    /**
 | 
				
			||||||
        if (channelCode != null) {
 | 
					     * Get payment method name from channel_code with comprehensive mapping
 | 
				
			||||||
            switch (channelCode.toUpperCase()) {
 | 
					     */
 | 
				
			||||||
 | 
					    private String getPaymentMethodFromChannelCode(String channelCode, String fallbackPaymentMethod) {
 | 
				
			||||||
 | 
					        if (channelCode != null && !channelCode.isEmpty()) {
 | 
				
			||||||
 | 
					            String code = channelCode.toUpperCase();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            switch (code) {
 | 
				
			||||||
                case "QRIS":
 | 
					                case "QRIS":
 | 
				
			||||||
                    return "QRIS";
 | 
					                    return "QRIS";
 | 
				
			||||||
                case "DEBIT":
 | 
					                case "DEBIT":
 | 
				
			||||||
@ -185,31 +252,314 @@ public class ReceiptActivity extends AppCompatActivity {
 | 
				
			|||||||
                case "CREDIT":
 | 
					                case "CREDIT":
 | 
				
			||||||
                    return "Kartu Kredit";
 | 
					                    return "Kartu Kredit";
 | 
				
			||||||
                case "BCA":
 | 
					                case "BCA":
 | 
				
			||||||
 | 
					                    return "BCA";
 | 
				
			||||||
                case "MANDIRI":
 | 
					                case "MANDIRI":
 | 
				
			||||||
 | 
					                    return "Bank Mandiri";
 | 
				
			||||||
                case "BNI":
 | 
					                case "BNI":
 | 
				
			||||||
 | 
					                    return "Bank BNI";
 | 
				
			||||||
                case "BRI":
 | 
					                case "BRI":
 | 
				
			||||||
                    return "Kartu " + channelCode.toUpperCase();
 | 
					                    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":
 | 
					                case "CASH":
 | 
				
			||||||
                    return "Tunai";
 | 
					                    return "Tunai";
 | 
				
			||||||
                case "EDC":
 | 
					                case "EDC":
 | 
				
			||||||
                    return "EDC";
 | 
					                    return "EDC";
 | 
				
			||||||
 | 
					                case "ALFAMART":
 | 
				
			||||||
 | 
					                    return "Alfamart";
 | 
				
			||||||
 | 
					                case "INDOMARET":
 | 
				
			||||||
 | 
					                    return "Indomaret";
 | 
				
			||||||
 | 
					                case "AKULAKU":
 | 
				
			||||||
 | 
					                    return "Akulaku";
 | 
				
			||||||
                default:
 | 
					                default:
 | 
				
			||||||
 | 
					                    return code; // Return the code as-is if not mapped
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Fallback to provided payment method or default
 | 
				
			||||||
 | 
					        return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ ENHANCED: Dynamic acquirer detection from webhook data
 | 
				
			||||||
 | 
					     * This method tries to get the REAL acquirer instead of defaulting to GoPay
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    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();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Comprehensive acquirer mapping based on Midtrans
 | 
				
			||||||
 | 
					            switch (acq) {
 | 
				
			||||||
 | 
					                // E-Wallet acquirers
 | 
				
			||||||
 | 
					                case "gopay": return "GoPay";
 | 
				
			||||||
 | 
					                case "shopeepay": return "ShopeePay";
 | 
				
			||||||
 | 
					                case "ovo": return "OVO";
 | 
				
			||||||
 | 
					                case "dana": return "DANA";
 | 
				
			||||||
 | 
					                case "linkaja": return "LinkAja";
 | 
				
			||||||
 | 
					                case "jenius": return "Jenius";
 | 
				
			||||||
 | 
					                case "kaspro": return "KasPro";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Bank acquirers
 | 
				
			||||||
 | 
					                case "bca": return "BCA";
 | 
				
			||||||
 | 
					                case "mandiri":
 | 
				
			||||||
 | 
					                case "mandiri_bill": return "Mandiri";
 | 
				
			||||||
 | 
					                case "bni":
 | 
				
			||||||
 | 
					                case "bni_va": return "BNI";
 | 
				
			||||||
 | 
					                case "bri":
 | 
				
			||||||
 | 
					                case "bri_va": return "BRI";
 | 
				
			||||||
 | 
					                case "permata":
 | 
				
			||||||
 | 
					                case "permata_va": return "Permata";
 | 
				
			||||||
 | 
					                case "cimb":
 | 
				
			||||||
 | 
					                case "cimb_va": return "CIMB Niaga";
 | 
				
			||||||
 | 
					                case "danamon":
 | 
				
			||||||
 | 
					                case "danamon_va": return "Danamon";
 | 
				
			||||||
 | 
					                case "bsi":
 | 
				
			||||||
 | 
					                case "bsi_va": return "BSI";
 | 
				
			||||||
 | 
					                case "maybank": return "Maybank";
 | 
				
			||||||
 | 
					                case "mega": return "Bank Mega";
 | 
				
			||||||
 | 
					                case "btn": return "BTN";
 | 
				
			||||||
 | 
					                case "bukopin": return "Bukopin";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Credit card acquirers
 | 
				
			||||||
 | 
					                case "visa": return "Visa";
 | 
				
			||||||
 | 
					                case "mastercard": return "Mastercard";
 | 
				
			||||||
 | 
					                case "jcb": return "JCB";
 | 
				
			||||||
 | 
					                case "amex":
 | 
				
			||||||
 | 
					                case "american_express": return "American Express";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Over-the-counter
 | 
				
			||||||
 | 
					                case "alfamart": return "Alfamart";
 | 
				
			||||||
 | 
					                case "indomaret": return "Indomaret";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Buy now pay later
 | 
				
			||||||
 | 
					                case "akulaku": return "Akulaku";
 | 
				
			||||||
 | 
					                case "kredivo": return "Kredivo";
 | 
				
			||||||
 | 
					                case "indodana": return "Indodana";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Other acquirers
 | 
				
			||||||
 | 
					                case "emoney": return "E-Money";
 | 
				
			||||||
 | 
					                case "flazz": return "Flazz";
 | 
				
			||||||
 | 
					                case "tapcash": return "TapCash";
 | 
				
			||||||
 | 
					                case "brizzi": return "Brizzi";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    // Return capitalized version of acquirer if not in mapping
 | 
				
			||||||
 | 
					                    return capitalizeFirstLetter(acquirer);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // STEP 2: For QRIS transactions, try to fetch real acquirer from webhook data
 | 
				
			||||||
 | 
					        if (channelCode != null && channelCode.equalsIgnoreCase("QRIS")) {
 | 
				
			||||||
 | 
					            String referenceId = getIntent().getStringExtra("reference_id");
 | 
				
			||||||
 | 
					            if (referenceId != null && !referenceId.isEmpty()) {
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "🔍 QRIS detected, fetching real acquirer for: " + referenceId);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Try to get real acquirer from webhook data asynchronously
 | 
				
			||||||
 | 
					                fetchRealAcquirerFromWebhook(referenceId);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // ✅ IMPROVED: Instead of defaulting to GoPay, show generic "QRIS" until real acquirer is found
 | 
				
			||||||
 | 
					                return "QRIS"; // Will be updated when real acquirer is found
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // STEP 3: Fallback based on channel code
 | 
				
			||||||
 | 
					        if (channelCode != null && !channelCode.isEmpty()) {
 | 
				
			||||||
 | 
					            String code = channelCode.toUpperCase();
 | 
				
			||||||
 | 
					            switch (code) {
 | 
				
			||||||
 | 
					                case "QRIS": return "QRIS"; // ✅ CHANGED: Generic QRIS instead of GoPay
 | 
				
			||||||
 | 
					                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 4: Final fallback
 | 
				
			||||||
 | 
					        return fallbackCardType != null ? fallbackCardType : "Unknown"; // ✅ CHANGED: Unknown instead of GoPay
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ ENHANCED: Fetch real acquirer from webhook data for QRIS transactions
 | 
				
			||||||
 | 
					     * This method searches webhook logs to find the actual acquirer used
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void fetchRealAcquirerFromWebhook(String referenceId) {
 | 
				
			||||||
 | 
					        new Thread(() -> {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "🔍 Searching for real acquirer for reference: " + referenceId);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Search webhook logs for this reference with broader search
 | 
				
			||||||
 | 
					                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(10000);
 | 
				
			||||||
 | 
					                conn.setReadTimeout(10000);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                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) {
 | 
				
			||||||
 | 
					                        String realAcquirer = searchForRealAcquirer(results, referenceId);
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
 | 
				
			||||||
 | 
					                            Log.d("ReceiptActivity", "✅ Found real acquirer: " + realAcquirer + " for reference: " + referenceId);
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                            // Update UI on main thread
 | 
				
			||||||
 | 
					                            final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
 | 
				
			||||||
 | 
					                            runOnUiThread(() -> {
 | 
				
			||||||
 | 
					                                if (cardType != null) {
 | 
				
			||||||
 | 
					                                    cardType.setText(displayAcquirer);
 | 
				
			||||||
 | 
					                                    Log.d("ReceiptActivity", "🎨 Updated card type from QRIS to: " + displayAcquirer);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            Log.w("ReceiptActivity", "⚠️ Could not determine real acquirer for: " + referenceId);
 | 
				
			||||||
 | 
					                            // Keep showing "QRIS" instead of defaulting to GoPay
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Log.w("ReceiptActivity", "⚠️ API call failed with code: " + conn.getResponseCode());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            } catch (Exception e) {
 | 
				
			||||||
 | 
					                Log.e("ReceiptActivity", "❌ Error fetching real acquirer: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).start();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ NEW METHOD: Advanced search for real acquirer in webhook logs
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String searchForRealAcquirer(org.json.JSONArray results, String referenceId) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            Log.d("ReceiptActivity", "🔍 Analyzing " + results.length() + " webhook logs for acquirer");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Strategy 1: Look for settlement/success transactions first (highest priority)
 | 
				
			||||||
 | 
					            String acquirerFromSettlement = searchLogsByStatus(results, referenceId, new String[]{"settlement", "capture", "success"});
 | 
				
			||||||
 | 
					            if (acquirerFromSettlement != null) {
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "🎯 Found acquirer from settlement: " + acquirerFromSettlement);
 | 
				
			||||||
 | 
					                return acquirerFromSettlement;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Strategy 2: Look for pending transactions
 | 
				
			||||||
 | 
					            String acquirerFromPending = searchLogsByStatus(results, referenceId, new String[]{"pending"});
 | 
				
			||||||
 | 
					            if (acquirerFromPending != null) {
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "🎯 Found acquirer from pending: " + acquirerFromPending);
 | 
				
			||||||
 | 
					                return acquirerFromPending;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Strategy 3: Look for any transaction with this reference
 | 
				
			||||||
 | 
					            String acquirerFromAny = searchLogsByStatus(results, referenceId, new String[]{});
 | 
				
			||||||
 | 
					            if (acquirerFromAny != null) {
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "🎯 Found acquirer from any status: " + acquirerFromAny);
 | 
				
			||||||
 | 
					                return acquirerFromAny;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Log.w("ReceiptActivity", "❌ No acquirer found in webhook logs for: " + referenceId);
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e("ReceiptActivity", "❌ Error analyzing webhook logs: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ HELPER METHOD: Search logs by specific status
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    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", "");
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Check for direct reference match
 | 
				
			||||||
 | 
					                    boolean isDirectMatch = referenceId.equals(logReferenceId);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 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) {
 | 
				
			||||||
 | 
					                        // If target statuses specified, check status
 | 
				
			||||||
 | 
					                        if (targetStatuses.length > 0) {
 | 
				
			||||||
 | 
					                            boolean statusMatches = false;
 | 
				
			||||||
 | 
					                            for (String targetStatus : targetStatuses) {
 | 
				
			||||||
 | 
					                                if (logTransactionStatus.equalsIgnoreCase(targetStatus)) {
 | 
				
			||||||
 | 
					                                    statusMatches = true;
 | 
				
			||||||
                                    break;
 | 
					                                    break;
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
        
 | 
					                            if (!statusMatches) continue; // Skip if status doesn't match
 | 
				
			||||||
        // Determine from channel category
 | 
					 | 
				
			||||||
        if (channelCategory != null && !channelCategory.isEmpty()) {
 | 
					 | 
				
			||||||
            return channelCategory.toUpperCase();
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        
 | 
					                        
 | 
				
			||||||
        // Default
 | 
					                        // Extract acquirer (prefer acquirer field over issuer)
 | 
				
			||||||
        return "QRIS";
 | 
					                        String foundAcquirer = !logAcquirer.isEmpty() ? logAcquirer : logIssuer;
 | 
				
			||||||
 | 
					                        if (!foundAcquirer.isEmpty() && !foundAcquirer.equalsIgnoreCase("qris")) {
 | 
				
			||||||
 | 
					                            Log.d("ReceiptActivity", "📋 Found acquirer: " + foundAcquirer + " (status: " + logTransactionStatus + ")");
 | 
				
			||||||
 | 
					                            return foundAcquirer;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e("ReceiptActivity", "❌ Error searching logs by status: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return null; // No acquirer found for specified criteria
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String capitalizeFirstLetter(String input) {
 | 
				
			||||||
 | 
					        if (input == null || input.isEmpty()) {
 | 
				
			||||||
 | 
					            return input;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return input.substring(0, 1).toUpperCase() + input.substring(1).toLowerCase();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void setAmountData(String amount, String grossAmount) {
 | 
					    private void setAmountData(String amount, String grossAmount) {
 | 
				
			||||||
        // Use gross amount if available, otherwise use amount
 | 
					        // Prioritize 'amount' over 'grossAmount' for transaction data
 | 
				
			||||||
        String amountToUse = grossAmount != null ? grossAmount : amount;
 | 
					        String amountToUse = amount != null ? amount : grossAmount;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        Log.d("ReceiptActivity", "Setting amount data - amount: " + amount + 
 | 
					        Log.d("ReceiptActivity", "Setting amount data - amount: " + amount + 
 | 
				
			||||||
            ", grossAmount: " + grossAmount + ", using: " + amountToUse);
 | 
					            ", grossAmount: " + grossAmount + ", using: " + amountToUse);
 | 
				
			||||||
@ -236,6 +586,8 @@ public class ReceiptActivity extends AppCompatActivity {
 | 
				
			|||||||
                serviceFee.setText("Rp 0");
 | 
					                serviceFee.setText("Rp 0");
 | 
				
			||||||
                finalTotal.setText("Rp " + formatCurrency(total));
 | 
					                finalTotal.setText("Rp " + formatCurrency(total));
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                Log.d("ReceiptActivity", "Amount formatting successful: " + amountLong + " -> Rp " + formatCurrency(total));
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
            } catch (NumberFormatException e) {
 | 
					            } catch (NumberFormatException e) {
 | 
				
			||||||
                Log.e("ReceiptActivity", "Error parsing amount: " + amountToUse, e);
 | 
					                Log.e("ReceiptActivity", "Error parsing amount: " + amountToUse, e);
 | 
				
			||||||
                // Fallback if parsing fails
 | 
					                // Fallback if parsing fails
 | 
				
			||||||
@ -254,7 +606,7 @@ public class ReceiptActivity extends AppCompatActivity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * FIXED: Clean amount string to extract raw number correctly
 | 
					     * Clean amount string to extract raw number correctly
 | 
				
			||||||
     * Input examples: "1000", "1000.00", "Rp 1.000", "1.000"
 | 
					     * Input examples: "1000", "1000.00", "Rp 1.000", "1.000"
 | 
				
			||||||
     * Output: "1000"
 | 
					     * Output: "1000"
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -323,8 +675,8 @@ public class ReceiptActivity extends AppCompatActivity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getCurrentDateTime() {
 | 
					    private String getCurrentDateTime() {
 | 
				
			||||||
        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", new java.util.Locale("id", "ID"));
 | 
					        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID"));
 | 
				
			||||||
        return sdf.format(new java.util.Date());
 | 
					        return sdf.format(new Date());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void handlePrintReceipt() {
 | 
					    private void handlePrintReceipt() {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
package com.example.bdkipoc;
 | 
					package com.example.bdkipoc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.SharedPreferences;
 | 
				
			||||||
import android.os.AsyncTask;
 | 
					import android.os.AsyncTask;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.text.Editable;
 | 
					import android.text.Editable;
 | 
				
			||||||
@ -31,7 +32,11 @@ import java.net.URI;
 | 
				
			|||||||
import java.net.URISyntaxException;
 | 
					import java.net.URISyntaxException;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
import java.text.SimpleDateFormat;
 | 
					import java.text.SimpleDateFormat;
 | 
				
			||||||
import java.util.Date;
 | 
					import java.util.Date;
 | 
				
			||||||
import java.util.Locale;
 | 
					import java.util.Locale;
 | 
				
			||||||
@ -45,9 +50,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
    private EditText searchEditText;
 | 
					    private EditText searchEditText;
 | 
				
			||||||
    private ImageButton searchButton;
 | 
					    private ImageButton searchButton;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    // ✅ FRONTEND DEDUPLICATION: Local caching and tracking
 | 
				
			||||||
 | 
					    private Map<String, Transaction> transactionCache = new HashMap<>();
 | 
				
			||||||
 | 
					    private Set<String> processedReferences = new HashSet<>();
 | 
				
			||||||
 | 
					    private SharedPreferences prefs;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // Pagination variables
 | 
					    // Pagination variables
 | 
				
			||||||
    private int page = 0;
 | 
					    private int page = 0;
 | 
				
			||||||
    private final int limit = 10;
 | 
					    private final int limit = 50; // ✅ INCREASED: Fetch more data for better deduplication
 | 
				
			||||||
    private boolean isLoading = false;
 | 
					    private boolean isLoading = false;
 | 
				
			||||||
    private boolean isLastPage = false;
 | 
					    private boolean isLastPage = false;
 | 
				
			||||||
    private String currentSearchQuery = "";
 | 
					    private String currentSearchQuery = "";
 | 
				
			||||||
@ -58,6 +68,9 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
        super.onCreate(savedInstanceState);
 | 
					        super.onCreate(savedInstanceState);
 | 
				
			||||||
        setContentView(R.layout.activity_transaction);
 | 
					        setContentView(R.layout.activity_transaction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ✅ Initialize SharedPreferences for local tracking
 | 
				
			||||||
 | 
					        prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Initialize views
 | 
					        // Initialize views
 | 
				
			||||||
        initViews();
 | 
					        initViews();
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@ -111,12 +124,6 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
                if (!recyclerView.canScrollVertically(1) && !isLoading && !isLastPage && currentSearchQuery.isEmpty()) {
 | 
					                if (!recyclerView.canScrollVertically(1) && !isLoading && !isLastPage && currentSearchQuery.isEmpty()) {
 | 
				
			||||||
                    loadTransactions(page + 1);
 | 
					                    loadTransactions(page + 1);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Detect scroll to top for refresh gesture (double tap on top)
 | 
					 | 
				
			||||||
                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
 | 
					 | 
				
			||||||
                if (layoutManager != null && layoutManager.findFirstCompletelyVisibleItemPosition() == 0 && dy < 0) {
 | 
					 | 
				
			||||||
                    // User is at top and scrolling up, enable refresh on search button long press
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -152,6 +159,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
        if (isRefreshing) return;
 | 
					        if (isRefreshing) return;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        isRefreshing = true;
 | 
					        isRefreshing = true;
 | 
				
			||||||
 | 
					        // ✅ CLEAR LOCAL CACHE on refresh
 | 
				
			||||||
 | 
					        transactionCache.clear();
 | 
				
			||||||
 | 
					        processedReferences.clear();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        // Clear search when refreshing
 | 
					        // Clear search when refreshing
 | 
				
			||||||
        searchEditText.setText("");
 | 
					        searchEditText.setText("");
 | 
				
			||||||
        currentSearchQuery = "";
 | 
					        currentSearchQuery = "";
 | 
				
			||||||
@ -215,13 +226,25 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
        protected List<Transaction> doInBackground(Void... voids) {
 | 
					        protected List<Transaction> doInBackground(Void... voids) {
 | 
				
			||||||
            List<Transaction> result = new ArrayList<>();
 | 
					            List<Transaction> result = new ArrayList<>();
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                String urlString = "https://be-edc.msvc.app/transactions?page=" + pageToLoad + "&limit=" + limit + "&sortOrder=DESC&from_date=&to_date=&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=id";
 | 
					                // ✅ FETCH MORE DATA: Increased limit for better deduplication
 | 
				
			||||||
 | 
					                int fetchLimit = limit * 3; // Get more records to handle all duplicates
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                String urlString = "https://be-edc.msvc.app/transactions?page=" + pageToLoad + 
 | 
				
			||||||
 | 
					                    "&limit=" + fetchLimit + "&sortOrder=DESC&from_date=&to_date=&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at";
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad + " with limit " + fetchLimit);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                URI uri = new URI(urlString);
 | 
					                URI uri = new URI(urlString);
 | 
				
			||||||
                URL url = uri.toURL();
 | 
					                URL url = uri.toURL();
 | 
				
			||||||
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
					                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
				
			||||||
                conn.setRequestMethod("GET");
 | 
					                conn.setRequestMethod("GET");
 | 
				
			||||||
                conn.setRequestProperty("accept", "*/*");
 | 
					                conn.setRequestProperty("accept", "*/*");
 | 
				
			||||||
 | 
					                conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
 | 
				
			||||||
 | 
					                conn.setConnectTimeout(15000);
 | 
				
			||||||
 | 
					                conn.setReadTimeout(15000);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                int responseCode = conn.getResponseCode();
 | 
					                int responseCode = conn.getResponseCode();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                if (responseCode == 200) {
 | 
					                if (responseCode == 200) {
 | 
				
			||||||
                    BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
 | 
					                    BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
 | 
				
			||||||
                    StringBuilder response = new StringBuilder();
 | 
					                    StringBuilder response = new StringBuilder();
 | 
				
			||||||
@ -230,10 +253,16 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
                        response.append(line);
 | 
					                        response.append(line);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    in.close();
 | 
					                    in.close();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                    JSONObject jsonObject = new JSONObject(response.toString());
 | 
					                    JSONObject jsonObject = new JSONObject(response.toString());
 | 
				
			||||||
                    JSONObject results = jsonObject.getJSONObject("results");
 | 
					                    JSONObject results = jsonObject.getJSONObject("results");
 | 
				
			||||||
                    total = results.getInt("total");
 | 
					                    total = results.getInt("total");
 | 
				
			||||||
                    JSONArray data = results.getJSONArray("data");
 | 
					                    JSONArray data = results.getJSONArray("data");
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    Log.d("TransactionActivity", "📊 Raw API response: " + data.length() + " records");
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // ✅ STEP 1: Parse all transactions from API
 | 
				
			||||||
 | 
					                    List<Transaction> rawTransactions = new ArrayList<>();
 | 
				
			||||||
                    for (int i = 0; i < data.length(); i++) {
 | 
					                    for (int i = 0; i < data.length(); i++) {
 | 
				
			||||||
                        JSONObject t = data.getJSONObject(i);
 | 
					                        JSONObject t = data.getJSONObject(i);
 | 
				
			||||||
                        Transaction tx = new Transaction(
 | 
					                        Transaction tx = new Transaction(
 | 
				
			||||||
@ -248,12 +277,20 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
                                t.getString("created_at"),
 | 
					                                t.getString("created_at"),
 | 
				
			||||||
                                t.getString("merchant_name")
 | 
					                                t.getString("merchant_name")
 | 
				
			||||||
                        );
 | 
					                        );
 | 
				
			||||||
                        result.add(tx);
 | 
					                        rawTransactions.add(tx);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // ✅ STEP 2: Apply intelligent deduplication
 | 
				
			||||||
 | 
					                    result = applyAdvancedDeduplication(rawTransactions);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    Log.d("TransactionActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Log.e("TransactionActivity", "❌ HTTP Error: " + responseCode);
 | 
				
			||||||
                    error = true;
 | 
					                    error = true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (IOException | JSONException | URISyntaxException e) {
 | 
					            } catch (IOException | JSONException | URISyntaxException e) {
 | 
				
			||||||
 | 
					                Log.e("TransactionActivity", "❌ Exception: " + e.getMessage(), e);
 | 
				
			||||||
                error = true;
 | 
					                error = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return result;
 | 
					            return result;
 | 
				
			||||||
@ -272,14 +309,44 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
            
 | 
					            
 | 
				
			||||||
            if (pageToLoad == 0) {
 | 
					            if (pageToLoad == 0) {
 | 
				
			||||||
                transactionList.clear();
 | 
					                transactionList.clear();
 | 
				
			||||||
 | 
					                transactionCache.clear(); // Clear cache on refresh
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            transactionList.addAll(transactions);
 | 
					            
 | 
				
			||||||
 | 
					            // ✅ SMART MERGE: Only add truly new transactions
 | 
				
			||||||
 | 
					            int addedCount = 0;
 | 
				
			||||||
 | 
					            for (Transaction newTx : transactions) {
 | 
				
			||||||
 | 
					                String refId = newTx.referenceId;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Check if we already have a better version of this transaction
 | 
				
			||||||
 | 
					                Transaction cachedTx = transactionCache.get(refId);
 | 
				
			||||||
 | 
					                if (cachedTx == null || isBetterTransaction(newTx, cachedTx)) {
 | 
				
			||||||
 | 
					                    // Update cache with better transaction
 | 
				
			||||||
 | 
					                    transactionCache.put(refId, newTx);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // Update or add to main list
 | 
				
			||||||
 | 
					                    boolean updated = false;
 | 
				
			||||||
 | 
					                    for (int i = 0; i < transactionList.size(); i++) {
 | 
				
			||||||
 | 
					                        if (transactionList.get(i).referenceId.equals(refId)) {
 | 
				
			||||||
 | 
					                            transactionList.set(i, newTx);
 | 
				
			||||||
 | 
					                            updated = true;
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (!updated) {
 | 
				
			||||||
 | 
					                        transactionList.add(newTx);
 | 
				
			||||||
 | 
					                        addedCount++;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Log.d("TransactionActivity", "📋 Added " + addedCount + " new unique transactions. Total: " + transactionList.size());
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Update filtered list based on current search
 | 
					            // Update filtered list based on current search
 | 
				
			||||||
            filterTransactions(currentSearchQuery);
 | 
					            filterTransactions(currentSearchQuery);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            page = pageToLoad;
 | 
					            page = pageToLoad;
 | 
				
			||||||
            if (transactionList.size() >= total) {
 | 
					            if (transactions.size() < limit) { // No more pages if returned less than requested
 | 
				
			||||||
                isLastPage = true;
 | 
					                isLastPage = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
@ -290,6 +357,186 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
 | 
				
			||||||
 | 
					        Log.d("TransactionActivity", "🧠 Starting advanced deduplication...");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Strategy 1: Group by reference_id
 | 
				
			||||||
 | 
					        Map<String, List<Transaction>> groupedByRef = new HashMap<>();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (Transaction tx : rawTransactions) {
 | 
				
			||||||
 | 
					            String refId = tx.referenceId;
 | 
				
			||||||
 | 
					            groupedByRef.computeIfAbsent(refId, k -> new ArrayList<>()).add(tx);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        List<Transaction> deduplicatedList = new ArrayList<>();
 | 
				
			||||||
 | 
					        int duplicatesRemoved = 0;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Strategy 2: For each group, select the best representative
 | 
				
			||||||
 | 
					        for (Map.Entry<String, List<Transaction>> entry : groupedByRef.entrySet()) {
 | 
				
			||||||
 | 
					            String referenceId = entry.getKey();
 | 
				
			||||||
 | 
					            List<Transaction> group = entry.getValue();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (group.size() == 1) {
 | 
				
			||||||
 | 
					                // No duplicates for this reference
 | 
				
			||||||
 | 
					                deduplicatedList.add(group.get(0));
 | 
				
			||||||
 | 
					                Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Multiple transactions with same reference_id
 | 
				
			||||||
 | 
					                Transaction bestTransaction = selectBestTransactionAdvanced(group, referenceId);
 | 
				
			||||||
 | 
					                deduplicatedList.add(bestTransaction);
 | 
				
			||||||
 | 
					                duplicatesRemoved += (group.size() - 1);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId + 
 | 
				
			||||||
 | 
					                    " (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ")");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        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");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return deduplicatedList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ ENHANCED SELECTION: Advanced algorithm to pick the best transaction
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Transaction selectBestTransactionAdvanced(List<Transaction> duplicates, String referenceId) {
 | 
				
			||||||
 | 
					        Log.d("TransactionActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Transaction bestTransaction = duplicates.get(0);
 | 
				
			||||||
 | 
					        int bestPriority = getStatusPriority(bestTransaction.status);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Detailed analysis of each candidate
 | 
				
			||||||
 | 
					        for (Transaction tx : duplicates) {
 | 
				
			||||||
 | 
					            int currentPriority = getStatusPriority(tx.status);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Log.d("TransactionActivity", "   📊 Candidate: ID=" + tx.id + 
 | 
				
			||||||
 | 
					                ", Status=" + tx.status + " (priority=" + currentPriority + ")" +
 | 
				
			||||||
 | 
					                ", Created=" + tx.createdAt);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            boolean shouldSelect = false;
 | 
				
			||||||
 | 
					            String reason = "";
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Rule 1: Higher status priority always wins
 | 
				
			||||||
 | 
					            if (currentPriority > bestPriority) {
 | 
				
			||||||
 | 
					                shouldSelect = true;
 | 
				
			||||||
 | 
					                reason = "better status (" + tx.status + " > " + bestTransaction.status + ")";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Rule 2: Same priority, choose newer timestamp
 | 
				
			||||||
 | 
					            else if (currentPriority == bestPriority) {
 | 
				
			||||||
 | 
					                if (isNewerTransaction(tx, bestTransaction)) {
 | 
				
			||||||
 | 
					                    shouldSelect = true;
 | 
				
			||||||
 | 
					                    reason = "newer timestamp";
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Rule 3: Same priority and time, choose higher ID
 | 
				
			||||||
 | 
					                else if (tx.createdAt.equals(bestTransaction.createdAt) && tx.id > bestTransaction.id) {
 | 
				
			||||||
 | 
					                    shouldSelect = true;
 | 
				
			||||||
 | 
					                    reason = "higher ID";
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (shouldSelect) {
 | 
				
			||||||
 | 
					                bestTransaction = tx;
 | 
				
			||||||
 | 
					                bestPriority = currentPriority;
 | 
				
			||||||
 | 
					                Log.d("TransactionActivity", "   ⭐ NEW BEST selected: " + reason);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Log.d("TransactionActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id + 
 | 
				
			||||||
 | 
					            ", Status=" + bestTransaction.status + ", Created=" + bestTransaction.createdAt);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return bestTransaction;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ TIMESTAMP COMPARISON: Smart date comparison
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private boolean isNewerTransaction(Transaction tx1, Transaction tx2) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
 | 
				
			||||||
 | 
					            Date date1 = sdf.parse(tx1.createdAt);
 | 
				
			||||||
 | 
					            Date date2 = sdf.parse(tx2.createdAt);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (date1 != null && date2 != null) {
 | 
				
			||||||
 | 
					                return date1.after(date2);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.w("TransactionActivity", "Date parsing error, falling back to ID comparison");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Fallback: higher ID usually means newer
 | 
				
			||||||
 | 
					        return tx1.id > tx2.id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ COMPARISON HELPER: Check if one transaction is better than another
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private boolean isBetterTransaction(Transaction newTx, Transaction existingTx) {
 | 
				
			||||||
 | 
					        int newPriority = getStatusPriority(newTx.status);
 | 
				
			||||||
 | 
					        int existingPriority = getStatusPriority(existingTx.status);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (newPriority > existingPriority) {
 | 
				
			||||||
 | 
					            return true; // Better status
 | 
				
			||||||
 | 
					        } else if (newPriority == existingPriority) {
 | 
				
			||||||
 | 
					            return isNewerTransaction(newTx, existingTx); // Same status, check if newer
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return false; // Existing transaction is better
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ STATUS PRIORITY SYSTEM: Comprehensive status ranking
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private int getStatusPriority(String status) {
 | 
				
			||||||
 | 
					        if (status == null) return 0;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        String statusLower = status.toLowerCase().trim();
 | 
				
			||||||
 | 
					        switch (statusLower) {
 | 
				
			||||||
 | 
					            // Tier 1: Confirmed payments (highest priority)
 | 
				
			||||||
 | 
					            case "paid":
 | 
				
			||||||
 | 
					            case "settlement":
 | 
				
			||||||
 | 
					            case "capture":
 | 
				
			||||||
 | 
					            case "success":
 | 
				
			||||||
 | 
					            case "completed":
 | 
				
			||||||
 | 
					                return 100;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            // Tier 2: Processing states
 | 
				
			||||||
 | 
					            case "pending":
 | 
				
			||||||
 | 
					            case "processing":
 | 
				
			||||||
 | 
					            case "authorized":
 | 
				
			||||||
 | 
					                return 50;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            // Tier 3: Initial states
 | 
				
			||||||
 | 
					            case "init":
 | 
				
			||||||
 | 
					            case "initialized":
 | 
				
			||||||
 | 
					            case "created":
 | 
				
			||||||
 | 
					                return 20;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            // Tier 4: Failed/cancelled states (lowest priority)
 | 
				
			||||||
 | 
					            case "failed":
 | 
				
			||||||
 | 
					            case "failure":
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					            case "declined":
 | 
				
			||||||
 | 
					            case "expire":
 | 
				
			||||||
 | 
					            case "expired":
 | 
				
			||||||
 | 
					            case "cancel":
 | 
				
			||||||
 | 
					            case "cancelled":
 | 
				
			||||||
 | 
					            case "timeout":
 | 
				
			||||||
 | 
					                return 10;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            // Tier 5: Unknown status
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                Log.w("TransactionActivity", "Unknown status encountered: " + status);
 | 
				
			||||||
 | 
					                return 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ✅ REST OF THE CLASS: Existing methods remain the same
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onPrintClick(Transaction transaction) {
 | 
					    public void onPrintClick(Transaction transaction) {
 | 
				
			||||||
        // Open ReceiptActivity with transaction data
 | 
					        // Open ReceiptActivity with transaction data
 | 
				
			||||||
@ -298,40 +545,95 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
        // Add calling activity information for proper back navigation
 | 
					        // Add calling activity information for proper back navigation
 | 
				
			||||||
        intent.putExtra("calling_activity", "TransactionActivity");
 | 
					        intent.putExtra("calling_activity", "TransactionActivity");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // FIXED: Extract and send raw amount properly
 | 
					        // Extract and send raw amount properly
 | 
				
			||||||
        String rawAmount = extractRawAmount(transaction.amount);
 | 
					        String rawAmount = extractRawAmount(transaction.amount);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        Log.d("TransactionActivity", "Opening receipt for transaction: " + transaction.referenceId + 
 | 
					        Log.d("TransactionActivity", "Opening receipt for transaction: " + transaction.referenceId + 
 | 
				
			||||||
            ", original amount: '" + transaction.amount + "', extracted: '" + rawAmount + "'");
 | 
					            ", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Send transaction data to ReceiptActivity
 | 
					        // Send transaction data to ReceiptActivity
 | 
				
			||||||
        intent.putExtra("transaction_id", transaction.referenceId);
 | 
					        intent.putExtra("transaction_id", transaction.referenceId);
 | 
				
			||||||
        intent.putExtra("reference_id", transaction.referenceId);
 | 
					        intent.putExtra("reference_id", transaction.referenceId); // Nomor Transaksi
 | 
				
			||||||
        intent.putExtra("transaction_amount", rawAmount); // Send raw amount
 | 
					        intent.putExtra("transaction_amount", rawAmount); // Total transaksi
 | 
				
			||||||
        intent.putExtra("gross_amount", rawAmount); // Consistent with transaction_amount
 | 
					        intent.putExtra("gross_amount", rawAmount); // Consistent with transaction_amount
 | 
				
			||||||
        intent.putExtra("transaction_date", formatDate(transaction.createdAt));
 | 
					        intent.putExtra("created_at", transaction.createdAt); // Tanggal transaksi (will be formatted)
 | 
				
			||||||
 | 
					        intent.putExtra("transaction_date", formatDate(transaction.createdAt)); // Backup formatted date
 | 
				
			||||||
        intent.putExtra("payment_method", getPaymentMethodName(transaction.channelCode, transaction.channelCategory));
 | 
					        intent.putExtra("payment_method", getPaymentMethodName(transaction.channelCode, transaction.channelCategory));
 | 
				
			||||||
        intent.putExtra("card_type", transaction.channelCategory);
 | 
					        intent.putExtra("channel_code", transaction.channelCode); // Metode Pembayaran
 | 
				
			||||||
        intent.putExtra("channel_code", transaction.channelCode);
 | 
					 | 
				
			||||||
        intent.putExtra("channel_category", transaction.channelCategory);
 | 
					        intent.putExtra("channel_category", transaction.channelCategory);
 | 
				
			||||||
 | 
					        intent.putExtra("card_type", transaction.channelCategory);
 | 
				
			||||||
        intent.putExtra("merchant_name", transaction.merchantName);
 | 
					        intent.putExtra("merchant_name", transaction.merchantName);
 | 
				
			||||||
        intent.putExtra("merchant_location", "Jakarta, Indonesia");
 | 
					        intent.putExtra("merchant_location", "Jakarta, Indonesia");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Add MID and TID (default values since not available in Transaction model)
 | 
				
			||||||
 | 
					        intent.putExtra("mid", "71000026521"); // MID
 | 
				
			||||||
 | 
					        intent.putExtra("tid", "73001500");   // TID
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // ✅ ENHANCED: Use improved acquirer determination
 | 
				
			||||||
 | 
					        String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
 | 
				
			||||||
 | 
					        intent.putExtra("acquirer", acquirer); // Jenis Kartu
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Log.d("TransactionActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        startActivity(intent);
 | 
					        startActivity(intent);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * FIXED: Extract raw amount from formatted string properly
 | 
					     * ✅ ENHANCED: Dynamic acquirer determination instead of defaulting to GoPay
 | 
				
			||||||
     * Handles various amount formats from backend
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 | 
					    private String determineAcquirerFromChannelCode(String channelCode) {
 | 
				
			||||||
 | 
					        if (channelCode == null) return "unknown"; // ✅ CHANGED: unknown instead of gopay
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        switch (channelCode.toLowerCase()) {
 | 
				
			||||||
 | 
					            case "qris":
 | 
				
			||||||
 | 
					                // ✅ IMPROVED: For QRIS, try to get real acquirer or return generic
 | 
				
			||||||
 | 
					                return "qris"; // Will be processed by receipt activity to find real acquirer
 | 
				
			||||||
 | 
					            case "bca":
 | 
				
			||||||
 | 
					                return "bca";
 | 
				
			||||||
 | 
					            case "mandiri":
 | 
				
			||||||
 | 
					                return "mandiri";
 | 
				
			||||||
 | 
					            case "bni":
 | 
				
			||||||
 | 
					                return "bni";
 | 
				
			||||||
 | 
					            case "bri":
 | 
				
			||||||
 | 
					                return "bri";
 | 
				
			||||||
 | 
					            case "permata":
 | 
				
			||||||
 | 
					                return "permata";
 | 
				
			||||||
 | 
					            case "cimb":
 | 
				
			||||||
 | 
					                return "cimb";
 | 
				
			||||||
 | 
					            case "danamon":
 | 
				
			||||||
 | 
					                return "danamon";
 | 
				
			||||||
 | 
					            case "bsi":
 | 
				
			||||||
 | 
					                return "bsi";
 | 
				
			||||||
 | 
					            case "debit":
 | 
				
			||||||
 | 
					                return "visa"; // Default for debit cards
 | 
				
			||||||
 | 
					            case "credit":
 | 
				
			||||||
 | 
					                return "mastercard"; // Default for credit cards
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                return "unknown"; // ✅ CHANGED: unknown instead of gopay for unknown channels
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ✅ NEW METHOD: Try to get real acquirer for QRIS transactions from current transaction data
 | 
				
			||||||
 | 
					     * This method can be enhanced to query webhook API for real acquirer
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String getRealAcquirerForQris(String referenceId, String channelCode) {
 | 
				
			||||||
 | 
					        // If not QRIS, return channel code
 | 
				
			||||||
 | 
					        if (!"QRIS".equalsIgnoreCase(channelCode)) {
 | 
				
			||||||
 | 
					            return determineAcquirerFromChannelCode(channelCode);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 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");
 | 
				
			||||||
 | 
					        return "qris";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String extractRawAmount(String formattedAmount) {
 | 
					    private String extractRawAmount(String formattedAmount) {
 | 
				
			||||||
        if (formattedAmount == null || formattedAmount.isEmpty()) {
 | 
					        if (formattedAmount == null || formattedAmount.isEmpty()) {
 | 
				
			||||||
            return "0";
 | 
					            return "0";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        Log.d("TransactionActivity", "Extracting raw amount from: '" + formattedAmount + "'");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Remove currency symbols and spaces
 | 
					 | 
				
			||||||
        String cleaned = formattedAmount
 | 
					        String cleaned = formattedAmount
 | 
				
			||||||
                .replace("Rp. ", "")
 | 
					                .replace("Rp. ", "")
 | 
				
			||||||
                .replace("Rp ", "")
 | 
					                .replace("Rp ", "")
 | 
				
			||||||
@ -339,48 +641,35 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
                .replace(" ", "")
 | 
					                .replace(" ", "")
 | 
				
			||||||
                .trim();
 | 
					                .trim();
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Handle dots correctly
 | 
					 | 
				
			||||||
        if (cleaned.contains(".")) {
 | 
					        if (cleaned.contains(".")) {
 | 
				
			||||||
            String[] parts = cleaned.split("\\.");
 | 
					            String[] parts = cleaned.split("\\.");
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (parts.length == 2) {
 | 
					            if (parts.length == 2) {
 | 
				
			||||||
                String beforeDot = parts[0];
 | 
					                String beforeDot = parts[0];
 | 
				
			||||||
                String afterDot = parts[1];
 | 
					                String afterDot = parts[1];
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // If after dot is "00" or single "0", it's decimal format
 | 
					 | 
				
			||||||
                if (afterDot.equals("00") || afterDot.equals("0")) {
 | 
					                if (afterDot.equals("00") || afterDot.equals("0")) {
 | 
				
			||||||
                    cleaned = beforeDot;
 | 
					                    cleaned = beforeDot;
 | 
				
			||||||
                }
 | 
					                } else if (afterDot.length() == 3) {
 | 
				
			||||||
                // If after dot has 3 digits, it's thousand separator
 | 
					 | 
				
			||||||
                else if (afterDot.length() == 3) {
 | 
					 | 
				
			||||||
                    cleaned = beforeDot + afterDot;
 | 
					                    cleaned = beforeDot + afterDot;
 | 
				
			||||||
                }
 | 
					                } else {
 | 
				
			||||||
                // Handle other cases based on context
 | 
					 | 
				
			||||||
                else {
 | 
					 | 
				
			||||||
                    // If beforeDot is 1-3 digits and afterDot is 3 digits, likely thousand separator
 | 
					 | 
				
			||||||
                    if (beforeDot.length() <= 3 && afterDot.length() == 3) {
 | 
					                    if (beforeDot.length() <= 3 && afterDot.length() == 3) {
 | 
				
			||||||
                        cleaned = beforeDot + afterDot;
 | 
					                        cleaned = beforeDot + afterDot;
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        // Otherwise treat as decimal and remove decimal part
 | 
					 | 
				
			||||||
                        cleaned = beforeDot;
 | 
					                        cleaned = beforeDot;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else if (parts.length > 2) {
 | 
					            } else if (parts.length > 2) {
 | 
				
			||||||
                // Multiple dots - treat as thousand separators
 | 
					 | 
				
			||||||
                cleaned = String.join("", parts);
 | 
					                cleaned = String.join("", parts);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Remove any remaining commas
 | 
					 | 
				
			||||||
        cleaned = cleaned.replace(",", "");
 | 
					        cleaned = cleaned.replace(",", "");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Validate result is numeric
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            Long.parseLong(cleaned);
 | 
					            Long.parseLong(cleaned);
 | 
				
			||||||
            Log.d("TransactionActivity", "Successfully extracted: '" + formattedAmount + "' -> '" + cleaned + "'");
 | 
					 | 
				
			||||||
            return cleaned;
 | 
					            return cleaned;
 | 
				
			||||||
        } catch (NumberFormatException e) {
 | 
					        } catch (NumberFormatException e) {
 | 
				
			||||||
            Log.e("TransactionActivity", "Invalid amount after cleaning: '" + cleaned + "' from '" + formattedAmount + "'");
 | 
					            Log.e("TransactionActivity", "Invalid amount: " + formattedAmount);
 | 
				
			||||||
            return "0";
 | 
					            return "0";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -389,21 +678,15 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
        if (channelCode == null) return "Unknown";
 | 
					        if (channelCode == null) return "Unknown";
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        switch (channelCode.toUpperCase()) {
 | 
					        switch (channelCode.toUpperCase()) {
 | 
				
			||||||
            case "QRIS":
 | 
					            case "QRIS": return "QRIS";
 | 
				
			||||||
                return "QRIS";
 | 
					            case "DEBIT": return "Kartu Debit";
 | 
				
			||||||
            case "DEBIT":
 | 
					            case "CREDIT": return "Kartu Kredit";
 | 
				
			||||||
                return "Kartu Debit";
 | 
					 | 
				
			||||||
            case "CREDIT":
 | 
					 | 
				
			||||||
                return "Kartu Kredit";
 | 
					 | 
				
			||||||
            case "BCA":
 | 
					            case "BCA":
 | 
				
			||||||
            case "MANDIRI":
 | 
					            case "MANDIRI":
 | 
				
			||||||
            case "BNI":
 | 
					            case "BNI":
 | 
				
			||||||
            case "BRI":
 | 
					            case "BRI": return "Kartu " + channelCode.toUpperCase();
 | 
				
			||||||
                return "Kartu " + channelCode.toUpperCase();
 | 
					            case "CASH": return "Tunai";
 | 
				
			||||||
            case "CASH":
 | 
					            case "EDC": return "EDC";
 | 
				
			||||||
                return "Tunai";
 | 
					 | 
				
			||||||
            case "EDC":
 | 
					 | 
				
			||||||
                return "EDC";
 | 
					 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                if (channelCategory != null && !channelCategory.isEmpty()) {
 | 
					                if (channelCategory != null && !channelCategory.isEmpty()) {
 | 
				
			||||||
                    return channelCategory.toUpperCase();
 | 
					                    return channelCategory.toUpperCase();
 | 
				
			||||||
@ -419,11 +702,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
 | 
				
			|||||||
            Date date = inputFormat.parse(rawDate);
 | 
					            Date date = inputFormat.parse(rawDate);
 | 
				
			||||||
            return outputFormat.format(date);
 | 
					            return outputFormat.format(date);
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            return rawDate; // Fallback ke format asli jika parsing gagal
 | 
					            return rawDate;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean onOptionsItemSelected(MenuItem item) {
 | 
					    public boolean onOptionsItemSelected(MenuItem item) {
 | 
				
			||||||
        if (item.getItemId() == android.R.id.home) {
 | 
					        if (item.getItemId() == android.R.id.home) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user