From 991f77dabe625fe9f71d50e945f2b0c7df9fc715 Mon Sep 17 00:00:00 2001 From: riz081 Date: Tue, 10 Jun 2025 16:58:42 +0700 Subject: [PATCH] transaction update --- .../com/example/bdkipoc/ReceiptActivity.java | 126 ++++++++++++++---- .../example/bdkipoc/TransactionAdapter.java | 124 ++++++++++++----- 2 files changed, 193 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java b/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java index a81e337..43072a5 100644 --- a/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java +++ b/app/src/main/java/com/example/bdkipoc/ReceiptActivity.java @@ -20,7 +20,9 @@ import java.util.Locale; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.io.OutputStream; import java.net.URL; +import java.net.URI; public class ReceiptActivity extends AppCompatActivity { @@ -286,10 +288,6 @@ public class ReceiptActivity extends AppCompatActivity { 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")) { @@ -299,7 +297,7 @@ public class ReceiptActivity extends AppCompatActivity { switch (acq) { // E-Wallet acquirers case "gopay": return "GoPay"; - case "shopeepay": return "ShopeePay"; + case "shopeepay": return "ShopeePay"; case "ovo": return "OVO"; case "dana": return "DANA"; case "linkaja": return "LinkAja"; @@ -323,9 +321,6 @@ public class ReceiptActivity extends AppCompatActivity { 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"; @@ -343,12 +338,6 @@ public class ReceiptActivity extends AppCompatActivity { 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); @@ -361,11 +350,16 @@ public class ReceiptActivity extends AppCompatActivity { 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); + // Try to get real acquirer from webhook data synchronously for initial load + String realAcquirer = fetchRealAcquirerSync(referenceId); + if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) { + Log.d("ReceiptActivity", "✅ Found real acquirer synchronously: " + realAcquirer); + return getCardTypeFromAcquirer(realAcquirer, null, null); + } - // ✅ IMPROVED: Instead of defaulting to GoPay, show generic "QRIS" until real acquirer is found - return "QRIS"; // Will be updated when real acquirer is found + // If sync fetch failed, try async and show generic QRIS for now + fetchRealAcquirerFromWebhook(referenceId); + return "QRIS"; // ✅ FIXED: Show QRIS instead of defaulting to GoPay } } @@ -373,7 +367,7 @@ public class ReceiptActivity extends AppCompatActivity { if (channelCode != null && !channelCode.isEmpty()) { String code = channelCode.toUpperCase(); switch (code) { - case "QRIS": return "QRIS"; // ✅ CHANGED: Generic QRIS instead of GoPay + case "QRIS": return "QRIS"; // ✅ FIXED: Generic QRIS instead of GoPay case "DEBIT": return "Debit"; case "CREDIT": return "Credit"; case "BCA": return "BCA"; @@ -385,19 +379,97 @@ public class ReceiptActivity extends AppCompatActivity { } // STEP 4: Final fallback - return fallbackCardType != null ? fallbackCardType : "Unknown"; // ✅ CHANGED: Unknown instead of GoPay + return fallbackCardType != null ? fallbackCardType : "Unknown"; // ✅ FIXED: Unknown instead of GoPay + } + + private String fetchRealAcquirerSync(String referenceId) { + try { + Log.d("ReceiptActivity", "🔍 Sync search for acquirer: " + referenceId); + + String queryUrl = "https://be-edc.msvc.app/api-logs?limit=50&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(5000); // Short timeout for sync call + 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); + } + + org.json.JSONObject json = new org.json.JSONObject(response.toString()); + org.json.JSONArray results = json.optJSONArray("results"); + + if (results != null && results.length() > 0) { + // Search for the most recent settlement/success transaction + 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) { + // Prioritize settlement/success status + if (logTransactionStatus.equals("settlement") || + logTransactionStatus.equals("capture") || + logTransactionStatus.equals("success")) { + + // Extract acquirer (prefer acquirer field over issuer) + String foundAcquirer = !logAcquirer.isEmpty() ? logAcquirer : logIssuer; + if (!foundAcquirer.isEmpty() && !foundAcquirer.equalsIgnoreCase("qris")) { + Log.d("ReceiptActivity", "📋 Sync found acquirer: " + foundAcquirer + " (status: " + logTransactionStatus + ")"); + return foundAcquirer; + } + } + } + } + } + } + } + + } catch (Exception e) { + Log.e("ReceiptActivity", "❌ Sync acquirer fetch error: " + e.getMessage()); + } + + return null; // No acquirer found } - /** - * ✅ 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); + Log.d("ReceiptActivity", "🔍 Async search for real acquirer: " + 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); @@ -423,7 +495,7 @@ public class ReceiptActivity extends AppCompatActivity { String realAcquirer = searchForRealAcquirer(results, referenceId); if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) { - Log.d("ReceiptActivity", "✅ Found real acquirer: " + realAcquirer + " for reference: " + referenceId); + Log.d("ReceiptActivity", "✅ Async found real acquirer: " + realAcquirer + " for reference: " + referenceId); // Update UI on main thread final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null); diff --git a/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java b/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java index 9089b78..ae80699 100644 --- a/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java +++ b/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java @@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStream; // ✅ ADDED: Missing import import java.net.HttpURLConnection; import java.net.URL; import java.util.List; @@ -211,14 +212,14 @@ public class TransactionAdapter extends RecyclerView.Adapter { try { Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId); - // STEP 1: Query ALL webhook logs (tidak filter specific order_id) - // Karena kita perlu cari semua order_id yang terkait dengan reference_id ini - String queryUrl = "https://be-edc.msvc.app/api-logs?limit=100&sortOrder=DESC&sortColumn=created_at"; + // STEP 1: Query webhook logs untuk semua order_id yang terkait + String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at"; URL url = new URL(queryUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); @@ -238,14 +239,14 @@ public class TransactionAdapter extends RecyclerView.Adapter 0) { Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries"); - // STEP 2: Comprehensive search untuk semua kemungkinan relasi + // STEP 2: Comprehensive search dengan multiple matching strategies for (int i = 0; i < results.length(); i++) { JSONObject log = results.getJSONObject(i); JSONObject reqBody = log.optJSONObject("request_body"); @@ -254,6 +255,7 @@ public class TransactionAdapter extends RecyclerView.Adapter capture > success > pending > init + // ✅ PRIORITY SYSTEM: settlement > capture > success > pending > init if (logTransactionStatus.equals("settlement") || logTransactionStatus.equals("capture") || logTransactionStatus.equals("success")) { - realStatus = "PAID"; + finalStatus = "PAID"; foundOrderId = logOrderId; - foundTransactionStatus = logTransactionStatus; + foundAcquirer = logAcquirer; Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus); break; // Found paid status, stop searching - } else if (logTransactionStatus.equals("pending") && realStatus.equals("INIT")) { - realStatus = "PENDING"; + } else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) { + finalStatus = "PENDING"; foundOrderId = logOrderId; - foundTransactionStatus = logTransactionStatus; + foundAcquirer = logAcquirer; Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId); + } else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) { + if (finalStatus.equals("INIT")) { // Only update if no better status found + finalStatus = "FAILED"; + foundOrderId = logOrderId; + foundAcquirer = logAcquirer; + Log.d("TransactionAdapter", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus); + } } } } } Log.d("TransactionAdapter", "🔍 FINAL RESULT for " + referenceId + ":"); - Log.d("TransactionAdapter", " Status: " + realStatus); + Log.d("TransactionAdapter", " Status: " + finalStatus); Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A")); - Log.d("TransactionAdapter", " Midtrans Status: " + (foundTransactionStatus != null ? foundTransactionStatus : "N/A")); + Log.d("TransactionAdapter", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A")); } // STEP 3: Update UI di main thread - final String finalStatus = realStatus; - final String finalOrderId = foundOrderId; - final String finalTransactionStatus = foundTransactionStatus; + final String displayStatus = finalStatus; + final String detectedAcquirer = foundAcquirer; statusTextView.post(() -> { - statusTextView.setText(finalStatus); - StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), finalStatus); + statusTextView.setText(displayStatus); + StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus); Log.d("TransactionAdapter", "🎨 UI UPDATED:"); Log.d("TransactionAdapter", " Reference: " + referenceId); - Log.d("TransactionAdapter", " Display Status: " + finalStatus); - Log.d("TransactionAdapter", " Found Order: " + (finalOrderId != null ? finalOrderId : "N/A")); + Log.d("TransactionAdapter", " Display Status: " + displayStatus); + Log.d("TransactionAdapter", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown")); }); + // ✅ BONUS: Update backend jika status berubah ke PAID + if (finalStatus.equals("PAID")) { + updateBackendTransactionStatus(referenceId, finalStatus, foundOrderId, detectedAcquirer); + } + } else { Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode()); statusTextView.post(() -> { @@ -364,6 +377,57 @@ public class TransactionAdapter extends RecyclerView.Adapter { + try { + Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId); + + JSONObject updatePayload = new JSONObject(); + updatePayload.put("status", status); + updatePayload.put("payment_status", status); + updatePayload.put("paid_order_id", orderId); + updatePayload.put("detected_acquirer", acquirer); + updatePayload.put("updated_at", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new Date())); + updatePayload.put("settlement_time", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new Date())); + + String updateUrl = "https://be-edc.msvc.app/transactions/update-by-reference"; + URL url = new URL(updateUrl); + 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); + + 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("TransactionAdapter", "📥 Backend update response: " + responseCode); + + if (responseCode == 200 || responseCode == 201) { + Log.d("TransactionAdapter", "✅ Backend status updated successfully"); + } else { + Log.e("TransactionAdapter", "❌ Backend update failed: " + responseCode); + } + + } catch (Exception e) { + Log.e("TransactionAdapter", "❌ Backend update error: " + e.getMessage(), e); + } + }).start(); + } + /** * Format created_at date to readable format */