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 @@ + + + + + + + + + +