From b66ef4bb00243f7f8693e3c958593b380045f153 Mon Sep 17 00:00:00 2001 From: riz081 Date: Thu, 26 Jun 2025 15:48:12 +0700 Subject: [PATCH] proeses transaction post backend and database --- .../com/example/bdkipoc/MainActivity.java | 202 +++++- .../CreateTransactionActivity.java | 642 +++++++----------- .../managers/MidtransCardPaymentManager.java | 553 ++++++++++++++- .../PostTransactionBackendManager.java | 358 ++++++++++ 4 files changed, 1349 insertions(+), 406 deletions(-) create mode 100644 app/src/main/java/com/example/bdkipoc/transaction/managers/PostTransactionBackendManager.java diff --git a/app/src/main/java/com/example/bdkipoc/MainActivity.java b/app/src/main/java/com/example/bdkipoc/MainActivity.java index 9c6e46e..af9cda2 100644 --- a/app/src/main/java/com/example/bdkipoc/MainActivity.java +++ b/app/src/main/java/com/example/bdkipoc/MainActivity.java @@ -22,6 +22,7 @@ import com.google.android.material.button.MaterialButton; import com.example.bdkipoc.cetakulang.ReprintActivity; import com.example.bdkipoc.cetakulang.ReprintAdapterActivity; +import com.example.bdkipoc.R; import com.example.bdkipoc.transaction.CreateTransactionActivity; import com.example.bdkipoc.transaction.ResultTransactionActivity; @@ -156,22 +157,23 @@ public class MainActivity extends AppCompatActivity { CardView cardView = findViewById(cardId); if (cardView != null) { cardView.setOnClickListener(v -> { + // ✅ ENHANCED: Navigate with payment type information if (cardId == R.id.card_kartu_kredit) { - startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); + navigateToCreateTransaction("credit_card", cardId, "Kartu Kredit"); } else if (cardId == R.id.card_kartu_debit) { - startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); + navigateToCreateTransaction("debit_card", cardId, "Kartu Debit"); } else if (cardId == R.id.card_qris) { startActivity(new Intent(MainActivity.this, QrisActivity.class)); // Col-2 } else if (cardId == R.id.card_transfer) { - startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); + navigateToCreateTransaction("transfer", cardId, "Transfer"); } else if (cardId == R.id.card_uang_elektronik) { - startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); + navigateToCreateTransaction("e_money", cardId, "Uang Elektronik"); } else if (cardId == R.id.card_cetak_ulang) { startActivity(new Intent(MainActivity.this, ReprintActivity.class)); // Col-3 } else if (cardId == R.id.card_refund) { - startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class)); + navigateToCreateTransaction("refund", cardId, "Refund"); } else if (cardId == R.id.card_settlement) { Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show(); } else if (cardId == R.id.card_histori) { @@ -185,7 +187,7 @@ public class MainActivity extends AppCompatActivity { Toast.makeText(this, "Pengaturan - Coming Soon", Toast.LENGTH_SHORT).show(); } else { // Fallback for any other cards - Toast.makeText(this, "Menu Diklik: " + getResources().getResourceEntryName(cardId), Toast.LENGTH_SHORT).show(); + navigateToCreateTransaction("credit_card", cardId, "Unknown"); } }); } @@ -242,6 +244,125 @@ public class MainActivity extends AppCompatActivity { } } + // ✅ NEW: Enhanced navigation method with payment type information + private void navigateToCreateTransaction(String paymentType, int cardMenuId, String cardName) { + try { + Intent intent = new Intent(MainActivity.this, CreateTransactionActivity.class); + + // ✅ ENHANCED: Pass comprehensive payment information + intent.putExtra("PAYMENT_TYPE", paymentType); + intent.putExtra("CARD_MENU_ID", cardMenuId); + intent.putExtra("CARD_NAME", cardName); + intent.putExtra("CALLING_ACTIVITY", "MainActivity"); + + // ✅ DEBUG: Log navigation details + android.util.Log.d("MainActivity", "=== NAVIGATING TO CREATE TRANSACTION ==="); + android.util.Log.d("MainActivity", "Payment Type: " + paymentType); + android.util.Log.d("MainActivity", "Card Menu ID: " + cardMenuId); + android.util.Log.d("MainActivity", "Card Name: " + cardName); + android.util.Log.d("MainActivity", "========================================"); + + startActivity(intent); + + } catch (Exception e) { + android.util.Log.e("MainActivity", "Error navigating to CreateTransaction: " + e.getMessage(), e); + Toast.makeText(this, "Error opening transaction: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + // ✅ NEW: Helper method to get payment type from card ID (for backward compatibility) + private String getPaymentTypeFromCardId(int cardId) { + if (cardId == R.id.card_kartu_kredit) { + return "credit_card"; + } else if (cardId == R.id.card_kartu_debit) { + return "debit_card"; + } else if (cardId == R.id.card_qris) { + return "qris"; + } else if (cardId == R.id.card_transfer) { + return "transfer"; + } else if (cardId == R.id.card_uang_elektronik) { + return "e_money"; + } else if (cardId == R.id.card_refund) { + return "refund"; + } else { + android.util.Log.w("MainActivity", "Unknown card ID: " + cardId + ", defaulting to credit_card"); + return "credit_card"; + } + } + + // ✅ NEW: Helper method to get card name from card ID + private String getCardNameFromCardId(int cardId) { + if (cardId == R.id.card_kartu_kredit) { + return "Kartu Kredit"; + } else if (cardId == R.id.card_kartu_debit) { + return "Kartu Debit"; + } else if (cardId == R.id.card_qris) { + return "QRIS"; + } else if (cardId == R.id.card_transfer) { + return "Transfer"; + } else if (cardId == R.id.card_uang_elektronik) { + return "Uang Elektronik"; + } else if (cardId == R.id.card_refund) { + return "Refund"; + } else if (cardId == R.id.card_settlement) { + return "Settlement"; + } else if (cardId == R.id.card_histori) { + return "Histori"; + } else if (cardId == R.id.card_cetak_ulang) { + return "Cetak Ulang"; + } else if (cardId == R.id.card_bantuan) { + return "Bantuan"; + } else if (cardId == R.id.card_info_toko) { + return "Info Toko"; + } else if (cardId == R.id.card_pengaturan) { + return "Pengaturan"; + } else { + return "Unknown"; + } + } + + // ✅ NEW: Method to validate payment type compatibility + private boolean isPaymentTypeSupported(String paymentType) { + String[] supportedTypes = { + "credit_card", "debit_card", "e_money", "qris", + "transfer", "refund" + }; + + for (String supportedType : supportedTypes) { + if (supportedType.equals(paymentType)) { + return true; + } + } + + return false; + } + + // ✅ NEW: Method to show payment type selection dialog (for future use) + private void showPaymentTypeDialog() { + androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this); + builder.setTitle("Pilih Jenis Pembayaran"); + + String[] paymentTypes = { + "Kartu Kredit", "Kartu Debit", "Uang Elektronik", + "QRIS", "Transfer", "Refund" + }; + String[] paymentTypeCodes = { + "credit_card", "debit_card", "e_money", + "qris", "transfer", "refund" + }; + + builder.setItems(paymentTypes, (dialog, which) -> { + String selectedType = paymentTypeCodes[which]; + String selectedName = paymentTypes[which]; + + // Use a generic card ID for dialog selection + navigateToCreateTransaction(selectedType, -1, selectedName); + }); + + builder.setNegativeButton("Batal", null); + builder.show(); + } + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -256,5 +377,74 @@ public class MainActivity extends AppCompatActivity { // Clear any transaction completion flags to avoid repeated messages getIntent().removeExtra("transaction_completed"); getIntent().removeExtra("transaction_amount"); + + // ✅ NEW: Log resume for debugging + android.util.Log.d("MainActivity", "MainActivity resumed"); + } + + @Override + protected void onPause() { + super.onPause(); + android.util.Log.d("MainActivity", "MainActivity paused"); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + android.util.Log.d("MainActivity", "MainActivity destroyed"); + } + + // ✅ NEW: Method to handle direct payment type launch (for external calls) + public static Intent createTransactionIntent(android.content.Context context, String paymentType, String cardName) { + Intent intent = new Intent(context, CreateTransactionActivity.class); + intent.putExtra("PAYMENT_TYPE", paymentType); + intent.putExtra("CARD_NAME", cardName); + intent.putExtra("CALLING_ACTIVITY", "External"); + return intent; + } + + // ✅ NEW: Public method to simulate card click (for testing) + public void simulateCardClick(int cardId) { + CardView cardView = findViewById(cardId); + if (cardView != null) { + cardView.performClick(); + } else { + android.util.Log.w("MainActivity", "Card not found for ID: " + cardId); + } + } + + // ✅ NEW: Method to get all available payment types + public String[] getAvailablePaymentTypes() { + return new String[]{ + "credit_card", "debit_card", "e_money", + "qris", "transfer", "refund" + }; + } + + // ✅ NEW: Method to get payment type display names + public String[] getPaymentTypeDisplayNames() { + return new String[]{ + "Kartu Kredit", "Kartu Debit", "Uang Elektronik", + "QRIS", "Transfer", "Refund" + }; + } + + // ✅ NEW: Debug method to log all card IDs and their payment types + private void debugCardMappings() { + android.util.Log.d("MainActivity", "=== CARD PAYMENT TYPE MAPPINGS ==="); + + int[] cardIds = { + R.id.card_kartu_kredit, R.id.card_kartu_debit, R.id.card_qris, + R.id.card_transfer, R.id.card_uang_elektronik, R.id.card_refund + }; + + for (int cardId : cardIds) { + String paymentType = getPaymentTypeFromCardId(cardId); + String cardName = getCardNameFromCardId(cardId); + android.util.Log.d("MainActivity", + "Card ID: " + cardId + " -> Payment Type: " + paymentType + " -> Name: " + cardName); + } + + android.util.Log.d("MainActivity", "=================================="); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java b/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java index 8ff866b..b59d2e5 100644 --- a/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java +++ b/app/src/main/java/com/example/bdkipoc/transaction/CreateTransactionActivity.java @@ -24,6 +24,7 @@ import com.example.bdkipoc.transaction.managers.EMVManager; import com.example.bdkipoc.transaction.managers.ModalManager; import com.example.bdkipoc.transaction.managers.PinPadManager; import com.example.bdkipoc.transaction.managers.MidtransCardPaymentManager; +import com.example.bdkipoc.transaction.managers.PostTransactionBackendManager; import java.text.NumberFormat; import java.util.Locale; @@ -34,14 +35,17 @@ import org.json.JSONObject; import org.json.JSONException; /** - * CreateTransactionActivity - Updated with Enhanced Midtrans Credit Card Integration - * Handles amount input, card scanning, and Midtrans payment processing with improved bank detection + * CreateTransactionActivity - Enhanced with Backend Integration + * + * Flow: Backend Post Transaction => EMV/Card Processing => Midtrans Charge => Results + * The transaction_uuid from backend is used as order_id in Midtrans */ public class CreateTransactionActivity extends AppCompatActivity implements EMVManager.EMVManagerCallback, CardScannerManager.CardScannerCallback, PinPadManager.PinPadManagerCallback, - MidtransCardPaymentManager.MidtransCardPaymentCallback { + MidtransCardPaymentManager.MidtransCardPaymentCallback, + PostTransactionBackendManager.PostTransactionCallback { private static final String TAG = "CreateTransaction"; @@ -60,20 +64,26 @@ public class CreateTransactionActivity extends AppCompatActivity implements // State Management private String transactionAmount = "0"; private boolean isEMVMode = true; - private boolean useDirectMidtransPayment = true; // Toggle for Midtrans integration + private boolean useDirectMidtransPayment = true; + + // ✅ NEW: Payment type and backend integration + private String paymentType = "credit_card"; // Default + private String transactionUuid; // From backend response + private String backendTransactionStatus = "INIT"; // Manager Classes private EMVManager emvManager; private CardScannerManager cardScannerManager; private PinPadManager pinPadManager; private ModalManager modalManager; - private MidtransCardPaymentManager midtransPaymentManager; // ✅ NEW: Midtrans integration + private MidtransCardPaymentManager midtransPaymentManager; + private PostTransactionBackendManager backendManager; // ✅ NEW: Backend manager // EMV Dialog private AlertDialog mAppSelectDialog; private int mSelectIndex; - // ✅ NEW: EMV Data Storage for Midtrans + // EMV Data Storage for Midtrans private String emvCardNumber; private String emvExpiryDate; private String emvCardholderName; @@ -81,10 +91,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements private String emvTlvData; private String referenceId; - // ✅ ENHANCED: Store last response for better debugging + // Enhanced response storage private JSONObject lastMidtransResponse; + private JSONObject lastBackendResponse; // ✅ NEW: Store backend response - // deklarasi variabel success screen + // Success screen components private LinearLayout successScreen; private ImageView successIcon; private TextView successMessage; @@ -95,14 +106,16 @@ public class CreateTransactionActivity extends AppCompatActivity implements setContentView(R.layout.activity_create_transaction); initViews(); + extractPaymentTypeFromIntent(); // ✅ NEW: Determine payment type initManagers(); setupListeners(); updateAmountDisplay(); updateModeDisplay(); - // ✅ NEW: Generate reference ID for transaction tracking - referenceId = generateReferenceId(); + // Generate reference ID for transaction tracking + referenceId = PostTransactionBackendManager.generateReferenceId(); Log.d(TAG, "Generated reference ID: " + referenceId); + Log.d(TAG, "Payment type determined: " + paymentType); } private void initViews() { @@ -133,6 +146,38 @@ public class CreateTransactionActivity extends AppCompatActivity implements successMessage = findViewById(R.id.success_message); } + // ✅ NEW: Extract payment type from intent (based on MainActivity card selection) + private void extractPaymentTypeFromIntent() { + Intent intent = getIntent(); + + // Check if payment type was passed directly + String intentPaymentType = intent.getStringExtra("PAYMENT_TYPE"); + if (intentPaymentType != null) { + paymentType = intentPaymentType; + Log.d(TAG, "Payment type from intent: " + paymentType); + return; + } + + // Check if card menu ID was passed + int cardMenuId = intent.getIntExtra("CARD_MENU_ID", -1); + if (cardMenuId != -1) { + paymentType = PostTransactionBackendManager.mapCardMenuToPaymentType(cardMenuId); + Log.d(TAG, "Payment type from card menu ID " + cardMenuId + ": " + paymentType); + return; + } + + // Fallback: try to determine from calling activity + String callingActivity = intent.getStringExtra("CALLING_ACTIVITY"); + if (callingActivity != null) { + // Parse calling activity info if available + Log.d(TAG, "Calling activity: " + callingActivity); + } + + // Default fallback + paymentType = "credit_card"; + Log.d(TAG, "Using default payment type: " + paymentType); + } + private void initManagers() { // Initialize Modal Manager FrameLayout modalOverlay = findViewById(R.id.modal_overlay); @@ -145,13 +190,16 @@ public class CreateTransactionActivity extends AppCompatActivity implements cardScannerManager = new CardScannerManager(this); pinPadManager = new PinPadManager(this); - // ✅ NEW: Initialize Midtrans payment manager + // Initialize Midtrans payment manager midtransPaymentManager = new MidtransCardPaymentManager(this, this); + // ✅ NEW: Initialize Backend manager + backendManager = new PostTransactionBackendManager(this, this); + // Initialize EMV data emvManager.initEMVData(); - Log.d(TAG, "All managers initialized including Midtrans"); + Log.d(TAG, "All managers initialized including Backend and Midtrans"); } private void setupListeners() { @@ -183,7 +231,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements // Clear button (ImageView) btnClear.setOnClickListener(v -> clearAmount()); - // Confirm button - shows modal and starts scanning + // Confirm button - starts the enhanced flow btnConfirm.setOnClickListener(v -> handleConfirmAmount()); // Toggle mode button (hidden but functional) @@ -205,7 +253,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements if (newAmount.length() <= 9) { transactionAmount = newAmount; updateAmountDisplay(); - // Update status tombol btnConfirm.setEnabled(!transactionAmount.equals("0")); } } @@ -217,7 +264,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements transactionAmount = "0"; } updateAmountDisplay(); - // Update status tombol btnConfirm.setEnabled(!transactionAmount.equals("0")); } @@ -225,19 +271,18 @@ public class CreateTransactionActivity extends AppCompatActivity implements if (tvAmountDisplay != null) { if (transactionAmount.equals("0")) { tvAmountDisplay.setText(""); - // Disable tombol dan akan otomatis pakai background inactive btnConfirm.setEnabled(false); } else { long amountCents = Long.parseLong(transactionAmount); NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID")); String formattedAmount = formatter.format(amountCents); tvAmountDisplay.setText(formattedAmount); - // Enable tombol dan akan otomatis pakai background active btnConfirm.setEnabled(true); } } } + // ✅ ENHANCED: New enhanced flow with backend integration private void handleConfirmAmount() { long amountCents = Long.parseLong(transactionAmount); if (amountCents < 100) { // Minimum Rp 1.00 @@ -245,15 +290,77 @@ public class CreateTransactionActivity extends AppCompatActivity implements return; } - // Show modal and start card scanning - showModalAndStartScanning(); + Log.d(TAG, "=== STARTING ENHANCED TRANSACTION FLOW ==="); + Log.d(TAG, "Payment Type: " + paymentType); + Log.d(TAG, "Amount: " + amountCents); + Log.d(TAG, "Reference ID: " + referenceId); + Log.d(TAG, "=========================================="); + + // Start enhanced flow: Backend => Card Processing => Midtrans + startEnhancedTransactionFlow(); } - - private void showModalAndStartScanning() { - Log.d(TAG, "Starting card scanning with modal..."); + // ✅ NEW: Enhanced transaction flow with backend integration + private void startEnhancedTransactionFlow() { + // Step 1: Post initial transaction to backend with INIT status + modalManager.showProcessingModal("Initializing transaction..."); - // Show modal first + // Debug transaction data + backendManager.debugTransactionData(paymentType, referenceId, Long.parseLong(transactionAmount), "INIT"); + + // Post to backend first + backendManager.postInitTransaction(paymentType, referenceId, Long.parseLong(transactionAmount)); + } + + // ====== ✅ NEW: BACKEND CALLBACK METHODS ====== + @Override + public void onPostTransactionSuccess(JSONObject response, String transactionUuid) { + Log.d(TAG, "✅ Backend transaction posted successfully!"); + Log.d(TAG, "Transaction UUID: " + transactionUuid); + + // Store backend response and transaction UUID + lastBackendResponse = response; + this.transactionUuid = transactionUuid; + + // Step 2: Start card scanning after backend success + modalManager.showProcessingModal("Backend initialized - Scanning card..."); + + // Start card scanning after short delay + new Handler(Looper.getMainLooper()).postDelayed(() -> { + startCardScanningFlow(); + }, 1000); + } + + @Override + public void onPostTransactionError(String errorMessage) { + Log.e(TAG, "❌ Backend transaction failed: " + errorMessage); + modalManager.hideModal(); + showToast("Backend initialization failed: " + errorMessage); + + // Option 1: Retry backend call + // Option 2: Proceed without backend (fallback mode) + // For now, let's offer retry + new AlertDialog.Builder(this) + .setTitle("Backend Error") + .setMessage("Failed to initialize transaction with backend. Retry?") + .setPositiveButton("Retry", (dialog, which) -> { + startEnhancedTransactionFlow(); + }) + .setNegativeButton("Cancel", (dialog, which) -> { + // Could implement fallback mode here + }) + .show(); + } + + @Override + public void onPostTransactionProgress(String message) { + Log.d(TAG, "Backend progress: " + message); + modalManager.showProcessingModal(message); + } + + // ✅ NEW: Start card scanning flow (after backend success) + private void startCardScanningFlow() { + Log.d(TAG, "Starting card scanning flow..."); modalManager.showScanCardModal(); // Start scanning after short delay @@ -284,10 +391,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements Log.d(TAG, "Simple card detected: " + cardType); modalManager.showProcessingModal("Kartu " + cardType + " Ditemukan - Memproses..."); - // For simple mode, navigate to results without Midtrans - new Handler(Looper.getMainLooper()).postDelayed(() -> { - navigateToResults(cardType, cardData, null); - }, 1500); + // For simple mode, update backend status and navigate to results + updateBackendToSuccessAndNavigate(cardType, cardData, null); } @Override @@ -312,7 +417,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements @Override public void onScanProgress(String message) { Log.d(TAG, "Scan progress: " + message); - // Can update UI with progress if needed } // ====== EMV MANAGER CALLBACK METHODS ====== @@ -329,7 +433,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements @Override public void onConfirmCardNo(String cardNo) { - // ✅ NEW: Store EMV card data for Midtrans emvCardNumber = cardNo; Log.d(TAG, "EMV Card Number extracted: " + maskCardNumber(cardNo)); @@ -382,13 +485,13 @@ public class CreateTransactionActivity extends AppCompatActivity implements public void onTransactionSuccess(int code, String desc) { Log.d(TAG, "EMV Transaction successful"); - // ✅ NEW: Process Midtrans payment after successful EMV - if (useDirectMidtransPayment && emvCardNumber != null) { + // Process Midtrans payment after successful EMV + if (useDirectMidtransPayment && emvCardNumber != null && transactionUuid != null) { processMidtransPayment(); } else { - // Traditional flow - navigate to results + // Fallback - navigate to results String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; - navigateToResults(cardType, null, emvManager.getCardNo()); + updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo()); } } @@ -437,6 +540,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements }) .start(); } + // ====== PIN PAD CALLBACK METHODS ====== @Override public void onPinInputLength(int length) { @@ -473,7 +577,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements @Override public void onTokenizeSuccess(String cardToken) { Log.d(TAG, "✅ Midtrans tokenization successful: " + cardToken); - // Tokenization successful, charge process will continue automatically } @Override @@ -482,14 +585,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements modalManager.hideModal(); showToast("Payment tokenization failed: " + errorMessage); - // ✅ IMPROVED: Better fallback handling - showToast("Tokenization failed, trying alternative method..."); - - // Try fallback to traditional results screen after delay + // Fallback to traditional results screen after delay new Handler(Looper.getMainLooper()).postDelayed(() -> { String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; - navigateToResults(cardType, null, emvManager.getCardNo()); + updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo()); }, 2000); } @@ -497,7 +597,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements public void onChargeSuccess(JSONObject chargeResponse) { Log.d(TAG, "✅ Midtrans charge successful!"); - // ✅ ENHANCED: Store response for debugging + // Store response for debugging lastMidtransResponse = chargeResponse; try { @@ -505,30 +605,29 @@ public class CreateTransactionActivity extends AppCompatActivity implements String transactionStatus = chargeResponse.getString("transaction_status"); String statusCode = chargeResponse.optString("status_code", ""); - // ✅ ENHANCED: Extract and log bank information - String bankInfo = extractBankFromResponse(chargeResponse); - Log.d(TAG, "✅ Payment Details:"); Log.d(TAG, " - Transaction ID: " + transactionId); Log.d(TAG, " - Transaction Status: " + transactionStatus); Log.d(TAG, " - Status Code: " + statusCode); - Log.d(TAG, " - Bank Info: " + bankInfo); - Log.d(TAG, " - Full Response: " + chargeResponse.toString()); + Log.d(TAG, " - Transaction UUID: " + transactionUuid); - // Check transaction status and navigate accordingly + // Update backend status to SUCCESS + backendTransactionStatus = "SUCCESS"; + backendManager.postSuccessTransaction(paymentType, referenceId, Long.parseLong(transactionAmount)); + + // Navigate to results with all data boolean isSuccess = "capture".equals(transactionStatus) || "settlement".equals(transactionStatus) || "pending".equals(transactionStatus); - // Navigate to results with Midtrans data - navigateToMidtransResults(chargeResponse, isSuccess); + navigateToEnhancedResults(chargeResponse, isSuccess); } catch (Exception e) { Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage()); // Fallback to traditional results String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; - navigateToResults(cardType, null, emvManager.getCardNo()); + updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo()); } } @@ -536,23 +635,17 @@ public class CreateTransactionActivity extends AppCompatActivity implements public void onChargeError(String errorMessage) { Log.e(TAG, "❌ Midtrans charge failed: " + errorMessage); - // ✅ ENHANCED: Try to get response even in error case JSONObject errorResponse = midtransPaymentManager.getLastResponse(); lastMidtransResponse = errorResponse; if (errorResponse != null) { - Log.d(TAG, "✅ Got Midtrans error response with data, using it for display"); - - // ✅ ENHANCED: Extract bank even from error response - String bankInfo = extractBankFromResponse(errorResponse); - Log.d(TAG, "Bank info from error response: " + bankInfo); - + Log.d(TAG, "Got Midtrans error response with data, using it for display"); modalManager.hideModal(); String userMessage = getUserFriendlyErrorMessage(errorMessage); showToast(userMessage); // Use Midtrans results even for failed transactions - navigateToMidtransResults(errorResponse, false); + navigateToEnhancedResults(errorResponse, false); } else { Log.d(TAG, "No Midtrans response data available, using fallback"); @@ -565,75 +658,21 @@ public class CreateTransactionActivity extends AppCompatActivity implements new Handler(Looper.getMainLooper()).postDelayed(() -> { String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC"; - navigateToResults(cardType, null, emvManager.getCardNo()); + updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo()); }, 3000); } } - // ✅ NEW: Method to extract bank information from Midtrans response - private String extractBankFromResponse(JSONObject response) { - if (response == null) { - return "Unknown"; - } - - try { - // Try different possible bank field names - String[] possibleBankFields = {"bank", "issuer", "acquiring_bank", "card_type"}; - - for (String field : possibleBankFields) { - if (response.has(field)) { - String value = response.getString(field); - if (value != null && !value.trim().isEmpty()) { - Log.d(TAG, "Found bank info in field '" + field + "': " + value); - return value; - } - } - } - - // If no bank field found, try to extract from card details - if (response.has("payment_method")) { - String paymentMethod = response.getString("payment_method"); - Log.d(TAG, "Payment method: " + paymentMethod); - } - - return "Unknown"; - - } catch (JSONException e) { - Log.e(TAG, "Error extracting bank from response: " + e.getMessage()); - return "Unknown"; - } - } - - private String getUserFriendlyErrorMessage(String errorMessage) { - if (errorMessage == null) { - return "Payment processing failed"; - } - - String lowerError = errorMessage.toLowerCase(); - - if (lowerError.contains("cvv") || lowerError.contains("cvv2")) { - return "Card verification failed"; - } else if (lowerError.contains("token expired")) { - return "Card session expired, please try again"; - } else if (lowerError.contains("network") || lowerError.contains("timeout")) { - return "Network connection issue, please try again"; - } else if (lowerError.contains("decline") || lowerError.contains("deny")) { - return "Transaction declined by bank"; - } else if (lowerError.contains("invalid")) { - return "Invalid card information"; - } else { - return "Payment processing failed, please try again"; - } - } - @Override public void onPaymentProgress(String message) { Log.d(TAG, "Midtrans payment progress: " + message); modalManager.showProcessingModal(message); } + // ✅ NEW: Enhanced Midtrans payment processing with transaction UUID private void processMidtransPayment() { - Log.d(TAG, "=== STARTING ENHANCED MIDTRANS PAYMENT PROCESS ==="); + Log.d(TAG, "=== STARTING ENHANCED MIDTRANS PAYMENT ==="); + Log.d(TAG, "Using transaction UUID as order_id: " + transactionUuid); try { // Extract additional EMV data if available @@ -661,19 +700,18 @@ public class CreateTransactionActivity extends AppCompatActivity implements return; } - // ✅ NEW: Use EMV-specific payment processing - modalManager.showProcessingModal("Processing EMV Payment..."); + modalManager.showProcessingModal("Processing EMV Payment with Backend UUID..."); - // Process as EMV card payment (no CVV required) - midtransPaymentManager.processEMVCardPayment( + // ✅ ENHANCED: Process with transaction UUID as order_id + midtransPaymentManager.processEMVCardPaymentWithOrderId( cardData, Long.parseLong(transactionAmount), - referenceId, + transactionUuid, // Use transaction UUID as order_id emvTlvData ); } catch (Exception e) { - Log.e(TAG, "Error preparing Midtrans payment: " + e.getMessage(), e); + Log.e(TAG, "Error preparing enhanced Midtrans payment: " + e.getMessage(), e); onChargeError("Failed to prepare payment data: " + e.getMessage()); } } @@ -684,7 +722,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements String rawExpiryDate = extractEMVTag("5F24", null); if (rawExpiryDate != null && rawExpiryDate.length() >= 4) { - emvExpiryDate = rawExpiryDate.substring(0, 4) + "01"; // Add day for YYMMDD format + emvExpiryDate = rawExpiryDate.substring(0, 4) + "01"; } else { java.util.Calendar cal = java.util.Calendar.getInstance(); cal.add(java.util.Calendar.YEAR, 2); @@ -692,10 +730,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements cal.get(java.util.Calendar.YEAR) % 100, cal.get(java.util.Calendar.MONTH) + 1); } - // Extract AID from EMV tag 9F06 (Application Identifier - Terminal) - emvAidIdentifier = extractEMVTag("9F06", "A0000000031010"); // Default to Visa AID - // ✅ NEW: Build comprehensive TLV data for EMV processing + emvAidIdentifier = extractEMVTag("9F06", "A0000000031010"); emvTlvData = buildEMVTLVData(); Log.d(TAG, "✅ Enhanced EMV data extracted:"); @@ -708,8 +744,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements Log.e(TAG, "Error extracting EMV data: " + e.getMessage(), e); // Set fallback values emvCardholderName = "EMV CARDHOLDER"; - emvExpiryDate = "251201"; // Dec 2025 - emvAidIdentifier = "A0000000031010"; // Visa AID + emvExpiryDate = "251201"; + emvAidIdentifier = "A0000000031010"; emvTlvData = ""; } } @@ -717,16 +753,12 @@ public class CreateTransactionActivity extends AppCompatActivity implements private String extractEMVTag(String tag, String defaultValue) { try { switch (tag) { - case "5F20": // Cardholder Name + case "5F20": return defaultValue != null ? defaultValue : "EMV CARDHOLDER"; - - case "5F24": // Application Expiration Date - // Return null to trigger date generation logic + case "5F24": return null; - - case "9F06": // Application Identifier (Terminal) + case "9F06": return defaultValue != null ? defaultValue : "A0000000031010"; - default: return defaultValue; } @@ -741,29 +773,13 @@ public class CreateTransactionActivity extends AppCompatActivity implements try { StringBuilder tlvBuilder = new StringBuilder(); - // Add key EMV tags that might be useful for Midtrans - // Format: TAG=VALUE;TAG=VALUE;... - - // Application Transaction Counter (9F36) tlvBuilder.append("9F36=").append(String.format("%04X", (int)(Math.random() * 65535))).append(";"); - - // Terminal Verification Results (95) tlvBuilder.append("95=0000000000;"); - - // Transaction Status Information (9B) tlvBuilder.append("9B=E800;"); - - // Application Interchange Profile (82) tlvBuilder.append("82=1C00;"); - - // Cryptogram Information Data (9F27) tlvBuilder.append("9F27=80;"); - - // Application Cryptogram (9F26) tlvBuilder.append("9F26=").append(generateRandomHex(16)).append(";"); - - // Unpredictable Number (9F37) tlvBuilder.append("9F37=").append(generateRandomHex(8)).append(";"); String tlvData = tlvBuilder.toString(); @@ -785,6 +801,84 @@ public class CreateTransactionActivity extends AppCompatActivity implements return hex.toString(); } + // ✅ NEW: Update backend to SUCCESS and navigate + private void updateBackendToSuccessAndNavigate(String cardType, Bundle cardData, String cardNo) { + // Update backend status to SUCCESS + backendTransactionStatus = "SUCCESS"; + modalManager.showProcessingModal("Finalizing transaction..."); + + // Update backend status + backendManager.postSuccessTransaction(paymentType, referenceId, Long.parseLong(transactionAmount)); + + // Navigate after delay + new Handler(Looper.getMainLooper()).postDelayed(() -> { + navigateToResults(cardType, cardData, cardNo); + }, 1500); + } + + // ====== NAVIGATION METHODS ====== + private void navigateToResults(String cardType, Bundle cardData, String cardNo) { + showSuccessScreen(() -> { + Intent intent = new Intent(this, ResultTransactionActivity.class); + intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); + intent.putExtra("CARD_TYPE", cardType); + intent.putExtra("EMV_MODE", isEMVMode); + intent.putExtra("REFERENCE_ID", referenceId); + intent.putExtra("PAYMENT_TYPE", paymentType); // ✅ NEW: Include payment type + intent.putExtra("TRANSACTION_UUID", transactionUuid); // ✅ NEW: Include transaction UUID + + if (cardData != null) { + intent.putExtra("CARD_DATA", cardData); + } + if (cardNo != null) { + intent.putExtra("CARD_NO", cardNo); + } + + // ✅ NEW: Include backend response + if (lastBackendResponse != null) { + intent.putExtra("BACKEND_RESPONSE", lastBackendResponse.toString()); + } + + startActivity(intent); + finish(); + }); + } + + // ✅ NEW: Enhanced navigation with comprehensive data + private void navigateToEnhancedResults(JSONObject midtransResponse, boolean paymentSuccess) { + Log.d(TAG, "=== NAVIGATING TO ENHANCED RESULTS ==="); + Log.d(TAG, "Payment Success: " + paymentSuccess); + Log.d(TAG, "Transaction UUID: " + transactionUuid); + + showSuccessScreen(() -> { + Intent intent = new Intent(this, ResultTransactionActivity.class); + intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); + intent.putExtra("CARD_TYPE", "EMV_MIDTRANS_BACKEND"); + intent.putExtra("EMV_MODE", true); + intent.putExtra("REFERENCE_ID", referenceId); + intent.putExtra("CARD_NO", emvCardNumber); + intent.putExtra("PAYMENT_SUCCESS", paymentSuccess); + intent.putExtra("PAYMENT_TYPE", paymentType); // ✅ NEW + intent.putExtra("TRANSACTION_UUID", transactionUuid); // ✅ NEW + + // Enhanced data + if (midtransResponse != null) { + intent.putExtra("MIDTRANS_RESPONSE", midtransResponse.toString()); + } + if (lastBackendResponse != null) { + intent.putExtra("BACKEND_RESPONSE", lastBackendResponse.toString()); + } + + // Add additional EMV data + intent.putExtra("EMV_CARDHOLDER_NAME", emvCardholderName); + intent.putExtra("EMV_AID", emvAidIdentifier); + intent.putExtra("EMV_EXPIRY", emvExpiryDate); + + startActivity(intent); + finish(); + }); + } + // ====== HELPER METHODS ====== private void showAppSelectDialog(String[] candidateNames) { mAppSelectDialog = new AlertDialog.Builder(this) @@ -797,233 +891,26 @@ public class CreateTransactionActivity extends AppCompatActivity implements mAppSelectDialog.show(); } - private void navigateToResults(String cardType, Bundle cardData, String cardNo) { - // modalManager.hideModal(); - - showSuccessScreen(() -> { - Intent intent = new Intent(this, ResultTransactionActivity.class); - intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); - intent.putExtra("CARD_TYPE", cardType); - intent.putExtra("EMV_MODE", isEMVMode); - intent.putExtra("REFERENCE_ID", referenceId); - - if (cardData != null) { - intent.putExtra("CARD_DATA", cardData); - } - if (cardNo != null) { - intent.putExtra("CARD_NO", cardNo); - } - - startActivity(intent); - finish(); - }); - } - - // ✅ ENHANCED: Better navigation with comprehensive data - private void navigateToMidtransResults(JSONObject midtransResponse, boolean paymentSuccess) { - Log.d(TAG, "=== NAVIGATING TO MIDTRANS RESULTS ==="); - Log.d(TAG, "Payment Success: " + paymentSuccess); - Log.d(TAG, "Response Length: " + (midtransResponse != null ? midtransResponse.length() : 0)); - - // ✅ ENHANCED: Validate and enhance response data - JSONObject enhancedResponse = enhanceMidtransResponse(midtransResponse); - - showSuccessScreen(() -> { - Intent intent = new Intent(this, ResultTransactionActivity.class); - intent.putExtra("TRANSACTION_AMOUNT", transactionAmount); - intent.putExtra("CARD_TYPE", "EMV_MIDTRANS"); - intent.putExtra("EMV_MODE", true); - intent.putExtra("REFERENCE_ID", referenceId); - intent.putExtra("CARD_NO", emvCardNumber); - intent.putExtra("PAYMENT_SUCCESS", paymentSuccess); - - // ✅ ENHANCED: Send enhanced response - if (enhancedResponse != null) { - String responseString = enhancedResponse.toString(); - intent.putExtra("MIDTRANS_RESPONSE", responseString); - Log.d(TAG, "✅ Sending Midtrans response: " + responseString); - } else { - Log.w(TAG, "⚠️ No Midtrans response to send"); - } - - // Add additional EMV data for receipt - intent.putExtra("EMV_CARDHOLDER_NAME", emvCardholderName); - intent.putExtra("EMV_AID", emvAidIdentifier); - intent.putExtra("EMV_EXPIRY", emvExpiryDate); - - // ✅ ENHANCED: Add debugging extras - intent.putExtra("DEBUG_BANK_SOURCE", "midtrans_response"); - - startActivity(intent); - finish(); - }); - } - - // ✅ NEW: Method to enhance Midtrans response with additional data - private JSONObject enhanceMidtransResponse(JSONObject originalResponse) { - if (originalResponse == null) { - Log.w(TAG, "Original response is null, creating fallback response"); - return createFallbackMidtransResponse(); + private String getUserFriendlyErrorMessage(String errorMessage) { + if (errorMessage == null) { + return "Payment processing failed"; } - try { - // Clone the original response - JSONObject enhanced = new JSONObject(originalResponse.toString()); - - // ✅ ENHANCED: Add bank information if missing - if (!enhanced.has("bank") || enhanced.getString("bank").trim().isEmpty()) { - String bankFromCard = determineBankFromCard(); - if (bankFromCard != null) { - enhanced.put("bank", bankFromCard); - Log.d(TAG, "✅ Added bank to response: " + bankFromCard); - } - } - - // ✅ ENHANCED: Add EMV data if available - if (emvCardNumber != null) { - enhanced.put("emv_card_number", maskCardNumber(emvCardNumber)); - } - if (emvCardholderName != null) { - enhanced.put("emv_cardholder_name", emvCardholderName); - } - if (emvAidIdentifier != null) { - enhanced.put("emv_aid", emvAidIdentifier); - } - - // Add transaction metadata - enhanced.put("processing_timestamp", System.currentTimeMillis()); - enhanced.put("emv_mode", true); - - Log.d(TAG, "✅ Enhanced response created with " + enhanced.length() + " fields"); - return enhanced; - - } catch (JSONException e) { - Log.e(TAG, "Error enhancing response: " + e.getMessage()); - return originalResponse; - } - } - - // ✅ NEW: Create fallback response when Midtrans response is unavailable - private JSONObject createFallbackMidtransResponse() { - try { - JSONObject fallback = new JSONObject(); - - // Basic transaction info - fallback.put("transaction_id", "FALLBACK_" + System.currentTimeMillis()); - fallback.put("order_id", "ORDER_" + System.currentTimeMillis()); - fallback.put("gross_amount", transactionAmount); - fallback.put("transaction_status", "processed"); - fallback.put("status_code", "200"); - fallback.put("status_message", "Success"); - - // ✅ ENHANCED: Determine bank from available data - String bankFromCard = determineBankFromCard(); - if (bankFromCard != null) { - fallback.put("bank", bankFromCard); - } else { - fallback.put("bank", "BCA"); // Default - } - - // EMV data - if (emvCardNumber != null) { - fallback.put("emv_card_number", maskCardNumber(emvCardNumber)); - } - if (emvCardholderName != null) { - fallback.put("emv_cardholder_name", emvCardholderName); - } - - fallback.put("payment_type", "credit_card"); - fallback.put("transaction_time", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); - fallback.put("is_fallback", true); - - Log.d(TAG, "✅ Created fallback response: " + fallback.toString()); - return fallback; - - } catch (JSONException e) { - Log.e(TAG, "Error creating fallback response: " + e.getMessage()); - return null; - } - } - - // ✅ NEW: Determine bank from card number or EMV data - private String determineBankFromCard() { - // Priority 1: Use card BIN - if (emvCardNumber != null && emvCardNumber.length() >= 6) { - String bin = emvCardNumber.substring(0, 6); - return getBankFromBin(bin); - } + String lowerError = errorMessage.toLowerCase(); - // Priority 2: Use EMV AID - if (emvAidIdentifier != null) { - return getBankFromAid(emvAidIdentifier); + if (lowerError.contains("cvv") || lowerError.contains("cvv2")) { + return "Card verification failed"; + } else if (lowerError.contains("token expired")) { + return "Card session expired, please try again"; + } else if (lowerError.contains("network") || lowerError.contains("timeout")) { + return "Network connection issue, please try again"; + } else if (lowerError.contains("decline") || lowerError.contains("deny")) { + return "Transaction declined by bank"; + } else if (lowerError.contains("invalid")) { + return "Invalid card information"; + } else { + return "Payment processing failed, please try again"; } - - return null; - } - - // ✅ ENHANCED: Better BIN to bank mapping - private String getBankFromBin(String bin) { - if (bin == null || bin.length() < 4) { - return "BCA"; // Default - } - - // Get first 4 digits for matching - String bin4 = bin.substring(0, 4); - - // Indonesian Bank BIN mapping - switch (bin4) { - // BCA - case "4621": case "4699": case "5221": case "6277": - return "BCA"; - - // MANDIRI - case "4313": case "5573": case "6011": case "6234": - case "5406": case "4097": - return "MANDIRI"; - - // BNI - case "4603": case "1946": case "5264": - return "BNI"; - - // BRI - case "4578": case "4479": case "5208": case "4486": - return "BRI"; - - // CIMB NIAGA - case "4599": case "5249": case "4543": - return "CIMB NIAGA"; - - // DANAMON - case "4055": case "5108": case "4631": - return "DANAMON"; - - default: - // Try 6-digit BIN patterns - if (bin.length() >= 6) { - String bin6 = bin.substring(0, 6); - // Add more specific 6-digit patterns here if needed - Log.d(TAG, "Unknown BIN pattern: " + bin6); - } - return "BCA"; // Default fallback - } - } - - private String getBankFromAid(String aid) { - if (aid == null) return "BCA"; - - // AID patterns for Indonesian banks - if (aid.contains("A0000000031010")) { - return "BCA"; // Common for VISA - } else if (aid.contains("A0000000041010")) { - return "MANDIRI"; // Common for Mastercard - } - - return "BCA"; // Default - } - - // ✅ NEW: Add getter for last response (for debugging) - public JSONObject getLastMidtransResponse() { - return lastMidtransResponse; } private void restartScanningAfterDelay() { @@ -1031,7 +918,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements new Handler(Looper.getMainLooper()).postDelayed(() -> { if (!isFinishing()) { - showModalAndStartScanning(); + startCardScanningFlow(); } }, 2000); } @@ -1041,11 +928,9 @@ public class CreateTransactionActivity extends AppCompatActivity implements try { Log.d(TAG, "Resetting EMV process..."); - // Cancel any existing operations cardScannerManager.stopScanning(); Thread.sleep(500); - // Reset EMV process emvManager.resetEMVProcess(); Thread.sleep(1500); @@ -1069,11 +954,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } - // ✅ NEW: Generate reference ID for transaction tracking - private String generateReferenceId() { - return "ref-" + System.currentTimeMillis() + "-" + (int)(Math.random() * 10000); - } - private String maskCardNumber(String cardNumber) { if (cardNumber == null || cardNumber.length() < 8) { return cardNumber; diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java index d58c848..5566b16 100644 --- a/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/MidtransCardPaymentManager.java @@ -16,17 +16,15 @@ import java.net.URI; import java.net.URL; /** - * MidtransCardPaymentManager - Enhanced Version for EMV Card Processing + * MidtransCardPaymentManager - Enhanced Version with Backend Integration * * Key Features: * - Uses static CVV "493" for all transactions (both EMV and regular cards) * - EMV-first approach with tokenization fallback + * - Supports custom order_id from backend transaction_uuid * - Handles Midtrans API requirements where CVV is mandatory even for EMV * - Comprehensive error handling and retry logic * - Enhanced response processing with bank detection - * - * Note: Midtrans sandbox environment requires CVV even for EMV chip transactions - * during tokenization, so we use static CVV "493" as per curl example. */ public class MidtransCardPaymentManager { private static final String TAG = "MidtransCardPayment"; @@ -46,9 +44,10 @@ public class MidtransCardPaymentManager { private int retryCount = 0; private static final int MAX_RETRY = 2; - // ✅ ENHANCED: Store last response for debugging + // ✅ ENHANCED: Store last response and custom order ID private JSONObject lastResponse; private String lastErrorMessage; + private String customOrderId; // ✅ NEW: For backend transaction_uuid public interface MidtransCardPaymentCallback { void onTokenizeSuccess(String cardToken); @@ -73,6 +72,30 @@ public class MidtransCardPaymentManager { return lastErrorMessage; } + /** + * ✅ NEW: Process EMV card payment with custom order ID (transaction_uuid from backend) + */ + public void processEMVCardPaymentWithOrderId(CardData cardData, long amount, String orderId, String emvData) { + this.customOrderId = orderId; // Store custom order ID + + Log.d(TAG, "=== PROCESSING EMV PAYMENT WITH CUSTOM ORDER ID ==="); + Log.d(TAG, "Custom Order ID (Transaction UUID): " + orderId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Card PAN: " + maskCardNumber(cardData.getPan())); + Log.d(TAG, "Payment Mode: EMV with Backend Integration"); + Log.d(TAG, "================================================"); + + // Reset retry counter + retryCount = 0; + + if (callback != null) { + callback.onPaymentProgress("Processing EMV payment with backend UUID..."); + } + + // Use direct charge with custom order ID + new EMVDirectChargeWithOrderIdTask(cardData, amount, orderId, emvData).execute(); + } + /** * Process EMV card payment - handles EMV-specific requirements */ @@ -84,8 +107,9 @@ public class MidtransCardPaymentManager { return; } - // Reset retry counter + // Reset retry counter and custom order ID retryCount = 0; + customOrderId = null; Log.d(TAG, "=== STARTING EMV MIDTRANS PAYMENT ==="); Log.d(TAG, "Reference ID: " + referenceId); @@ -114,6 +138,7 @@ public class MidtransCardPaymentManager { } retryCount = 0; + customOrderId = null; Log.d(TAG, "=== STARTING REGULAR CARD PAYMENT ==="); Log.d(TAG, "Using tokenization flow"); @@ -126,6 +151,498 @@ public class MidtransCardPaymentManager { new TokenizeCardTask(cardData, amount, referenceId).execute(); } + /** + * ✅ NEW: EMV Direct Charge with Custom Order ID - uses backend transaction_uuid + */ + private class EMVDirectChargeWithOrderIdTask extends AsyncTask { + private CardData cardData; + private long amount; + private String orderId; + private String emvData; + private String errorMessage; + private JSONObject chargeResponse; + + public EMVDirectChargeWithOrderIdTask(CardData cardData, long amount, String orderId, String emvData) { + this.cardData = cardData; + this.amount = amount; + this.orderId = orderId; + this.emvData = emvData; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + // Build EMV-specific charge payload with custom order ID + JSONObject payload = new JSONObject(); + payload.put("payment_type", "credit_card"); + + // Transaction details with custom order ID + JSONObject transactionDetails = new JSONObject(); + transactionDetails.put("order_id", orderId); // ✅ Use backend transaction_uuid + transactionDetails.put("gross_amount", amount); + payload.put("transaction_details", transactionDetails); + + // EMV Credit card data (no tokenization) + JSONObject creditCard = new JSONObject(); + creditCard.put("card_number", cardData.getPan()); + creditCard.put("card_exp_month", cardData.getExpiryMonth()); + creditCard.put("card_exp_year", cardData.getExpiryYear()); + + // Include static CVV even for EMV (Midtrans may require it) + creditCard.put("card_cvv", STATIC_CVV); + Log.d(TAG, "EMV Transaction: Including static CVV (" + STATIC_CVV + ") for Midtrans compatibility"); + + // Add EMV data if available + if (emvData != null && !emvData.isEmpty()) { + creditCard.put("emv_data", emvData); + creditCard.put("authentication_mode", "chip"); + } + + payload.put("credit_card", creditCard); + + // Item details + JSONArray itemDetails = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("id", "emv_backend1"); + item.put("price", amount); + item.put("quantity", 1); + item.put("name", "EMV Backend Transaction"); + item.put("brand", "EMV Backend Payment"); + item.put("category", "Backend Transaction"); + item.put("merchant_name", "EDC-Store-Backend"); + itemDetails.put(item); + payload.put("item_details", itemDetails); + + // Customer details (same as curl example) + addCustomerDetails(payload); + + Log.d(TAG, "=== EMV BACKEND DIRECT CHARGE ==="); + Log.d(TAG, "Order ID (Backend UUID): " + orderId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Card: " + maskCardNumber(cardData.getPan())); + Log.d(TAG, "Mode: EMV Backend Direct (No Token)"); + Log.d(TAG, "================================="); + + // Make charge request + return makeChargeRequest(payload); + + } catch (Exception e) { + Log.e(TAG, "EMV Backend Direct Charge exception: " + e.getMessage(), e); + errorMessage = "EMV backend payment error: " + e.getMessage(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + if (success && chargeResponse != null && callback != null) { + Log.d(TAG, "✅ EMV Backend charge successful!"); + callback.onChargeSuccess(chargeResponse); + } else if (callback != null) { + // Fallback to tokenization if direct charge fails + Log.w(TAG, "EMV backend direct charge failed, trying tokenization fallback..."); + callback.onPaymentProgress("Retrying with tokenization..."); + new TokenizeCardWithOrderIdTask(cardData, amount, orderId).execute(); + } + } + + private Boolean makeChargeRequest(JSONObject payload) { + try { + URL url = new URI(MIDTRANS_CHARGE_URL).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", MIDTRANS_SERVER_AUTH); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + try (OutputStream os = conn.getOutputStream()) { + byte[] input = payload.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + } + + int responseCode = conn.getResponseCode(); + Log.d(TAG, "EMV Backend Charge response code: " + responseCode); + + BufferedReader br; + StringBuilder response = new StringBuilder(); + String responseLine; + + if (responseCode == 200 || responseCode == 201) { + br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + } else { + br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + } + + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + String responseString = response.toString(); + Log.d(TAG, "EMV Backend Charge response: " + responseString); + + // Store response for debugging + try { + chargeResponse = new JSONObject(responseString); + lastResponse = chargeResponse; + + // Enhanced: Add bank detection if missing + enhanceResponseWithBankInfo(chargeResponse); + + } catch (JSONException e) { + Log.e(TAG, "Error parsing response JSON: " + e.getMessage()); + chargeResponse = createFallbackResponse(responseString, responseCode); + lastResponse = chargeResponse; + } + + return processChargeResponse(chargeResponse, responseCode); + + } catch (Exception e) { + Log.e(TAG, "EMV Backend Charge request exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + lastErrorMessage = errorMessage; + return false; + } + } + + private Boolean processChargeResponse(JSONObject response, int httpCode) { + try { + String statusCode = response.optString("status_code", ""); + String statusMessage = response.optString("status_message", ""); + String transactionStatus = response.optString("transaction_status", ""); + String fraudStatus = response.optString("fraud_status", ""); + + Log.d(TAG, "=== BACKEND CHARGE RESPONSE ANALYSIS ==="); + Log.d(TAG, "HTTP Code: " + httpCode); + Log.d(TAG, "Status Code: " + statusCode); + Log.d(TAG, "Status Message: " + statusMessage); + Log.d(TAG, "Transaction Status: " + transactionStatus); + Log.d(TAG, "Fraud Status: " + fraudStatus); + Log.d(TAG, "Order ID: " + response.optString("order_id", "")); + Log.d(TAG, "========================================"); + + // Handle specific error cases + if ("411".equals(statusCode)) { + errorMessage = "Token expired: " + statusMessage; + return false; + } else if ("400".equals(statusCode)) { + errorMessage = "Bad request: " + statusMessage; + return false; + } else if ("202".equals(statusCode) && "deny".equals(transactionStatus)) { + errorMessage = "Transaction denied: " + statusMessage; + return false; + } else if (httpCode != 200 && httpCode != 201) { + errorMessage = "HTTP error: " + httpCode + " - " + statusMessage; + return false; + } + + // Success conditions + if ("200".equals(statusCode) && + ("capture".equals(transactionStatus) || + "settlement".equals(transactionStatus) || + "pending".equals(transactionStatus))) { + return true; + } else if ("201".equals(statusCode)) { + return true; + } else { + errorMessage = "Transaction failed: " + statusMessage; + return false; + } + + } catch (Exception e) { + Log.e(TAG, "Error processing backend charge response: " + e.getMessage(), e); + errorMessage = "Response processing error: " + e.getMessage(); + return false; + } + } + } + + /** + * ✅ NEW: Tokenize Card with Custom Order ID - fallback method + */ + private class TokenizeCardWithOrderIdTask extends AsyncTask { + private CardData cardData; + private long amount; + private String orderId; + private String errorMessage; + + public TokenizeCardWithOrderIdTask(CardData cardData, long amount, String orderId) { + this.cardData = cardData; + this.amount = amount; + this.orderId = orderId; + } + + @Override + protected String doInBackground(Void... voids) { + try { + StringBuilder urlBuilder = new StringBuilder(MIDTRANS_TOKEN_URL); + urlBuilder.append("?card_number=").append(cardData.getPan()); + urlBuilder.append("&card_exp_month=").append(cardData.getExpiryMonth()); + urlBuilder.append("&card_exp_year=").append(cardData.getExpiryYear()); + + // Always include CVV for tokenization + String cvvToUse = determineCVV(cardData); + urlBuilder.append("&card_cvv=").append(cvvToUse); + Log.d(TAG, "Using CVV " + cvvToUse + " for backend tokenization"); + + urlBuilder.append("&client_key=").append(MIDTRANS_CLIENT_KEY); + + Log.d(TAG, "Backend Tokenization URL: " + maskUrl(urlBuilder.toString())); + + URL url = new URI(urlBuilder.toString()).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + int responseCode = conn.getResponseCode(); + Log.d(TAG, "Backend Tokenization response code: " + responseCode); + + if (responseCode == 200) { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + Log.d(TAG, "Backend Tokenization success response: " + response.toString()); + + JSONObject jsonResponse = new JSONObject(response.toString()); + if (jsonResponse.has("token_id")) { + return jsonResponse.getString("token_id"); + } else { + errorMessage = "Token ID not found in backend response"; + return null; + } + + } else { + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + StringBuilder errorResponse = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + errorResponse.append(responseLine.trim()); + } + + Log.e(TAG, "Backend Tokenization error: " + errorResponse.toString()); + errorMessage = "Backend tokenization failed: " + errorResponse.toString(); + return null; + } + + } catch (Exception e) { + Log.e(TAG, "Backend Tokenization exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + return null; + } + } + + @Override + protected void onPostExecute(String cardToken) { + if (cardToken != null && callback != null) { + callback.onTokenizeSuccess(cardToken); + + if (callback != null) { + callback.onPaymentProgress("Processing backend payment..."); + } + new ChargeCardWithOrderIdTask(cardToken, amount, orderId, cardData).execute(); + + } else if (callback != null) { + callback.onTokenizeError(errorMessage != null ? errorMessage : "Unknown backend tokenization error"); + } + } + } + + /** + * ✅ NEW: Charge Card with Custom Order ID + */ + private class ChargeCardWithOrderIdTask extends AsyncTask { + private String cardToken; + private long amount; + private String orderId; + private String errorMessage; + private JSONObject chargeResponse; + private CardData cardData; + + public ChargeCardWithOrderIdTask(String cardToken, long amount, String orderId, CardData cardData) { + this.cardToken = cardToken; + this.amount = amount; + this.orderId = orderId; + this.cardData = cardData; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + JSONObject payload = new JSONObject(); + payload.put("payment_type", "credit_card"); + + JSONObject transactionDetails = new JSONObject(); + transactionDetails.put("order_id", orderId); // ✅ Use backend transaction_uuid + transactionDetails.put("gross_amount", amount); + payload.put("transaction_details", transactionDetails); + + JSONObject creditCard = new JSONObject(); + creditCard.put("token_id", cardToken); + payload.put("credit_card", creditCard); + + // Item details + JSONArray itemDetails = new JSONArray(); + JSONObject item = new JSONObject(); + item.put("id", "tkn_backend1"); + item.put("price", amount); + item.put("quantity", 1); + item.put("name", "Token Backend Transaction"); + item.put("brand", "Token Backend Payment"); + item.put("category", "Backend Transaction"); + item.put("merchant_name", "EDC-Store-Backend"); + itemDetails.put(item); + payload.put("item_details", itemDetails); + + addCustomerDetails(payload); + + Log.d(TAG, "=== TOKEN BACKEND CHARGE ==="); + Log.d(TAG, "Order ID (Backend UUID): " + orderId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Token: " + maskToken(cardToken)); + Log.d(TAG, "============================"); + + return makeChargeRequest(payload); + + } catch (Exception e) { + Log.e(TAG, "Token backend charge exception: " + e.getMessage(), e); + errorMessage = "Token backend charge error: " + e.getMessage(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + if (success && chargeResponse != null && callback != null) { + Log.d(TAG, "✅ Token backend charge successful!"); + callback.onChargeSuccess(chargeResponse); + } else if (callback != null) { + // Check for retry scenarios + if (shouldRetry(errorMessage) && retryCount < MAX_RETRY) { + retryCount++; + Log.w(TAG, "Retrying backend charge... (attempt " + retryCount + "/" + MAX_RETRY + ")"); + callback.onPaymentProgress("Retrying backend... (" + retryCount + "/" + MAX_RETRY + ")"); + new TokenizeCardWithOrderIdTask(cardData, amount, orderId).execute(); + } else { + if (retryCount >= MAX_RETRY) { + errorMessage = "Max retry attempts reached. " + errorMessage; + } + callback.onChargeError(errorMessage != null ? errorMessage : "Unknown backend charge error"); + } + } + } + + private Boolean makeChargeRequest(JSONObject payload) { + try { + URL url = new URI(MIDTRANS_CHARGE_URL).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "application/json"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", MIDTRANS_SERVER_AUTH); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + try (OutputStream os = conn.getOutputStream()) { + byte[] input = payload.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + } + + int responseCode = conn.getResponseCode(); + Log.d(TAG, "Token Backend Charge response code: " + responseCode); + + BufferedReader br; + StringBuilder response = new StringBuilder(); + String responseLine; + + if (responseCode == 200 || responseCode == 201) { + br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + } else { + br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + } + + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + String responseString = response.toString(); + Log.d(TAG, "Token Backend Charge response: " + responseString); + + // Store response for debugging + try { + chargeResponse = new JSONObject(responseString); + lastResponse = chargeResponse; + + // Enhanced: Add bank detection if missing + enhanceResponseWithBankInfo(chargeResponse); + + } catch (JSONException e) { + Log.e(TAG, "Error parsing response JSON: " + e.getMessage()); + chargeResponse = createFallbackResponse(responseString, responseCode); + lastResponse = chargeResponse; + } + + return processChargeResponse(chargeResponse, responseCode); + + } catch (Exception e) { + Log.e(TAG, "Token backend charge request exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + lastErrorMessage = errorMessage; + return false; + } + } + + private Boolean processChargeResponse(JSONObject response, int httpCode) { + try { + String statusCode = response.optString("status_code", ""); + String statusMessage = response.optString("status_message", ""); + String transactionStatus = response.optString("transaction_status", ""); + + Log.d(TAG, "Token Backend Charge Response - Status: " + statusCode + ", Message: " + statusMessage); + + if ("411".equals(statusCode)) { + errorMessage = "Token expired: " + statusMessage; + return false; + } else if ("400".equals(statusCode)) { + errorMessage = "Bad request: " + statusMessage; + return false; + } else if ("202".equals(statusCode) && "deny".equals(transactionStatus)) { + errorMessage = "Transaction denied: " + statusMessage; + return false; + } else if (httpCode != 200 && httpCode != 201) { + errorMessage = "HTTP error: " + httpCode + " - " + statusMessage; + return false; + } + + if ("200".equals(statusCode) && + ("capture".equals(transactionStatus) || + "settlement".equals(transactionStatus) || + "pending".equals(transactionStatus))) { + return true; + } else if ("201".equals(statusCode)) { + return true; + } else { + errorMessage = "Transaction failed: " + statusMessage; + return false; + } + + } catch (Exception e) { + Log.e(TAG, "Error processing token backend charge response: " + e.getMessage(), e); + errorMessage = "Response processing error: " + e.getMessage(); + return false; + } + } + } + /** * EMV Direct Charge - bypasses tokenization for EMV cards */ @@ -259,17 +776,16 @@ public class MidtransCardPaymentManager { String responseString = response.toString(); Log.d(TAG, "EMV Charge response: " + responseString); - // ✅ ENHANCED: Store response for debugging + // Store response for debugging try { chargeResponse = new JSONObject(responseString); - lastResponse = chargeResponse; // Store for later access + lastResponse = chargeResponse; - // ✅ ENHANCED: Add bank detection if missing + // Enhanced: Add bank detection if missing enhanceResponseWithBankInfo(chargeResponse); } catch (JSONException e) { Log.e(TAG, "Error parsing response JSON: " + e.getMessage()); - // Create fallback response object chargeResponse = createFallbackResponse(responseString, responseCode); lastResponse = chargeResponse; } @@ -554,17 +1070,16 @@ public class MidtransCardPaymentManager { String responseString = response.toString(); Log.d(TAG, "Token Charge response: " + responseString); - // ✅ ENHANCED: Store response for debugging + // Store response for debugging try { chargeResponse = new JSONObject(responseString); - lastResponse = chargeResponse; // Store for later access + lastResponse = chargeResponse; - // ✅ ENHANCED: Add bank detection if missing + // Enhanced: Add bank detection if missing enhanceResponseWithBankInfo(chargeResponse); } catch (JSONException e) { Log.e(TAG, "Error parsing response JSON: " + e.getMessage()); - // Create fallback response object chargeResponse = createFallbackResponse(responseString, responseCode); lastResponse = chargeResponse; } @@ -621,7 +1136,7 @@ public class MidtransCardPaymentManager { } } - // ✅ ENHANCED: Method to enhance response with bank information + // Method to enhance response with bank information private void enhanceResponseWithBankInfo(JSONObject response) { if (response == null) return; @@ -648,7 +1163,7 @@ public class MidtransCardPaymentManager { } } - // ✅ ENHANCED: Method to detect bank from various response fields + // Method to detect bank from various response fields private String detectBankFromResponse(JSONObject response) { try { // Try various fields that might contain bank information @@ -680,7 +1195,7 @@ public class MidtransCardPaymentManager { } } - // ✅ ENHANCED: Map various bank identifiers to standard bank names + // Map various bank identifiers to standard bank names private String mapToBankName(String identifier) { if (identifier == null || identifier.trim().isEmpty()) { return null; @@ -710,7 +1225,7 @@ public class MidtransCardPaymentManager { return null; // No mapping found } - // ✅ ENHANCED: Create fallback response when JSON parsing fails + // Create fallback response when JSON parsing fails private JSONObject createFallbackResponse(String rawResponse, int httpCode) { try { JSONObject fallback = new JSONObject(); @@ -789,7 +1304,7 @@ public class MidtransCardPaymentManager { error.contains("timeout"); } - // ✅ ENHANCED: Debug method to inspect response + // Debug method to inspect response public void debugResponse() { if (lastResponse != null) { Log.d(TAG, "=== DEBUGGING LAST RESPONSE ==="); diff --git a/app/src/main/java/com/example/bdkipoc/transaction/managers/PostTransactionBackendManager.java b/app/src/main/java/com/example/bdkipoc/transaction/managers/PostTransactionBackendManager.java new file mode 100644 index 0000000..bf9119f --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/transaction/managers/PostTransactionBackendManager.java @@ -0,0 +1,358 @@ +package com.example.bdkipoc.transaction.managers; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.UUID; + +/** + * PostTransactionBackendManager - Handles backend transaction posting + * + * This manager handles the communication with the backend service for transaction posting + * and provides the transaction_uuid needed for Midtrans integration. + */ +public class PostTransactionBackendManager { + private static final String TAG = "PostTransactionBackend"; + + // Backend Configuration + private static final String BACKEND_BASE_URL = "https://be-edc.msvc.app"; + private static final String TRANSACTIONS_ENDPOINT = BACKEND_BASE_URL + "/transactions"; + + // Default values + private static final String DEFAULT_DEVICE_CODE = "PB4K252T00021"; + private static final int DEFAULT_DEVICE_ID = 1; + private static final String DEFAULT_CASHFLOW = "MONEY_IN"; + private static final String DEFAULT_CHANNEL_CATEGORY = "RETAIL_OUTLET"; + + // ✅ NEW: Static merchant data + private static final String DEFAULT_MERCHANT_NAME = "BUDIAJAIB123"; + private static final String DEFAULT_MID = "542531513"; + private static final String DEFAULT_TID = "535151521"; + + private Context context; + private PostTransactionCallback callback; + + public interface PostTransactionCallback { + void onPostTransactionSuccess(JSONObject response, String transactionUuid); + void onPostTransactionError(String errorMessage); + void onPostTransactionProgress(String message); + } + + public PostTransactionBackendManager(Context context, PostTransactionCallback callback) { + this.context = context; + this.callback = callback; + } + + /** + * Post transaction to backend service + */ + public void postTransaction(String paymentType, String referenceId, long amount, String status) { + String channelCode = mapPaymentTypeToChannelCode(paymentType); + String transactionUuid = generateUUID(); + + Log.d(TAG, "=== POSTING TRANSACTION TO BACKEND ==="); + Log.d(TAG, "Payment Type: " + paymentType); + Log.d(TAG, "Channel Code: " + channelCode); + Log.d(TAG, "Reference ID: " + referenceId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Status: " + status); + Log.d(TAG, "Transaction UUID: " + transactionUuid); + Log.d(TAG, "====================================="); + + if (callback != null) { + callback.onPostTransactionProgress("Posting transaction to backend..."); + } + + new PostTransactionTask(paymentType, channelCode, referenceId, amount, status, transactionUuid).execute(); + } + + /** + * Post transaction with INIT status (for pre-authorization) + */ + public void postInitTransaction(String paymentType, String referenceId, long amount) { + postTransaction(paymentType, referenceId, amount, "INIT"); + } + + /** + * Post transaction with SUCCESS status (for completed transactions) + */ + public void postSuccessTransaction(String paymentType, String referenceId, long amount) { + postTransaction(paymentType, referenceId, amount, "SUCCESS"); + } + + /** + * Update existing transaction status + */ + public void updateTransactionStatus(String transactionUuid, String newStatus) { + Log.d(TAG, "Updating transaction " + transactionUuid + " to status: " + newStatus); + // TODO: Implement update endpoint if available + // For now, we'll create a new transaction with updated status + } + + /** + * Map payment type to channel code for backend + */ + private String mapPaymentTypeToChannelCode(String paymentType) { + if (paymentType == null) { + return "CREDIT_CARD"; // Default + } + + switch (paymentType.toLowerCase()) { + case "credit_card": + return "CREDIT_CARD"; + case "debit_card": + return "DEBIT_CARD"; + case "e_money": + return "E_MONEY"; + case "qris": + return "QRIS"; + default: + Log.w(TAG, "Unknown payment type: " + paymentType + ", using CREDIT_CARD"); + return "CREDIT_CARD"; + } + } + + /** + * Generate UUID v4 for transaction + */ + private String generateUUID() { + return UUID.randomUUID().toString(); + } + + /** + * Get device serial number (Sunmi device code) + */ + private String getDeviceCode() { + try { + // Try to get actual device serial number + // For Sunmi devices, this might be available through system properties + String serialNumber = android.os.Build.SERIAL; + if (serialNumber != null && !serialNumber.equals("unknown") && !serialNumber.equals(android.os.Build.UNKNOWN)) { + return serialNumber; + } + } catch (Exception e) { + Log.w(TAG, "Could not get device serial number: " + e.getMessage()); + } + + // Fallback to default device code + return DEFAULT_DEVICE_CODE; + } + + /** + * AsyncTask for posting transaction to backend + */ + private class PostTransactionTask extends AsyncTask { + private String paymentType; + private String channelCode; + private String referenceId; + private long amount; + private String status; + private String transactionUuid; + private String errorMessage; + private JSONObject responseData; + + public PostTransactionTask(String paymentType, String channelCode, String referenceId, + long amount, String status, String transactionUuid) { + this.paymentType = paymentType; + this.channelCode = channelCode; + this.referenceId = referenceId; + this.amount = amount; + this.status = status; + this.transactionUuid = transactionUuid; + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + // Build transaction payload + JSONObject payload = buildTransactionPayload(); + + Log.d(TAG, "Backend payload: " + payload.toString()); + + // Make HTTP request + return makeBackendRequest(payload); + + } catch (Exception e) { + Log.e(TAG, "Backend transaction exception: " + e.getMessage(), e); + errorMessage = "Backend transaction error: " + e.getMessage(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + if (success && responseData != null && callback != null) { + try { + // Extract transaction_uuid from response + JSONObject data = responseData.optJSONObject("data"); + String returnedUuid = null; + + if (data != null) { + returnedUuid = data.optString("transaction_uuid", transactionUuid); + } + + Log.d(TAG, "✅ Backend transaction successful!"); + Log.d(TAG, "Original UUID: " + transactionUuid); + Log.d(TAG, "Returned UUID: " + returnedUuid); + + callback.onPostTransactionSuccess(responseData, returnedUuid != null ? returnedUuid : transactionUuid); + + } catch (Exception e) { + Log.e(TAG, "Error processing backend response: " + e.getMessage()); + if (callback != null) { + callback.onPostTransactionError("Error processing backend response: " + e.getMessage()); + } + } + } else if (callback != null) { + callback.onPostTransactionError(errorMessage != null ? errorMessage : "Unknown backend error"); + } + } + + private JSONObject buildTransactionPayload() throws JSONException { + JSONObject payload = new JSONObject(); + + // Required fields + payload.put("type", "PAYMENT"); + payload.put("channel_category", DEFAULT_CHANNEL_CATEGORY); + payload.put("channel_code", channelCode); + payload.put("reference_id", referenceId); + payload.put("amount", amount); + payload.put("cashflow", DEFAULT_CASHFLOW); + payload.put("status", status); + payload.put("device_id", DEFAULT_DEVICE_ID); + payload.put("transaction_uuid", transactionUuid); + payload.put("transaction_time_seconds", 2.2); // Default value as mentioned + payload.put("device_code", getDeviceCode()); + + // ✅ NEW: Static merchant data (no longer null) + payload.put("merchant_name", DEFAULT_MERCHANT_NAME); + payload.put("mid", DEFAULT_MID); + payload.put("tid", DEFAULT_TID); + + return payload; + } + + private Boolean makeBackendRequest(JSONObject payload) { + try { + URL url = new URI(TRANSACTIONS_ENDPOINT).toURL(); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + // Set request properties + conn.setRequestMethod("POST"); + conn.setRequestProperty("Accept", "*/*"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + + // Send payload + try (OutputStream os = conn.getOutputStream()) { + byte[] input = payload.toString().getBytes("utf-8"); + os.write(input, 0, input.length); + } + + // Get response + int responseCode = conn.getResponseCode(); + Log.d(TAG, "Backend response code: " + responseCode); + + BufferedReader br; + StringBuilder response = new StringBuilder(); + String responseLine; + + if (responseCode >= 200 && responseCode < 300) { + br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + } else { + br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + } + + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + String responseString = response.toString(); + Log.d(TAG, "Backend response: " + responseString); + + // Parse response + try { + responseData = new JSONObject(responseString); + + // Check if response indicates success + int status = responseData.optInt("status", 0); + String message = responseData.optString("message", ""); + + if (status == 200 && "Successfully".equals(message)) { + return true; + } else { + errorMessage = "Backend error: " + message + " (Status: " + status + ")"; + return false; + } + + } catch (JSONException e) { + Log.e(TAG, "Error parsing backend response: " + e.getMessage()); + errorMessage = "Invalid backend response format"; + return false; + } + + } catch (Exception e) { + Log.e(TAG, "Backend request exception: " + e.getMessage(), e); + errorMessage = "Network error: " + e.getMessage(); + return false; + } + } + } + + /** + * Utility method to generate reference ID + */ + public static String generateReferenceId() { + return "ref" + System.currentTimeMillis() + (int)(Math.random() * 10000); + } + + /** + * Utility method to map card menu ID to payment type + */ + public static String mapCardMenuToPaymentType(int cardMenuId) { + // Based on MainActivity.java card IDs + switch (cardMenuId) { + case 2131296346: // R.id.card_kartu_kredit + return "credit_card"; + case 2131296344: // R.id.card_kartu_debit + return "debit_card"; + case 2131296360: // R.id.card_uang_elektronik + return "e_money"; + case 2131296352: // R.id.card_qris + return "qris"; + default: + Log.w(TAG, "Unknown card menu ID: " + cardMenuId + ", defaulting to credit_card"); + return "credit_card"; + } + } + + /** + * Debug method to log transaction details + */ + public void debugTransactionData(String paymentType, String referenceId, long amount, String status) { + Log.d(TAG, "=== TRANSACTION DEBUG INFO ==="); + Log.d(TAG, "Payment Type: " + paymentType); + Log.d(TAG, "Channel Code: " + mapPaymentTypeToChannelCode(paymentType)); + Log.d(TAG, "Reference ID: " + referenceId); + Log.d(TAG, "Amount: " + amount); + Log.d(TAG, "Status: " + status); + Log.d(TAG, "Device Code: " + getDeviceCode()); + Log.d(TAG, "Device ID: " + DEFAULT_DEVICE_ID); + Log.d(TAG, "Merchant Name: " + DEFAULT_MERCHANT_NAME); + Log.d(TAG, "MID: " + DEFAULT_MID); + Log.d(TAG, "TID: " + DEFAULT_TID); + Log.d(TAG, "=============================="); + } +} \ No newline at end of file