From 729bdddad43442a8ea463b1cdc57dafe9be72428 Mon Sep 17 00:00:00 2001 From: riz081 Date: Fri, 13 Jun 2025 15:40:56 +0700 Subject: [PATCH] safepoint qris result activity --- .../example/bdkipoc/QrisResultActivity.java | 1456 +++++------------ 1 file changed, 427 insertions(+), 1029 deletions(-) diff --git a/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java b/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java index c3610c0..0eb3db2 100644 --- a/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java +++ b/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java @@ -9,94 +9,55 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - +import android.widget.*; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - import org.json.JSONArray; -import org.json.JSONException; import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; +import java.io.*; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.text.NumberFormat; -import java.util.Locale; -import java.util.List; import java.util.ArrayList; +import java.util.List; +import java.util.Locale; public class QrisResultActivity extends AppCompatActivity { + // UI Components private ImageView qrImageView; - private TextView amountTextView; - private TextView referenceTextView; - private Button downloadQrisButton; - private Button checkStatusButton; - private TextView statusTextView; - private Button returnMainButton; + private TextView amountTextView, referenceTextView, statusTextView; + private TextView timerTextView, qrStatusTextView; + private Button downloadQrisButton, checkStatusButton, returnMainButton; private ProgressBar progressBar; // QR Refresh Components - private TextView timerTextView; - private TextView qrStatusTextView; private Handler qrRefreshHandler; private Runnable qrRefreshRunnable; private int countdownSeconds = 60; private boolean isQrRefreshActive = true; - private String orderId; - private String grossAmount; - private String referenceId; - private String transactionId; - private String transactionTime; - private String acquirer; - private String merchantId; - private String currentQrImageUrl; + // Transaction Data + private String orderId, grossAmount, referenceId, transactionId; + private String transactionTime, acquirer, merchantId, currentQrImageUrl; private int originalAmount; + private List allOrderIds = new ArrayList<>(); - private String backendBase = "https://be-edc.msvc.app"; - private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans"; - - // Server key for signature generation + // Configuration + private static final String BACKEND_BASE = "https://be-edc.msvc.app"; + private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans"; private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge"; - private List allOrderIds = new ArrayList<>(); - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_qris_result); - // Initialize views initializeViews(); - - // Get intent data - getIntentData(); - - // Initialize order ID tracking - allOrderIds.add(orderId); // Tambahkan order ID pertama - Log.d("QrisResultFlow", "🆔 Initial order ID added: " + orderId); - - // Setup UI - setupUI(); - - // Start QR refresh timer - startQrRefreshTimer(); - - // Start polling for pending payment log - pollPendingPaymentLog(orderId); - - // Set up click listeners + extractIntentData(); + validateAndSetupUI(); + startMonitoring(); setupClickListeners(); } @@ -111,12 +72,10 @@ public class QrisResultActivity extends AppCompatActivity { progressBar = findViewById(R.id.progressBar); timerTextView = findViewById(R.id.timerTextView); qrStatusTextView = findViewById(R.id.qrStatusTextView); - - // Initialize handler for QR refresh qrRefreshHandler = new Handler(Looper.getMainLooper()); } - private void getIntentData() { + private void extractIntentData() { Intent intent = getIntent(); currentQrImageUrl = intent.getStringExtra("qrImageUrl"); originalAmount = intent.getIntExtra("amount", 0); @@ -128,44 +87,32 @@ public class QrisResultActivity extends AppCompatActivity { acquirer = intent.getStringExtra("acquirer"); merchantId = intent.getStringExtra("merchantId"); - // Enhanced logging for debugging - Log.d("QrisResultFlow", "=== QRIS RESULT ACTIVITY STARTED ==="); - Log.d("QrisResultFlow", "QR Image URL: " + currentQrImageUrl); - Log.d("QrisResultFlow", "Amount (int): " + originalAmount); - Log.d("QrisResultFlow", "Gross Amount (string): " + grossAmount); - Log.d("QrisResultFlow", "Reference ID: " + referenceId); - Log.d("QrisResultFlow", "Order ID: " + orderId); - Log.d("QrisResultFlow", "Transaction ID: " + transactionId); - Log.d("QrisResultFlow", "======================================"); + allOrderIds.add(orderId); + Log.d("QrisResult", "Initialized with Order ID: " + orderId); } - private void setupUI() { - // Validate required data + private void validateAndSetupUI() { if (orderId == null || transactionId == null) { - Log.e("QrisResultFlow", "Critical error: orderId or transactionId is null!"); - Toast.makeText(this, "Missing transaction details! Cannot proceed.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, "Missing transaction details!", Toast.LENGTH_LONG).show(); finish(); return; } - // Display formatted amount String formattedAmount = formatCurrency(grossAmount != null ? grossAmount : String.valueOf(originalAmount)); amountTextView.setText(formattedAmount); referenceTextView.setText("Reference ID: " + referenceId); - - // Load initial QR image + loadQrImage(currentQrImageUrl); - - // Initialize UI state + checkStatusButton.setEnabled(false); statusTextView.setText("Waiting for payment..."); - - // Setup QR refresh status text qrStatusTextView.setText("QR Code akan refresh dalam"); - qrStatusTextView.setVisibility(View.VISIBLE); - timerTextView.setVisibility(View.VISIBLE); + } - startContinuousPaymentMonitoring(); + private void startMonitoring() { + startQrRefreshTimer(); + startPaymentMonitoring(); + pollPendingPaymentLog(orderId); } private void startQrRefreshTimer() { @@ -175,268 +122,128 @@ public class QrisResultActivity extends AppCompatActivity { qrRefreshRunnable = new Runnable() { @Override public void run() { - if (!isQrRefreshActive) { - return; - } + if (!isQrRefreshActive) return; if (countdownSeconds > 0) { - // Update countdown display timerTextView.setText(String.valueOf(countdownSeconds)); countdownSeconds--; - - // Schedule next update in 1 second qrRefreshHandler.postDelayed(this, 1000); } else { - // Time to refresh QR code - Log.d("QrisResultFlow", "🔄 QR Code refresh time reached - generating new QR"); refreshQrCode(); } } }; - qrRefreshHandler.post(qrRefreshRunnable); - Log.d("QrisResultFlow", "🕒 QR refresh timer started - 60 seconds countdown"); } private void refreshQrCode() { - if (!isQrRefreshActive) { - return; - } + if (!isQrRefreshActive) return; - Log.d("QrisResultFlow", "🔄 Starting QR code refresh..."); - - // Show loading state timerTextView.setText("..."); qrStatusTextView.setText("Generating new QR Code..."); - // Generate new QR code in background new Thread(() -> { try { String newQrUrl = generateNewQrCode(); - runOnUiThread(() -> { - if (newQrUrl != null && !newQrUrl.isEmpty()) { - // Successfully generated new QR + if (newQrUrl != null) { currentQrImageUrl = newQrUrl; loadQrImage(newQrUrl); - - // ✅ TAMBAHKAN: Track new order ID - allOrderIds.add(orderId); // orderId sudah diupdate di generateNewQrCode() - Log.d("QrisResultFlow", "🆔 New order ID added: " + orderId + " (Total: " + allOrderIds.size() + ")"); - - // Update UI with new data + allOrderIds.add(orderId); updateUIAfterRefresh(); - - // Restart timer countdownSeconds = 60; qrStatusTextView.setText("QR Code akan refresh dalam"); - Log.d("QrisResultFlow", "✅ QR code refreshed successfully"); - Toast.makeText(QrisResultActivity.this, "QR Code refreshed", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, "QR Code refreshed", Toast.LENGTH_SHORT).show(); } else { - // Failed to generate new QR - Log.e("QrisResultFlow", "❌ Failed to refresh QR code"); qrStatusTextView.setText("Failed to refresh QR - trying again in 30s"); - countdownSeconds = 30; // Retry in 30 seconds - Toast.makeText(QrisResultActivity.this, "QR refresh failed, retrying...", Toast.LENGTH_SHORT).show(); + countdownSeconds = 30; } - - // Continue timer qrRefreshHandler.postDelayed(qrRefreshRunnable, 1000); }); - } catch (Exception e) { - Log.e("QrisResultFlow", "❌ QR refresh error: " + e.getMessage(), e); - + Log.e("QrisResult", "QR refresh error: " + e.getMessage(), e); runOnUiThread(() -> { qrStatusTextView.setText("QR refresh error - retrying in 30s"); countdownSeconds = 30; qrRefreshHandler.postDelayed(qrRefreshRunnable, 1000); - Toast.makeText(QrisResultActivity.this, "QR refresh error", Toast.LENGTH_SHORT).show(); }); } }).start(); } - private void pollMultipleOrderIds() { - Log.d("QrisResultFlow", "🔍 Starting polling for multiple order IDs"); - - Handler multiPollHandler = new Handler(Looper.getMainLooper()); - - Runnable multiPollRunnable = new Runnable() { - @Override - public void run() { - // Check semua order IDs yang pernah dibuat - checkAllOrderIdsStatus(); - - // Schedule next check jika activity masih aktif - if (!isFinishing() && isQrRefreshActive) { - multiPollHandler.postDelayed(this, 3000); // Check every 3 seconds - } - } - }; - - multiPollHandler.post(multiPollRunnable); - } - - // ✅ METHOD BARU untuk check status dari semua order IDs - private void checkAllOrderIdsStatus() { - new Thread(() -> { - try { - Log.d("QrisResultFlow", "🔍 Checking status for " + allOrderIds.size() + " order IDs"); - - for (String checkOrderId : allOrderIds) { - if (checkOrderId == null || checkOrderId.isEmpty()) continue; - - // Check di webhook logs - String urlStr = backendBase + "/api-logs?request_body_search_strict=" + - java.net.URLEncoder.encode("{\"order_id\":\"" + checkOrderId + "\"}", "UTF-8"); - - URL url = new URL(urlStr); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - int responseCode = conn.getResponseCode(); - - if (responseCode == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - - JSONObject json = new JSONObject(response.toString()); - JSONArray results = json.optJSONArray("results"); - - if (results != null && results.length() > 0) { - for (int i = 0; i < results.length(); i++) { - JSONObject log = results.getJSONObject(i); - JSONObject reqBody = log.optJSONObject("request_body"); - - if (reqBody != null) { - String transactionStatus = reqBody.optString("transaction_status"); - String logOrderId = reqBody.optString("order_id"); - - Log.d("QrisResultFlow", "📊 Order ID: " + logOrderId + " -> Status: " + transactionStatus); - - // Check untuk settlement/success status - if (checkOrderId.equals(logOrderId) && - (transactionStatus.equals("settlement") || - transactionStatus.equals("capture") || - transactionStatus.equals("success"))) { - - Log.d("QrisResultFlow", "🎉 PAYMENT FOUND! Order ID: " + checkOrderId + " Status: " + transactionStatus); - - // Update transaction ID untuk sync - String foundTransactionId = reqBody.optString("transaction_id", transactionId); - if (!foundTransactionId.isEmpty()) { - transactionId = foundTransactionId; - } - - // Stop polling dan tampilkan success - runOnUiThread(() -> { - stopQrRefresh(); - syncTransactionStatusToBackend("PAID"); - showPaymentSuccess(); - Toast.makeText(QrisResultActivity.this, - "Payment Successful! 🎉 (QR #" + (allOrderIds.indexOf(checkOrderId) + 1) + ")", - Toast.LENGTH_LONG).show(); - }); - - return; // Exit method - } - } - } - } - } - } - - } catch (Exception e) { - Log.e("QrisResultFlow", "❌ Multi-order polling error: " + e.getMessage(), e); - } - }).start(); - } - - private void updateUIAfterRefresh() { - // You can update reference text to show it's refreshed - String refreshTime = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date()); - referenceTextView.setText("Reference ID: " + referenceId + " (Refreshed at " + refreshTime + ")"); - - // Reset polling for new order ID if needed - // Note: You might want to restart polling for the new orderId - Log.d("QrisResultFlow", "🔄 UI updated after QR refresh"); - } - private String generateNewQrCode() { try { - Log.d("QrisResultFlow", "🔧 Generating new QR code with fresh UUID"); - - // FIXED: Generate NEW UUID for refresh to avoid duplicate order_id String newOrderId = java.util.UUID.randomUUID().toString(); - Log.d("QrisResultFlow", "🆕 New Order ID for refresh: " + newOrderId); - Log.d("QrisResultFlow", "🔄 Original Order ID: " + orderId); - // ✅ FIXED: Create custom_field FIRST, before logging - JSONObject customField1 = new JSONObject(); - customField1.put("refresh_of", orderId); // Track original order - customField1.put("refresh_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date())); - customField1.put("original_reference", referenceId); // Key untuk tracking! - customField1.put("refresh_count", System.currentTimeMillis()); // Unique identifier - customField1.put("parent_transaction_id", transactionId); // Link ke parent transaction + JSONObject customField = new JSONObject(); + customField.put("refresh_of", orderId); + customField.put("refresh_time", getCurrentISOTime()); + customField.put("original_reference", referenceId); - // ✅ NOW log AFTER customField1 is defined - Log.d("QrisResultFlow", "🔄 Enhanced refresh tracking:"); - Log.d("QrisResultFlow", " Original Order: " + orderId); - Log.d("QrisResultFlow", " New Order: " + newOrderId); - Log.d("QrisResultFlow", " Reference ID: " + referenceId); - Log.d("QrisResultFlow", " Custom Field: " + customField1.toString()); + JSONObject payload = createQrisPayload(newOrderId, customField); + String response = sendMidtransRequest(payload); - // Create QRIS charge JSON payload with NEW order_id - JSONObject payload = new JSONObject(); - payload.put("payment_type", "qris"); - - JSONObject transactionDetails = new JSONObject(); - transactionDetails.put("order_id", newOrderId); // Use NEW UUID - transactionDetails.put("gross_amount", originalAmount); - payload.put("transaction_details", transactionDetails); - - // Add customer details - JSONObject customerDetails = new JSONObject(); - customerDetails.put("first_name", "Test"); - customerDetails.put("last_name", "Customer"); - customerDetails.put("email", "test@example.com"); - customerDetails.put("phone", "081234567890"); - payload.put("customer_details", customerDetails); - - // Add item details - JSONArray itemDetails = new JSONArray(); - JSONObject item = new JSONObject(); - item.put("id", "item1_refresh_" + System.currentTimeMillis()); - item.put("price", originalAmount); - item.put("quantity", 1); - item.put("name", "QRIS Payment - Refreshed " + new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date()) + - " (Ref: " + referenceId + ")"); // Include reference in item name - itemDetails.put(item); - payload.put("item_details", itemDetails); - - // ✅ Add custom_field for tracking refresh (AFTER it's created) - payload.put("custom_field1", customField1.toString()); - - Log.d("QrisResultFlow", "📤 Refresh payload: " + payload.toString()); - - // Make API call to Midtrans + if (response != null) { + JSONObject jsonResponse = new JSONObject(response); + if (jsonResponse.has("actions")) { + JSONArray actions = jsonResponse.getJSONArray("actions"); + if (actions.length() > 0) { + String newQrUrl = actions.getJSONObject(0).getString("url"); + + // Update transaction info + this.transactionId = jsonResponse.optString("transaction_id", transactionId); + this.transactionTime = jsonResponse.optString("transaction_time", transactionTime); + this.orderId = newOrderId; + + return newQrUrl; + } + } + } + } catch (Exception e) { + Log.e("QrisResult", "Generate QR error: " + e.getMessage(), e); + } + return null; + } + + private JSONObject createQrisPayload(String orderIdParam, JSONObject customField) throws Exception { + JSONObject payload = new JSONObject(); + payload.put("payment_type", "qris"); + + JSONObject transactionDetails = new JSONObject(); + transactionDetails.put("order_id", orderIdParam); + transactionDetails.put("gross_amount", originalAmount); + payload.put("transaction_details", transactionDetails); + + JSONObject customerDetails = new JSONObject(); + customerDetails.put("first_name", "Test"); + customerDetails.put("last_name", "Customer"); + customerDetails.put("email", "test@example.com"); + customerDetails.put("phone", "081234567890"); + payload.put("customer_details", customerDetails); + + JSONArray itemDetails = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("id", "item1_refresh_" + System.currentTimeMillis()); + item.put("price", originalAmount); + item.put("quantity", 1); + item.put("name", "QRIS Payment - Refreshed (Ref: " + referenceId + ")"); + itemDetails.put(item); + payload.put("item_details", itemDetails); + + payload.put("custom_field1", customField.toString()); + + return payload; + } + + private String sendMidtransRequest(JSONObject payload) { + try { URL url = new URI(MIDTRANS_CHARGE_URL).toURL(); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Accept", "application/json"); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Authorization", MIDTRANS_AUTH); - conn.setRequestProperty("X-Override-Notification", webhookUrl); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0 QR-Refresh"); + conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL); conn.setDoOutput(true); conn.setConnectTimeout(30000); conn.setReadTimeout(30000); @@ -447,284 +254,169 @@ public class QrisResultActivity extends AppCompatActivity { } int responseCode = conn.getResponseCode(); - Log.d("QrisResultFlow", "📥 QR refresh response code: " + responseCode); - if (responseCode == 200 || responseCode == 201) { BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); StringBuilder response = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - response.append(responseLine.trim()); + String line; + while ((line = br.readLine()) != null) { + response.append(line.trim()); } - - Log.d("QrisResultFlow", "📥 QR refresh response preview: " + - (response.length() > 200 ? response.substring(0, 200) + "..." : response.toString())); - - JSONObject jsonResponse = new JSONObject(response.toString()); - - // Check for errors in response - if (jsonResponse.has("status_code")) { - String statusCode = jsonResponse.getString("status_code"); - if (!statusCode.equals("201")) { - String statusMessage = jsonResponse.optString("status_message", "Unknown error"); - Log.e("QrisResultFlow", "❌ QR refresh failed with status: " + statusCode + " - " + statusMessage); - - // Log full response for debugging - Log.e("QrisResultFlow", "❌ Full error response: " + response.toString()); - return null; - } - } - - // Extract new QR URL and update transaction info - if (jsonResponse.has("actions")) { - JSONArray actionsArray = jsonResponse.getJSONArray("actions"); - if (actionsArray.length() > 0) { - JSONObject actions = actionsArray.getJSONObject(0); - String newQrUrl = actions.getString("url"); - - // Update transaction info with new data - String newTransactionId = jsonResponse.optString("transaction_id", transactionId); - String newTransactionTime = jsonResponse.optString("transaction_time", transactionTime); - - // Update class variables for webhook simulation - this.transactionId = newTransactionId; - this.transactionTime = newTransactionTime; - this.orderId = newOrderId; // Update to new order ID - - Log.d("QrisResultFlow", "✅ QR refresh successful!"); - Log.d("QrisResultFlow", "🆕 New QR URL: " + newQrUrl); - Log.d("QrisResultFlow", "🆕 New Transaction ID: " + newTransactionId); - Log.d("QrisResultFlow", "🆕 New Order ID: " + newOrderId); - - return newQrUrl; - } - } - - Log.e("QrisResultFlow", "❌ No actions found in refresh response"); - return null; - - } else { - // Handle error response - InputStream errorStream = conn.getErrorStream(); - String errorResponse = ""; - - if (errorStream != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(errorStream, "utf-8")); - StringBuilder errorBuilder = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - errorBuilder.append(responseLine.trim()); - } - errorResponse = errorBuilder.toString(); - } - - Log.e("QrisResultFlow", "❌ QR refresh HTTP error " + responseCode + ": " + errorResponse); - - // Try to parse error for better logging - try { - if (!errorResponse.isEmpty()) { - JSONObject errorJson = new JSONObject(errorResponse); - String errorMessage = errorJson.optString("error_messages", - errorJson.optString("status_message", - errorJson.optString("message", "Unknown error"))); - Log.e("QrisResultFlow", "❌ Parsed error message: " + errorMessage); - } - } catch (JSONException e) { - Log.e("QrisResultFlow", "❌ Could not parse error response"); - } - - return null; + return response.toString(); } - } catch (Exception e) { - Log.e("QrisResultFlow", "❌ QR refresh exception: " + e.getMessage(), e); - return null; + Log.e("QrisResult", "Midtrans request error: " + e.getMessage(), e); } + return null; + } + + private void startPaymentMonitoring() { + Handler paymentHandler = new Handler(Looper.getMainLooper()); + Runnable paymentRunnable = new Runnable() { + @Override + public void run() { + checkAllOrderIdsStatus(); + if (!isFinishing() && isQrRefreshActive) { + paymentHandler.postDelayed(this, 3000); + } + } + }; + paymentHandler.post(paymentRunnable); + } + + private void checkAllOrderIdsStatus() { + new Thread(() -> { + try { + for (String checkOrderId : allOrderIds) { + if (checkOrderId == null || checkOrderId.isEmpty()) continue; + + if (checkPaymentStatus(checkOrderId)) { + runOnUiThread(() -> { + stopQrRefresh(); + syncTransactionStatusToBackend("PAID"); + showPaymentSuccess(); + Toast.makeText(this, "Payment Successful! 🎉", Toast.LENGTH_LONG).show(); + }); + return; + } + } + } catch (Exception e) { + Log.e("QrisResult", "Payment status check error: " + e.getMessage(), e); + } + }).start(); + } + + private boolean checkPaymentStatus(String checkOrderId) { + try { + String urlStr = BACKEND_BASE + "/api-logs?request_body_search_strict=" + + java.net.URLEncoder.encode("{\"order_id\":\"" + checkOrderId + "\"}", "UTF-8"); + + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + 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); + } + + JSONObject json = new JSONObject(response.toString()); + JSONArray results = json.optJSONArray("results"); + + if (results != null && results.length() > 0) { + for (int i = 0; i < results.length(); i++) { + JSONObject log = results.getJSONObject(i); + JSONObject reqBody = log.optJSONObject("request_body"); + + if (reqBody != null) { + String transactionStatus = reqBody.optString("transaction_status"); + String logOrderId = reqBody.optString("order_id"); + + if (checkOrderId.equals(logOrderId) && + (transactionStatus.equals("settlement") || + transactionStatus.equals("capture") || + transactionStatus.equals("success"))) { + return true; + } + } + } + } + } + } catch (Exception e) { + Log.e("QrisResult", "Payment check error: " + e.getMessage(), e); + } + return false; } private void loadQrImage(String qrImageUrl) { if (qrImageUrl != null && !qrImageUrl.isEmpty()) { - Log.d("QrisResultFlow", "🖼️ Loading QR image from: " + qrImageUrl); new DownloadImageTask(qrImageView).execute(qrImageUrl); } else { - Log.w("QrisResultFlow", "⚠️ QR image URL is not available"); qrImageView.setVisibility(View.GONE); downloadQrisButton.setEnabled(false); } } + private void setupClickListeners() { + downloadQrisButton.setOnClickListener(v -> downloadQrCode()); + checkStatusButton.setOnClickListener(v -> { + stopQrRefresh(); + simulateWebhook(); + }); + returnMainButton.setOnClickListener(v -> returnToMain()); + } + private void stopQrRefresh() { isQrRefreshActive = false; if (qrRefreshHandler != null && qrRefreshRunnable != null) { qrRefreshHandler.removeCallbacks(qrRefreshRunnable); } - - // Hide timer elements timerTextView.setVisibility(View.GONE); qrStatusTextView.setVisibility(View.GONE); + } + + private void showPaymentSuccess() { + stopQrRefresh(); - Log.d("QrisResultFlow", "🛑 QR refresh timer stopped"); - } - - private void setupClickListeners() { - // Download QRIS button - downloadQrisButton.setOnClickListener(v -> downloadQrCode()); - - // Check Payment Status button - checkStatusButton.setOnClickListener(v -> { - Log.d("QrisResultFlow", "Check status button clicked"); - stopQrRefresh(); // Stop QR refresh when payment is being processed - simulateWebhook(); - }); - - // Return to Main Screen button - returnMainButton.setOnClickListener(v -> { - stopQrRefresh(); // Stop timer when leaving - Intent intent = new Intent(QrisResultActivity.this, com.example.bdkipoc.MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finishAffinity(); - }); - } - - private String formatCurrency(String amount) { - try { - double amountDouble = Double.parseDouble(amount); - NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID")); - return formatter.format(amountDouble); - } catch (NumberFormatException e) { - Log.w("QrisResultFlow", "Error formatting currency: " + e.getMessage()); - return "IDR " + amount; - } - } - - private void downloadQrCode() { - try { - qrImageView.setDrawingCacheEnabled(true); - qrImageView.buildDrawingCache(); - Bitmap bitmap = qrImageView.getDrawingCache(); - if (bitmap != null) { - saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis()); - } else { - Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show(); - } - } catch (Exception e) { - Log.e("QrisResultFlow", "Error downloading QR code: " + e.getMessage(), e); - Toast.makeText(this, "Error downloading QR code: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } finally { - qrImageView.setDrawingCacheEnabled(false); - } - } - - private static class DownloadImageTask extends AsyncTask { - private ImageView bmImage; - private String errorMessage; + qrImageView.setVisibility(View.GONE); + amountTextView.setVisibility(View.GONE); + referenceTextView.setVisibility(View.GONE); + downloadQrisButton.setVisibility(View.GONE); + checkStatusButton.setVisibility(View.GONE); - DownloadImageTask(ImageView bmImage) { - this.bmImage = bmImage; - } + statusTextView.setText("✅ Payment Successful!\n\nTransaction ID: " + transactionId + + "\nReference: " + referenceId + + "\nAmount: " + formatCurrency(grossAmount)); - @Override - protected Bitmap doInBackground(String... urls) { - String urlDisplay = urls[0]; - Bitmap bitmap = null; - try { - Log.d("QrisResultFlow", "Downloading image from: " + urlDisplay); - URL url = new URI(urlDisplay).toURL(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setDoInput(true); - connection.setConnectTimeout(10000); - connection.setReadTimeout(10000); - connection.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); - connection.connect(); - - int responseCode = connection.getResponseCode(); - Log.d("QrisResultFlow", "Image download response code: " + responseCode); - - if (responseCode == 200) { - InputStream input = connection.getInputStream(); - bitmap = BitmapFactory.decodeStream(input); - if (bitmap != null) { - Log.d("QrisResultFlow", "Image downloaded successfully. Size: " + - bitmap.getWidth() + "x" + bitmap.getHeight()); - } else { - Log.e("QrisResultFlow", "Failed to decode bitmap from stream"); - errorMessage = "Failed to decode QR code image"; - } - } else { - Log.e("QrisResultFlow", "Failed to download image. HTTP code: " + responseCode); - errorMessage = "Failed to download QR code (HTTP " + responseCode + ")"; - } - } catch (Exception e) { - Log.e("QrisResultFlow", "Exception downloading image: " + e.getMessage(), e); - errorMessage = "Error downloading QR code: " + e.getMessage(); - } - return bitmap; - } + returnMainButton.setVisibility(View.VISIBLE); - @Override - protected void onPostExecute(Bitmap result) { - if (result != null) { - bmImage.setImageBitmap(result); - Log.d("QrisResultFlow", "QR code image displayed successfully"); - } else { - Log.e("QrisResultFlow", "Failed to display QR code image"); - bmImage.setImageResource(android.R.drawable.ic_menu_report_image); - if (errorMessage != null && bmImage.getContext() != null) { - Toast.makeText(bmImage.getContext(), errorMessage, Toast.LENGTH_LONG).show(); - } - } - } + new Handler(Looper.getMainLooper()).postDelayed(this::launchReceiptActivity, 2000); } - // Save bitmap to gallery - private void saveImageToGallery(Bitmap bitmap, String fileName) { - try { - String savedImageURL = android.provider.MediaStore.Images.Media.insertImage( - getContentResolver(), bitmap, fileName, "QRIS Payment QR Code"); - if (savedImageURL != null) { - Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show(); - Log.d("QrisResultFlow", "QR code saved to gallery: " + savedImageURL); - } else { - Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show(); - Log.e("QrisResultFlow", "Failed to save QR code to gallery"); - } - } catch (Exception e) { - Log.e("QrisResultFlow", "Error saving QR code: " + e.getMessage(), e); - Toast.makeText(this, "Error saving QRIS: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - } - - private void syncTransactionStatusToBackend(String finalStatus) { - Log.d("QrisResultFlow", "🔄 Syncing status '" + finalStatus + "' to backend for reference: " + referenceId); - + private void syncTransactionStatusToBackend(String status) { new Thread(() -> { try { - // Update status di backend berdasarkan reference_id (bukan order_id yang berubah) JSONObject updatePayload = new JSONObject(); - updatePayload.put("status", finalStatus); - updatePayload.put("transaction_status", finalStatus); + updatePayload.put("status", status); + updatePayload.put("transaction_status", status); updatePayload.put("updated_at", getCurrentISOTime()); updatePayload.put("settlement_time", getCurrentISOTime()); - // FIXED: Update berdasarkan reference_id yang tetap, bukan order_id yang berubah - String updateUrl = backendBase + "/transactions/update-by-reference"; + JSONObject body = new JSONObject(); + body.put("reference_id", referenceId); + body.put("update_data", updatePayload); + + String updateUrl = BACKEND_BASE + "/transactions/update-by-reference"; URL url = new URI(updateUrl).toURL(); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); conn.setDoOutput(true); - conn.setConnectTimeout(15000); - conn.setReadTimeout(15000); - - // Body dengan reference_id sebagai identifier - JSONObject body = new JSONObject(); - body.put("reference_id", referenceId); // FIXED: Gunakan reference_id yang tidak berubah - body.put("update_data", updatePayload); - - Log.d("QrisResultFlow", "📤 Backend update payload: " + body.toString()); try (OutputStream os = conn.getOutputStream()) { byte[] input = body.toString().getBytes("utf-8"); @@ -732,305 +424,96 @@ public class QrisResultActivity extends AppCompatActivity { } int responseCode = conn.getResponseCode(); - Log.d("QrisResultFlow", "📥 Backend update response: " + responseCode); - - if (responseCode == 200 || responseCode == 201) { - // Read response - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - - Log.d("QrisResultFlow", "✅ Backend status updated successfully: " + response.toString()); - } else { - // Read error response - InputStream errorStream = conn.getErrorStream(); - if (errorStream != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(errorStream)); - StringBuilder errorResponse = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - errorResponse.append(line); - } - Log.e("QrisResultFlow", "❌ Backend update failed: " + responseCode + " - " + errorResponse.toString()); - } - } + Log.d("QrisResult", "Backend sync response: " + responseCode); } catch (Exception e) { - Log.e("QrisResultFlow", "❌ Backend sync error: " + e.getMessage(), e); + Log.e("QrisResult", "Backend sync error: " + e.getMessage(), e); } }).start(); } + private void simulateWebhook() { + progressBar.setVisibility(View.VISIBLE); + statusTextView.setText("Simulating payment..."); + checkStatusButton.setEnabled(false); + stopQrRefresh(); + + new Thread(() -> { + try { + JSONObject payload = createWebhookPayload(); + sendWebhookRequest(payload); + Thread.sleep(2000); + } catch (Exception e) { + Log.e("QrisResult", "Webhook simulation error: " + e.getMessage(), e); + } + + runOnUiThread(() -> { + progressBar.setVisibility(View.GONE); + showPaymentSuccess(); + }); + }).start(); + } + + private JSONObject createWebhookPayload() throws Exception { + String serverKey = getServerKey(); + String signatureKey = generateSignature(orderId, "200", grossAmount, serverKey); + + JSONObject payload = new JSONObject(); + payload.put("transaction_type", "on-us"); + payload.put("transaction_time", transactionTime != null ? transactionTime : getCurrentISOTime()); + payload.put("transaction_status", "settlement"); + payload.put("transaction_id", transactionId); + payload.put("status_message", "midtrans payment notification"); + payload.put("status_code", "200"); + payload.put("signature_key", signatureKey); + payload.put("payment_type", "qris"); + payload.put("order_id", orderId); + payload.put("gross_amount", grossAmount); + payload.put("reference_id", referenceId); + + return payload; + } + + private void sendWebhookRequest(JSONObject payload) { + try { + URL url = new URL(WEBHOOK_URL); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + + try (OutputStream os = conn.getOutputStream()) { + os.write(payload.toString().getBytes()); + } + + Log.d("QrisResult", "Webhook response: " + conn.getResponseCode()); + } catch (Exception e) { + Log.e("QrisResult", "Webhook request error: " + e.getMessage(), e); + } + } + + // Utility Methods + private String formatCurrency(String amount) { + try { + double amountDouble = Double.parseDouble(amount); + NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID")); + return formatter.format(amountDouble); + } catch (NumberFormatException e) { + return "IDR " + amount; + } + } + private String getCurrentISOTime() { return new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") .format(new java.util.Date()); } - private void syncStatusToBackend(String referenceId, String finalStatus, String paidOrderId) { - new Thread(() -> { - try { - Log.d("QrisResultFlow", "🔄 Syncing status to backend - Ref: " + referenceId + " Status: " + finalStatus); - - // Create update payload - JSONObject updatePayload = new JSONObject(); - updatePayload.put("status", finalStatus); - updatePayload.put("payment_status", finalStatus); - updatePayload.put("paid_order_id", paidOrderId); // Order ID yang actual dibayar - updatePayload.put("updated_at", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date())); - updatePayload.put("payment_completed_at", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date())); - - // API call to backend untuk update transaction berdasarkan reference_id - String updateUrl = backendBase + "/transactions/update-by-reference"; - URL url = new URI(updateUrl).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); - conn.setDoOutput(true); - conn.setConnectTimeout(15000); - conn.setReadTimeout(15000); - - // Request body dengan reference_id sebagai filter - JSONObject requestBody = new JSONObject(); - requestBody.put("reference_id", referenceId); - requestBody.put("update_data", updatePayload); - - try (OutputStream os = conn.getOutputStream()) { - byte[] input = requestBody.toString().getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - Log.d("QrisResultFlow", "📥 Backend sync response: " + responseCode); - - if (responseCode == 200 || responseCode == 201) { - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - Log.d("QrisResultFlow", "✅ Backend sync successful: " + response.toString()); - } else { - // Log error response - InputStream errorStream = conn.getErrorStream(); - if (errorStream != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(errorStream)); - StringBuilder errorResponse = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - errorResponse.append(line); - } - Log.e("QrisResultFlow", "❌ Backend sync failed: " + responseCode + " - " + errorResponse.toString()); - } - } - - } catch (Exception e) { - Log.e("QrisResultFlow", "❌ Backend sync error: " + e.getMessage(), e); - } - }).start(); - } - - private void showPaymentSuccess() { - Log.d("QrisResultFlow", "Showing payment success screen"); - - // Stop QR refresh when payment is successful - stopQrRefresh(); - - // ✅ SYNC STATUS KE BACKEND - syncStatusToBackend(referenceId, "PAID", orderId); - - // Hide payment elements - qrImageView.setVisibility(View.GONE); - amountTextView.setVisibility(View.GONE); - referenceTextView.setVisibility(View.GONE); - downloadQrisButton.setVisibility(View.GONE); - checkStatusButton.setVisibility(View.GONE); - - // Show success elements - statusTextView.setVisibility(View.VISIBLE); - statusTextView.setText("✅ Payment Successful!\n\nTransaction ID: " + transactionId + - "\nReference: " + referenceId + - "\nAmount: " + formatCurrency(grossAmount)); - - 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(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - stopQrRefresh(); // Cleanup timer when activity is destroyed - } - - @Override - protected void onPause() { - super.onPause(); - // Keep timer running in background, but could be paused if needed - } - - @Override - protected void onResume() { - super.onResume(); - // Resume timer if it was paused - } - - @Override - public void onBackPressed() { - stopQrRefresh(); // Stop timer when user goes back - Intent intent = new Intent(this, com.example.bdkipoc.MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finishAffinity(); - super.onBackPressed(); - } - - private void pollPendingPaymentLog(final String orderId) { - Log.d("QrisResultFlow", "Starting polling for orderId: " + orderId); - progressBar.setVisibility(View.VISIBLE); - statusTextView.setText("Checking payment status..."); - - new Thread(() -> { - int maxAttempts = 12; - int intervalMs = 2000; - int attempt = 0; - boolean found = false; - - while (attempt < maxAttempts && !found) { - try { - // FIXED: Poll for CURRENT orderId (might be refreshed) - String currentOrderId = this.orderId; // Use current order ID - String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + currentOrderId + "\"}"; - Log.d("QrisResultFlow", "Polling attempt " + (attempt + 1) + "/" + maxAttempts + " for: " + currentOrderId); - - URL url = new URL(urlStr); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - int responseCode = conn.getResponseCode(); - - if (responseCode == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - - JSONObject json = new JSONObject(response.toString()); - JSONArray results = json.optJSONArray("results"); - - if (results != null && results.length() > 0) { - Log.d("QrisResultFlow", "Found " + results.length() + " log entries"); - - for (int i = 0; i < results.length(); i++) { - JSONObject log = results.getJSONObject(i); - JSONObject reqBody = log.optJSONObject("request_body"); - - if (reqBody != null) { - String transactionStatus = reqBody.optString("transaction_status"); - String logOrderId = reqBody.optString("order_id"); - - Log.d("QrisResultFlow", "Log entry " + i + ": order_id=" + logOrderId + - ", transaction_status=" + transactionStatus); - - // FIXED: Check for any valid payment status, not just pending - if (currentOrderId.equals(logOrderId) && - (transactionStatus.equals("pending") || - transactionStatus.equals("settlement") || - transactionStatus.equals("capture") || - transactionStatus.equals("success"))) { - found = true; - - // FIXED: If payment is already settled, show success immediately - if (transactionStatus.equals("settlement") || - transactionStatus.equals("capture") || - transactionStatus.equals("success")) { - - Log.d("QrisResultFlow", "🎉 Payment already completed with status: " + transactionStatus); - - // Stop polling and show success - new Handler(Looper.getMainLooper()).post(() -> { - progressBar.setVisibility(View.GONE); - stopQrRefresh(); // Stop QR refresh - showPaymentSuccess(); - Toast.makeText(QrisResultActivity.this, "Payment completed!", Toast.LENGTH_LONG).show(); - }); - return; // Exit polling thread - } - - Log.d("QrisResultFlow", "Found matching payment log with status: " + transactionStatus); - break; - } - } - } - } else { - Log.d("QrisResultFlow", "No log entries found in response"); - } - } else { - Log.w("QrisResultFlow", "Polling failed with HTTP code: " + responseCode); - } - } catch (Exception e) { - Log.e("QrisResultFlow", "Polling error on attempt " + (attempt + 1) + ": " + e.getMessage()); - } - - if (!found) { - attempt++; - if (attempt < maxAttempts) { - try { - Thread.sleep(intervalMs); - } catch (InterruptedException ignored) { - break; - } - } - } - } - - final boolean logFound = found; - new Handler(Looper.getMainLooper()).post(() -> { - progressBar.setVisibility(View.GONE); - if (logFound) { - checkStatusButton.setEnabled(true); - statusTextView.setText("Ready to simulate payment"); - Toast.makeText(QrisResultActivity.this, "Payment log found!", Toast.LENGTH_SHORT).show(); - } else { - statusTextView.setText("Payment log not found"); - Toast.makeText(QrisResultActivity.this, "Payment log not found. Manual simulation available.", Toast.LENGTH_LONG).show(); - checkStatusButton.setEnabled(true); - } - }); - }).start(); - } - - private String getServerKey() { try { String base64 = MIDTRANS_AUTH.replace("Basic ", ""); byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT); - String decodedString = new String(decoded); - return decodedString.replace(":", ""); + return new String(decoded).replace(":", ""); } catch (Exception e) { - Log.e("QrisResultFlow", "Error decoding server key: " + e.getMessage()); return ""; } } @@ -1047,245 +530,160 @@ public class QrisResultActivity extends AppCompatActivity { hexString.append(hex); } return hexString.toString(); - } catch (java.security.NoSuchAlgorithmException e) { - Log.e("QrisResultFlow", "Error generating signature: " + e.getMessage()); + } catch (Exception e) { return "dummy_signature"; } } - private void startContinuousPaymentMonitoring() { - Log.d("QrisResultFlow", "🔍 Starting continuous payment monitoring"); - - // Create a monitoring handler separate from QR refresh - Handler paymentMonitorHandler = new Handler(Looper.getMainLooper()); - - Runnable paymentMonitorRunnable = new Runnable() { - @Override - public void run() { - // Check payment status every 5 seconds - checkCurrentPaymentStatus(); - - // Schedule next check if activity is still active - if (!isFinishing() && isQrRefreshActive) { - paymentMonitorHandler.postDelayed(this, 5000); // Check every 5 seconds - } - } - }; - - // Start monitoring - paymentMonitorHandler.post(paymentMonitorRunnable); - } - - private void checkCurrentPaymentStatus() { - new Thread(() -> { - try { - // Check status untuk current transaction ID - String statusUrl = "https://api.sandbox.midtrans.com/v2/" + transactionId + "/status"; - - Log.d("QrisResultFlow", "🔍 Checking payment status for: " + transactionId); - - URL url = new URI(statusUrl).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("Authorization", MIDTRANS_AUTH); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); - conn.setConnectTimeout(10000); - conn.setReadTimeout(10000); - - int responseCode = conn.getResponseCode(); - - if (responseCode == 200) { - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - - JSONObject statusResponse = new JSONObject(response.toString()); - String transactionStatus = statusResponse.optString("transaction_status", ""); - String paymentType = statusResponse.optString("payment_type", ""); - String grossAmount = statusResponse.optString("gross_amount", ""); - - Log.d("QrisResultFlow", "💳 Payment status check result:"); - Log.d("QrisResultFlow", " Status: " + transactionStatus); - Log.d("QrisResultFlow", " Payment Type: " + paymentType); - Log.d("QrisResultFlow", " Amount: " + grossAmount); - - // Check for success/settlement status - if (transactionStatus.equals("settlement") || - transactionStatus.equals("capture") || - transactionStatus.equals("success")) { - - Log.d("QrisResultFlow", "🎉 Payment detected as PAID! Status: " + transactionStatus); - - // ✅ TAMBAHKAN: Sync ke backend sebelum update UI - syncTransactionStatusToBackend("PAID"); - - // Update UI on main thread - runOnUiThread(() -> { - stopQrRefresh(); // Stop QR refresh - showPaymentSuccess(); - Toast.makeText(QrisResultActivity.this, "Payment Successful! 🎉", Toast.LENGTH_LONG).show(); - }); - - } else if (transactionStatus.equals("pending")) { - Log.d("QrisResultFlow", "⏳ Payment still pending"); - } else if (transactionStatus.equals("expire") || transactionStatus.equals("cancel")) { - Log.w("QrisResultFlow", "⚠️ Payment expired or cancelled: " + transactionStatus); - - // ✅ TAMBAHKAN: Sync status failed ke backend - syncTransactionStatusToBackend("FAILED"); - } else { - Log.d("QrisResultFlow", "📊 Payment status: " + transactionStatus); - } - - } else { - Log.w("QrisResultFlow", "⚠️ Payment status check failed: HTTP " + responseCode); - } - - } catch (Exception e) { - Log.e("QrisResultFlow", "❌ Payment status check error: " + e.getMessage(), e); - } - }).start(); - } - - // FIXED: Update simulateWebhook to use current transaction data - private void simulateWebhook() { - Log.d("QrisResultFlow", "🚀 Starting webhook simulation"); - progressBar.setVisibility(View.VISIBLE); - statusTextView.setText("Simulating payment..."); - checkStatusButton.setEnabled(false); - - // Stop QR refresh during payment simulation + // Navigation and Lifecycle + private void returnToMain() { stopQrRefresh(); - - new Thread(() -> { - try { - String serverKey = getServerKey(); - - // FIXED: Use current transaction data (after potential refresh) - String currentOrderId = this.orderId; - String currentTransactionId = this.transactionId; - String currentGrossAmount = this.grossAmount; - - Log.d("QrisResultFlow", "🎯 Simulating webhook for:"); - Log.d("QrisResultFlow", " Order ID: " + currentOrderId); - Log.d("QrisResultFlow", " Transaction ID: " + currentTransactionId); - Log.d("QrisResultFlow", " Amount: " + currentGrossAmount); - - String signatureKey = generateSignature(currentOrderId, "200", currentGrossAmount, serverKey); - - JSONObject payload = new JSONObject(); - payload.put("transaction_type", "on-us"); - payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z"); - payload.put("transaction_status", "settlement"); - payload.put("transaction_id", currentTransactionId); - payload.put("status_message", "midtrans payment notification"); - payload.put("status_code", "200"); - payload.put("signature_key", signatureKey); - payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z"); - payload.put("payment_type", "qris"); - payload.put("order_id", currentOrderId); - payload.put("merchant_id", merchantId != null ? merchantId : "DUMMY_MERCHANT_ID"); - payload.put("issuer", acquirer != null ? acquirer : "gopay"); - payload.put("gross_amount", currentGrossAmount); - payload.put("fraud_status", "accept"); - payload.put("currency", "IDR"); - payload.put("acquirer", acquirer != null ? acquirer : "gopay"); - payload.put("shopeepay_reference_number", ""); - payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID"); - - Log.d("QrisResultFlow", "📤 Webhook payload preview: " + - (payload.toString().length() > 200 ? payload.toString().substring(0, 200) + "..." : payload.toString())); - - URL url = new URL(webhookUrl); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0"); - conn.setDoOutput(true); - conn.setConnectTimeout(15000); - conn.setReadTimeout(15000); - - OutputStream os = conn.getOutputStream(); - os.write(payload.toString().getBytes()); - os.flush(); - os.close(); - - int responseCode = conn.getResponseCode(); - Log.d("QrisResultFlow", "📥 Webhook response code: " + responseCode); - - BufferedReader br = new BufferedReader(new InputStreamReader( - responseCode < 400 ? conn.getInputStream() : conn.getErrorStream())); - StringBuilder response = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - - Log.d("QrisResultFlow", "📥 Webhook response: " + - (response.length() > 200 ? response.substring(0, 200) + "..." : response.toString())); - - Thread.sleep(2000); - - } catch (Exception e) { - Log.e("QrisResultFlow", "❌ Webhook simulation error: " + e.getMessage(), e); - } - - new Handler(Looper.getMainLooper()).post(() -> { - progressBar.setVisibility(View.GONE); - showPaymentSuccess(); - }); - }).start(); + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finishAffinity(); } private void launchReceiptActivity() { Intent intent = new Intent(this, ReceiptActivity.class); - - // Add calling activity information for proper back navigation 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("reference_id", referenceId); // Nomor Transaksi + intent.putExtra("reference_id", referenceId); intent.putExtra("order_id", orderId); - intent.putExtra("transaction_amount", String.valueOf(originalAmount)); // Total transaksi + intent.putExtra("transaction_amount", String.valueOf(originalAmount)); intent.putExtra("gross_amount", grossAmount != null ? grossAmount : String.valueOf(originalAmount)); - intent.putExtra("created_at", getCurrentISOTime()); // Tanggal transaksi (current time for QRIS) - intent.putExtra("transaction_date", getCurrentDateTime()); // Backup formatted date + intent.putExtra("created_at", getCurrentISOTime()); intent.putExtra("payment_method", "QRIS"); - intent.putExtra("channel_code", "QRIS"); // Metode Pembayaran - intent.putExtra("channel_category", "RETAIL_OUTLET"); - intent.putExtra("card_type", "QRIS"); - intent.putExtra("merchant_name", "Marcel Panjaitan"); - intent.putExtra("merchant_location", "Jakarta, Indonesia"); - 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 - + intent.putExtra("acquirer", acquirer != null ? acquirer : "qris"); + intent.putExtra("mid", "71000026521"); + intent.putExtra("tid", "73001500"); startActivity(intent); } - private String getCurrentDateTime() { - java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", new java.util.Locale("id", "ID")); - return sdf.format(new java.util.Date()); + private void downloadQrCode() { + try { + qrImageView.setDrawingCacheEnabled(true); + qrImageView.buildDrawingCache(); + Bitmap bitmap = qrImageView.getDrawingCache(); + if (bitmap != null) { + saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis()); + } else { + Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + Toast.makeText(this, "Error downloading QR code", Toast.LENGTH_LONG).show(); + } finally { + qrImageView.setDrawingCacheEnabled(false); + } + } + + private void saveImageToGallery(Bitmap bitmap, String fileName) { + try { + String savedImageURL = android.provider.MediaStore.Images.Media.insertImage( + getContentResolver(), bitmap, fileName, "QRIS Payment QR Code"); + if (savedImageURL != null) { + Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + Toast.makeText(this, "Error saving QRIS", Toast.LENGTH_LONG).show(); + } + } + + private void updateUIAfterRefresh() { + String refreshTime = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date()); + referenceTextView.setText("Reference ID: " + referenceId + " (Refreshed at " + refreshTime + ")"); + } + + private void pollPendingPaymentLog(String orderId) { + progressBar.setVisibility(View.VISIBLE); + statusTextView.setText("Checking payment status..."); + + new Thread(() -> { + int maxAttempts = 12; + boolean found = false; + + for (int attempt = 0; attempt < maxAttempts && !found; attempt++) { + try { + found = checkPaymentStatus(orderId); + if (!found && attempt < maxAttempts - 1) { + Thread.sleep(2000); + } + } catch (Exception e) { + Log.e("QrisResult", "Polling error: " + e.getMessage()); + } + } + + final boolean logFound = found; + runOnUiThread(() -> { + progressBar.setVisibility(View.GONE); + if (logFound) { + checkStatusButton.setEnabled(true); + statusTextView.setText("Ready to simulate payment"); + Toast.makeText(this, "Payment log found!", Toast.LENGTH_SHORT).show(); + } else { + statusTextView.setText("Payment log not found"); + checkStatusButton.setEnabled(true); + } + }); + }).start(); } @Override - public boolean onOptionsItemSelected(android.view.MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; + protected void onDestroy() { + super.onDestroy(); + stopQrRefresh(); + } + + @Override + public void onBackPressed() { + stopQrRefresh(); + returnToMain(); + super.onBackPressed(); + } + + // AsyncTask for downloading QR image + private static class DownloadImageTask extends AsyncTask { + private ImageView imageView; + private String errorMessage; + + DownloadImageTask(ImageView imageView) { + this.imageView = imageView; + } + + @Override + protected Bitmap doInBackground(String... urls) { + try { + URL url = new URI(urls[0]).toURL(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); + connection.connect(); + + if (connection.getResponseCode() == 200) { + InputStream input = connection.getInputStream(); + return BitmapFactory.decodeStream(input); + } else { + errorMessage = "Failed to download QR code"; + } + } catch (Exception e) { + errorMessage = "Error downloading QR code: " + e.getMessage(); + } + return null; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null) { + imageView.setImageBitmap(result); + } else { + imageView.setImageResource(android.R.drawable.ic_menu_report_image); + if (errorMessage != null && imageView.getContext() != null) { + Toast.makeText(imageView.getContext(), errorMessage, Toast.LENGTH_LONG).show(); + } + } } - return super.onOptionsItemSelected(item); } } \ No newline at end of file