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