QRIS FLOW

This commit is contained in:
riz081 2025-06-09 12:04:58 +07:00
parent 074a4b1f53
commit 99fab68e71
2 changed files with 1048 additions and 167 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
package com.example.bdkipoc;
import android.util.Log; // ADD THIS IMPORT
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -11,10 +11,22 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.core.content.ContextCompat;
// TAMBAHKAN MISSING IMPORTS INI:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.text.NumberFormat;
import java.util.Locale;
// TAMBAHKAN JSON IMPORTS:
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
private List<TransactionActivity.Transaction> transactionList;
private OnPrintClickListener printClickListener;
@ -42,32 +54,58 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
TransactionActivity.Transaction t = transactionList.get(position);
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":");
Log.d("TransactionAdapter", " Reference: " + t.referenceId);
Log.d("TransactionAdapter", " Status: " + t.status);
Log.d("TransactionAdapter", " Amount: " + t.amount);
// Set reference ID
holder.referenceId.setText(t.referenceId);
// Format the amount as Indonesian Rupiah
try {
// Clean and format amount
String cleanAmount = cleanAmountString(t.amount);
long amountValue = Long.parseLong(cleanAmount);
// Format using Indonesian formatting (dots as thousand separators)
String formattedAmount = formatRupiah(amountValue);
holder.amount.setText(formattedAmount);
Log.d("TransactionAdapter", "Original: '" + t.amount + "' -> Cleaned: '" +
cleanAmount + "' -> Formatted: '" + formattedAmount + "'");
Log.d("TransactionAdapter", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
} catch (NumberFormatException e) {
Log.e("TransactionAdapter", "Error formatting amount: " + t.amount, e);
// Fallback: show original amount with Rp prefix if not already present
Log.e("TransactionAdapter", "❌ Amount format error: " + t.amount, e);
String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount;
holder.amount.setText(fallback);
}
// Set status with appropriate color
holder.status.setText(t.status.toUpperCase());
setStatusColor(holder.status, t.status);
// ENHANCED STATUS HANDLING dengan comprehensive checking
String displayStatus = t.status;
Log.d("TransactionAdapter", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")");
// Jika status adalah INIT atau PENDING, lakukan comprehensive check
if ("INIT".equalsIgnoreCase(t.status) || "PENDING".equalsIgnoreCase(t.status)) {
if (t.referenceId != null && !t.referenceId.isEmpty()) {
// Show checking state
holder.status.setText("CHECKING...");
holder.status.setTextColor(ContextCompat.getColor(holder.itemView.getContext(),
android.R.color.holo_orange_dark));
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId);
// Check real status dari semua kemungkinan sources
checkMidtransStatus(t.referenceId, holder.status);
} else {
// No reference ID to check
holder.status.setText(displayStatus.toUpperCase());
setStatusColor(holder.status, displayStatus);
Log.w("TransactionAdapter", "⚠️ No reference ID for status check");
}
} else {
// Use existing status yang sudah confirmed
holder.status.setText(displayStatus.toUpperCase());
setStatusColor(holder.status, displayStatus);
Log.d("TransactionAdapter", "✅ Using confirmed status: " + displayStatus);
}
// Set payment method
String paymentMethod = getPaymentMethodName(t.channelCode, t.channelCategory);
@ -85,12 +123,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
printClickListener.onPrintClick(t);
}
});
Log.d("TransactionAdapter", "✅ Transaction binding complete for: " + t.referenceId);
}
/**
* FIXED: Clean amount string to extract raw number correctly
* Handles various input formats properly
*/
private String cleanAmountString(String amount) {
if (amount == null || amount.isEmpty()) {
return "0";
@ -157,17 +193,22 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
int color;
if (statusLower.equals("failed") || statusLower.equals("failure") ||
statusLower.equals("error") || statusLower.equals("declined")) {
// Red for failed statuses
statusLower.equals("error") || statusLower.equals("declined") ||
statusLower.equals("expire") || statusLower.equals("cancel")) {
// Red for failed/error statuses
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_red_dark);
} else if (statusLower.equals("success") || statusLower.equals("paid") ||
statusLower.equals("settlement") || statusLower.equals("completed")) {
statusLower.equals("settlement") || statusLower.equals("completed") ||
statusLower.equals("capture")) {
// Green for successful statuses
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_green_dark);
} else if (statusLower.equals("pending") || statusLower.equals("processing") ||
statusLower.equals("waiting") || statusLower.equals("init")) {
// Orange/Yellow for pending statuses
statusLower.equals("waiting") || statusLower.equals("checking...")) {
// Orange for pending/processing statuses
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_orange_dark);
} else if (statusLower.equals("init")) {
// Yellow for init status
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_orange_light);
} else {
// Default gray for unknown statuses
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.darker_gray);
@ -176,6 +217,159 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
statusTextView.setTextColor(color);
}
private void checkMidtransStatus(String referenceId, TextView statusTextView) {
new Thread(() -> {
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";
URL url = new URL(queryUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
if (conn.getResponseCode() == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
JSONObject json = new JSONObject(response.toString());
JSONArray results = json.optJSONArray("results");
String realStatus = "INIT"; // Default
String foundOrderId = null;
String foundTransactionStatus = "";
if (results != null && results.length() > 0) {
Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries");
// STEP 2: Comprehensive search untuk semua kemungkinan relasi
for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null) {
String logOrderId = reqBody.optString("order_id", "");
String logTransactionStatus = reqBody.optString("transaction_status", "");
String logReferenceId = reqBody.optString("reference_id", "");
// METHOD 1: Direct reference_id match
boolean isDirectMatch = referenceId.equals(logReferenceId);
// METHOD 2: Check custom_field1 untuk QR refresh tracking
boolean isRefreshMatch = false;
String customField1 = reqBody.optString("custom_field1", "");
if (!customField1.isEmpty()) {
try {
JSONObject customData = new JSONObject(customField1);
String originalReference = customData.optString("original_reference", "");
if (referenceId.equals(originalReference)) {
isRefreshMatch = true;
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId +
" (original ref: " + originalReference + ")");
}
} catch (JSONException e) {
// Ignore custom field parsing errors
}
}
// METHOD 3: Check item details untuk backup tracking
boolean isItemMatch = false;
JSONArray itemDetails = reqBody.optJSONArray("item_details");
if (itemDetails != null && itemDetails.length() > 0) {
for (int j = 0; j < itemDetails.length(); j++) {
JSONObject item = itemDetails.optJSONObject(j);
if (item != null) {
String itemName = item.optString("name", "");
if (itemName.contains("(Ref: " + referenceId + ")")) {
isItemMatch = true;
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId +
" (item: " + itemName + ")");
break;
}
}
}
}
// COMPREHENSIVE MATCH: Any of the three methods
boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch;
if (isRelatedTransaction) {
Log.d("TransactionAdapter", "🎯 MATCH FOUND!");
Log.d("TransactionAdapter", " Order ID: " + logOrderId);
Log.d("TransactionAdapter", " Status: " + logTransactionStatus);
Log.d("TransactionAdapter", " Reference: " + logReferenceId);
Log.d("TransactionAdapter", " Match Type: " +
(isDirectMatch ? "DIRECT" : "") +
(isRefreshMatch ? "REFRESH" : "") +
(isItemMatch ? "ITEM" : ""));
// Priority check: settlement > capture > success > pending > init
if (logTransactionStatus.equals("settlement") ||
logTransactionStatus.equals("capture") ||
logTransactionStatus.equals("success")) {
realStatus = "PAID";
foundOrderId = logOrderId;
foundTransactionStatus = logTransactionStatus;
Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
break; // Found paid status, stop searching
} else if (logTransactionStatus.equals("pending") && realStatus.equals("INIT")) {
realStatus = "PENDING";
foundOrderId = logOrderId;
foundTransactionStatus = logTransactionStatus;
Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId);
}
}
}
}
Log.d("TransactionAdapter", "🔍 FINAL RESULT for " + referenceId + ":");
Log.d("TransactionAdapter", " Status: " + realStatus);
Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
Log.d("TransactionAdapter", " Midtrans Status: " + (foundTransactionStatus != null ? foundTransactionStatus : "N/A"));
}
// STEP 3: Update UI di main thread
final String finalStatus = realStatus;
final String finalOrderId = foundOrderId;
final String finalTransactionStatus = foundTransactionStatus;
statusTextView.post(() -> {
statusTextView.setText(finalStatus);
setStatusColor(statusTextView, finalStatus);
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"));
});
} else {
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
statusTextView.post(() -> {
statusTextView.setText("ERROR");
setStatusColor(statusTextView, "ERROR");
});
}
} catch (IOException | JSONException e) {
Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e);
statusTextView.post(() -> {
statusTextView.setText("INIT");
setStatusColor(statusTextView, "INIT");
});
}
}).start();
}
private String getPaymentMethodName(String channelCode, String channelCategory) {
// Convert channel code to readable payment method name
if (channelCode == null) return "Unknown";