diff --git a/app/build.gradle b/app/build.gradle
index a01b099..977fe05 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,4 +39,6 @@ dependencies {
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
+
+ implementation(name: 'PayLib-release-2.0.17', ext: 'aar')
}
\ No newline at end of file
diff --git a/app/libs/PayLib-release-2.0.17.aar b/app/libs/PayLib-release-2.0.17.aar
new file mode 100644
index 0000000..2f01592
Binary files /dev/null and b/app/libs/PayLib-release-2.0.17.aar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c24742c..78d8c49 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bdkipoc/CreditCardActivity.java b/app/src/main/java/com/example/bdkipoc/CreditCardActivity.java
new file mode 100644
index 0000000..9a347a6
--- /dev/null
+++ b/app/src/main/java/com/example/bdkipoc/CreditCardActivity.java
@@ -0,0 +1,3 @@
+public class CreditCardActivity {
+
+}
diff --git a/app/src/main/java/com/example/bdkipoc/MainActivity.java b/app/src/main/java/com/example/bdkipoc/MainActivity.java
index 1a3db64..24c457d 100644
--- a/app/src/main/java/com/example/bdkipoc/MainActivity.java
+++ b/app/src/main/java/com/example/bdkipoc/MainActivity.java
@@ -158,7 +158,7 @@ public class MainActivity extends AppCompatActivity {
if (cardView != null) {
cardView.setOnClickListener(v -> {
if (cardId == R.id.card_kartu_kredit) {
- startActivity(new Intent(MainActivity.this, PaymentActivity.class));
+ startActivity(new Intent(MainActivity.this, CreditCardActivity.class));
} else if (cardId == R.id.card_kartu_debit) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
} else if (cardId == R.id.card_qris) {
diff --git a/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java b/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java
index c3610c0..8d7e014 100644
--- a/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java
+++ b/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java
@@ -32,8 +32,8 @@ import java.net.URI;
import java.net.URL;
import java.text.NumberFormat;
import java.util.Locale;
-import java.util.List;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
public class QrisResultActivity extends AppCompatActivity {
private ImageView qrImageView;
@@ -63,6 +63,14 @@ public class QrisResultActivity extends AppCompatActivity {
private String currentQrImageUrl;
private int originalAmount;
+ // ✅ FIXED: Store actual issuer/acquirer from Midtrans response
+ private String actualIssuerFromMidtrans = "";
+ private String actualAcquirerFromMidtrans = "";
+
+ // ✅ ADD: Track QR refresh transaction for payment monitoring
+ private String currentQrTransactionId = "";
+ private boolean isMonitoringQrRefreshTransaction = false;
+
private String backendBase = "https://be-edc.msvc.app";
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
@@ -70,7 +78,27 @@ public class QrisResultActivity extends AppCompatActivity {
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM=";
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
- private List allOrderIds = new ArrayList<>();
+ // ✅ ADD: Mapping dari technical issuer ke display name
+ private static final Map ISSUER_DISPLAY_MAP = new HashMap() {{
+ put("airpay shopee", "ShopeePay");
+ put("shopeepay", "ShopeePay");
+ put("shopee", "ShopeePay");
+ put("linkaja", "LinkAja");
+ put("link aja", "LinkAja");
+ put("dana", "DANA");
+ put("ovo", "OVO");
+ put("gopay", "GoPay");
+ put("jenius", "Jenius");
+ put("sakuku", "Sakuku");
+ put("bni", "BNI");
+ put("bca", "BCA");
+ put("mandiri", "Mandiri");
+ put("bri", "BRI");
+ put("cimb", "CIMB Niaga");
+ put("permata", "Permata");
+ put("maybank", "Maybank");
+ put("qris", "QRIS");
+ }};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -83,9 +111,11 @@ public class QrisResultActivity extends AppCompatActivity {
// Get intent data
getIntentData();
- // Initialize order ID tracking
- allOrderIds.add(orderId); // Tambahkan order ID pertama
- Log.d("QrisResultFlow", "🆔 Initial order ID added: " + orderId);
+ // ✅ Initialize parent transaction tracking
+ currentQrTransactionId = transactionId; // Initially monitor parent transaction
+ isMonitoringQrRefreshTransaction = false;
+ Log.d("QrisResultFlow", "🆔 Initial monitoring - parent transaction ID: " + transactionId);
+ Log.d("QrisResultFlow", "🆔 Parent order ID: " + orderId);
// Setup UI
setupUI();
@@ -187,15 +217,15 @@ public class QrisResultActivity extends AppCompatActivity {
// Schedule next update in 1 second
qrRefreshHandler.postDelayed(this, 1000);
} else {
- // Time to refresh QR code
- Log.d("QrisResultFlow", "🔄 QR Code refresh time reached - generating new QR");
+ // ✅ Time to refresh QR code - CREATE NEW QR TRANSACTION
+ Log.d("QrisResultFlow", "🔄 QR Code refresh time reached - creating new QR transaction");
refreshQrCode();
}
}
};
qrRefreshHandler.post(qrRefreshRunnable);
- Log.d("QrisResultFlow", "🕒 QR refresh timer started - 60 seconds countdown");
+ Log.d("QrisResultFlow", "🕒 QR refresh timer started - 60 seconds countdown (dynamic monitoring)");
}
private void refreshQrCode() {
@@ -203,11 +233,11 @@ public class QrisResultActivity extends AppCompatActivity {
return;
}
- Log.d("QrisResultFlow", "🔄 Starting QR code refresh...");
+ Log.d("QrisResultFlow", "🔄 Starting QR code refresh for parent transaction...");
// Show loading state
timerTextView.setText("...");
- qrStatusTextView.setText("Generating new QR Code...");
+ qrStatusTextView.setText("Refreshing QR Code (will create new QR transaction)...");
// Generate new QR code in background
new Thread(() -> {
@@ -216,27 +246,28 @@ public class QrisResultActivity extends AppCompatActivity {
runOnUiThread(() -> {
if (newQrUrl != null && !newQrUrl.isEmpty()) {
- // Successfully generated new QR
+ // ✅ Successfully refreshed QR - NOW MONITORING NEW QR TRANSACTION
currentQrImageUrl = newQrUrl;
loadQrImage(newQrUrl);
- // ✅ TAMBAHKAN: Track new order ID
- allOrderIds.add(orderId); // orderId sudah diupdate di generateNewQrCode()
- Log.d("QrisResultFlow", "🆔 New order ID added: " + orderId + " (Total: " + allOrderIds.size() + ")");
+ // ✅ IMPORTANT: Now monitoring QR refresh transaction for payment
+ Log.d("QrisResultFlow", "🔄 QR refreshed - now monitoring new QR transaction");
+ Log.d("QrisResultFlow", "🔄 Parent transaction ID: " + transactionId + " (reference only)");
+ Log.d("QrisResultFlow", "🔄 Monitoring QR transaction ID: " + currentQrTransactionId);
- // Update UI with new data
+ // Update UI with refresh info
updateUIAfterRefresh();
// Restart timer
countdownSeconds = 60;
qrStatusTextView.setText("QR Code akan refresh dalam");
- Log.d("QrisResultFlow", "✅ QR code refreshed successfully");
- Toast.makeText(QrisResultActivity.this, "QR Code refreshed", Toast.LENGTH_SHORT).show();
+ Log.d("QrisResultFlow", "✅ QR code refreshed successfully - monitoring new QR transaction");
+ Toast.makeText(QrisResultActivity.this, "QR refreshed - scan new QR for payment", Toast.LENGTH_SHORT).show();
} else {
// Failed to generate new QR
Log.e("QrisResultFlow", "❌ Failed to refresh QR code");
qrStatusTextView.setText("Failed to refresh QR - trying again in 30s");
- countdownSeconds = 30; // Retry in 30 seconds
+ countdownSeconds = 30;
Toast.makeText(QrisResultActivity.this, "QR refresh failed, retrying...", Toast.LENGTH_SHORT).show();
}
@@ -257,154 +288,53 @@ public class QrisResultActivity extends AppCompatActivity {
}).start();
}
- private void pollMultipleOrderIds() {
- Log.d("QrisResultFlow", "🔍 Starting polling for multiple order IDs");
-
- Handler multiPollHandler = new Handler(Looper.getMainLooper());
-
- Runnable multiPollRunnable = new Runnable() {
- @Override
- public void run() {
- // Check semua order IDs yang pernah dibuat
- checkAllOrderIdsStatus();
-
- // Schedule next check jika activity masih aktif
- if (!isFinishing() && isQrRefreshActive) {
- multiPollHandler.postDelayed(this, 3000); // Check every 3 seconds
- }
- }
- };
-
- multiPollHandler.post(multiPollRunnable);
- }
-
- // ✅ METHOD BARU untuk check status dari semua order IDs
- private void checkAllOrderIdsStatus() {
- new Thread(() -> {
- try {
- Log.d("QrisResultFlow", "🔍 Checking status for " + allOrderIds.size() + " order IDs");
-
- for (String checkOrderId : allOrderIds) {
- if (checkOrderId == null || checkOrderId.isEmpty()) continue;
-
- // Check di webhook logs
- String urlStr = backendBase + "/api-logs?request_body_search_strict=" +
- java.net.URLEncoder.encode("{\"order_id\":\"" + checkOrderId + "\"}", "UTF-8");
-
- URL url = new URL(urlStr);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("GET");
- conn.setRequestProperty("Accept", "application/json");
- conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
- conn.setConnectTimeout(5000);
- conn.setReadTimeout(5000);
-
- int responseCode = conn.getResponseCode();
-
- if (responseCode == 200) {
- BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- StringBuilder response = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- response.append(line);
- }
-
- JSONObject json = new JSONObject(response.toString());
- JSONArray results = json.optJSONArray("results");
-
- if (results != null && results.length() > 0) {
- for (int i = 0; i < results.length(); i++) {
- JSONObject log = results.getJSONObject(i);
- JSONObject reqBody = log.optJSONObject("request_body");
-
- if (reqBody != null) {
- String transactionStatus = reqBody.optString("transaction_status");
- String logOrderId = reqBody.optString("order_id");
-
- Log.d("QrisResultFlow", "📊 Order ID: " + logOrderId + " -> Status: " + transactionStatus);
-
- // Check untuk settlement/success status
- if (checkOrderId.equals(logOrderId) &&
- (transactionStatus.equals("settlement") ||
- transactionStatus.equals("capture") ||
- transactionStatus.equals("success"))) {
-
- Log.d("QrisResultFlow", "🎉 PAYMENT FOUND! Order ID: " + checkOrderId + " Status: " + transactionStatus);
-
- // Update transaction ID untuk sync
- String foundTransactionId = reqBody.optString("transaction_id", transactionId);
- if (!foundTransactionId.isEmpty()) {
- transactionId = foundTransactionId;
- }
-
- // Stop polling dan tampilkan success
- runOnUiThread(() -> {
- stopQrRefresh();
- syncTransactionStatusToBackend("PAID");
- showPaymentSuccess();
- Toast.makeText(QrisResultActivity.this,
- "Payment Successful! 🎉 (QR #" + (allOrderIds.indexOf(checkOrderId) + 1) + ")",
- Toast.LENGTH_LONG).show();
- });
-
- return; // Exit method
- }
- }
- }
- }
- }
- }
-
- } catch (Exception e) {
- Log.e("QrisResultFlow", "❌ Multi-order polling error: " + e.getMessage(), e);
- }
- }).start();
- }
-
private void updateUIAfterRefresh() {
- // You can update reference text to show it's refreshed
String refreshTime = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date());
- referenceTextView.setText("Reference ID: " + referenceId + " (Refreshed at " + refreshTime + ")");
-
- // Reset polling for new order ID if needed
- // Note: You might want to restart polling for the new orderId
- Log.d("QrisResultFlow", "🔄 UI updated after QR refresh");
+ String transactionType = isMonitoringQrRefreshTransaction ? "new QR" : "same payment";
+ referenceTextView.setText("Reference ID: " + referenceId + " (QR refreshed at " + refreshTime + " - " + transactionType + ")");
+ Log.d("QrisResultFlow", "🔄 UI updated after QR refresh - monitoring: " + currentQrTransactionId);
}
private String generateNewQrCode() {
try {
- Log.d("QrisResultFlow", "🔧 Generating new QR code with fresh UUID");
+ Log.d("QrisResultFlow", "🔧 Refreshing QR code for existing transaction");
+ Log.d("QrisResultFlow", "🔄 Parent Transaction ID: " + transactionId);
+ Log.d("QrisResultFlow", "🔄 Parent Order ID: " + orderId);
- // FIXED: Generate NEW UUID for refresh to avoid duplicate order_id
- String newOrderId = java.util.UUID.randomUUID().toString();
- Log.d("QrisResultFlow", "🆕 New Order ID for refresh: " + newOrderId);
- Log.d("QrisResultFlow", "🔄 Original Order ID: " + orderId);
+ // ✅ GENERATE SHORT ORDER ID to avoid 50 character limit
+ // Get last part of timestamp (6 digits) to ensure uniqueness
+ String shortTimestamp = String.valueOf(System.currentTimeMillis()).substring(7);
+ String newOrderId = orderId.substring(0, Math.min(orderId.length(), 43)) + "-q" + shortTimestamp;
- // ✅ FIXED: Create custom_field FIRST, before logging
+ Log.d("QrisResultFlow", "🆕 New QR Order ID: " + newOrderId + " (Length: " + newOrderId.length() + ")");
+
+ // ✅ VALIDATE ORDER ID LENGTH
+ if (newOrderId.length() > 50) {
+ // Fallback: use first 36 chars of original + short suffix
+ newOrderId = orderId.substring(0, 36) + "-q" + shortTimestamp.substring(0, Math.min(shortTimestamp.length(), 7));
+ Log.w("QrisResultFlow", "⚠️ Order ID too long, using fallback: " + newOrderId + " (Length: " + newOrderId.length() + ")");
+ }
+
+ // ✅ CREATE LINK TO PARENT TRANSACTION
JSONObject customField1 = new JSONObject();
- customField1.put("refresh_of", orderId); // Track original order
- customField1.put("refresh_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
- customField1.put("original_reference", referenceId); // Key untuk tracking!
- customField1.put("refresh_count", System.currentTimeMillis()); // Unique identifier
- customField1.put("parent_transaction_id", transactionId); // Link ke parent transaction
+ customField1.put("parent_transaction_id", transactionId);
+ customField1.put("parent_order_id", orderId);
+ customField1.put("parent_reference_id", referenceId);
+ customField1.put("qr_refresh_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
+ customField1.put("qr_refresh_count", System.currentTimeMillis());
+ customField1.put("is_qr_refresh", true);
- // ✅ NOW log AFTER customField1 is defined
- Log.d("QrisResultFlow", "🔄 Enhanced refresh tracking:");
- Log.d("QrisResultFlow", " Original Order: " + orderId);
- Log.d("QrisResultFlow", " New Order: " + newOrderId);
- Log.d("QrisResultFlow", " Reference ID: " + referenceId);
- Log.d("QrisResultFlow", " Custom Field: " + customField1.toString());
-
- // Create QRIS charge JSON payload with NEW order_id
+ // ✅ CREATE QRIS PAYLOAD WITH NEW ORDER ID
JSONObject payload = new JSONObject();
payload.put("payment_type", "qris");
+ // ✅ USE SHORT ORDER ID to avoid 50 character limit
JSONObject transactionDetails = new JSONObject();
- transactionDetails.put("order_id", newOrderId); // Use NEW UUID
+ transactionDetails.put("order_id", newOrderId); // ✅ SHORT ORDER ID
transactionDetails.put("gross_amount", originalAmount);
payload.put("transaction_details", transactionDetails);
- // Add customer details
+ // Add customer details (sama seperti sebelumnya)
JSONObject customerDetails = new JSONObject();
customerDetails.put("first_name", "Test");
customerDetails.put("last_name", "Customer");
@@ -412,23 +342,31 @@ public class QrisResultActivity extends AppCompatActivity {
customerDetails.put("phone", "081234567890");
payload.put("customer_details", customerDetails);
- // Add item details
+ // ✅ UPDATE ITEM NAME UNTUK MENANDAKAN QR REFRESH
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
- item.put("id", "item1_refresh_" + System.currentTimeMillis());
+ item.put("id", "item1_qr_refresh_" + System.currentTimeMillis());
item.put("price", originalAmount);
item.put("quantity", 1);
- item.put("name", "QRIS Payment - Refreshed " + new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date()) +
- " (Ref: " + referenceId + ")"); // Include reference in item name
+ item.put("name", "QRIS Payment QR Refresh - " + new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date()) +
+ " (Parent Ref: " + referenceId + ")");
itemDetails.put(item);
payload.put("item_details", itemDetails);
- // ✅ Add custom_field for tracking refresh (AFTER it's created)
+ // ✅ ADD CUSTOM FIELD UNTUK TRACKING PARENT TRANSACTION
payload.put("custom_field1", customField1.toString());
- Log.d("QrisResultFlow", "📤 Refresh payload: " + payload.toString());
+ // ✅ ADD QR REFRESH INDICATOR
+ JSONObject qrisDetails = new JSONObject();
+ qrisDetails.put("acquirer", "gopay");
+ qrisDetails.put("qr_refresh", true);
+ qrisDetails.put("parent_transaction_id", transactionId);
+ qrisDetails.put("refresh_timestamp", System.currentTimeMillis());
+ payload.put("qris", qrisDetails);
- // Make API call to Midtrans
+ Log.d("QrisResultFlow", "📤 QR Refresh payload: " + payload.toString());
+
+ // ✅ MAKE API CALL TO MIDTRANS
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
@@ -437,6 +375,8 @@ public class QrisResultActivity extends AppCompatActivity {
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
conn.setRequestProperty("X-Override-Notification", webhookUrl);
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0 QR-Refresh");
+ conn.setRequestProperty("X-QR-Refresh", "true");
+ conn.setRequestProperty("X-Parent-Transaction", transactionId); // ✅ Header indicator
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
@@ -457,54 +397,59 @@ public class QrisResultActivity extends AppCompatActivity {
response.append(responseLine.trim());
}
- Log.d("QrisResultFlow", "📥 QR refresh response preview: " +
- (response.length() > 200 ? response.substring(0, 200) + "..." : response.toString()));
-
JSONObject jsonResponse = new JSONObject(response.toString());
- // Check for errors in response
+ // ✅ CHECK FOR ERRORS IN RESPONSE
if (jsonResponse.has("status_code")) {
String statusCode = jsonResponse.getString("status_code");
if (!statusCode.equals("201")) {
String statusMessage = jsonResponse.optString("status_message", "Unknown error");
Log.e("QrisResultFlow", "❌ QR refresh failed with status: " + statusCode + " - " + statusMessage);
- // Log full response for debugging
- Log.e("QrisResultFlow", "❌ Full error response: " + response.toString());
+ // ✅ DETAILED ERROR LOGGING
+ Log.e("QrisResultFlow", "❌ Failed payload was: " + payload.toString());
+ Log.e("QrisResultFlow", "❌ Full response: " + response.toString());
return null;
}
}
- // Extract new QR URL and update transaction info
+ // ✅ EXTRACT NEW QR URL
if (jsonResponse.has("actions")) {
JSONArray actionsArray = jsonResponse.getJSONArray("actions");
if (actionsArray.length() > 0) {
JSONObject actions = actionsArray.getJSONObject(0);
String newQrUrl = actions.getString("url");
- // Update transaction info with new data
- String newTransactionId = jsonResponse.optString("transaction_id", transactionId);
- String newTransactionTime = jsonResponse.optString("transaction_time", transactionTime);
+ // ✅ GET NEW TRANSACTION INFO & STORE QR REFRESH TRANSACTION ID
+ String newTransactionId = jsonResponse.optString("transaction_id", "");
+ String newOrderIdFromResponse = jsonResponse.optString("order_id", "");
- // Update class variables for webhook simulation
- this.transactionId = newTransactionId;
- this.transactionTime = newTransactionTime;
- this.orderId = newOrderId; // Update to new order ID
+ // ✅ CRITICAL: Store QR refresh transaction ID for payment monitoring
+ if (!newTransactionId.isEmpty()) {
+ currentQrTransactionId = newTransactionId;
+ isMonitoringQrRefreshTransaction = true;
+ Log.d("QrisResultFlow", "🔄 Now monitoring QR refresh transaction: " + newTransactionId);
+ }
Log.d("QrisResultFlow", "✅ QR refresh successful!");
Log.d("QrisResultFlow", "🆕 New QR URL: " + newQrUrl);
- Log.d("QrisResultFlow", "🆕 New Transaction ID: " + newTransactionId);
- Log.d("QrisResultFlow", "🆕 New Order ID: " + newOrderId);
+ Log.d("QrisResultFlow", "🆕 New QR Transaction ID: " + newTransactionId);
+ Log.d("QrisResultFlow", "🆕 New QR Order ID: " + newOrderIdFromResponse + " (Length: " + newOrderIdFromResponse.length() + ")");
+ Log.d("QrisResultFlow", "🔗 Linked to Parent Transaction: " + transactionId);
+ Log.d("QrisResultFlow", "🔗 Linked to Parent Order: " + orderId);
+
+ // ✅ IMPORTANT: Now monitor QR refresh transaction for payment
+ Log.d("QrisResultFlow", "📝 Payment monitoring switched to QR refresh transaction: " + newTransactionId);
return newQrUrl;
}
}
Log.e("QrisResultFlow", "❌ No actions found in refresh response");
+ Log.e("QrisResultFlow", "❌ Full response: " + response.toString());
return null;
} else {
- // Handle error response
InputStream errorStream = conn.getErrorStream();
String errorResponse = "";
@@ -519,20 +464,7 @@ public class QrisResultActivity extends AppCompatActivity {
}
Log.e("QrisResultFlow", "❌ QR refresh HTTP error " + responseCode + ": " + errorResponse);
-
- // Try to parse error for better logging
- try {
- if (!errorResponse.isEmpty()) {
- JSONObject errorJson = new JSONObject(errorResponse);
- String errorMessage = errorJson.optString("error_messages",
- errorJson.optString("status_message",
- errorJson.optString("message", "Unknown error")));
- Log.e("QrisResultFlow", "❌ Parsed error message: " + errorMessage);
- }
- } catch (JSONException e) {
- Log.e("QrisResultFlow", "❌ Could not parse error response");
- }
-
+ Log.e("QrisResultFlow", "❌ Request payload was: " + payload.toString());
return null;
}
@@ -559,7 +491,6 @@ public class QrisResultActivity extends AppCompatActivity {
qrRefreshHandler.removeCallbacks(qrRefreshRunnable);
}
- // Hide timer elements
timerTextView.setVisibility(View.GONE);
qrStatusTextView.setVisibility(View.GONE);
@@ -567,19 +498,16 @@ public class QrisResultActivity extends AppCompatActivity {
}
private void setupClickListeners() {
- // Download QRIS button
downloadQrisButton.setOnClickListener(v -> downloadQrCode());
- // Check Payment Status button
checkStatusButton.setOnClickListener(v -> {
Log.d("QrisResultFlow", "Check status button clicked");
- stopQrRefresh(); // Stop QR refresh when payment is being processed
+ stopQrRefresh();
simulateWebhook();
});
- // Return to Main Screen button
returnMainButton.setOnClickListener(v -> {
- stopQrRefresh(); // Stop timer when leaving
+ stopQrRefresh();
Intent intent = new Intent(QrisResultActivity.this, com.example.bdkipoc.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
@@ -677,7 +605,6 @@ public class QrisResultActivity extends AppCompatActivity {
}
}
- // Save bitmap to gallery
private void saveImageToGallery(Bitmap bitmap, String fileName) {
try {
String savedImageURL = android.provider.MediaStore.Images.Media.insertImage(
@@ -695,21 +622,93 @@ public class QrisResultActivity extends AppCompatActivity {
}
}
+ // ✅ HELPER: Convert technical issuer name ke display name
+ private String getDisplayName(String technicalName) {
+ if (technicalName == null || technicalName.isEmpty()) {
+ return "QRIS";
+ }
+
+ String lowerTechnicalName = technicalName.toLowerCase().trim();
+ String displayName = ISSUER_DISPLAY_MAP.get(lowerTechnicalName);
+
+ if (displayName != null) {
+ Log.d("QrisResultFlow", "🏷️ Mapped '" + technicalName + "' -> '" + displayName + "'");
+ return displayName;
+ }
+
+ // Fallback: capitalize first letter of each word
+ String[] words = technicalName.split("\\s+");
+ StringBuilder result = new StringBuilder();
+ for (String word : words) {
+ if (word.length() > 0) {
+ result.append(Character.toUpperCase(word.charAt(0)))
+ .append(word.substring(1).toLowerCase())
+ .append(" ");
+ }
+ }
+ String fallbackName = result.toString().trim();
+ Log.d("QrisResultFlow", "🏷️ No mapping found for '" + technicalName + "', using fallback: '" + fallbackName + "'");
+ return fallbackName;
+ }
+
+ // ✅ COMPLETELY FIXED: Use actual issuer from Midtrans response
private void syncTransactionStatusToBackend(String finalStatus) {
Log.d("QrisResultFlow", "🔄 Syncing status '" + finalStatus + "' to backend for reference: " + referenceId);
new Thread(() -> {
try {
- // Update status di backend berdasarkan reference_id (bukan order_id yang berubah)
- JSONObject updatePayload = new JSONObject();
- updatePayload.put("status", finalStatus);
- updatePayload.put("transaction_status", finalStatus);
- updatePayload.put("updated_at", getCurrentISOTime());
- updatePayload.put("settlement_time", getCurrentISOTime());
+ // ✅ GET FINAL ISSUER AND ACQUIRER VALUES
+ String finalIssuer = actualIssuerFromMidtrans;
+ String finalAcquirer = actualAcquirerFromMidtrans;
- // FIXED: Update berdasarkan reference_id yang tetap, bukan order_id yang berubah
- String updateUrl = backendBase + "/transactions/update-by-reference";
- URL url = new URI(updateUrl).toURL();
+ // ✅ FALLBACK IF NOT SET
+ if (finalIssuer.isEmpty()) {
+ finalIssuer = acquirer != null ? acquirer : "qris";
+ }
+ if (finalAcquirer.isEmpty()) {
+ finalAcquirer = acquirer != null ? acquirer : "gopay";
+ }
+
+ // ✅ USE MONITORING TRANSACTION FOR WEBHOOK (could be parent or QR refresh)
+ String webhookTransactionId = !currentQrTransactionId.isEmpty() ? currentQrTransactionId : transactionId;
+ String transactionType = isMonitoringQrRefreshTransaction ? "QR refresh transaction" : "parent transaction";
+
+ Log.d("QrisResultFlow", "🏷️ Final webhook values:");
+ Log.d("QrisResultFlow", " Transaction ID: '" + webhookTransactionId + "' (" + transactionType + ")");
+ Log.d("QrisResultFlow", " Order ID: '" + orderId + "'");
+ Log.d("QrisResultFlow", " Issuer: '" + finalIssuer + "'");
+ Log.d("QrisResultFlow", " Acquirer: '" + finalAcquirer + "'");
+
+ // ✅ FORMAT WEBHOOK MIDTRANS STANDARD
+ JSONObject payload = new JSONObject();
+ payload.put("status_code", "200");
+ payload.put("status_message", "Success, transaction is found");
+ payload.put("transaction_id", webhookTransactionId); // ✅ Use monitoring transaction
+ payload.put("order_id", orderId); // ✅ Keep parent order ID for reference
+ payload.put("merchant_id", merchantId != null ? merchantId : "G900255786");
+ payload.put("gross_amount", grossAmount != null ? grossAmount : String.valueOf(originalAmount));
+ payload.put("currency", "IDR");
+ payload.put("payment_type", "qris");
+ payload.put("transaction_time", transactionTime != null ? transactionTime : getCurrentDateTime());
+ payload.put("transaction_status", finalStatus.equals("PAID") ? "settlement" : finalStatus.toLowerCase());
+ payload.put("fraud_status", "accept");
+ payload.put("acquirer", finalAcquirer);
+ payload.put("issuer", finalIssuer); // ✅ Use actual issuer
+ payload.put("settlement_time", getCurrentISOTime());
+ payload.put("reference_id", referenceId);
+ payload.put("shopeepay_reference_number", "");
+
+ // ✅ SIGNATURE untuk validasi
+ String serverKey = getServerKey();
+ String signature = generateSignature(orderId, "200",
+ grossAmount != null ? grossAmount : String.valueOf(originalAmount), serverKey);
+ payload.put("signature_key", signature);
+
+ Log.d("QrisResultFlow", "📤 Webhook payload: " + payload.toString());
+
+ // ✅ KIRIM KE WEBHOOK ENDPOINT
+ String webhookUrl = backendBase + "/webhooks/midtrans";
+ URL url = new URI(webhookUrl).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
@@ -719,33 +718,23 @@ public class QrisResultActivity extends AppCompatActivity {
conn.setConnectTimeout(15000);
conn.setReadTimeout(15000);
- // Body dengan reference_id sebagai identifier
- JSONObject body = new JSONObject();
- body.put("reference_id", referenceId); // FIXED: Gunakan reference_id yang tidak berubah
- body.put("update_data", updatePayload);
-
- Log.d("QrisResultFlow", "📤 Backend update payload: " + body.toString());
-
try (OutputStream os = conn.getOutputStream()) {
- byte[] input = body.toString().getBytes("utf-8");
+ byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
- Log.d("QrisResultFlow", "📥 Backend update response: " + responseCode);
+ Log.d("QrisResultFlow", "📥 Webhook response: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
- // Read response
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
-
- Log.d("QrisResultFlow", "✅ Backend status updated successfully: " + response.toString());
+ Log.d("QrisResultFlow", "✅ Webhook successful: " + response.toString());
} else {
- // Read error response
InputStream errorStream = conn.getErrorStream();
if (errorStream != null) {
BufferedReader br = new BufferedReader(new InputStreamReader(errorStream));
@@ -754,12 +743,12 @@ public class QrisResultActivity extends AppCompatActivity {
while ((line = br.readLine()) != null) {
errorResponse.append(line);
}
- Log.e("QrisResultFlow", "❌ Backend update failed: " + responseCode + " - " + errorResponse.toString());
+ Log.e("QrisResultFlow", "❌ Webhook failed: " + responseCode + " - " + errorResponse.toString());
}
}
} catch (Exception e) {
- Log.e("QrisResultFlow", "❌ Backend sync error: " + e.getMessage(), e);
+ Log.e("QrisResultFlow", "❌ Webhook error: " + e.getMessage(), e);
}
}).start();
}
@@ -769,80 +758,18 @@ public class QrisResultActivity extends AppCompatActivity {
.format(new java.util.Date());
}
+ // ✅ SIMPLIFIED: Use the main webhook method
private void syncStatusToBackend(String referenceId, String finalStatus, String paidOrderId) {
- new Thread(() -> {
- try {
- Log.d("QrisResultFlow", "🔄 Syncing status to backend - Ref: " + referenceId + " Status: " + finalStatus);
-
- // Create update payload
- JSONObject updatePayload = new JSONObject();
- updatePayload.put("status", finalStatus);
- updatePayload.put("payment_status", finalStatus);
- updatePayload.put("paid_order_id", paidOrderId); // Order ID yang actual dibayar
- updatePayload.put("updated_at", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
- updatePayload.put("payment_completed_at", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
-
- // API call to backend untuk update transaction berdasarkan reference_id
- String updateUrl = backendBase + "/transactions/update-by-reference";
- URL url = new URI(updateUrl).toURL();
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-Type", "application/json");
- conn.setRequestProperty("Accept", "application/json");
- conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
- conn.setDoOutput(true);
- conn.setConnectTimeout(15000);
- conn.setReadTimeout(15000);
-
- // Request body dengan reference_id sebagai filter
- JSONObject requestBody = new JSONObject();
- requestBody.put("reference_id", referenceId);
- requestBody.put("update_data", updatePayload);
-
- try (OutputStream os = conn.getOutputStream()) {
- byte[] input = requestBody.toString().getBytes("utf-8");
- os.write(input, 0, input.length);
- }
-
- int responseCode = conn.getResponseCode();
- Log.d("QrisResultFlow", "📥 Backend sync response: " + responseCode);
-
- if (responseCode == 200 || responseCode == 201) {
- BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- StringBuilder response = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- response.append(line);
- }
- Log.d("QrisResultFlow", "✅ Backend sync successful: " + response.toString());
- } else {
- // Log error response
- InputStream errorStream = conn.getErrorStream();
- if (errorStream != null) {
- BufferedReader br = new BufferedReader(new InputStreamReader(errorStream));
- StringBuilder errorResponse = new StringBuilder();
- String line;
- while ((line = br.readLine()) != null) {
- errorResponse.append(line);
- }
- Log.e("QrisResultFlow", "❌ Backend sync failed: " + responseCode + " - " + errorResponse.toString());
- }
- }
-
- } catch (Exception e) {
- Log.e("QrisResultFlow", "❌ Backend sync error: " + e.getMessage(), e);
- }
- }).start();
+ syncTransactionStatusToBackend(finalStatus);
}
private void showPaymentSuccess() {
Log.d("QrisResultFlow", "Showing payment success screen");
- // Stop QR refresh when payment is successful
stopQrRefresh();
// ✅ SYNC STATUS KE BACKEND
- syncStatusToBackend(referenceId, "PAID", orderId);
+ syncTransactionStatusToBackend("PAID");
// Hide payment elements
qrImageView.setVisibility(View.GONE);
@@ -859,16 +786,10 @@ public class QrisResultActivity extends AppCompatActivity {
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
+ // Automatically launch receipt after a short delay
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
launchReceiptActivity();
- }, 2000); // Launch receipt after 2 seconds
+ }, 2000);
Toast.makeText(this, "Payment simulation completed successfully!", Toast.LENGTH_LONG).show();
}
@@ -876,24 +797,12 @@ public class QrisResultActivity extends AppCompatActivity {
@Override
protected void onDestroy() {
super.onDestroy();
- stopQrRefresh(); // Cleanup timer when activity is destroyed
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- // Keep timer running in background, but could be paused if needed
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- // Resume timer if it was paused
+ stopQrRefresh();
}
@Override
public void onBackPressed() {
- stopQrRefresh(); // Stop timer when user goes back
+ stopQrRefresh();
Intent intent = new Intent(this, com.example.bdkipoc.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
@@ -914,10 +823,9 @@ public class QrisResultActivity extends AppCompatActivity {
while (attempt < maxAttempts && !found) {
try {
- // FIXED: Poll for CURRENT orderId (might be refreshed)
- String currentOrderId = this.orderId; // Use current order ID
+ String currentOrderId = this.orderId;
String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + currentOrderId + "\"}";
- Log.d("QrisResultFlow", "Polling attempt " + (attempt + 1) + "/" + maxAttempts + " for: " + currentOrderId);
+ Log.d("QrisResultFlow", "Polling attempt " + (attempt + 1) + "/" + maxAttempts + " for parent order: " + currentOrderId);
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@@ -954,7 +862,6 @@ public class QrisResultActivity extends AppCompatActivity {
Log.d("QrisResultFlow", "Log entry " + i + ": order_id=" + logOrderId +
", transaction_status=" + transactionStatus);
- // FIXED: Check for any valid payment status, not just pending
if (currentOrderId.equals(logOrderId) &&
(transactionStatus.equals("pending") ||
transactionStatus.equals("settlement") ||
@@ -962,21 +869,19 @@ public class QrisResultActivity extends AppCompatActivity {
transactionStatus.equals("success"))) {
found = true;
- // FIXED: If payment is already settled, show success immediately
if (transactionStatus.equals("settlement") ||
transactionStatus.equals("capture") ||
transactionStatus.equals("success")) {
Log.d("QrisResultFlow", "🎉 Payment already completed with status: " + transactionStatus);
- // Stop polling and show success
new Handler(Looper.getMainLooper()).post(() -> {
progressBar.setVisibility(View.GONE);
- stopQrRefresh(); // Stop QR refresh
+ stopQrRefresh();
showPaymentSuccess();
Toast.makeText(QrisResultActivity.this, "Payment completed!", Toast.LENGTH_LONG).show();
});
- return; // Exit polling thread
+ return;
}
Log.d("QrisResultFlow", "Found matching payment log with status: " + transactionStatus);
@@ -984,8 +889,6 @@ public class QrisResultActivity extends AppCompatActivity {
}
}
}
- } else {
- Log.d("QrisResultFlow", "No log entries found in response");
}
} else {
Log.w("QrisResultFlow", "Polling failed with HTTP code: " + responseCode);
@@ -1022,7 +925,6 @@ public class QrisResultActivity extends AppCompatActivity {
}).start();
}
-
private String getServerKey() {
try {
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
@@ -1056,33 +958,31 @@ public class QrisResultActivity extends AppCompatActivity {
private void startContinuousPaymentMonitoring() {
Log.d("QrisResultFlow", "🔍 Starting continuous payment monitoring");
- // Create a monitoring handler separate from QR refresh
Handler paymentMonitorHandler = new Handler(Looper.getMainLooper());
Runnable paymentMonitorRunnable = new Runnable() {
@Override
public void run() {
- // Check payment status every 5 seconds
checkCurrentPaymentStatus();
- // Schedule next check if activity is still active
if (!isFinishing() && isQrRefreshActive) {
- paymentMonitorHandler.postDelayed(this, 5000); // Check every 5 seconds
+ paymentMonitorHandler.postDelayed(this, 5000);
}
}
};
- // Start monitoring
paymentMonitorHandler.post(paymentMonitorRunnable);
}
private void checkCurrentPaymentStatus() {
new Thread(() -> {
try {
- // Check status untuk current transaction ID
- String statusUrl = "https://api.sandbox.midtrans.com/v2/" + transactionId + "/status";
+ // ✅ CRITICAL: Monitor current QR transaction (could be parent or QR refresh)
+ String monitoringTransactionId = !currentQrTransactionId.isEmpty() ? currentQrTransactionId : transactionId;
+ String statusUrl = "https://api.sandbox.midtrans.com/v2/" + monitoringTransactionId + "/status";
- Log.d("QrisResultFlow", "🔍 Checking payment status for: " + transactionId);
+ String transactionType = isMonitoringQrRefreshTransaction ? "QR refresh transaction" : "parent transaction";
+ Log.d("QrisResultFlow", "🔍 Checking payment status for " + transactionType + ": " + monitoringTransactionId);
URL url = new URI(statusUrl).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@@ -1108,41 +1008,57 @@ public class QrisResultActivity extends AppCompatActivity {
String paymentType = statusResponse.optString("payment_type", "");
String grossAmount = statusResponse.optString("gross_amount", "");
- Log.d("QrisResultFlow", "💳 Payment status check result:");
+ // ✅ PROPERLY EXTRACT AND STORE ACTUAL ISSUER/ACQUIRER
+ String actualIssuer = statusResponse.optString("issuer", "");
+ String actualAcquirer = statusResponse.optString("acquirer", "");
+
+ Log.d("QrisResultFlow", "💳 Payment status check result (" + transactionType + "):");
Log.d("QrisResultFlow", " Status: " + transactionStatus);
Log.d("QrisResultFlow", " Payment Type: " + paymentType);
Log.d("QrisResultFlow", " Amount: " + grossAmount);
+ Log.d("QrisResultFlow", " Actual Issuer: " + actualIssuer);
+ Log.d("QrisResultFlow", " Actual Acquirer: " + actualAcquirer);
+
+ // ✅ CRITICAL FIX: Store the actual values from Midtrans
+ if (!actualIssuer.isEmpty() && !actualIssuer.equalsIgnoreCase("qris")) {
+ actualIssuerFromMidtrans = actualIssuer;
+ Log.d("QrisResultFlow", "✅ Updated issuer from Midtrans: " + actualIssuer);
+ }
+
+ if (!actualAcquirer.isEmpty() && !actualAcquirer.equalsIgnoreCase("qris")) {
+ actualAcquirerFromMidtrans = actualAcquirer;
+ Log.d("QrisResultFlow", "✅ Updated acquirer from Midtrans: " + actualAcquirer);
+ }
+
+ // Update backward compatibility variable
+ if (!actualIssuer.isEmpty()) {
+ acquirer = actualIssuer;
+ }
- // Check for success/settlement status
if (transactionStatus.equals("settlement") ||
transactionStatus.equals("capture") ||
transactionStatus.equals("success")) {
- Log.d("QrisResultFlow", "🎉 Payment detected as PAID! Status: " + transactionStatus);
+ Log.d("QrisResultFlow", "🎉 Payment detected as PAID! Status: " + transactionStatus + " (" + transactionType + ")");
- // ✅ TAMBAHKAN: Sync ke backend sebelum update UI
- syncTransactionStatusToBackend("PAID");
-
- // Update UI on main thread
runOnUiThread(() -> {
- stopQrRefresh(); // Stop QR refresh
+ stopQrRefresh();
+ syncTransactionStatusToBackend("PAID");
showPaymentSuccess();
Toast.makeText(QrisResultActivity.this, "Payment Successful! 🎉", Toast.LENGTH_LONG).show();
});
} else if (transactionStatus.equals("pending")) {
- Log.d("QrisResultFlow", "⏳ Payment still pending");
+ Log.d("QrisResultFlow", "⏳ Payment still pending (" + transactionType + ")");
} else if (transactionStatus.equals("expire") || transactionStatus.equals("cancel")) {
- Log.w("QrisResultFlow", "⚠️ Payment expired or cancelled: " + transactionStatus);
-
- // ✅ TAMBAHKAN: Sync status failed ke backend
+ Log.w("QrisResultFlow", "⚠️ Payment expired or cancelled: " + transactionStatus + " (" + transactionType + ")");
syncTransactionStatusToBackend("FAILED");
} else {
- Log.d("QrisResultFlow", "📊 Payment status: " + transactionStatus);
+ Log.d("QrisResultFlow", "📊 Payment status: " + transactionStatus + " (" + transactionType + ")");
}
} else {
- Log.w("QrisResultFlow", "⚠️ Payment status check failed: HTTP " + responseCode);
+ Log.w("QrisResultFlow", "⚠️ Payment status check failed: HTTP " + responseCode + " (" + transactionType + ")");
}
} catch (Exception e) {
@@ -1151,54 +1067,53 @@ public class QrisResultActivity extends AppCompatActivity {
}).start();
}
- // FIXED: Update simulateWebhook to use current transaction data
private void simulateWebhook() {
Log.d("QrisResultFlow", "🚀 Starting webhook simulation");
progressBar.setVisibility(View.VISIBLE);
statusTextView.setText("Simulating payment...");
checkStatusButton.setEnabled(false);
- // Stop QR refresh during payment simulation
stopQrRefresh();
new Thread(() -> {
try {
String serverKey = getServerKey();
- // FIXED: Use current transaction data (after potential refresh)
+ // ✅ USE MONITORING TRANSACTION FOR SIMULATION
String currentOrderId = this.orderId;
- String currentTransactionId = this.transactionId;
+ String currentTransactionId = !this.currentQrTransactionId.isEmpty() ? this.currentQrTransactionId : this.transactionId;
String currentGrossAmount = this.grossAmount;
+ String transactionType = isMonitoringQrRefreshTransaction ? "QR refresh transaction" : "parent transaction";
- Log.d("QrisResultFlow", "🎯 Simulating webhook for:");
- Log.d("QrisResultFlow", " Order ID: " + currentOrderId);
- Log.d("QrisResultFlow", " Transaction ID: " + currentTransactionId);
- Log.d("QrisResultFlow", " Amount: " + currentGrossAmount);
+ Log.d("QrisResultFlow", "🚀 Simulating webhook for " + transactionType + ": " + currentTransactionId);
String signatureKey = generateSignature(currentOrderId, "200", currentGrossAmount, serverKey);
+ // ✅ GET ACTUAL ISSUER AND ACQUIRER
+ String finalIssuer = actualIssuerFromMidtrans.isEmpty() ?
+ (acquirer != null ? acquirer : "qris") : actualIssuerFromMidtrans;
+ String finalAcquirer = actualAcquirerFromMidtrans.isEmpty() ?
+ (acquirer != null ? acquirer : "gopay") : actualAcquirerFromMidtrans;
+
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
payload.put("transaction_status", "settlement");
- payload.put("transaction_id", currentTransactionId);
+ payload.put("transaction_id", currentTransactionId); // ✅ Use monitoring transaction
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", signatureKey);
payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
payload.put("payment_type", "qris");
payload.put("order_id", currentOrderId);
- payload.put("merchant_id", merchantId != null ? merchantId : "DUMMY_MERCHANT_ID");
- payload.put("issuer", acquirer != null ? acquirer : "gopay");
+ payload.put("merchant_id", merchantId != null ? merchantId : "G900255786");
+ payload.put("issuer", finalIssuer); // ✅ Use actual issuer
payload.put("gross_amount", currentGrossAmount);
payload.put("fraud_status", "accept");
payload.put("currency", "IDR");
- payload.put("acquirer", acquirer != null ? acquirer : "gopay");
+ payload.put("acquirer", finalAcquirer); // ✅ Use actual acquirer
payload.put("shopeepay_reference_number", "");
payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID");
-
- Log.d("QrisResultFlow", "📤 Webhook payload preview: " +
- (payload.toString().length() > 200 ? payload.toString().substring(0, 200) + "..." : payload.toString()));
URL url = new URL(webhookUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@@ -1244,39 +1159,48 @@ public class QrisResultActivity extends AppCompatActivity {
private void launchReceiptActivity() {
Intent intent = new Intent(this, ReceiptActivity.class);
- // Add calling activity information for proper back navigation
intent.putExtra("calling_activity", "QrisResultActivity");
+ // ✅ GET FINAL ISSUER FOR RECEIPT
+ String finalIssuer = actualIssuerFromMidtrans.isEmpty() ?
+ (acquirer != null ? acquirer : "qris") : actualIssuerFromMidtrans;
+
+ String displayCardType = getDisplayName(finalIssuer);
+
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);
+ Log.d("QrisResultFlow", " Actual Issuer: " + finalIssuer);
+ Log.d("QrisResultFlow", " Display Card Type: " + displayCardType);
intent.putExtra("transaction_id", transactionId);
- intent.putExtra("reference_id", referenceId); // Nomor Transaksi
+ intent.putExtra("reference_id", referenceId);
intent.putExtra("order_id", orderId);
- intent.putExtra("transaction_amount", String.valueOf(originalAmount)); // Total transaksi
+ intent.putExtra("transaction_amount", String.valueOf(originalAmount));
intent.putExtra("gross_amount", grossAmount != null ? grossAmount : String.valueOf(originalAmount));
- intent.putExtra("created_at", getCurrentISOTime()); // Tanggal transaksi (current time for QRIS)
- intent.putExtra("transaction_date", getCurrentDateTime()); // Backup formatted date
+ intent.putExtra("created_at", getReceiptDateTime());
+ intent.putExtra("transaction_date", getReceiptDateTime());
intent.putExtra("payment_method", "QRIS");
- intent.putExtra("channel_code", "QRIS"); // Metode Pembayaran
+ intent.putExtra("channel_code", "QRIS");
intent.putExtra("channel_category", "RETAIL_OUTLET");
- intent.putExtra("card_type", "QRIS");
+ intent.putExtra("card_type", displayCardType); // ✅ Use display name
intent.putExtra("merchant_name", "Marcel Panjaitan");
intent.putExtra("merchant_location", "Jakarta, Indonesia");
- intent.putExtra("acquirer", acquirer != null ? acquirer : "qris"); // Jenis Kartu
-
- // Add MID and TID with default values
- intent.putExtra("mid", "71000026521"); // MID
- intent.putExtra("tid", "73001500"); // TID
+ intent.putExtra("acquirer", finalIssuer); // ✅ Use actual issuer
+ intent.putExtra("mid", "71000026521");
+ intent.putExtra("tid", "73001500");
startActivity(intent);
}
private String getCurrentDateTime() {
- java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", new java.util.Locale("id", "ID"));
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("d/M/y H:m:s", new java.util.Locale("id", "ID"));
+ return sdf.format(new java.util.Date());
+ }
+
+ private String getReceiptDateTime() {
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("d/M/y H:m:s", new java.util.Locale("id", "ID"));
return sdf.format(new java.util.Date());
}
diff --git a/app/src/main/res/layout/activity_credit_card.xml b/app/src/main/res/layout/activity_credit_card.xml
new file mode 100644
index 0000000..bde7175
--- /dev/null
+++ b/app/src/main/res/layout/activity_credit_card.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4664116..0e49a9a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,4 +13,6 @@
Payment Successful!
Return to Main Screen
POC
+
+ Connection to payment service failed
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 2d00053..a81fd20 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -16,6 +16,11 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ repositories {
+ flatDir {
+ dirs 'app/libs'
+ }
+ }
}
}