transaction update

This commit is contained in:
riz081 2025-06-10 16:58:42 +07:00
parent da8bcf17cc
commit 991f77dabe
2 changed files with 193 additions and 57 deletions

View File

@ -20,7 +20,9 @@ import java.util.Locale;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.net.URI;
public class ReceiptActivity extends AppCompatActivity { public class ReceiptActivity extends AppCompatActivity {
@ -286,10 +288,6 @@ public class ReceiptActivity extends AppCompatActivity {
return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS"; 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) { private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) {
// STEP 1: If we have a valid acquirer that's not generic "qris", use it // STEP 1: If we have a valid acquirer that's not generic "qris", use it
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) { if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
@ -299,7 +297,7 @@ public class ReceiptActivity extends AppCompatActivity {
switch (acq) { switch (acq) {
// E-Wallet acquirers // E-Wallet acquirers
case "gopay": return "GoPay"; case "gopay": return "GoPay";
case "shopeepay": return "ShopeePay"; case "shopeepay": return "ShopeePay";
case "ovo": return "OVO"; case "ovo": return "OVO";
case "dana": return "DANA"; case "dana": return "DANA";
case "linkaja": return "LinkAja"; case "linkaja": return "LinkAja";
@ -323,9 +321,6 @@ public class ReceiptActivity extends AppCompatActivity {
case "bsi": case "bsi":
case "bsi_va": return "BSI"; case "bsi_va": return "BSI";
case "maybank": return "Maybank"; case "maybank": return "Maybank";
case "mega": return "Bank Mega";
case "btn": return "BTN";
case "bukopin": return "Bukopin";
// Credit card acquirers // Credit card acquirers
case "visa": return "Visa"; case "visa": return "Visa";
@ -343,12 +338,6 @@ public class ReceiptActivity extends AppCompatActivity {
case "kredivo": return "Kredivo"; case "kredivo": return "Kredivo";
case "indodana": return "Indodana"; case "indodana": return "Indodana";
// Other acquirers
case "emoney": return "E-Money";
case "flazz": return "Flazz";
case "tapcash": return "TapCash";
case "brizzi": return "Brizzi";
default: default:
// Return capitalized version of acquirer if not in mapping // Return capitalized version of acquirer if not in mapping
return capitalizeFirstLetter(acquirer); return capitalizeFirstLetter(acquirer);
@ -361,11 +350,16 @@ public class ReceiptActivity extends AppCompatActivity {
if (referenceId != null && !referenceId.isEmpty()) { if (referenceId != null && !referenceId.isEmpty()) {
Log.d("ReceiptActivity", "🔍 QRIS detected, fetching real acquirer for: " + referenceId); Log.d("ReceiptActivity", "🔍 QRIS detected, fetching real acquirer for: " + referenceId);
// Try to get real acquirer from webhook data asynchronously // Try to get real acquirer from webhook data synchronously for initial load
fetchRealAcquirerFromWebhook(referenceId); 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 // If sync fetch failed, try async and show generic QRIS for now
return "QRIS"; // Will be updated when real acquirer is found 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()) { if (channelCode != null && !channelCode.isEmpty()) {
String code = channelCode.toUpperCase(); String code = channelCode.toUpperCase();
switch (code) { 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 "DEBIT": return "Debit";
case "CREDIT": return "Credit"; case "CREDIT": return "Credit";
case "BCA": return "BCA"; case "BCA": return "BCA";
@ -385,19 +379,97 @@ public class ReceiptActivity extends AppCompatActivity {
} }
// STEP 4: Final fallback // 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) { private void fetchRealAcquirerFromWebhook(String referenceId) {
new Thread(() -> { new Thread(() -> {
try { 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"; String queryUrl = "https://be-edc.msvc.app/api-logs?limit=100&sortOrder=DESC&sortColumn=created_at";
URL url = new URL(queryUrl); URL url = new URL(queryUrl);
@ -423,7 +495,7 @@ public class ReceiptActivity extends AppCompatActivity {
String realAcquirer = searchForRealAcquirer(results, referenceId); String realAcquirer = searchForRealAcquirer(results, referenceId);
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) { 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 // Update UI on main thread
final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null); final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);

View File

@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; // ADDED: Missing import
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
@ -211,14 +212,14 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
return "Rp. " + formatted; return "Rp. " + formatted;
} }
// FIXED: Enhanced status checking with comprehensive search
private void checkMidtransStatus(String referenceId, TextView statusTextView) { private void checkMidtransStatus(String referenceId, TextView statusTextView) {
new Thread(() -> { new Thread(() -> {
try { try {
Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId); Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId);
// STEP 1: Query ALL webhook logs (tidak filter specific order_id) // STEP 1: Query webhook logs untuk semua order_id yang terkait
// Karena kita perlu cari semua order_id yang terkait dengan reference_id ini String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at";
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=100&sortOrder=DESC&sortColumn=created_at";
URL url = new URL(queryUrl); URL url = new URL(queryUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@ -238,14 +239,14 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
JSONObject json = new JSONObject(response.toString()); JSONObject json = new JSONObject(response.toString());
JSONArray results = json.optJSONArray("results"); JSONArray results = json.optJSONArray("results");
String realStatus = "INIT"; // Default String finalStatus = "INIT"; // Default
String foundOrderId = null; String foundOrderId = null;
String foundTransactionStatus = ""; String foundAcquirer = null;
if (results != null && results.length() > 0) { if (results != null && results.length() > 0) {
Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries"); 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++) { for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i); JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body"); JSONObject reqBody = log.optJSONObject("request_body");
@ -254,6 +255,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
String logOrderId = reqBody.optString("order_id", ""); String logOrderId = reqBody.optString("order_id", "");
String logTransactionStatus = reqBody.optString("transaction_status", ""); String logTransactionStatus = reqBody.optString("transaction_status", "");
String logReferenceId = reqBody.optString("reference_id", ""); String logReferenceId = reqBody.optString("reference_id", "");
String logAcquirer = reqBody.optString("acquirer", "");
// METHOD 1: Direct reference_id match // METHOD 1: Direct reference_id match
boolean isDirectMatch = referenceId.equals(logReferenceId); boolean isDirectMatch = referenceId.equals(logReferenceId);
@ -265,10 +267,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
try { try {
JSONObject customData = new JSONObject(customField1); JSONObject customData = new JSONObject(customField1);
String originalReference = customData.optString("original_reference", ""); String originalReference = customData.optString("original_reference", "");
if (referenceId.equals(originalReference)) { String appReferenceId = customData.optString("app_reference_id", "");
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
isRefreshMatch = true; isRefreshMatch = true;
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId + Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId);
" (original ref: " + originalReference + ")");
} }
} catch (JSONException e) { } catch (JSONException e) {
// Ignore custom field parsing errors // Ignore custom field parsing errors
@ -283,10 +285,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
JSONObject item = itemDetails.optJSONObject(j); JSONObject item = itemDetails.optJSONObject(j);
if (item != null) { if (item != null) {
String itemName = item.optString("name", ""); String itemName = item.optString("name", "");
if (itemName.contains("(Ref: " + referenceId + ")")) { if (itemName.contains("(Ref: " + referenceId + ")") ||
itemName.contains("- " + referenceId)) {
isItemMatch = true; isItemMatch = true;
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId + Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId);
" (item: " + itemName + ")");
break; break;
} }
} }
@ -300,52 +302,63 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
Log.d("TransactionAdapter", "🎯 MATCH FOUND!"); Log.d("TransactionAdapter", "🎯 MATCH FOUND!");
Log.d("TransactionAdapter", " Order ID: " + logOrderId); Log.d("TransactionAdapter", " Order ID: " + logOrderId);
Log.d("TransactionAdapter", " Status: " + logTransactionStatus); Log.d("TransactionAdapter", " Status: " + logTransactionStatus);
Log.d("TransactionAdapter", " Reference: " + logReferenceId); Log.d("TransactionAdapter", " Acquirer: " + logAcquirer);
Log.d("TransactionAdapter", " Match Type: " + Log.d("TransactionAdapter", " Match Type: " +
(isDirectMatch ? "DIRECT" : "") + (isDirectMatch ? "DIRECT " : "") +
(isRefreshMatch ? "REFRESH" : "") + (isRefreshMatch ? "REFRESH " : "") +
(isItemMatch ? "ITEM" : "")); (isItemMatch ? "ITEM" : ""));
// Priority check: settlement > capture > success > pending > init // PRIORITY SYSTEM: settlement > capture > success > pending > init
if (logTransactionStatus.equals("settlement") || if (logTransactionStatus.equals("settlement") ||
logTransactionStatus.equals("capture") || logTransactionStatus.equals("capture") ||
logTransactionStatus.equals("success")) { logTransactionStatus.equals("success")) {
realStatus = "PAID"; finalStatus = "PAID";
foundOrderId = logOrderId; foundOrderId = logOrderId;
foundTransactionStatus = logTransactionStatus; foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus); Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
break; // Found paid status, stop searching break; // Found paid status, stop searching
} else if (logTransactionStatus.equals("pending") && realStatus.equals("INIT")) { } else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) {
realStatus = "PENDING"; finalStatus = "PENDING";
foundOrderId = logOrderId; foundOrderId = logOrderId;
foundTransactionStatus = logTransactionStatus; foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId); 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", "🔍 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", " 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 // STEP 3: Update UI di main thread
final String finalStatus = realStatus; final String displayStatus = finalStatus;
final String finalOrderId = foundOrderId; final String detectedAcquirer = foundAcquirer;
final String finalTransactionStatus = foundTransactionStatus;
statusTextView.post(() -> { statusTextView.post(() -> {
statusTextView.setText(finalStatus); statusTextView.setText(displayStatus);
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), finalStatus); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus);
Log.d("TransactionAdapter", "🎨 UI UPDATED:"); Log.d("TransactionAdapter", "🎨 UI UPDATED:");
Log.d("TransactionAdapter", " Reference: " + referenceId); Log.d("TransactionAdapter", " Reference: " + referenceId);
Log.d("TransactionAdapter", " Display Status: " + finalStatus); Log.d("TransactionAdapter", " Display Status: " + displayStatus);
Log.d("TransactionAdapter", " Found Order: " + (finalOrderId != null ? finalOrderId : "N/A")); 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 { } else {
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode()); Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
statusTextView.post(() -> { statusTextView.post(() -> {
@ -364,6 +377,57 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
}).start(); }).start();
} }
/**
* NEW METHOD: Update backend transaction status when payment confirmed
*/
private void updateBackendTransactionStatus(String referenceId, String status, String orderId, String acquirer) {
new Thread(() -> {
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 * Format created_at date to readable format
*/ */