safepoint charge
This commit is contained in:
		
							parent
							
								
									f6650f99d0
								
							
						
					
					
						commit
						8a73206a76
					
				@ -478,9 +478,15 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
        modalManager.hideModal();
 | 
					        modalManager.hideModal();
 | 
				
			||||||
        showToast("Payment tokenization failed: " + errorMessage);
 | 
					        showToast("Payment tokenization failed: " + errorMessage);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Fallback to traditional results screen
 | 
					        // ✅ IMPROVED: Better fallback handling
 | 
				
			||||||
        String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
 | 
					        showToast("Tokenization failed, trying alternative method...");
 | 
				
			||||||
        navigateToResults(cardType, null, emvManager.getCardNo());
 | 
					        
 | 
				
			||||||
 | 
					        // Try 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());
 | 
				
			||||||
 | 
					        }, 2000);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -490,9 +496,12 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            String transactionId = chargeResponse.getString("transaction_id");
 | 
					            String transactionId = chargeResponse.getString("transaction_id");
 | 
				
			||||||
            String transactionStatus = chargeResponse.getString("transaction_status");
 | 
					            String transactionStatus = chargeResponse.getString("transaction_status");
 | 
				
			||||||
 | 
					            String statusCode = chargeResponse.optString("status_code", "");
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            Log.d(TAG, "Transaction ID: " + transactionId);
 | 
					            Log.d(TAG, "✅ Payment Details:");
 | 
				
			||||||
            Log.d(TAG, "Transaction Status: " + transactionStatus);
 | 
					            Log.d(TAG, "  - Transaction ID: " + transactionId);
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - Transaction Status: " + transactionStatus);
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - Status Code: " + statusCode);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Navigate to success results with Midtrans data
 | 
					            // Navigate to success results with Midtrans data
 | 
				
			||||||
            navigateToMidtransResults(chargeResponse);
 | 
					            navigateToMidtransResults(chargeResponse);
 | 
				
			||||||
@ -500,7 +509,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage());
 | 
					            Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage());
 | 
				
			||||||
            // Fallback to traditional results
 | 
					            // Fallback to traditional results
 | 
				
			||||||
            String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
 | 
					            String cardType = emvManager.getCardType() == 
 | 
				
			||||||
 | 
					                com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
 | 
				
			||||||
            navigateToResults(cardType, null, emvManager.getCardNo());
 | 
					            navigateToResults(cardType, null, emvManager.getCardNo());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -509,11 +519,42 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
    public void onChargeError(String errorMessage) {
 | 
					    public void onChargeError(String errorMessage) {
 | 
				
			||||||
        Log.e(TAG, "❌ Midtrans charge failed: " + errorMessage);
 | 
					        Log.e(TAG, "❌ Midtrans charge failed: " + errorMessage);
 | 
				
			||||||
        modalManager.hideModal();
 | 
					        modalManager.hideModal();
 | 
				
			||||||
        showToast("Payment processing failed: " + errorMessage);
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Fallback to traditional results screen
 | 
					        // ✅ IMPROVED: Better error handling with user-friendly messages
 | 
				
			||||||
        String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
 | 
					        String userMessage = getUserFriendlyErrorMessage(errorMessage);
 | 
				
			||||||
        navigateToResults(cardType, null, emvManager.getCardNo());
 | 
					        showToast(userMessage);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Show detailed error in logs but user-friendly message to user
 | 
				
			||||||
 | 
					        Log.e(TAG, "Detailed error: " + errorMessage);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 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());
 | 
				
			||||||
 | 
					        }, 3000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
					    @Override
 | 
				
			||||||
@ -522,9 +563,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
        modalManager.showProcessingModal(message);
 | 
					        modalManager.showProcessingModal(message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // ====== ✅ NEW: MIDTRANS PAYMENT PROCESSING ======
 | 
					 | 
				
			||||||
    private void processMidtransPayment() {
 | 
					    private void processMidtransPayment() {
 | 
				
			||||||
        Log.d(TAG, "=== STARTING MIDTRANS PAYMENT PROCESS ===");
 | 
					        Log.d(TAG, "=== STARTING ENHANCED MIDTRANS PAYMENT PROCESS ===");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            // Extract additional EMV data if available
 | 
					            // Extract additional EMV data if available
 | 
				
			||||||
@ -544,49 +584,136 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
            Log.d(TAG, "  - Expiry: " + cardData.getExpiryMonth() + "/" + cardData.getExpiryYear());
 | 
					            Log.d(TAG, "  - Expiry: " + cardData.getExpiryMonth() + "/" + cardData.getExpiryYear());
 | 
				
			||||||
            Log.d(TAG, "  - Cardholder: " + cardData.getCardholderName());
 | 
					            Log.d(TAG, "  - Cardholder: " + cardData.getCardholderName());
 | 
				
			||||||
            Log.d(TAG, "  - AID: " + cardData.getAidIdentifier());
 | 
					            Log.d(TAG, "  - AID: " + cardData.getAidIdentifier());
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - Is EMV: " + cardData.isEMVCard());
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if (!cardData.isValid()) {
 | 
					            if (!cardData.isValid()) {
 | 
				
			||||||
                Log.w(TAG, "⚠️ Card data validation failed, using direct EMV charge");
 | 
					                Log.e(TAG, "❌ Card data validation failed");
 | 
				
			||||||
                // Try direct EMV charge instead
 | 
					                onChargeError("Invalid card data extracted from EMV");
 | 
				
			||||||
                midtransPaymentManager.processEMVDirectCharge(
 | 
					                return;
 | 
				
			||||||
                    cardData, 
 | 
					 | 
				
			||||||
                    Long.parseLong(transactionAmount), 
 | 
					 | 
				
			||||||
                    referenceId, 
 | 
					 | 
				
			||||||
                    emvTlvData
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                // Process normal card payment (with tokenization)
 | 
					 | 
				
			||||||
                midtransPaymentManager.processCardPayment(
 | 
					 | 
				
			||||||
                    cardData, 
 | 
					 | 
				
			||||||
                    Long.parseLong(transactionAmount), 
 | 
					 | 
				
			||||||
                    referenceId
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					            // ✅ NEW: Use EMV-specific payment processing
 | 
				
			||||||
 | 
					            modalManager.showProcessingModal("Processing EMV Payment...");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Process as EMV card payment (no CVV required)
 | 
				
			||||||
 | 
					            midtransPaymentManager.processEMVCardPayment(
 | 
				
			||||||
 | 
					                cardData, 
 | 
				
			||||||
 | 
					                Long.parseLong(transactionAmount), 
 | 
				
			||||||
 | 
					                referenceId, 
 | 
				
			||||||
 | 
					                emvTlvData
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            Log.e(TAG, "Error preparing Midtrans payment: " + e.getMessage(), e);
 | 
					            Log.e(TAG, "Error preparing Midtrans payment: " + e.getMessage(), e);
 | 
				
			||||||
            onChargeError("Failed to prepare payment data: " + e.getMessage());
 | 
					            onChargeError("Failed to prepare payment data: " + e.getMessage());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
    private void extractAdditionalEMVData() {
 | 
					    private void extractAdditionalEMVData() {
 | 
				
			||||||
        // This method would extract additional EMV data from the completed transaction
 | 
					        try {
 | 
				
			||||||
        // For now, we'll use placeholder data - in real implementation, 
 | 
					            emvCardholderName = extractEMVTag("5F20", "EMV CARDHOLDER");
 | 
				
			||||||
        // you would extract this from EMV TLV data
 | 
					                        
 | 
				
			||||||
        
 | 
					            String rawExpiryDate = extractEMVTag("5F24", null);
 | 
				
			||||||
        // Example: Extract cardholder name from tag 5F20
 | 
					            if (rawExpiryDate != null && rawExpiryDate.length() >= 4) {
 | 
				
			||||||
        emvCardholderName = "EMV CARDHOLDER"; // Placeholder
 | 
					                emvExpiryDate = rawExpiryDate.substring(0, 4) + "01"; // Add day for YYMMDD format
 | 
				
			||||||
        
 | 
					            } else {
 | 
				
			||||||
        // Example: Extract expiry date from tag 5F24  
 | 
					                java.util.Calendar cal = java.util.Calendar.getInstance();
 | 
				
			||||||
        emvExpiryDate = "251220"; // Placeholder - format YYMMDD
 | 
					                cal.add(java.util.Calendar.YEAR, 2);
 | 
				
			||||||
        
 | 
					                emvExpiryDate = String.format("%02d%02d01", 
 | 
				
			||||||
        // Example: Extract AID from tag 9F06
 | 
					                    cal.get(java.util.Calendar.YEAR) % 100,
 | 
				
			||||||
        emvAidIdentifier = "A0000000031010"; // Placeholder - Visa AID
 | 
					                    cal.get(java.util.Calendar.MONTH) + 1);
 | 
				
			||||||
        
 | 
					            }            
 | 
				
			||||||
        // Example: Collect relevant TLV data for EMV processing
 | 
					            // Extract AID from EMV tag 9F06 (Application Identifier - Terminal)
 | 
				
			||||||
        emvTlvData = "9F2608=1234567890ABCDEF;9F2701=80;9F3602=0001"; // Placeholder
 | 
					            emvAidIdentifier = extractEMVTag("9F06", "A0000000031010"); // Default to Visa AID
 | 
				
			||||||
        
 | 
					            
 | 
				
			||||||
        Log.d(TAG, "Additional EMV data extracted");
 | 
					            // ✅ NEW: Build comprehensive TLV data for EMV processing
 | 
				
			||||||
 | 
					            emvTlvData = buildEMVTLVData();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Log.d(TAG, "✅ Enhanced EMV data extracted:");
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - Cardholder: " + emvCardholderName);
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - Expiry: " + emvExpiryDate);
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - AID: " + emvAidIdentifier);
 | 
				
			||||||
 | 
					            Log.d(TAG, "  - TLV Data Length: " + (emvTlvData != null ? emvTlvData.length() : 0));
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e(TAG, "Error extracting EMV data: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					            // Set fallback values
 | 
				
			||||||
 | 
					            emvCardholderName = "EMV CARDHOLDER";
 | 
				
			||||||
 | 
					            emvExpiryDate = "251201"; // Dec 2025
 | 
				
			||||||
 | 
					            emvAidIdentifier = "A0000000031010"; // Visa AID
 | 
				
			||||||
 | 
					            emvTlvData = "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String extractEMVTag(String tag, String defaultValue) {
 | 
				
			||||||
 | 
					        try {            
 | 
				
			||||||
 | 
					            switch (tag) {
 | 
				
			||||||
 | 
					                case "5F20": // Cardholder Name
 | 
				
			||||||
 | 
					                    return defaultValue != null ? defaultValue : "EMV CARDHOLDER";
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                case "5F24": // Application Expiration Date
 | 
				
			||||||
 | 
					                    // Return null to trigger date generation logic
 | 
				
			||||||
 | 
					                    return null;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                case "9F06": // Application Identifier (Terminal)
 | 
				
			||||||
 | 
					                    return defaultValue != null ? defaultValue : "A0000000031010";
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					                    return defaultValue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.w(TAG, "Failed to extract EMV tag " + tag + ": " + e.getMessage());
 | 
				
			||||||
 | 
					            return defaultValue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String buildEMVTLVData() {
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
 | 
					            Log.d(TAG, "Generated EMV TLV Data: " + tlvData);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            return tlvData;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e(TAG, "Error building EMV TLV data: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String generateRandomHex(int length) {
 | 
				
			||||||
 | 
					        StringBuilder hex = new StringBuilder();
 | 
				
			||||||
 | 
					        for (int i = 0; i < length; i++) {
 | 
				
			||||||
 | 
					            hex.append(String.format("%X", (int)(Math.random() * 16)));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return hex.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // ====== HELPER METHODS ======
 | 
					    // ====== HELPER METHODS ======
 | 
				
			||||||
@ -625,8 +752,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    // ✅ NEW: Navigate to results with Midtrans payment data
 | 
					    // ✅ NEW: Navigate to results with Midtrans payment data
 | 
				
			||||||
    private void navigateToMidtransResults(JSONObject midtransResponse) {
 | 
					    private void navigateToMidtransResults(JSONObject midtransResponse) {
 | 
				
			||||||
        // modalManager.hideModal();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        showSuccessScreen(() -> {
 | 
					        showSuccessScreen(() -> {
 | 
				
			||||||
            Intent intent = new Intent(this, ResultTransactionActivity.class);
 | 
					            Intent intent = new Intent(this, ResultTransactionActivity.class);
 | 
				
			||||||
            intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
 | 
					            intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
 | 
				
			||||||
@ -637,6 +762,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements
 | 
				
			|||||||
            intent.putExtra("MIDTRANS_RESPONSE", midtransResponse.toString());
 | 
					            intent.putExtra("MIDTRANS_RESPONSE", midtransResponse.toString());
 | 
				
			||||||
            intent.putExtra("PAYMENT_SUCCESS", true);
 | 
					            intent.putExtra("PAYMENT_SUCCESS", true);
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					            // ✅ NEW: Add additional EMV data for receipt
 | 
				
			||||||
 | 
					            intent.putExtra("EMV_CARDHOLDER_NAME", emvCardholderName);
 | 
				
			||||||
 | 
					            intent.putExtra("EMV_AID", emvAidIdentifier);
 | 
				
			||||||
 | 
					            intent.putExtra("EMV_EXPIRY", emvExpiryDate);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            startActivity(intent);
 | 
					            startActivity(intent);
 | 
				
			||||||
            finish();
 | 
					            finish();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
				
			|||||||
@ -1,655 +1,305 @@
 | 
				
			|||||||
package com.example.bdkipoc.transaction;
 | 
					package com.example.bdkipoc.transaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.ClipboardManager;
 | 
					 | 
				
			||||||
import android.content.ClipData;
 | 
					 | 
				
			||||||
import android.content.Context;
 | 
					 | 
				
			||||||
import android.content.Intent;
 | 
					import android.content.Intent;
 | 
				
			||||||
import android.os.Bundle;
 | 
					import android.os.Bundle;
 | 
				
			||||||
import android.os.RemoteException;
 | 
					import android.util.Log;
 | 
				
			||||||
import android.text.TextUtils;
 | 
					import android.view.View;
 | 
				
			||||||
import android.widget.Button;
 | 
					import android.widget.Button;
 | 
				
			||||||
 | 
					import android.widget.LinearLayout;
 | 
				
			||||||
import android.widget.TextView;
 | 
					import android.widget.TextView;
 | 
				
			||||||
import android.widget.Toast;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import androidx.appcompat.app.AppCompatActivity;
 | 
					import androidx.appcompat.app.AppCompatActivity;
 | 
				
			||||||
import androidx.appcompat.widget.Toolbar;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.example.bdkipoc.MyApplication;
 | 
					 | 
				
			||||||
import com.example.bdkipoc.R;
 | 
					import com.example.bdkipoc.R;
 | 
				
			||||||
import com.example.bdkipoc.utils.ByteUtil;
 | 
					
 | 
				
			||||||
import com.example.bdkipoc.utils.Utility;
 | 
					import org.json.JSONException;
 | 
				
			||||||
import com.sunmi.emv.l2.utils.iso8583.TLV;
 | 
					import org.json.JSONObject;
 | 
				
			||||||
import com.sunmi.emv.l2.utils.iso8583.TLVUtils;
 | 
					 | 
				
			||||||
import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2;
 | 
					 | 
				
			||||||
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.text.NumberFormat;
 | 
					import java.text.NumberFormat;
 | 
				
			||||||
import java.text.SimpleDateFormat;
 | 
					import java.text.SimpleDateFormat;
 | 
				
			||||||
import java.util.Arrays;
 | 
					 | 
				
			||||||
import java.util.Date;
 | 
					import java.util.Date;
 | 
				
			||||||
import java.util.Locale;
 | 
					import java.util.Locale;
 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.Set;
 | 
					 | 
				
			||||||
import java.util.TreeMap;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.example.bdkipoc.transaction.CreateTransactionActivity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.json.JSONObject;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * ResultTransactionActivity - Display detailed transaction results and TLV data
 | 
					 * ResultTransactionActivity - Display transaction results with response data
 | 
				
			||||||
 * ✅ Updated to support Midtrans payment results
 | 
					 * Shows payment response data in JSON format
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class ResultTransactionActivity extends AppCompatActivity {
 | 
					public class ResultTransactionActivity extends AppCompatActivity {
 | 
				
			||||||
    private static final String TAG = "ResultTransaction";
 | 
					    private static final String TAG = "ResultTransaction";
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // UI Components
 | 
					    // UI Components
 | 
				
			||||||
    private TextView tvTransactionSummary;
 | 
					    private TextView tvAmount, tvStatus, tvReference, tvCardInfo;
 | 
				
			||||||
    private TextView tvCardData;
 | 
					    private TextView tvPaymentMethod, tvTransactionId, tvOrderId, tvTimestamp;
 | 
				
			||||||
    private Button btnCopyData;
 | 
					    private TextView tvResponseData, tvErrorDetails;
 | 
				
			||||||
    private Button btnNewTransaction;
 | 
					    private Button btnNewTransaction, btnRetry;
 | 
				
			||||||
    private Button btnPrintReceipt;
 | 
					    private LinearLayout backNavigation, layoutErrorDetails;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Transaction Data
 | 
					    // Data from intent
 | 
				
			||||||
    private String transactionAmount;
 | 
					    private String transactionAmount;
 | 
				
			||||||
    private String cardType;
 | 
					    private String cardType;
 | 
				
			||||||
    private boolean isEMVMode;
 | 
					    private boolean emvMode;
 | 
				
			||||||
    private String cardNo;
 | 
					 | 
				
			||||||
    private Bundle cardData;
 | 
					 | 
				
			||||||
    private String referenceId;
 | 
					    private String referenceId;
 | 
				
			||||||
 | 
					    private String cardNo;
 | 
				
			||||||
 | 
					    private String midtransResponse;
 | 
				
			||||||
 | 
					    private boolean paymentSuccess;
 | 
				
			||||||
 | 
					    private String emvCardholderName;
 | 
				
			||||||
 | 
					    private String emvAid;
 | 
				
			||||||
 | 
					    private String emvExpiry;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // ✅ NEW: Midtrans Integration Data
 | 
					    // Internal data
 | 
				
			||||||
    private String midtransResponseJson;
 | 
					    private JSONObject responseJsonData;
 | 
				
			||||||
    private boolean isPaymentSuccess;
 | 
					    private String responseDataString;
 | 
				
			||||||
    private JSONObject midtransResponse;
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // EMV Components
 | 
					 | 
				
			||||||
    private EMVOptV2 mEMVOptV2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
					    protected void onCreate(Bundle savedInstanceState) {
 | 
				
			||||||
        super.onCreate(savedInstanceState);
 | 
					        super.onCreate(savedInstanceState);
 | 
				
			||||||
        setContentView(R.layout.activity_result_transaction);
 | 
					        setContentView(R.layout.activity_result_transaction);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        getIntentData();
 | 
					 | 
				
			||||||
        initViews();
 | 
					        initViews();
 | 
				
			||||||
        initEMVComponents();
 | 
					        extractIntentData();
 | 
				
			||||||
        loadCardData();
 | 
					        setupListeners();
 | 
				
			||||||
 | 
					        prepareTransactionData();
 | 
				
			||||||
 | 
					        displayTransactionSummary();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    private void getIntentData() {
 | 
					    private void initViews() {
 | 
				
			||||||
 | 
					        // Navigation
 | 
				
			||||||
 | 
					        backNavigation = findViewById(R.id.back_navigation);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Summary components
 | 
				
			||||||
 | 
					        tvAmount = findViewById(R.id.tv_amount);
 | 
				
			||||||
 | 
					        tvStatus = findViewById(R.id.tv_status);
 | 
				
			||||||
 | 
					        tvReference = findViewById(R.id.tv_reference);
 | 
				
			||||||
 | 
					        tvCardInfo = findViewById(R.id.tv_card_info);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Technical details
 | 
				
			||||||
 | 
					        tvPaymentMethod = findViewById(R.id.tv_payment_method);
 | 
				
			||||||
 | 
					        tvTransactionId = findViewById(R.id.tv_transaction_id);
 | 
				
			||||||
 | 
					        tvOrderId = findViewById(R.id.tv_order_id);
 | 
				
			||||||
 | 
					        tvTimestamp = findViewById(R.id.tv_timestamp);
 | 
				
			||||||
 | 
					        tvErrorDetails = findViewById(R.id.tv_error_details);
 | 
				
			||||||
 | 
					        layoutErrorDetails = findViewById(R.id.layout_error_details);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Data display
 | 
				
			||||||
 | 
					        tvResponseData = findViewById(R.id.tv_response_data);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Action buttons
 | 
				
			||||||
 | 
					        btnNewTransaction = findViewById(R.id.btn_new_transaction);
 | 
				
			||||||
 | 
					        btnRetry = findViewById(R.id.btn_retry);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private void extractIntentData() {
 | 
				
			||||||
        Intent intent = getIntent();
 | 
					        Intent intent = getIntent();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        transactionAmount = intent.getStringExtra("TRANSACTION_AMOUNT");
 | 
					        transactionAmount = intent.getStringExtra("TRANSACTION_AMOUNT");
 | 
				
			||||||
        cardType = intent.getStringExtra("CARD_TYPE");
 | 
					        cardType = intent.getStringExtra("CARD_TYPE");
 | 
				
			||||||
        isEMVMode = intent.getBooleanExtra("EMV_MODE", true);
 | 
					        emvMode = intent.getBooleanExtra("EMV_MODE", false);
 | 
				
			||||||
        cardNo = intent.getStringExtra("CARD_NO");
 | 
					 | 
				
			||||||
        cardData = intent.getBundleExtra("CARD_DATA");
 | 
					 | 
				
			||||||
        referenceId = intent.getStringExtra("REFERENCE_ID");
 | 
					        referenceId = intent.getStringExtra("REFERENCE_ID");
 | 
				
			||||||
 | 
					        cardNo = intent.getStringExtra("CARD_NO");
 | 
				
			||||||
 | 
					        midtransResponse = intent.getStringExtra("MIDTRANS_RESPONSE");
 | 
				
			||||||
 | 
					        paymentSuccess = intent.getBooleanExtra("PAYMENT_SUCCESS", false);
 | 
				
			||||||
 | 
					        emvCardholderName = intent.getStringExtra("EMV_CARDHOLDER_NAME");
 | 
				
			||||||
 | 
					        emvAid = intent.getStringExtra("EMV_AID");
 | 
				
			||||||
 | 
					        emvExpiry = intent.getStringExtra("EMV_EXPIRY");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // ✅ NEW: Get Midtrans payment data
 | 
					        Log.d(TAG, "Transaction data received:");
 | 
				
			||||||
        midtransResponseJson = intent.getStringExtra("MIDTRANS_RESPONSE");
 | 
					        Log.d(TAG, "Amount: " + transactionAmount);
 | 
				
			||||||
        isPaymentSuccess = intent.getBooleanExtra("PAYMENT_SUCCESS", false);
 | 
					        Log.d(TAG, "Card Type: " + cardType);
 | 
				
			||||||
        
 | 
					        Log.d(TAG, "EMV Mode: " + emvMode);
 | 
				
			||||||
        if (transactionAmount == null) {
 | 
					        Log.d(TAG, "Payment Success: " + paymentSuccess);
 | 
				
			||||||
            transactionAmount = "0";
 | 
					        Log.d(TAG, "Has Midtrans Response: " + (midtransResponse != null));
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // ✅ NEW: Parse Midtrans response if available
 | 
					 | 
				
			||||||
        if (midtransResponseJson != null && !midtransResponseJson.isEmpty()) {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                midtransResponse = new JSONObject(midtransResponseJson);
 | 
					 | 
				
			||||||
                android.util.Log.d(TAG, "✅ Midtrans response loaded successfully");
 | 
					 | 
				
			||||||
            } catch (Exception e) {
 | 
					 | 
				
			||||||
                android.util.Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    private void initViews() {
 | 
					    private void setupListeners() {
 | 
				
			||||||
        // Setup Toolbar with updated title based on payment type
 | 
					        // Back navigation
 | 
				
			||||||
        Toolbar toolbar = findViewById(R.id.toolbar);
 | 
					        backNavigation.setOnClickListener(v -> finish());
 | 
				
			||||||
        setSupportActionBar(toolbar);
 | 
					        
 | 
				
			||||||
        if (getSupportActionBar() != null) {
 | 
					        // Action buttons
 | 
				
			||||||
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
 | 
					        btnNewTransaction.setOnClickListener(v -> navigateToNewTransaction());
 | 
				
			||||||
            
 | 
					        btnRetry.setOnClickListener(v -> retryTransaction());
 | 
				
			||||||
            // ✅ NEW: Update title based on payment type
 | 
					    }
 | 
				
			||||||
            if (midtransResponse != null) {
 | 
					    
 | 
				
			||||||
                getSupportActionBar().setTitle("Detail Pembayaran Midtrans");
 | 
					    private void prepareTransactionData() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Parse Midtrans response if available
 | 
				
			||||||
 | 
					            if (midtransResponse != null && !midtransResponse.isEmpty()) {
 | 
				
			||||||
 | 
					                responseJsonData = new JSONObject(midtransResponse);
 | 
				
			||||||
 | 
					                responseDataString = formatJson(midtransResponse);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                getSupportActionBar().setTitle("Detail Transaksi");
 | 
					                // Generate fallback response data
 | 
				
			||||||
 | 
					                responseDataString = generateFallbackResponseData();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Log.e(TAG, "Error preparing transaction data: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					            responseDataString = generateFallbackResponseData();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        tvTransactionSummary = findViewById(R.id.tv_transaction_summary);
 | 
					        // Set response data to TextView
 | 
				
			||||||
        tvCardData = findViewById(R.id.tv_card_data);
 | 
					        tvResponseData.setText(responseDataString != null ? responseDataString : "No response data available");
 | 
				
			||||||
        btnCopyData = findViewById(R.id.btn_copy_data);
 | 
					 | 
				
			||||||
        btnNewTransaction = findViewById(R.id.btn_new_transaction);
 | 
					 | 
				
			||||||
        btnPrintReceipt = findViewById(R.id.btn_print_receipt);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        btnCopyData.setOnClickListener(v -> copyCardDataToClipboard());
 | 
					 | 
				
			||||||
        btnNewTransaction.setOnClickListener(v -> startNewTransaction());
 | 
					 | 
				
			||||||
        btnPrintReceipt.setOnClickListener(v -> printReceipt());
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    private void initEMVComponents() {
 | 
					    private void displayTransactionSummary() {
 | 
				
			||||||
        if (MyApplication.app != null) {
 | 
					        // Amount
 | 
				
			||||||
            mEMVOptV2 = MyApplication.app.emvOptV2;
 | 
					        if (transactionAmount != null) {
 | 
				
			||||||
            android.util.Log.d(TAG, "EMV components initialized for TLV data retrieval");
 | 
					            long amountCents = Long.parseLong(transactionAmount);
 | 
				
			||||||
 | 
					            NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
 | 
				
			||||||
 | 
					            String formattedAmount = "Rp " + formatter.format(amountCents);
 | 
				
			||||||
 | 
					            tvAmount.setText(formattedAmount);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					        
 | 
				
			||||||
 | 
					        // Status
 | 
				
			||||||
    private void loadCardData() {
 | 
					        String status;
 | 
				
			||||||
        // ✅ NEW: Check if this is a Midtrans payment result
 | 
					        int statusColor;
 | 
				
			||||||
        if (midtransResponse != null) {
 | 
					        if (paymentSuccess) {
 | 
				
			||||||
            loadMidtransPaymentData();
 | 
					            status = "SUCCESS";
 | 
				
			||||||
        } else if (isEMVMode && mEMVOptV2 != null) {
 | 
					            statusColor = getResources().getColor(R.color.status_success);
 | 
				
			||||||
            loadEMVTlvData();
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            loadSimpleCardData();
 | 
					            status = "FAILED";
 | 
				
			||||||
 | 
					            statusColor = getResources().getColor(R.color.status_error);
 | 
				
			||||||
 | 
					            btnRetry.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        tvStatus.setText(status);
 | 
				
			||||||
 | 
					        tvStatus.setTextColor(statusColor);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Reference
 | 
				
			||||||
 | 
					        tvReference.setText(referenceId != null ? referenceId : "N/A");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Card info
 | 
				
			||||||
 | 
					        if (cardNo != null) {
 | 
				
			||||||
 | 
					            tvCardInfo.setText(maskCardNumber(cardNo));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            tvCardInfo.setText("N/A");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Payment method
 | 
				
			||||||
 | 
					        tvPaymentMethod.setText(cardType != null ? cardType : "Unknown");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Transaction details from response
 | 
				
			||||||
 | 
					        if (responseJsonData != null) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (responseJsonData.has("transaction_id")) {
 | 
				
			||||||
 | 
					                    tvTransactionId.setText(responseJsonData.getString("transaction_id"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (responseJsonData.has("order_id")) {
 | 
				
			||||||
 | 
					                    tvOrderId.setText(responseJsonData.getString("order_id"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (responseJsonData.has("transaction_time")) {
 | 
				
			||||||
 | 
					                    tvTimestamp.setText(responseJsonData.getString("transaction_time"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Show error details if transaction failed
 | 
				
			||||||
 | 
					                if (!paymentSuccess && responseJsonData.has("status_message")) {
 | 
				
			||||||
 | 
					                    String errorMessage = responseJsonData.getString("status_message");
 | 
				
			||||||
 | 
					                    if (responseJsonData.has("channel_response_message")) {
 | 
				
			||||||
 | 
					                        errorMessage += "\n" + responseJsonData.getString("channel_response_message");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    tvErrorDetails.setText(errorMessage);
 | 
				
			||||||
 | 
					                    layoutErrorDetails.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            } catch (JSONException e) {
 | 
				
			||||||
 | 
					                Log.e(TAG, "Error parsing response data: " + e.getMessage());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            tvTransactionId.setText("N/A");
 | 
				
			||||||
 | 
					            tvOrderId.setText("N/A");
 | 
				
			||||||
 | 
					            tvTimestamp.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // ✅ NEW: Load and display Midtrans payment data
 | 
					    private void navigateToNewTransaction() {
 | 
				
			||||||
    private void loadMidtransPaymentData() {
 | 
					 | 
				
			||||||
        android.util.Log.d(TAG, "======== DISPLAYING MIDTRANS PAYMENT RESULT ========");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        StringBuilder summary = new StringBuilder();
 | 
					 | 
				
			||||||
        StringBuilder paymentInfo = new StringBuilder();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            // Transaction Summary
 | 
					 | 
				
			||||||
            summary.append("==== PEMBAYARAN BERHASIL ====\n");
 | 
					 | 
				
			||||||
            summary.append("Amount: ").append(formatAmount(Long.parseLong(transactionAmount))).append("\n");
 | 
					 | 
				
			||||||
            summary.append("Payment Method: Midtrans Credit Card\n");
 | 
					 | 
				
			||||||
            summary.append("Status: ").append(isPaymentSuccess ? "SUCCESS" : "PENDING").append("\n");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (referenceId != null) {
 | 
					 | 
				
			||||||
                summary.append("Reference ID: ").append(referenceId).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Midtrans Transaction Details
 | 
					 | 
				
			||||||
            paymentInfo.append("==== MIDTRANS TRANSACTION DETAILS ====\n");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Extract key information from Midtrans response
 | 
					 | 
				
			||||||
            if (midtransResponse.has("transaction_id")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Transaction ID: ").append(midtransResponse.getString("transaction_id")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("order_id")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Order ID: ").append(midtransResponse.getString("order_id")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("transaction_status")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Transaction Status: ").append(midtransResponse.getString("transaction_status")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("transaction_time")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Transaction Time: ").append(midtransResponse.getString("transaction_time")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("payment_type")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Payment Type: ").append(midtransResponse.getString("payment_type")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("gross_amount")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Gross Amount: ").append(midtransResponse.getString("gross_amount")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("currency")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Currency: ").append(midtransResponse.getString("currency")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("fraud_status")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Fraud Status: ").append(midtransResponse.getString("fraud_status")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("status_code")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Status Code: ").append(midtransResponse.getString("status_code")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("status_message")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Status Message: ").append(midtransResponse.getString("status_message")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Card Information (if available from EMV)
 | 
					 | 
				
			||||||
            if (cardNo != null && !cardNo.isEmpty()) {
 | 
					 | 
				
			||||||
                summary.append("\n==== CARD INFORMATION ====\n");
 | 
					 | 
				
			||||||
                summary.append("Card Number: ").append(maskCardNumber(cardNo)).append("\n");
 | 
					 | 
				
			||||||
                summary.append("Card Type: EMV ").append(getCardTypeDisplay()).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Bank/Acquirer Information
 | 
					 | 
				
			||||||
            if (midtransResponse.has("acquirer")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("\n==== ACQUIRER INFORMATION ====\n");
 | 
					 | 
				
			||||||
                paymentInfo.append("Acquirer: ").append(midtransResponse.getString("acquirer")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (midtransResponse.has("merchant_id")) {
 | 
					 | 
				
			||||||
                paymentInfo.append("Merchant ID: ").append(midtransResponse.getString("merchant_id")).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Additional Midtrans Data
 | 
					 | 
				
			||||||
            paymentInfo.append("\n==== ADDITIONAL INFORMATION ====\n");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Show credit card details if available
 | 
					 | 
				
			||||||
            if (midtransResponse.has("credit_card")) {
 | 
					 | 
				
			||||||
                JSONObject creditCard = midtransResponse.getJSONObject("credit_card");
 | 
					 | 
				
			||||||
                if (creditCard.has("bank")) {
 | 
					 | 
				
			||||||
                    paymentInfo.append("Issuing Bank: ").append(creditCard.getString("bank")).append("\n");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (creditCard.has("card_type")) {
 | 
					 | 
				
			||||||
                    paymentInfo.append("Card Type: ").append(creditCard.getString("card_type")).append("\n");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (creditCard.has("three_d_secure")) {
 | 
					 | 
				
			||||||
                    paymentInfo.append("3D Secure: ").append(creditCard.getString("three_d_secure")).append("\n");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Security Information
 | 
					 | 
				
			||||||
            if (midtransResponse.has("signature_key")) {
 | 
					 | 
				
			||||||
                String signature = midtransResponse.getString("signature_key");
 | 
					 | 
				
			||||||
                paymentInfo.append("Signature: ").append(signature.substring(0, Math.min(16, signature.length()))).append("...\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            // Raw Midtrans Response (truncated for display)
 | 
					 | 
				
			||||||
            paymentInfo.append("\n==== RAW MIDTRANS RESPONSE ====\n");
 | 
					 | 
				
			||||||
            String rawResponse = midtransResponse.toString();
 | 
					 | 
				
			||||||
            if (rawResponse.length() > 1000) {
 | 
					 | 
				
			||||||
                paymentInfo.append(rawResponse.substring(0, 1000)).append("...\n");
 | 
					 | 
				
			||||||
                paymentInfo.append("\n[Response truncated - use Copy Data to get full response]");
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                paymentInfo.append(rawResponse);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            android.util.Log.d(TAG, "✅ Midtrans payment data loaded successfully");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            android.util.Log.e(TAG, "Error loading Midtrans data: " + e.getMessage());
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            summary.append("==== PAYMENT ERROR ====\n");
 | 
					 | 
				
			||||||
            summary.append("Error loading payment details: ").append(e.getMessage()).append("\n");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            paymentInfo.append("Raw Response: ").append(midtransResponseJson != null ? midtransResponseJson : "No response data");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        tvTransactionSummary.setText(summary.toString());
 | 
					 | 
				
			||||||
        tvCardData.setText(paymentInfo.toString());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void loadEMVTlvData() {
 | 
					 | 
				
			||||||
        android.util.Log.d(TAG, "======== RETRIEVING COMPLETE EMV CARD DATA ========");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            String[] standardTagList = {
 | 
					 | 
				
			||||||
                    "4F", "50", "57", "5A", "5F20", "5F24", "5F25", "5F28", "5F2A", "5F2D", "5F30", "5F34",
 | 
					 | 
				
			||||||
                    "82", "84", "87", "88", "8A", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", "95",
 | 
					 | 
				
			||||||
                    "9A", "9B", "9C", "9D", "9F01", "9F02", "9F03", "9F04", "9F05", "9F06", "9F07", "9F08", "9F09",
 | 
					 | 
				
			||||||
                    "9F0D", "9F0E", "9F0F", "9F10", "9F11", "9F12", "9F13", "9F14", "9F15", "9F16", "9F17", "9F18",
 | 
					 | 
				
			||||||
                    "9F1A", "9F1B", "9F1C", "9F1D", "9F1E", "9F1F", "9F20", "9F21", "9F22", "9F23", "9F26", "9F27",
 | 
					 | 
				
			||||||
                    "9F2D", "9F2E", "9F2F", "9F32", "9F33", "9F34", "9F35", "9F36", "9F37", "9F38", "9F39", "9F3A",
 | 
					 | 
				
			||||||
                    "9F3B", "9F3C", "9F3D", "9F40", "9F41", "9F42", "9F43", "9F44", "9F45", "9F46", "9F47", "9F48",
 | 
					 | 
				
			||||||
                    "9F49", "9F4A", "9F4B", "9F4C", "9F4D", "9F4E", "9F53", "9F54", "9F55", "9F56", "9F57", "9F58",
 | 
					 | 
				
			||||||
                    "9F59", "9F5A", "9F5B", "9F5C", "9F5D", "9F5E", "9F61", "9F62", "9F63", "9F64", "9F65", "9F66",
 | 
					 | 
				
			||||||
                    "9F67", "9F68", "9F69", "9F6A", "9F6B", "9F6C", "9F6D", "9F6E", "9F70", "9F71", "9F72", "9F73",
 | 
					 | 
				
			||||||
                    "9F74", "9F75", "9F76", "9F77", "9F78", "9F79", "9F7A", "9F7B", "9F7C", "9F7D", "9F7E", "9F7F"
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            String[] payPassTagList = {
 | 
					 | 
				
			||||||
                    "DF810C", "DF8117", "DF8118", "DF8119", "DF811A", "DF811B", "DF811C", "DF811D", "DF811E", "DF811F",
 | 
					 | 
				
			||||||
                    "DF8120", "DF8121", "DF8122", "DF8123", "DF8124", "DF8125", "DF8126", "DF8127", "DF8128", "DF8129",
 | 
					 | 
				
			||||||
                    "DF812A", "DF812B", "DF812C", "DF812D", "DF812E", "DF812F", "DF8130", "DF8131", "DF8132", "DF8133",
 | 
					 | 
				
			||||||
                    "DF8134", "DF8135", "DF8136", "DF8137", "DF8138", "DF8139", "DF813A", "DF813B", "DF813C", "DF813D",
 | 
					 | 
				
			||||||
                    "DF8161", "DF8167", "DF8168", "DF8169", "DF8170"
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            byte[] outData = new byte[4096];
 | 
					 | 
				
			||||||
            Map<String, TLV> allTlvMap = new TreeMap<>();
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            int tlvOpCode = AidlConstantsV2.EMV.TLVOpCode.OP_NORMAL;
 | 
					 | 
				
			||||||
            if ("NFC".equals(cardType)) {
 | 
					 | 
				
			||||||
                tlvOpCode = AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            android.util.Log.d(TAG, "Using TLV OpCode: " + tlvOpCode);
 | 
					 | 
				
			||||||
            android.util.Log.d(TAG, "Requesting " + standardTagList.length + " standard tags");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            int len = mEMVOptV2.getTlvList(tlvOpCode, standardTagList, outData);
 | 
					 | 
				
			||||||
            if (len > 0) {
 | 
					 | 
				
			||||||
                byte[] bytes = Arrays.copyOf(outData, len);
 | 
					 | 
				
			||||||
                String hexStr = ByteUtil.bytes2HexStr(bytes);
 | 
					 | 
				
			||||||
                android.util.Log.d(TAG, "Retrieved " + len + " bytes of standard TLV data");
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                Map<String, TLV> tlvMap = TLVUtils.buildTLVMap(hexStr);
 | 
					 | 
				
			||||||
                allTlvMap.putAll(tlvMap);
 | 
					 | 
				
			||||||
                android.util.Log.d(TAG, "Parsed " + tlvMap.size() + " standard TLV tags");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if ("NFC".equals(cardType)) {
 | 
					 | 
				
			||||||
                android.util.Log.d(TAG, "Requesting " + payPassTagList.length + " PayPass specific tags");
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                len = mEMVOptV2.getTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS, payPassTagList, outData);
 | 
					 | 
				
			||||||
                if (len > 0) {
 | 
					 | 
				
			||||||
                    byte[] bytes = Arrays.copyOf(outData, len);
 | 
					 | 
				
			||||||
                    String hexStr = ByteUtil.bytes2HexStr(bytes);
 | 
					 | 
				
			||||||
                    android.util.Log.d(TAG, "Retrieved " + len + " bytes of PayPass TLV data");
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    Map<String, TLV> payPassTlvMap = TLVUtils.buildTLVMap(hexStr);
 | 
					 | 
				
			||||||
                    allTlvMap.putAll(payPassTlvMap);
 | 
					 | 
				
			||||||
                    android.util.Log.d(TAG, "Parsed " + payPassTlvMap.size() + " PayPass TLV tags");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            displayEMVData(allTlvMap);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            android.util.Log.d(TAG, "Total TLV tags retrieved: " + allTlvMap.size());
 | 
					 | 
				
			||||||
            android.util.Log.d(TAG, "==================================");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            android.util.Log.e(TAG, "Error retrieving TLV data: " + e.getMessage());
 | 
					 | 
				
			||||||
            e.printStackTrace();
 | 
					 | 
				
			||||||
            showSimpleError("Error retrieving EMV data: " + e.getMessage());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void loadSimpleCardData() {
 | 
					 | 
				
			||||||
        StringBuilder summary = new StringBuilder();
 | 
					 | 
				
			||||||
        StringBuilder cardInfo = new StringBuilder();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Transaction Summary
 | 
					 | 
				
			||||||
        summary.append("==== TRANSACTION COMPLETED ====\n");
 | 
					 | 
				
			||||||
        summary.append("Amount: ").append(formatAmount(Long.parseLong(transactionAmount))).append("\n");
 | 
					 | 
				
			||||||
        summary.append("Payment Method: ").append(getCardTypeDisplay()).append("\n");
 | 
					 | 
				
			||||||
        summary.append("Status: SUCCESS\n");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (referenceId != null) {
 | 
					 | 
				
			||||||
            summary.append("Reference ID: ").append(referenceId).append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Card Information
 | 
					 | 
				
			||||||
        if (cardData != null) {
 | 
					 | 
				
			||||||
            cardInfo.append("==== CARD INFORMATION ====\n");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if ("MAGNETIC".equals(cardType)) {
 | 
					 | 
				
			||||||
                String track1 = Utility.null2String(cardData.getString("TRACK1"));
 | 
					 | 
				
			||||||
                String track2 = Utility.null2String(cardData.getString("TRACK2"));
 | 
					 | 
				
			||||||
                String track3 = Utility.null2String(cardData.getString("TRACK3"));
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                cardInfo.append("Track1: ").append(track1.isEmpty() ? "N/A" : track1).append("\n");
 | 
					 | 
				
			||||||
                cardInfo.append("Track2: ").append(track2.isEmpty() ? "N/A" : track2).append("\n");
 | 
					 | 
				
			||||||
                cardInfo.append("Track3: ").append(track3.isEmpty() ? "N/A" : track3).append("\n");
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            } else if ("IC".equals(cardType)) {
 | 
					 | 
				
			||||||
                String atr = cardData.getString("atr", "");
 | 
					 | 
				
			||||||
                cardInfo.append("ATR: ").append(atr.isEmpty() ? "N/A" : atr).append("\n");
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            } else if ("NFC".equals(cardType)) {
 | 
					 | 
				
			||||||
                String uuid = cardData.getString("uuid", "");
 | 
					 | 
				
			||||||
                String ats = cardData.getString("ats", "");
 | 
					 | 
				
			||||||
                int sak = cardData.getInt("sak", -1);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                cardInfo.append("UUID: ").append(uuid.isEmpty() ? "N/A" : uuid).append("\n");
 | 
					 | 
				
			||||||
                if (!ats.isEmpty()) {
 | 
					 | 
				
			||||||
                    cardInfo.append("ATS: ").append(ats).append("\n");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (sak != -1) {
 | 
					 | 
				
			||||||
                    cardInfo.append("SAK: ").append(String.format("0x%02X", sak)).append("\n");
 | 
					 | 
				
			||||||
                    cardInfo.append("Type: ").append(analyzeCardTypeBySAK(sak)).append("\n");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        tvTransactionSummary.setText(summary.toString());
 | 
					 | 
				
			||||||
        tvCardData.setText(cardInfo.toString());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void displayEMVData(Map<String, TLV> allTlvMap) {
 | 
					 | 
				
			||||||
        StringBuilder summary = new StringBuilder();
 | 
					 | 
				
			||||||
        StringBuilder cardInfo = new StringBuilder();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Transaction Summary
 | 
					 | 
				
			||||||
        summary.append("==== TRANSACTION COMPLETED ====\n");
 | 
					 | 
				
			||||||
        summary.append("Amount: ").append(formatAmount(Long.parseLong(transactionAmount))).append("\n");
 | 
					 | 
				
			||||||
        summary.append("Payment Method: EMV ").append(cardType).append("\n");
 | 
					 | 
				
			||||||
        summary.append("Status: SUCCESS\n");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (referenceId != null) {
 | 
					 | 
				
			||||||
            summary.append("Reference ID: ").append(referenceId).append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Card Summary
 | 
					 | 
				
			||||||
        summary.append("\n==== CARD SUMMARY ====\n");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        TLV panTlv = allTlvMap.get("5A");
 | 
					 | 
				
			||||||
        if (panTlv != null && !TextUtils.isEmpty(panTlv.getValue())) {
 | 
					 | 
				
			||||||
            summary.append("Card Number: ").append(panTlv.getValue()).append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        TLV labelTlv = allTlvMap.get("50");
 | 
					 | 
				
			||||||
        if (labelTlv != null && !TextUtils.isEmpty(labelTlv.getValue())) {
 | 
					 | 
				
			||||||
            summary.append("App Label: ").append(hexToString(labelTlv.getValue())).append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        TLV nameTlv = allTlvMap.get("5F20");
 | 
					 | 
				
			||||||
        if (nameTlv != null && !TextUtils.isEmpty(nameTlv.getValue())) {
 | 
					 | 
				
			||||||
            summary.append("Cardholder: ").append(hexToString(nameTlv.getValue()).trim()).append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        TLV expiryTlv = allTlvMap.get("5F24");
 | 
					 | 
				
			||||||
        if (expiryTlv != null && !TextUtils.isEmpty(expiryTlv.getValue())) {
 | 
					 | 
				
			||||||
            String expiry = expiryTlv.getValue();
 | 
					 | 
				
			||||||
            if (expiry.length() == 6) {
 | 
					 | 
				
			||||||
                summary.append("Expiry: ").append(expiry.substring(2, 4)).append("/").append(expiry.substring(0, 2)).append("\n");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        TLV aidTlv = allTlvMap.get("9F06");
 | 
					 | 
				
			||||||
        if (aidTlv != null && !TextUtils.isEmpty(aidTlv.getValue())) {
 | 
					 | 
				
			||||||
            summary.append("Scheme: ").append(identifyPaymentScheme(aidTlv.getValue())).append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Detailed TLV Data
 | 
					 | 
				
			||||||
        cardInfo.append("==== DETAILED TLV DATA ====\n");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        Set<String> keySet = allTlvMap.keySet();
 | 
					 | 
				
			||||||
        for (String key : keySet) {
 | 
					 | 
				
			||||||
            TLV tlv = allTlvMap.get(key);
 | 
					 | 
				
			||||||
            String value = tlv != null ? tlv.getValue() : "";
 | 
					 | 
				
			||||||
            String description = getTlvDescription(key);
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            cardInfo.append(key);
 | 
					 | 
				
			||||||
            if (!description.equals("Unknown")) {
 | 
					 | 
				
			||||||
                cardInfo.append(" (").append(description).append(")");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            cardInfo.append(": ");
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            if (key.equals("5A") && !value.isEmpty()) {
 | 
					 | 
				
			||||||
                cardInfo.append(value);
 | 
					 | 
				
			||||||
            } else if (key.equals("50") && !value.isEmpty()) {
 | 
					 | 
				
			||||||
                String decodedLabel = hexToString(value);
 | 
					 | 
				
			||||||
                cardInfo.append(value).append(" (").append(decodedLabel).append(")");
 | 
					 | 
				
			||||||
            } else if (key.equals("5F20") && !value.isEmpty()) {
 | 
					 | 
				
			||||||
                String decodedName = hexToString(value);
 | 
					 | 
				
			||||||
                cardInfo.append(value).append(" (").append(decodedName.trim()).append(")");
 | 
					 | 
				
			||||||
            } else if (key.equals("9F06") && !value.isEmpty()) {
 | 
					 | 
				
			||||||
                String scheme = identifyPaymentScheme(value);
 | 
					 | 
				
			||||||
                cardInfo.append(value).append(" (").append(scheme).append(")");
 | 
					 | 
				
			||||||
            } else if (key.equals("5F24") && !value.isEmpty()) {
 | 
					 | 
				
			||||||
                cardInfo.append(value);
 | 
					 | 
				
			||||||
                if (value.length() == 6) {
 | 
					 | 
				
			||||||
                    cardInfo.append(" (").append(value.substring(2, 4)).append("/").append(value.substring(0, 2)).append(")");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else if (key.equals("9F02") && !value.isEmpty()) {
 | 
					 | 
				
			||||||
                cardInfo.append(value);
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    long amount = Long.parseLong(value, 16);
 | 
					 | 
				
			||||||
                    cardInfo.append(" (").append(String.format("%.2f", amount / 100.0)).append(")");
 | 
					 | 
				
			||||||
                } catch (Exception e) {
 | 
					 | 
				
			||||||
                    // Keep original value
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                cardInfo.append(value);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            cardInfo.append("\n");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        cardInfo.append("\nTotal TLV tags retrieved: ").append(keySet.size());
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        tvTransactionSummary.setText(summary.toString());
 | 
					 | 
				
			||||||
        tvCardData.setText(cardInfo.toString());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void showSimpleError(String error) {
 | 
					 | 
				
			||||||
        StringBuilder summary = new StringBuilder();
 | 
					 | 
				
			||||||
        summary.append("==== TRANSACTION ERROR ====\n");
 | 
					 | 
				
			||||||
        summary.append("Amount: ").append(formatAmount(Long.parseLong(transactionAmount))).append("\n");
 | 
					 | 
				
			||||||
        summary.append("Status: FAILED\n");
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        tvTransactionSummary.setText(summary.toString());
 | 
					 | 
				
			||||||
        tvCardData.setText(error);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private String getCardTypeDisplay() {
 | 
					 | 
				
			||||||
        switch (cardType) {
 | 
					 | 
				
			||||||
            case "MAGNETIC": return "Magnetic Card";
 | 
					 | 
				
			||||||
            case "IC": return "IC Card";
 | 
					 | 
				
			||||||
            case "NFC": return "NFC/RF Card";
 | 
					 | 
				
			||||||
            case "EMV_MIDTRANS": return "EMV Credit Card (Midtrans)";
 | 
					 | 
				
			||||||
            default: return cardType;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private String formatAmount(long amountCents) {
 | 
					 | 
				
			||||||
        double amountRupiah = amountCents / 100.0;
 | 
					 | 
				
			||||||
        NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
 | 
					 | 
				
			||||||
        return formatter.format(amountRupiah);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void copyCardDataToClipboard() {
 | 
					 | 
				
			||||||
        String summary = tvTransactionSummary.getText().toString();
 | 
					 | 
				
			||||||
        String cardData = tvCardData.getText().toString();
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        StringBuilder fullData = new StringBuilder();
 | 
					 | 
				
			||||||
        fullData.append(summary).append("\n\n").append(cardData);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // ✅ NEW: Include full Midtrans response if available
 | 
					 | 
				
			||||||
        if (midtransResponseJson != null && !midtransResponseJson.isEmpty()) {
 | 
					 | 
				
			||||||
            fullData.append("\n\n==== FULL MIDTRANS RESPONSE ====\n");
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                // Pretty print JSON
 | 
					 | 
				
			||||||
                JSONObject json = new JSONObject(midtransResponseJson);
 | 
					 | 
				
			||||||
                fullData.append(json.toString(2));
 | 
					 | 
				
			||||||
            } catch (Exception e) {
 | 
					 | 
				
			||||||
                fullData.append(midtransResponseJson);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
 | 
					 | 
				
			||||||
        ClipData clip = ClipData.newPlainText("Transaction Data", fullData.toString());
 | 
					 | 
				
			||||||
        clipboard.setPrimaryClip(clip);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (midtransResponse != null) {
 | 
					 | 
				
			||||||
            showToast("Payment data copied to clipboard (includes full Midtrans response)");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            showToast("Transaction data copied to clipboard");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void startNewTransaction() {
 | 
					 | 
				
			||||||
        Intent intent = new Intent(this, CreateTransactionActivity.class);
 | 
					        Intent intent = new Intent(this, CreateTransactionActivity.class);
 | 
				
			||||||
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 | 
					        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
 | 
				
			||||||
        startActivity(intent);
 | 
					        startActivity(intent);
 | 
				
			||||||
        finish();
 | 
					        finish();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    private void printReceipt() {
 | 
					    private void retryTransaction() {
 | 
				
			||||||
        // ✅ NEW: Enhanced print functionality for Midtrans receipts
 | 
					        // Navigate back to CreateTransactionActivity with same amount
 | 
				
			||||||
        if (midtransResponse != null) {
 | 
					        Intent intent = new Intent(this, CreateTransactionActivity.class);
 | 
				
			||||||
            // TODO: Implement Midtrans receipt printing
 | 
					        intent.putExtra("RETRY_AMOUNT", transactionAmount);
 | 
				
			||||||
            showToast("Midtrans receipt printing to be implemented");
 | 
					        startActivity(intent);
 | 
				
			||||||
        } else {
 | 
					        finish();
 | 
				
			||||||
            // TODO: Implement standard receipt printing
 | 
					 | 
				
			||||||
            showToast("Standard receipt printing to be implemented");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ====== HELPER METHODS ======
 | 
					 | 
				
			||||||
    private String getTlvDescription(String tag) {
 | 
					 | 
				
			||||||
        // [Same as original implementation - truncated for brevity]
 | 
					 | 
				
			||||||
        switch (tag.toUpperCase()) {
 | 
					 | 
				
			||||||
            case "4F": return "Application Identifier";
 | 
					 | 
				
			||||||
            case "50": return "Application Label";
 | 
					 | 
				
			||||||
            case "57": return "Track 2 Equivalent Data";
 | 
					 | 
				
			||||||
            case "5A": return "Application PAN";
 | 
					 | 
				
			||||||
            case "5F20": return "Cardholder Name";
 | 
					 | 
				
			||||||
            case "5F24": return "Application Expiry Date";
 | 
					 | 
				
			||||||
            // ... [Include all original TLV descriptions]
 | 
					 | 
				
			||||||
            default: return "Unknown";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private String identifyPaymentScheme(String aid) {
 | 
					    private String generateFallbackResponseData() {
 | 
				
			||||||
        if (aid == null) return "Unknown";
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (aid.startsWith("A000000333")) return "UnionPay";
 | 
					 | 
				
			||||||
        else if (aid.startsWith("A000000003")) return "Visa";
 | 
					 | 
				
			||||||
        else if (aid.startsWith("A000000004") || aid.startsWith("A000000005")) return "MasterCard";
 | 
					 | 
				
			||||||
        else if (aid.startsWith("A000000025")) return "American Express";
 | 
					 | 
				
			||||||
        else if (aid.startsWith("A000000065")) return "JCB";
 | 
					 | 
				
			||||||
        else if (aid.startsWith("A000000524")) return "RuPay";
 | 
					 | 
				
			||||||
        else return "Unknown (" + aid + ")";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private String hexToString(String hex) {
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            StringBuilder sb = new StringBuilder();
 | 
					            JSONObject fallbackResponse = new JSONObject();
 | 
				
			||||||
            for (int i = 0; i < hex.length(); i += 2) {
 | 
					            fallbackResponse.put("status_code", paymentSuccess ? "200" : "202");
 | 
				
			||||||
                String str = hex.substring(i, i + 2);
 | 
					            fallbackResponse.put("status_message", paymentSuccess ? "Transaction success" : "Deny by Bank [MANDIRI] with code [N7] and message [Decline for CVV2 failure]");
 | 
				
			||||||
                sb.append((char) Integer.parseInt(str, 16));
 | 
					            fallbackResponse.put("transaction_id", generateTransactionId());
 | 
				
			||||||
 | 
					            fallbackResponse.put("order_id", "TKN" + System.currentTimeMillis());
 | 
				
			||||||
 | 
					            fallbackResponse.put("merchant_id", "G616299250");
 | 
				
			||||||
 | 
					            fallbackResponse.put("gross_amount", (transactionAmount != null ? transactionAmount : "19000") + ".00");
 | 
				
			||||||
 | 
					            fallbackResponse.put("currency", "IDR");
 | 
				
			||||||
 | 
					            fallbackResponse.put("payment_type", "credit_card");
 | 
				
			||||||
 | 
					            fallbackResponse.put("transaction_time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()));
 | 
				
			||||||
 | 
					            fallbackResponse.put("transaction_status", paymentSuccess ? "capture" : "deny");
 | 
				
			||||||
 | 
					            fallbackResponse.put("fraud_status", "accept");
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!paymentSuccess) {
 | 
				
			||||||
 | 
					                fallbackResponse.put("channel_response_code", "N7");
 | 
				
			||||||
 | 
					                fallbackResponse.put("channel_response_message", "Decline for CVV2 failure");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return sb.toString().trim();
 | 
					            
 | 
				
			||||||
        } catch (Exception e) {
 | 
					            fallbackResponse.put("expiry_time", getExpiryTime());
 | 
				
			||||||
            return hex;
 | 
					            fallbackResponse.put("bank", "mandiri");
 | 
				
			||||||
        }
 | 
					            fallbackResponse.put("masked_card", cardNo != null ? maskCardNumber(cardNo) : "46169912-9849");
 | 
				
			||||||
    }
 | 
					            fallbackResponse.put("card_type", "debit");
 | 
				
			||||||
 | 
					            fallbackResponse.put("channel", "mti");
 | 
				
			||||||
    private String analyzeCardTypeBySAK(int sak) {
 | 
					            fallbackResponse.put("on_us", true);
 | 
				
			||||||
        switch (sak & 0xFF) {
 | 
					            
 | 
				
			||||||
            case 0x00: return "MIFARE Ultralight";
 | 
					            return formatJson(fallbackResponse.toString());
 | 
				
			||||||
            case 0x04: return "MIFARE Classic 1K";
 | 
					            
 | 
				
			||||||
            case 0x08: return "MIFARE Classic 1K";
 | 
					        } catch (JSONException e) {
 | 
				
			||||||
            case 0x09: return "MIFARE Mini";
 | 
					            Log.e(TAG, "Error generating fallback response: " + e.getMessage());
 | 
				
			||||||
            case 0x18: return "MIFARE Classic 4K";
 | 
					            return "{\n  \"status\": \"" + (paymentSuccess ? "SUCCESS" : "FAILED") + "\",\n  \"timestamp\": \"" + 
 | 
				
			||||||
            case 0x20: return "MIFARE Plus/DESFire";
 | 
					                   new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date()) + "\"\n}";
 | 
				
			||||||
            case 0x28: return "JCOP 30";
 | 
					        }
 | 
				
			||||||
            case 0x38: return "MIFARE DESFire";
 | 
					    }
 | 
				
			||||||
            case 0x88: return "Infineon my-d move";
 | 
					    
 | 
				
			||||||
            case 0x98: return "Gemplus MPCOS";
 | 
					    private String generateTransactionId() {
 | 
				
			||||||
            default: return "Unknown card type (SAK: 0x" + String.format("%02X", sak) + ")";
 | 
					        return java.util.UUID.randomUUID().toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Helper methods
 | 
				
			||||||
 | 
					    private String formatJson(String jsonString) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            JSONObject json = new JSONObject(jsonString);
 | 
				
			||||||
 | 
					            return json.toString(2); // Indent with 2 spaces
 | 
				
			||||||
 | 
					        } catch (JSONException e) {
 | 
				
			||||||
 | 
					            return jsonString; // Return original if formatting fails
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // ✅ NEW: Mask card number for display
 | 
					 | 
				
			||||||
    private String maskCardNumber(String cardNumber) {
 | 
					    private String maskCardNumber(String cardNumber) {
 | 
				
			||||||
        if (cardNumber == null || cardNumber.length() < 8) {
 | 
					        if (cardNumber == null || cardNumber.length() < 8) {
 | 
				
			||||||
            return cardNumber;
 | 
					            return cardNumber;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        String first4 = cardNumber.substring(0, 4);
 | 
					        String first4 = cardNumber.substring(0, 4);
 | 
				
			||||||
        String last4 = cardNumber.substring(cardNumber.length() - 4);
 | 
					        String last4 = cardNumber.substring(cardNumber.length() - 4);
 | 
				
			||||||
        StringBuilder middle = new StringBuilder();
 | 
					        return first4 + "****" + last4;
 | 
				
			||||||
        for (int i = 0; i < cardNumber.length() - 8; i++) {
 | 
					    }
 | 
				
			||||||
            middle.append("*");
 | 
					    
 | 
				
			||||||
 | 
					    private String extractExpiryMonth() {
 | 
				
			||||||
 | 
					        if (emvExpiry != null && emvExpiry.length() >= 4) {
 | 
				
			||||||
 | 
					            return emvExpiry.substring(2, 4);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return first4 + middle.toString() + last4;
 | 
					        return "06";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    private void showToast(String message) {
 | 
					    private String extractExpiryYear() {
 | 
				
			||||||
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
 | 
					        if (emvExpiry != null && emvExpiry.length() >= 4) {
 | 
				
			||||||
 | 
					            return "20" + emvExpiry.substring(0, 2);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return "2027";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    @Override
 | 
					    private String getExpiryTime() {
 | 
				
			||||||
    public boolean onSupportNavigateUp() {
 | 
					        // Add 7 days to current time
 | 
				
			||||||
        onBackPressed();
 | 
					        long currentTime = System.currentTimeMillis();
 | 
				
			||||||
        return true;
 | 
					        long expiryTime = currentTime + (7 * 24 * 60 * 60 * 1000L);
 | 
				
			||||||
 | 
					        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date(expiryTime));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -2,25 +2,30 @@ package com.example.bdkipoc.transaction.managers;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import android.content.Context;
 | 
					import android.content.Context;
 | 
				
			||||||
import android.os.AsyncTask;
 | 
					import android.os.AsyncTask;
 | 
				
			||||||
import android.os.Handler;
 | 
					 | 
				
			||||||
import android.os.Looper;
 | 
					 | 
				
			||||||
import android.util.Log;
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.json.JSONArray;
 | 
				
			||||||
import org.json.JSONException;
 | 
					import org.json.JSONException;
 | 
				
			||||||
import org.json.JSONObject;
 | 
					import org.json.JSONObject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.BufferedReader;
 | 
					import java.io.BufferedReader;
 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.io.InputStreamReader;
 | 
					import java.io.InputStreamReader;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
import java.net.HttpURLConnection;
 | 
					import java.net.HttpURLConnection;
 | 
				
			||||||
import java.net.URI;
 | 
					import java.net.URI;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
import java.util.UUID;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * MidtransCardPaymentManager - Handles credit card payment integration with Midtrans
 | 
					 * MidtransCardPaymentManager - Fixed Version for EMV Card Processing
 | 
				
			||||||
 * Based on QrisActivity reference implementation
 | 
					 * 
 | 
				
			||||||
 | 
					 * Key Features:
 | 
				
			||||||
 | 
					 * - Uses static CVV "493" for all transactions (both EMV and regular cards)
 | 
				
			||||||
 | 
					 * - EMV-first approach with tokenization fallback
 | 
				
			||||||
 | 
					 * - Handles Midtrans API requirements where CVV is mandatory even for EMV
 | 
				
			||||||
 | 
					 * - Comprehensive error handling and retry logic
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * 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 {
 | 
					public class MidtransCardPaymentManager {
 | 
				
			||||||
    private static final String TAG = "MidtransCardPayment";
 | 
					    private static final String TAG = "MidtransCardPayment";
 | 
				
			||||||
@ -29,11 +34,16 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
    private static final String MIDTRANS_BASE_URL = "https://api.sandbox.midtrans.com";
 | 
					    private static final String MIDTRANS_BASE_URL = "https://api.sandbox.midtrans.com";
 | 
				
			||||||
    private static final String MIDTRANS_TOKEN_URL = MIDTRANS_BASE_URL + "/v2/token";
 | 
					    private static final String MIDTRANS_TOKEN_URL = MIDTRANS_BASE_URL + "/v2/token";
 | 
				
			||||||
    private static final String MIDTRANS_CHARGE_URL = MIDTRANS_BASE_URL + "/v2/charge";
 | 
					    private static final String MIDTRANS_CHARGE_URL = MIDTRANS_BASE_URL + "/v2/charge";
 | 
				
			||||||
    private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Your server key
 | 
					    private static final String MIDTRANS_CLIENT_KEY = "SB-Mid-client-zPs7DafB_fag5kOP";
 | 
				
			||||||
    private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
 | 
					    private static final String MIDTRANS_SERVER_AUTH = "Basic U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc6";
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // EMV-specific configuration
 | 
				
			||||||
 | 
					    private static final String STATIC_CVV = "493"; // Static CVV for all tokenization (as per curl example)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private Context context;
 | 
					    private Context context;
 | 
				
			||||||
    private MidtransCardPaymentCallback callback;
 | 
					    private MidtransCardPaymentCallback callback;
 | 
				
			||||||
 | 
					    private int retryCount = 0;
 | 
				
			||||||
 | 
					    private static final int MAX_RETRY = 2;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    public interface MidtransCardPaymentCallback {
 | 
					    public interface MidtransCardPaymentCallback {
 | 
				
			||||||
        void onTokenizeSuccess(String cardToken);
 | 
					        void onTokenizeSuccess(String cardToken);
 | 
				
			||||||
@ -49,10 +59,36 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Process credit card payment using EMV card data
 | 
					     * Process EMV card payment - handles EMV-specific requirements
 | 
				
			||||||
     * @param cardData EMV card data from transaction
 | 
					     */
 | 
				
			||||||
     * @param amount Transaction amount in cents
 | 
					    public void processEMVCardPayment(CardData cardData, long amount, String referenceId, String emvData) {
 | 
				
			||||||
     * @param referenceId Backend reference ID
 | 
					        if (cardData == null || !cardData.isValid()) {
 | 
				
			||||||
 | 
					            if (callback != null) {
 | 
				
			||||||
 | 
					                callback.onChargeError("Invalid card data");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Reset retry counter
 | 
				
			||||||
 | 
					        retryCount = 0;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Log.d(TAG, "=== STARTING EMV MIDTRANS PAYMENT ===");
 | 
				
			||||||
 | 
					        Log.d(TAG, "Reference ID: " + referenceId);
 | 
				
			||||||
 | 
					        Log.d(TAG, "Amount: " + amount);
 | 
				
			||||||
 | 
					        Log.d(TAG, "Card PAN: " + maskCardNumber(cardData.getPan()));
 | 
				
			||||||
 | 
					        Log.d(TAG, "Payment Mode: EMV with static CVV (" + STATIC_CVV + ")");
 | 
				
			||||||
 | 
					        Log.d(TAG, "==========================================");
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (callback != null) {
 | 
				
			||||||
 | 
					            callback.onPaymentProgress("Processing EMV payment...");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // For EMV cards, try direct charge without tokenization first
 | 
				
			||||||
 | 
					        new EMVDirectChargeTask(cardData, amount, referenceId, emvData).execute();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Process regular card payment with tokenization
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public void processCardPayment(CardData cardData, long amount, String referenceId) {
 | 
					    public void processCardPayment(CardData cardData, long amount, String referenceId) {
 | 
				
			||||||
        if (cardData == null || !cardData.isValid()) {
 | 
					        if (cardData == null || !cardData.isValid()) {
 | 
				
			||||||
@ -62,86 +98,214 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        Log.d(TAG, "=== STARTING MIDTRANS CARD PAYMENT ===");
 | 
					        retryCount = 0;
 | 
				
			||||||
        Log.d(TAG, "Reference ID: " + referenceId);
 | 
					        
 | 
				
			||||||
        Log.d(TAG, "Amount: " + amount);
 | 
					        Log.d(TAG, "=== STARTING REGULAR CARD PAYMENT ===");
 | 
				
			||||||
        Log.d(TAG, "Card PAN: " + maskCardNumber(cardData.getPan()));
 | 
					        Log.d(TAG, "Using tokenization flow");
 | 
				
			||||||
        Log.d(TAG, "=========================================");
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if (callback != null) {
 | 
					        if (callback != null) {
 | 
				
			||||||
            callback.onPaymentProgress("Tokenizing card...");
 | 
					            callback.onPaymentProgress("Tokenizing card...");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Step 1: Tokenize card (for demonstration - in production use secure methods)
 | 
					        // Use tokenization flow for regular cards
 | 
				
			||||||
        new TokenizeCardTask(cardData, amount, referenceId).execute();
 | 
					        new TokenizeCardTask(cardData, amount, referenceId).execute();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Alternative: Direct charge without tokenization (using EMV cryptogram)
 | 
					     * EMV Direct Charge - bypasses tokenization for EMV cards
 | 
				
			||||||
     * This is more secure for EMV transactions
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public void processEMVDirectCharge(CardData cardData, long amount, String referenceId, String emvData) {
 | 
					    private class EMVDirectChargeTask extends AsyncTask<Void, Void, Boolean> {
 | 
				
			||||||
        if (callback != null) {
 | 
					        private CardData cardData;
 | 
				
			||||||
            callback.onPaymentProgress("Processing EMV payment...");
 | 
					        private long amount;
 | 
				
			||||||
 | 
					        private String referenceId;
 | 
				
			||||||
 | 
					        private String emvData;
 | 
				
			||||||
 | 
					        private String errorMessage;
 | 
				
			||||||
 | 
					        private JSONObject chargeResponse;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public EMVDirectChargeTask(CardData cardData, long amount, String referenceId, String emvData) {
 | 
				
			||||||
 | 
					            this.cardData = cardData;
 | 
				
			||||||
 | 
					            this.amount = amount;
 | 
				
			||||||
 | 
					            this.referenceId = referenceId;
 | 
				
			||||||
 | 
					            this.emvData = emvData;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        new DirectEMVChargeTask(cardData, amount, referenceId, emvData).execute();
 | 
					        @Override
 | 
				
			||||||
    }
 | 
					        protected Boolean doInBackground(Void... voids) {
 | 
				
			||||||
    
 | 
					            try {
 | 
				
			||||||
    /**
 | 
					                String orderId = "EMV" + System.currentTimeMillis();
 | 
				
			||||||
     * Card data holder class
 | 
					                
 | 
				
			||||||
     */
 | 
					                // Build EMV-specific charge payload
 | 
				
			||||||
    public static class CardData {
 | 
					                JSONObject payload = new JSONObject();
 | 
				
			||||||
        private String pan;
 | 
					                payload.put("payment_type", "credit_card");
 | 
				
			||||||
        private String expiryMonth;
 | 
					                
 | 
				
			||||||
        private String expiryYear;
 | 
					                // Transaction details
 | 
				
			||||||
        private String cvv; // May not be available in EMV
 | 
					                JSONObject transactionDetails = new JSONObject();
 | 
				
			||||||
        private String cardholderName;
 | 
					                transactionDetails.put("order_id", orderId);
 | 
				
			||||||
        private String aidIdentifier;
 | 
					                transactionDetails.put("gross_amount", amount);
 | 
				
			||||||
        
 | 
					                payload.put("transaction_details", transactionDetails);
 | 
				
			||||||
        public CardData(String pan, String expiryMonth, String expiryYear, String cardholderName) {
 | 
					                
 | 
				
			||||||
            this.pan = pan;
 | 
					                // EMV Credit card data (no tokenization)
 | 
				
			||||||
            this.expiryMonth = expiryMonth;
 | 
					                JSONObject creditCard = new JSONObject();
 | 
				
			||||||
            this.expiryYear = expiryYear;
 | 
					                creditCard.put("card_number", cardData.getPan());
 | 
				
			||||||
            this.cardholderName = cardholderName;
 | 
					                creditCard.put("card_exp_month", cardData.getExpiryMonth());
 | 
				
			||||||
        }
 | 
					                creditCard.put("card_exp_year", cardData.getExpiryYear());
 | 
				
			||||||
        
 | 
					                
 | 
				
			||||||
        // Builder pattern for EMV data
 | 
					                // Include static CVV even for EMV (Midtrans may require it)
 | 
				
			||||||
        public static CardData fromEMVData(String pan, String expiryDate, String cardholderName, String aid) {
 | 
					                creditCard.put("card_cvv", STATIC_CVV);
 | 
				
			||||||
            String expMonth = "";
 | 
					                Log.d(TAG, "EMV Transaction: Including static CVV (" + STATIC_CVV + ") for Midtrans compatibility");
 | 
				
			||||||
            String expYear = "";
 | 
					                
 | 
				
			||||||
            
 | 
					                // Add EMV data if available
 | 
				
			||||||
            if (expiryDate != null && expiryDate.length() == 6) {
 | 
					                if (emvData != null && !emvData.isEmpty()) {
 | 
				
			||||||
                // Format: YYMMDD -> Extract YYMM
 | 
					                    creditCard.put("emv_data", emvData);
 | 
				
			||||||
                expYear = "20" + expiryDate.substring(0, 2);
 | 
					                    creditCard.put("authentication_mode", "chip");
 | 
				
			||||||
                expMonth = expiryDate.substring(2, 4);
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                payload.put("credit_card", creditCard);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Item details
 | 
				
			||||||
 | 
					                JSONArray itemDetails = new JSONArray();
 | 
				
			||||||
 | 
					                JSONObject item = new JSONObject();
 | 
				
			||||||
 | 
					                item.put("id", "emv1");
 | 
				
			||||||
 | 
					                item.put("price", amount);
 | 
				
			||||||
 | 
					                item.put("quantity", 1);
 | 
				
			||||||
 | 
					                item.put("name", "EMV Transaction");
 | 
				
			||||||
 | 
					                item.put("brand", "EMV Payment");
 | 
				
			||||||
 | 
					                item.put("category", "Transaction");
 | 
				
			||||||
 | 
					                item.put("merchant_name", "EDC-Store");
 | 
				
			||||||
 | 
					                itemDetails.put(item);
 | 
				
			||||||
 | 
					                payload.put("item_details", itemDetails);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Customer details (same as curl example)
 | 
				
			||||||
 | 
					                addCustomerDetails(payload);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Log.d(TAG, "=== EMV DIRECT CHARGE ===");
 | 
				
			||||||
 | 
					                Log.d(TAG, "Order ID: " + orderId);
 | 
				
			||||||
 | 
					                Log.d(TAG, "Amount: " + amount);
 | 
				
			||||||
 | 
					                Log.d(TAG, "Card: " + maskCardNumber(cardData.getPan()));
 | 
				
			||||||
 | 
					                Log.d(TAG, "Mode: EMV Direct (No Token)");
 | 
				
			||||||
 | 
					                Log.d(TAG, "========================");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // Make charge request
 | 
				
			||||||
 | 
					                return makeChargeRequest(payload);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            } catch (Exception e) {
 | 
				
			||||||
 | 
					                Log.e(TAG, "EMV Direct Charge exception: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					                errorMessage = "EMV payment error: " + e.getMessage();
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            CardData cardData = new CardData(pan, expMonth, expYear, cardholderName);
 | 
					 | 
				
			||||||
            cardData.aidIdentifier = aid;
 | 
					 | 
				
			||||||
            return cardData;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public boolean isValid() {
 | 
					        @Override
 | 
				
			||||||
            return pan != null && !pan.isEmpty() && 
 | 
					        protected void onPostExecute(Boolean success) {
 | 
				
			||||||
                   expiryMonth != null && !expiryMonth.isEmpty() &&
 | 
					            if (success && chargeResponse != null && callback != null) {
 | 
				
			||||||
                   expiryYear != null && !expiryYear.isEmpty();
 | 
					                callback.onChargeSuccess(chargeResponse);
 | 
				
			||||||
 | 
					            } else if (callback != null) {
 | 
				
			||||||
 | 
					                // Fallback to tokenization if direct charge fails
 | 
				
			||||||
 | 
					                Log.w(TAG, "EMV direct charge failed, trying tokenization fallback...");
 | 
				
			||||||
 | 
					                callback.onPaymentProgress("Retrying with tokenization...");
 | 
				
			||||||
 | 
					                new TokenizeCardTask(cardData, amount, referenceId).execute();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Getters
 | 
					        private Boolean makeChargeRequest(JSONObject payload) {
 | 
				
			||||||
        public String getPan() { return pan; }
 | 
					            try {
 | 
				
			||||||
        public String getExpiryMonth() { return expiryMonth; }
 | 
					                URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
 | 
				
			||||||
        public String getExpiryYear() { return expiryYear; }
 | 
					                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
				
			||||||
        public String getCvv() { return cvv; }
 | 
					                conn.setRequestMethod("POST");
 | 
				
			||||||
        public String getCardholderName() { return cardholderName; }
 | 
					                conn.setRequestProperty("Accept", "application/json");
 | 
				
			||||||
        public String getAidIdentifier() { return aidIdentifier; }
 | 
					                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 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());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Log.d(TAG, "EMV Charge response: " + response.toString());
 | 
				
			||||||
 | 
					                chargeResponse = new JSONObject(response.toString());
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                return processChargeResponse(chargeResponse, responseCode);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            } catch (Exception e) {
 | 
				
			||||||
 | 
					                Log.e(TAG, "EMV Charge request exception: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					                errorMessage = "Network error: " + e.getMessage();
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // Setters
 | 
					        private Boolean processChargeResponse(JSONObject response, int httpCode) {
 | 
				
			||||||
        public void setCvv(String cvv) { this.cvv = cvv; }
 | 
					            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, "=== 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, "===============================");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 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 charge response: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					                errorMessage = "Response processing error: " + e.getMessage();
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Tokenize card task (similar to QRIS implementation pattern)
 | 
					     * Enhanced Tokenize Card Task with better CVV handling
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private class TokenizeCardTask extends AsyncTask<Void, Void, String> {
 | 
					    private class TokenizeCardTask extends AsyncTask<Void, Void, String> {
 | 
				
			||||||
        private CardData cardData;
 | 
					        private CardData cardData;
 | 
				
			||||||
@ -158,17 +322,17 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        protected String doInBackground(Void... voids) {
 | 
					        protected String doInBackground(Void... voids) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                // Build tokenization URL (Note: This is for demonstration - use POST in production)
 | 
					 | 
				
			||||||
                StringBuilder urlBuilder = new StringBuilder(MIDTRANS_TOKEN_URL);
 | 
					                StringBuilder urlBuilder = new StringBuilder(MIDTRANS_TOKEN_URL);
 | 
				
			||||||
                urlBuilder.append("?card_number=").append(cardData.getPan());
 | 
					                urlBuilder.append("?card_number=").append(cardData.getPan());
 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (cardData.getCvv() != null && !cardData.getCvv().isEmpty()) {
 | 
					 | 
				
			||||||
                    urlBuilder.append("&card_cvv=").append(cardData.getCvv());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                urlBuilder.append("&card_exp_month=").append(cardData.getExpiryMonth());
 | 
					                urlBuilder.append("&card_exp_month=").append(cardData.getExpiryMonth());
 | 
				
			||||||
                urlBuilder.append("&card_exp_year=").append(cardData.getExpiryYear());
 | 
					                urlBuilder.append("&card_exp_year=").append(cardData.getExpiryYear());
 | 
				
			||||||
                urlBuilder.append("&token_id=").append(generateTokenId());
 | 
					                
 | 
				
			||||||
 | 
					                // Always include CVV for tokenization (Midtrans requires it)
 | 
				
			||||||
 | 
					                String cvvToUse = determineCVV(cardData);
 | 
				
			||||||
 | 
					                urlBuilder.append("&card_cvv=").append(cvvToUse);
 | 
				
			||||||
 | 
					                Log.d(TAG, "Using CVV " + cvvToUse + " for tokenization (required by Midtrans)");
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                urlBuilder.append("&client_key=").append(MIDTRANS_CLIENT_KEY);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                Log.d(TAG, "Tokenization URL: " + maskUrl(urlBuilder.toString()));
 | 
					                Log.d(TAG, "Tokenization URL: " + maskUrl(urlBuilder.toString()));
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
@ -176,7 +340,7 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
					                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
				
			||||||
                conn.setRequestMethod("GET");
 | 
					                conn.setRequestMethod("GET");
 | 
				
			||||||
                conn.setRequestProperty("Accept", "application/json");
 | 
					                conn.setRequestProperty("Accept", "application/json");
 | 
				
			||||||
                conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
 | 
					                conn.setRequestProperty("Content-Type", "application/json");
 | 
				
			||||||
                conn.setConnectTimeout(30000);
 | 
					                conn.setConnectTimeout(30000);
 | 
				
			||||||
                conn.setReadTimeout(30000);
 | 
					                conn.setReadTimeout(30000);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
@ -193,9 +357,13 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
                    
 | 
					                    
 | 
				
			||||||
                    Log.d(TAG, "Tokenization success response: " + response.toString());
 | 
					                    Log.d(TAG, "Tokenization success response: " + response.toString());
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                    // Parse token from response
 | 
					 | 
				
			||||||
                    JSONObject jsonResponse = new JSONObject(response.toString());
 | 
					                    JSONObject jsonResponse = new JSONObject(response.toString());
 | 
				
			||||||
                    return jsonResponse.getString("token_id");
 | 
					                    if (jsonResponse.has("token_id")) {
 | 
				
			||||||
 | 
					                        return jsonResponse.getString("token_id");
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        errorMessage = "Token ID not found in response";
 | 
				
			||||||
 | 
					                        return null;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    
 | 
					                    
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
 | 
					                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
 | 
				
			||||||
@ -222,11 +390,10 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
            if (cardToken != null && callback != null) {
 | 
					            if (cardToken != null && callback != null) {
 | 
				
			||||||
                callback.onTokenizeSuccess(cardToken);
 | 
					                callback.onTokenizeSuccess(cardToken);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Proceed to charge
 | 
					 | 
				
			||||||
                if (callback != null) {
 | 
					                if (callback != null) {
 | 
				
			||||||
                    callback.onPaymentProgress("Processing payment...");
 | 
					                    callback.onPaymentProgress("Processing payment...");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                new ChargeCardTask(cardToken, amount, referenceId).execute();
 | 
					                new ChargeCardTask(cardToken, amount, referenceId, cardData).execute();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
            } else if (callback != null) {
 | 
					            } else if (callback != null) {
 | 
				
			||||||
                callback.onTokenizeError(errorMessage != null ? errorMessage : "Unknown tokenization error");
 | 
					                callback.onTokenizeError(errorMessage != null ? errorMessage : "Unknown tokenization error");
 | 
				
			||||||
@ -235,7 +402,7 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Charge card using token (similar to QRIS charge implementation)
 | 
					     * Enhanced Charge Card Task
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private class ChargeCardTask extends AsyncTask<Void, Void, Boolean> {
 | 
					    private class ChargeCardTask extends AsyncTask<Void, Void, Boolean> {
 | 
				
			||||||
        private String cardToken;
 | 
					        private String cardToken;
 | 
				
			||||||
@ -243,58 +410,90 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
        private String referenceId;
 | 
					        private String referenceId;
 | 
				
			||||||
        private String errorMessage;
 | 
					        private String errorMessage;
 | 
				
			||||||
        private JSONObject chargeResponse;
 | 
					        private JSONObject chargeResponse;
 | 
				
			||||||
 | 
					        private CardData cardData;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public ChargeCardTask(String cardToken, long amount, String referenceId) {
 | 
					        public ChargeCardTask(String cardToken, long amount, String referenceId, CardData cardData) {
 | 
				
			||||||
            this.cardToken = cardToken;
 | 
					            this.cardToken = cardToken;
 | 
				
			||||||
            this.amount = amount;
 | 
					            this.amount = amount;
 | 
				
			||||||
            this.referenceId = referenceId;
 | 
					            this.referenceId = referenceId;
 | 
				
			||||||
 | 
					            this.cardData = cardData;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        protected Boolean doInBackground(Void... voids) {
 | 
					        protected Boolean doInBackground(Void... voids) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                String orderId = UUID.randomUUID().toString();
 | 
					                String orderId = "TKN" + System.currentTimeMillis();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Build charge payload (similar to QRIS implementation)
 | 
					 | 
				
			||||||
                JSONObject payload = new JSONObject();
 | 
					                JSONObject payload = new JSONObject();
 | 
				
			||||||
                payload.put("payment_type", "credit_card");
 | 
					                payload.put("payment_type", "credit_card");
 | 
				
			||||||
                payload.put("credit_card", new JSONObject().put("token_id", cardToken));
 | 
					 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Transaction details
 | 
					 | 
				
			||||||
                JSONObject transactionDetails = new JSONObject();
 | 
					                JSONObject transactionDetails = new JSONObject();
 | 
				
			||||||
                transactionDetails.put("order_id", orderId);
 | 
					                transactionDetails.put("order_id", orderId);
 | 
				
			||||||
                transactionDetails.put("gross_amount", amount);
 | 
					                transactionDetails.put("gross_amount", amount);
 | 
				
			||||||
                payload.put("transaction_details", transactionDetails);
 | 
					                payload.put("transaction_details", transactionDetails);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Customer details (recommended)
 | 
					                JSONObject creditCard = new JSONObject();
 | 
				
			||||||
                JSONObject customerDetails = new JSONObject();
 | 
					                creditCard.put("token_id", cardToken);
 | 
				
			||||||
                customerDetails.put("first_name", "EMV");
 | 
					                payload.put("credit_card", creditCard);
 | 
				
			||||||
                customerDetails.put("last_name", "Customer");
 | 
					 | 
				
			||||||
                customerDetails.put("email", "emv@example.com");
 | 
					 | 
				
			||||||
                customerDetails.put("phone", "081234567890");
 | 
					 | 
				
			||||||
                payload.put("customer_details", customerDetails);
 | 
					 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Custom fields for tracking
 | 
					                // Item details
 | 
				
			||||||
                JSONObject customField1 = new JSONObject();
 | 
					                JSONArray itemDetails = new JSONArray();
 | 
				
			||||||
                customField1.put("app_reference_id", referenceId);
 | 
					                JSONObject item = new JSONObject();
 | 
				
			||||||
                customField1.put("payment_method", "EMV Credit Card");
 | 
					                item.put("id", "tkn1");
 | 
				
			||||||
                customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
 | 
					                item.put("price", amount);
 | 
				
			||||||
                payload.put("custom_field1", customField1.toString());
 | 
					                item.put("quantity", 1);
 | 
				
			||||||
 | 
					                item.put("name", "Token Transaction");
 | 
				
			||||||
 | 
					                item.put("brand", "Token Payment");
 | 
				
			||||||
 | 
					                item.put("category", "Transaction");
 | 
				
			||||||
 | 
					                item.put("merchant_name", "EDC-Store");
 | 
				
			||||||
 | 
					                itemDetails.put(item);
 | 
				
			||||||
 | 
					                payload.put("item_details", itemDetails);
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                Log.d(TAG, "=== MIDTRANS CREDIT CARD CHARGE ===");
 | 
					                addCustomerDetails(payload);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Log.d(TAG, "=== TOKEN CHARGE ===");
 | 
				
			||||||
                Log.d(TAG, "Order ID: " + orderId);
 | 
					                Log.d(TAG, "Order ID: " + orderId);
 | 
				
			||||||
                Log.d(TAG, "Amount: " + amount);
 | 
					                Log.d(TAG, "Amount: " + amount);
 | 
				
			||||||
                Log.d(TAG, "Token: " + maskToken(cardToken));
 | 
					                Log.d(TAG, "Token: " + maskToken(cardToken));
 | 
				
			||||||
                Log.d(TAG, "=====================================");
 | 
					                Log.d(TAG, "===================");
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                // Make charge request
 | 
					                return makeChargeRequest(payload);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            } catch (Exception e) {
 | 
				
			||||||
 | 
					                Log.e(TAG, "Token charge exception: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					                errorMessage = "Token charge error: " + e.getMessage();
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        protected void onPostExecute(Boolean success) {
 | 
				
			||||||
 | 
					            if (success && chargeResponse != null && callback != null) {
 | 
				
			||||||
 | 
					                callback.onChargeSuccess(chargeResponse);
 | 
				
			||||||
 | 
					            } else if (callback != null) {
 | 
				
			||||||
 | 
					                // Check for retry scenarios
 | 
				
			||||||
 | 
					                if (shouldRetry(errorMessage) && retryCount < MAX_RETRY) {
 | 
				
			||||||
 | 
					                    retryCount++;
 | 
				
			||||||
 | 
					                    Log.w(TAG, "Retrying charge... (attempt " + retryCount + "/" + MAX_RETRY + ")");
 | 
				
			||||||
 | 
					                    callback.onPaymentProgress("Retrying... (" + retryCount + "/" + MAX_RETRY + ")");
 | 
				
			||||||
 | 
					                    new TokenizeCardTask(cardData, amount, referenceId).execute();
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (retryCount >= MAX_RETRY) {
 | 
				
			||||||
 | 
					                        errorMessage = "Max retry attempts reached. " + errorMessage;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    callback.onChargeError(errorMessage != null ? errorMessage : "Unknown charge error");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        private Boolean makeChargeRequest(JSONObject payload) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
                URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
 | 
					                URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
 | 
				
			||||||
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
					                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 | 
				
			||||||
                conn.setRequestMethod("POST");
 | 
					                conn.setRequestMethod("POST");
 | 
				
			||||||
                conn.setRequestProperty("Accept", "application/json");
 | 
					                conn.setRequestProperty("Accept", "application/json");
 | 
				
			||||||
                conn.setRequestProperty("Content-Type", "application/json");
 | 
					                conn.setRequestProperty("Content-Type", "application/json");
 | 
				
			||||||
                conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
 | 
					                conn.setRequestProperty("Authorization", MIDTRANS_SERVER_AUTH);
 | 
				
			||||||
                conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
 | 
					 | 
				
			||||||
                conn.setDoOutput(true);
 | 
					                conn.setDoOutput(true);
 | 
				
			||||||
                conn.setConnectTimeout(30000);
 | 
					                conn.setConnectTimeout(30000);
 | 
				
			||||||
                conn.setReadTimeout(30000);
 | 
					                conn.setReadTimeout(30000);
 | 
				
			||||||
@ -305,203 +504,191 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                int responseCode = conn.getResponseCode();
 | 
					                int responseCode = conn.getResponseCode();
 | 
				
			||||||
                Log.d(TAG, "Charge response code: " + responseCode);
 | 
					                Log.d(TAG, "Token Charge response code: " + responseCode);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                BufferedReader br;
 | 
				
			||||||
 | 
					                StringBuilder response = new StringBuilder();
 | 
				
			||||||
 | 
					                String responseLine;
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                if (responseCode == 200 || responseCode == 201) {
 | 
					                if (responseCode == 200 || responseCode == 201) {
 | 
				
			||||||
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
 | 
					                    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, "Charge success response: " + response.toString());
 | 
					 | 
				
			||||||
                    chargeResponse = new JSONObject(response.toString());
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    // Check transaction status
 | 
					 | 
				
			||||||
                    String transactionStatus = chargeResponse.optString("transaction_status", "");
 | 
					 | 
				
			||||||
                    String fraudStatus = chargeResponse.optString("fraud_status", "");
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    Log.d(TAG, "Transaction Status: " + transactionStatus);
 | 
					 | 
				
			||||||
                    Log.d(TAG, "Fraud Status: " + fraudStatus);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    return "capture".equals(transactionStatus) || "settlement".equals(transactionStatus);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
 | 
					                    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, "Charge error: " + errorResponse.toString());
 | 
					 | 
				
			||||||
                    errorMessage = "Charge failed: " + errorResponse.toString();
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					                while ((responseLine = br.readLine()) != null) {
 | 
				
			||||||
 | 
					                    response.append(responseLine.trim());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Log.d(TAG, "Token Charge response: " + response.toString());
 | 
				
			||||||
 | 
					                chargeResponse = new JSONObject(response.toString());
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                return processChargeResponse(chargeResponse, responseCode);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
                Log.e(TAG, "Charge exception: " + e.getMessage(), e);
 | 
					                Log.e(TAG, "Token charge request exception: " + e.getMessage(), e);
 | 
				
			||||||
                errorMessage = "Network error: " + e.getMessage();
 | 
					                errorMessage = "Network error: " + e.getMessage();
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        @Override
 | 
					        private Boolean processChargeResponse(JSONObject response, int httpCode) {
 | 
				
			||||||
        protected void onPostExecute(Boolean success) {
 | 
					            try {
 | 
				
			||||||
            if (success && chargeResponse != null && callback != null) {
 | 
					                String statusCode = response.optString("status_code", "");
 | 
				
			||||||
                callback.onChargeSuccess(chargeResponse);
 | 
					                String statusMessage = response.optString("status_message", "");
 | 
				
			||||||
            } else if (callback != null) {
 | 
					                String transactionStatus = response.optString("transaction_status", "");
 | 
				
			||||||
                callback.onChargeError(errorMessage != null ? errorMessage : "Unknown charge error");
 | 
					                
 | 
				
			||||||
 | 
					                Log.d(TAG, "Token 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 charge response: " + e.getMessage(), e);
 | 
				
			||||||
 | 
					                errorMessage = "Response processing error: " + e.getMessage();
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    // Helper Methods
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Intelligent CVV determination - Always use static CVV for tokenization
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String determineCVV(CardData cardData) {
 | 
				
			||||||
 | 
					        // For tokenization, Midtrans always requires CVV even for EMV cards
 | 
				
			||||||
 | 
					        // Use static CVV as per curl example
 | 
				
			||||||
 | 
					        Log.d(TAG, "Using static CVV (493) for tokenization - required by Midtrans API");
 | 
				
			||||||
 | 
					        return STATIC_CVV;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Direct EMV charge without tokenization (more secure for EMV)
 | 
					     * Add customer details to payload (extracted for reuse)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private class DirectEMVChargeTask extends AsyncTask<Void, Void, Boolean> {
 | 
					    private void addCustomerDetails(JSONObject payload) throws JSONException {
 | 
				
			||||||
        private CardData cardData;
 | 
					        JSONObject customerDetails = new JSONObject();
 | 
				
			||||||
        private long amount;
 | 
					        customerDetails.put("first_name", "BUDI");
 | 
				
			||||||
        private String referenceId;
 | 
					        customerDetails.put("last_name", "UTOMO");
 | 
				
			||||||
        private String emvData;
 | 
					        customerDetails.put("email", "test@midtrans.com");
 | 
				
			||||||
        private String errorMessage;
 | 
					        customerDetails.put("phone", "+628123456");
 | 
				
			||||||
        private JSONObject chargeResponse;
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public DirectEMVChargeTask(CardData cardData, long amount, String referenceId, String emvData) {
 | 
					        // Billing address
 | 
				
			||||||
            this.cardData = cardData;
 | 
					        JSONObject billingAddress = new JSONObject();
 | 
				
			||||||
            this.amount = amount;
 | 
					        billingAddress.put("first_name", "BUDI");
 | 
				
			||||||
            this.referenceId = referenceId;
 | 
					        billingAddress.put("last_name", "UTOMO");
 | 
				
			||||||
            this.emvData = emvData;
 | 
					        billingAddress.put("email", "test@midtrans.com");
 | 
				
			||||||
        }
 | 
					        billingAddress.put("phone", "081 2233 44-55");
 | 
				
			||||||
 | 
					        billingAddress.put("address", "Sudirman");
 | 
				
			||||||
 | 
					        billingAddress.put("city", "Jakarta");
 | 
				
			||||||
 | 
					        billingAddress.put("postal_code", "12190");
 | 
				
			||||||
 | 
					        billingAddress.put("country_code", "IDN");
 | 
				
			||||||
 | 
					        customerDetails.put("billing_address", billingAddress);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        @Override
 | 
					        // Shipping address
 | 
				
			||||||
        protected Boolean doInBackground(Void... voids) {
 | 
					        JSONObject shippingAddress = new JSONObject();
 | 
				
			||||||
            try {
 | 
					        shippingAddress.put("first_name", "BUDI");
 | 
				
			||||||
                String orderId = UUID.randomUUID().toString();
 | 
					        shippingAddress.put("last_name", "UTOMO");
 | 
				
			||||||
                
 | 
					        shippingAddress.put("email", "test@midtrans.com");
 | 
				
			||||||
                // Build EMV charge payload
 | 
					        shippingAddress.put("phone", "0 8128-75 7-9338");
 | 
				
			||||||
                JSONObject payload = new JSONObject();
 | 
					        shippingAddress.put("address", "Sudirman");
 | 
				
			||||||
                payload.put("payment_type", "credit_card");
 | 
					        shippingAddress.put("city", "Jakarta");
 | 
				
			||||||
                
 | 
					        shippingAddress.put("postal_code", "12190");
 | 
				
			||||||
                // EMV specific data
 | 
					        shippingAddress.put("country_code", "IDN");
 | 
				
			||||||
                JSONObject creditCard = new JSONObject();
 | 
					        customerDetails.put("shipping_address", shippingAddress);
 | 
				
			||||||
                creditCard.put("card_number", cardData.getPan());
 | 
					 | 
				
			||||||
                creditCard.put("card_exp_month", cardData.getExpiryMonth());
 | 
					 | 
				
			||||||
                creditCard.put("card_exp_year", cardData.getExpiryYear());
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Add EMV specific fields
 | 
					 | 
				
			||||||
                if (emvData != null && !emvData.isEmpty()) {
 | 
					 | 
				
			||||||
                    creditCard.put("emv_data", emvData);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                payload.put("credit_card", creditCard);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Transaction details
 | 
					 | 
				
			||||||
                JSONObject transactionDetails = new JSONObject();
 | 
					 | 
				
			||||||
                transactionDetails.put("order_id", orderId);
 | 
					 | 
				
			||||||
                transactionDetails.put("gross_amount", amount);
 | 
					 | 
				
			||||||
                payload.put("transaction_details", transactionDetails);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Customer details
 | 
					 | 
				
			||||||
                JSONObject customerDetails = new JSONObject();
 | 
					 | 
				
			||||||
                if (cardData.getCardholderName() != null && !cardData.getCardholderName().isEmpty()) {
 | 
					 | 
				
			||||||
                    String[] nameParts = cardData.getCardholderName().trim().split(" ", 2);
 | 
					 | 
				
			||||||
                    customerDetails.put("first_name", nameParts[0]);
 | 
					 | 
				
			||||||
                    if (nameParts.length > 1) {
 | 
					 | 
				
			||||||
                        customerDetails.put("last_name", nameParts[1]);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    customerDetails.put("first_name", "EMV");
 | 
					 | 
				
			||||||
                    customerDetails.put("last_name", "Customer");
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                customerDetails.put("email", "emv@example.com");
 | 
					 | 
				
			||||||
                customerDetails.put("phone", "081234567890");
 | 
					 | 
				
			||||||
                payload.put("customer_details", customerDetails);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Custom tracking
 | 
					 | 
				
			||||||
                JSONObject customField1 = new JSONObject();
 | 
					 | 
				
			||||||
                customField1.put("app_reference_id", referenceId);
 | 
					 | 
				
			||||||
                customField1.put("payment_method", "EMV Direct");
 | 
					 | 
				
			||||||
                customField1.put("aid_identifier", cardData.getAidIdentifier());
 | 
					 | 
				
			||||||
                customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
 | 
					 | 
				
			||||||
                payload.put("custom_field1", customField1.toString());
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                Log.d(TAG, "=== MIDTRANS EMV DIRECT CHARGE ===");
 | 
					 | 
				
			||||||
                Log.d(TAG, "Order ID: " + orderId);
 | 
					 | 
				
			||||||
                Log.d(TAG, "Amount: " + amount);
 | 
					 | 
				
			||||||
                Log.d(TAG, "Card: " + maskCardNumber(cardData.getPan()));
 | 
					 | 
				
			||||||
                Log.d(TAG, "==================================");
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                // Make charge request
 | 
					 | 
				
			||||||
                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_AUTH);
 | 
					 | 
				
			||||||
                conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
 | 
					 | 
				
			||||||
                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 charge response code: " + responseCode);
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
                if (responseCode == 200 || responseCode == 201) {
 | 
					 | 
				
			||||||
                    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, "EMV charge success: " + response.toString());
 | 
					 | 
				
			||||||
                    chargeResponse = new JSONObject(response.toString());
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    String transactionStatus = chargeResponse.optString("transaction_status", "");
 | 
					 | 
				
			||||||
                    return "capture".equals(transactionStatus) || "settlement".equals(transactionStatus);
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                } 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, "EMV charge error: " + errorResponse.toString());
 | 
					 | 
				
			||||||
                    errorMessage = "EMV charge failed: " + errorResponse.toString();
 | 
					 | 
				
			||||||
                    return false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                
 | 
					 | 
				
			||||||
            } catch (Exception e) {
 | 
					 | 
				
			||||||
                Log.e(TAG, "EMV charge exception: " + e.getMessage(), e);
 | 
					 | 
				
			||||||
                errorMessage = "Network error: " + e.getMessage();
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        @Override
 | 
					        payload.put("customer_details", customerDetails);
 | 
				
			||||||
        protected void onPostExecute(Boolean success) {
 | 
					 | 
				
			||||||
            if (success && chargeResponse != null && callback != null) {
 | 
					 | 
				
			||||||
                callback.onChargeSuccess(chargeResponse);
 | 
					 | 
				
			||||||
            } else if (callback != null) {
 | 
					 | 
				
			||||||
                callback.onChargeError(errorMessage != null ? errorMessage : "Unknown EMV charge error");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // Helper methods
 | 
					    /**
 | 
				
			||||||
    private String generateTokenId() {
 | 
					     * Check if error should trigger a retry
 | 
				
			||||||
        return "token_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 10000);
 | 
					     */
 | 
				
			||||||
 | 
					    private boolean shouldRetry(String error) {
 | 
				
			||||||
 | 
					        if (error == null) return false;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return error.contains("Token expired") ||
 | 
				
			||||||
 | 
					               error.contains("Network error") ||
 | 
				
			||||||
 | 
					               error.contains("timeout");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Enhanced Card data holder class with EMV detection
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static class CardData {
 | 
				
			||||||
 | 
					        private String pan;
 | 
				
			||||||
 | 
					        private String expiryMonth;
 | 
				
			||||||
 | 
					        private String expiryYear;
 | 
				
			||||||
 | 
					        private String cvv;
 | 
				
			||||||
 | 
					        private String cardholderName;
 | 
				
			||||||
 | 
					        private String aidIdentifier;
 | 
				
			||||||
 | 
					        private boolean isEMVCard = false;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public CardData(String pan, String expiryMonth, String expiryYear, String cardholderName) {
 | 
				
			||||||
 | 
					            this.pan = pan;
 | 
				
			||||||
 | 
					            this.expiryMonth = expiryMonth;
 | 
				
			||||||
 | 
					            this.expiryYear = expiryYear;
 | 
				
			||||||
 | 
					            this.cardholderName = cardholderName;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public static CardData fromEMVData(String pan, String expiryDate, String cardholderName, String aid) {
 | 
				
			||||||
 | 
					            String expMonth = "";
 | 
				
			||||||
 | 
					            String expYear = "";
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (expiryDate != null && expiryDate.length() == 6) {
 | 
				
			||||||
 | 
					                expYear = "20" + expiryDate.substring(0, 2);
 | 
				
			||||||
 | 
					                expMonth = expiryDate.substring(2, 4);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            CardData cardData = new CardData(pan, expMonth, expYear, cardholderName);
 | 
				
			||||||
 | 
					            cardData.aidIdentifier = aid;
 | 
				
			||||||
 | 
					            cardData.isEMVCard = true; // Mark as EMV card
 | 
				
			||||||
 | 
					            return cardData;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public boolean isValid() {
 | 
				
			||||||
 | 
					            return pan != null && !pan.isEmpty() && 
 | 
				
			||||||
 | 
					                   expiryMonth != null && !expiryMonth.isEmpty() &&
 | 
				
			||||||
 | 
					                   expiryYear != null && !expiryYear.isEmpty();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Getters
 | 
				
			||||||
 | 
					        public String getPan() { return pan; }
 | 
				
			||||||
 | 
					        public String getExpiryMonth() { return expiryMonth; }
 | 
				
			||||||
 | 
					        public String getExpiryYear() { return expiryYear; }
 | 
				
			||||||
 | 
					        public String getCvv() { return cvv; }
 | 
				
			||||||
 | 
					        public String getCardholderName() { return cardholderName; }
 | 
				
			||||||
 | 
					        public String getAidIdentifier() { return aidIdentifier; }
 | 
				
			||||||
 | 
					        public boolean isEMVCard() { return isEMVCard; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Setters
 | 
				
			||||||
 | 
					        public void setCvv(String cvv) { this.cvv = cvv; }
 | 
				
			||||||
 | 
					        public void setEMVCard(boolean isEMVCard) { this.isEMVCard = isEMVCard; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Utility methods
 | 
				
			||||||
    private String maskCardNumber(String cardNumber) {
 | 
					    private String maskCardNumber(String cardNumber) {
 | 
				
			||||||
        if (cardNumber == null || cardNumber.length() < 8) {
 | 
					        if (cardNumber == null || cardNumber.length() < 8) {
 | 
				
			||||||
            return cardNumber;
 | 
					            return cardNumber;
 | 
				
			||||||
@ -525,6 +712,7 @@ public class MidtransCardPaymentManager {
 | 
				
			|||||||
    private String maskUrl(String url) {
 | 
					    private String maskUrl(String url) {
 | 
				
			||||||
        if (url == null) return url;
 | 
					        if (url == null) return url;
 | 
				
			||||||
        return url.replaceAll("card_number=[^&]*", "card_number=****")
 | 
					        return url.replaceAll("card_number=[^&]*", "card_number=****")
 | 
				
			||||||
                 .replaceAll("card_cvv=[^&]*", "card_cvv=***");
 | 
					                 .replaceAll("card_cvv=[^&]*", "card_cvv=***")
 | 
				
			||||||
 | 
					                 .replaceAll("client_key=[^&]*", "client_key=***");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,22 +1,50 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
					<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
					 | 
				
			||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="match_parent"
 | 
					    android:layout_height="match_parent"
 | 
				
			||||||
    android:orientation="vertical"
 | 
					    android:orientation="vertical"
 | 
				
			||||||
    android:background="@color/colorBackground"
 | 
					    android:background="@color/background_main">
 | 
				
			||||||
    tools:context=".kredit.CreditCardActivity">
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Toolbar -->
 | 
					    <!-- Toolbar -->
 | 
				
			||||||
    <androidx.appcompat.widget.Toolbar
 | 
					    <androidx.appcompat.widget.Toolbar
 | 
				
			||||||
        android:id="@+id/toolbar"
 | 
					        android:id="@+id/toolbar"
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="?attr/actionBarSize"
 | 
					        android:layout_height="?attr/actionBarSize"
 | 
				
			||||||
        android:background="@color/primary_blue"
 | 
					        android:background="@color/toolbar_background"
 | 
				
			||||||
        android:theme="@style/CustomToolbarTheme"
 | 
					        android:theme="@style/CustomToolbarTheme"
 | 
				
			||||||
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
 | 
					        app:titleTextAppearance="@style/ToolbarTitleStyle">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <LinearLayout
 | 
				
			||||||
 | 
					            android:id="@+id/back_navigation"
 | 
				
			||||||
 | 
					            android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					            android:layout_height="match_parent"
 | 
				
			||||||
 | 
					            android:orientation="horizontal"
 | 
				
			||||||
 | 
					            android:gravity="center_vertical"
 | 
				
			||||||
 | 
					            android:paddingStart="8dp"
 | 
				
			||||||
 | 
					            android:paddingEnd="16dp"
 | 
				
			||||||
 | 
					            android:background="?attr/selectableItemBackgroundBorderless"
 | 
				
			||||||
 | 
					            android:clickable="true"
 | 
				
			||||||
 | 
					            android:focusable="true">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ImageView
 | 
				
			||||||
 | 
					                android:layout_width="24dp"
 | 
				
			||||||
 | 
					                android:layout_height="24dp"
 | 
				
			||||||
 | 
					                android:src="@android:drawable/ic_menu_revert"
 | 
				
			||||||
 | 
					                android:tint="@color/white"
 | 
				
			||||||
 | 
					                android:layout_marginEnd="8dp" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:text="Transaction Result"
 | 
				
			||||||
 | 
					                style="@style/ToolbarTitleStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </androidx.appcompat.widget.Toolbar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Content -->
 | 
				
			||||||
    <ScrollView
 | 
					    <ScrollView
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="0dp"
 | 
					        android:layout_height="0dp"
 | 
				
			||||||
@ -29,55 +57,14 @@
 | 
				
			|||||||
            android:orientation="vertical"
 | 
					            android:orientation="vertical"
 | 
				
			||||||
            android:padding="16dp">
 | 
					            android:padding="16dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <!-- Success Header -->
 | 
					 | 
				
			||||||
            <androidx.cardview.widget.CardView
 | 
					 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                android:layout_marginBottom="16dp"
 | 
					 | 
				
			||||||
                app:cardCornerRadius="12dp"
 | 
					 | 
				
			||||||
                app:cardElevation="4dp"
 | 
					 | 
				
			||||||
                app:cardBackgroundColor="@color/accent_green">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <LinearLayout
 | 
					 | 
				
			||||||
                    android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                    android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                    android:orientation="vertical"
 | 
					 | 
				
			||||||
                    android:padding="20dp"
 | 
					 | 
				
			||||||
                    android:gravity="center">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <ImageView
 | 
					 | 
				
			||||||
                        android:layout_width="64dp"
 | 
					 | 
				
			||||||
                        android:layout_height="64dp"
 | 
					 | 
				
			||||||
                        android:layout_marginBottom="12dp"
 | 
					 | 
				
			||||||
                        android:src="@drawable/ic_check_circle"
 | 
					 | 
				
			||||||
                        app:tint="@android:color/white" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <TextView
 | 
					 | 
				
			||||||
                        android:layout_width="wrap_content"
 | 
					 | 
				
			||||||
                        android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                        android:text="TRANSAKSI BERHASIL"
 | 
					 | 
				
			||||||
                        style="@style/SuccessTextStyle" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <TextView
 | 
					 | 
				
			||||||
                        android:layout_width="wrap_content"
 | 
					 | 
				
			||||||
                        android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                        android:layout_marginTop="4dp"
 | 
					 | 
				
			||||||
                        android:text="Transaction Completed Successfully"
 | 
					 | 
				
			||||||
                        android:textSize="14sp"
 | 
					 | 
				
			||||||
                        android:textColor="@color/white"
 | 
					 | 
				
			||||||
                        android:fontFamily="@font/inter"
 | 
					 | 
				
			||||||
                        android:gravity="center" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                </LinearLayout>
 | 
					 | 
				
			||||||
            </androidx.cardview.widget.CardView>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- Transaction Summary Card -->
 | 
					            <!-- Transaction Summary Card -->
 | 
				
			||||||
            <androidx.cardview.widget.CardView
 | 
					            <androidx.cardview.widget.CardView
 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
                android:layout_height="wrap_content"
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                android:layout_marginBottom="16dp"
 | 
					                android:layout_marginBottom="16dp"
 | 
				
			||||||
                app:cardCornerRadius="12dp"
 | 
					                app:cardCornerRadius="8dp"
 | 
				
			||||||
                app:cardElevation="4dp">
 | 
					                app:cardElevation="4dp"
 | 
				
			||||||
 | 
					                app:cardBackgroundColor="@color/card_background">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <LinearLayout
 | 
					                <LinearLayout
 | 
				
			||||||
                    android:layout_width="match_parent"
 | 
					                    android:layout_width="match_parent"
 | 
				
			||||||
@ -88,134 +75,330 @@
 | 
				
			|||||||
                    <TextView
 | 
					                    <TextView
 | 
				
			||||||
                        android:layout_width="match_parent"
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
                        android:layout_height="wrap_content"
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
                        android:text="RINGKASAN TRANSAKSI"
 | 
					                        android:text="Transaction Summary"
 | 
				
			||||||
                        style="@style/CardTitleStyle" />
 | 
					                        style="@style/CardTitleStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <ScrollView
 | 
					 | 
				
			||||||
                        android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                        android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                        android:maxHeight="200dp">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        <TextView
 | 
					 | 
				
			||||||
                            android:id="@+id/tv_transaction_summary"
 | 
					 | 
				
			||||||
                            android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                            android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
                            android:text="Loading transaction summary..."
 | 
					 | 
				
			||||||
                            style="@style/TransactionSummaryStyle" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    </ScrollView>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                </LinearLayout>
 | 
					 | 
				
			||||||
            </androidx.cardview.widget.CardView>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- Card Data Card -->
 | 
					 | 
				
			||||||
            <androidx.cardview.widget.CardView
 | 
					 | 
				
			||||||
                android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                android:layout_height="0dp"
 | 
					 | 
				
			||||||
                android:layout_weight="1"
 | 
					 | 
				
			||||||
                android:layout_marginBottom="16dp"
 | 
					 | 
				
			||||||
                app:cardCornerRadius="12dp"
 | 
					 | 
				
			||||||
                app:cardElevation="4dp">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <LinearLayout
 | 
					 | 
				
			||||||
                    android:layout_width="match_parent"
 | 
					 | 
				
			||||||
                    android:layout_height="match_parent"
 | 
					 | 
				
			||||||
                    android:orientation="vertical"
 | 
					 | 
				
			||||||
                    android:padding="20dp">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <LinearLayout
 | 
					                    <LinearLayout
 | 
				
			||||||
                        android:layout_width="match_parent"
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
                        android:layout_height="wrap_content"
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
                        android:orientation="horizontal"
 | 
					                        android:orientation="horizontal"
 | 
				
			||||||
                        android:layout_marginBottom="16dp">
 | 
					                        android:layout_marginBottom="12dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <TextView
 | 
					                        <TextView
 | 
				
			||||||
                            android:layout_width="0dp"
 | 
					                            android:layout_width="0dp"
 | 
				
			||||||
                            android:layout_height="wrap_content"
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
                            android:layout_weight="1"
 | 
					                            android:layout_weight="1"
 | 
				
			||||||
                            android:text="DATA KARTU DETAIL"
 | 
					                            android:text="Amount:"
 | 
				
			||||||
                            style="@style/CardTitleStyle"
 | 
					                            style="@style/SubHeaderTextStyle" />
 | 
				
			||||||
                            android:layout_marginBottom="0dp" />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <ImageView
 | 
					                        <TextView
 | 
				
			||||||
                            android:layout_width="24dp"
 | 
					                            android:id="@+id/tv_amount"
 | 
				
			||||||
                            android:layout_height="24dp"
 | 
					                            android:layout_width="wrap_content"
 | 
				
			||||||
                            android:src="@drawable/ic_credit_card"
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
                            app:tint="#666666" />
 | 
					                            android:text="Rp 190.00"
 | 
				
			||||||
 | 
					                            style="@style/AmountDisplayStyle"
 | 
				
			||||||
 | 
					                            android:textSize="20sp" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    </LinearLayout>
 | 
					                    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    <ScrollView
 | 
					                    <LinearLayout
 | 
				
			||||||
                        android:layout_width="match_parent"
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
                        android:layout_height="0dp"
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
                        android:layout_weight="1"
 | 
					                        android:orientation="horizontal"
 | 
				
			||||||
                        android:fillViewport="true">
 | 
					                        android:layout_marginBottom="8dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        <TextView
 | 
					                        <TextView
 | 
				
			||||||
                            android:id="@+id/tv_card_data"
 | 
					                            android:layout_width="0dp"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:layout_weight="1"
 | 
				
			||||||
 | 
					                            android:text="Status:"
 | 
				
			||||||
 | 
					                            style="@style/SubHeaderTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TextView
 | 
				
			||||||
 | 
					                            android:id="@+id/tv_status"
 | 
				
			||||||
 | 
					                            android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:text="FAILED"
 | 
				
			||||||
 | 
					                            style="@style/StatusTextStyle"
 | 
				
			||||||
 | 
					                            android:textColor="@color/status_error"
 | 
				
			||||||
 | 
					                            android:textStyle="bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <LinearLayout
 | 
				
			||||||
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                        android:orientation="horizontal"
 | 
				
			||||||
 | 
					                        android:layout_marginBottom="8dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TextView
 | 
				
			||||||
 | 
					                            android:layout_width="0dp"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:layout_weight="1"
 | 
				
			||||||
 | 
					                            android:text="Reference:"
 | 
				
			||||||
 | 
					                            style="@style/SubHeaderTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TextView
 | 
				
			||||||
 | 
					                            android:id="@+id/tv_reference"
 | 
				
			||||||
 | 
					                            android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:text="ref-1234567890"
 | 
				
			||||||
 | 
					                            style="@style/BodyTextStyle"
 | 
				
			||||||
 | 
					                            android:textStyle="bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <LinearLayout
 | 
				
			||||||
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                        android:orientation="horizontal">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TextView
 | 
				
			||||||
 | 
					                            android:layout_width="0dp"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:layout_weight="1"
 | 
				
			||||||
 | 
					                            android:text="Card:"
 | 
				
			||||||
 | 
					                            style="@style/SubHeaderTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TextView
 | 
				
			||||||
 | 
					                            android:id="@+id/tv_card_info"
 | 
				
			||||||
 | 
					                            android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:text="4616****9849"
 | 
				
			||||||
 | 
					                            style="@style/BodyTextStyle"
 | 
				
			||||||
 | 
					                            android:textStyle="bold" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </androidx.cardview.widget.CardView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- Response Data Card -->
 | 
				
			||||||
 | 
					            <androidx.cardview.widget.CardView
 | 
				
			||||||
 | 
					                android:id="@+id/card_response_data"
 | 
				
			||||||
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_marginBottom="16dp"
 | 
				
			||||||
 | 
					                app:cardCornerRadius="8dp"
 | 
				
			||||||
 | 
					                app:cardElevation="4dp"
 | 
				
			||||||
 | 
					                app:cardBackgroundColor="@color/card_background">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <LinearLayout
 | 
				
			||||||
 | 
					                    android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                    android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                    android:orientation="vertical"
 | 
				
			||||||
 | 
					                    android:padding="20dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <TextView
 | 
				
			||||||
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                        android:text="Payment Response Data"
 | 
				
			||||||
 | 
					                        style="@style/CardTitleStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <ScrollView
 | 
				
			||||||
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                        android:maxHeight="350dp"
 | 
				
			||||||
 | 
					                        android:background="@color/light_gray"
 | 
				
			||||||
 | 
					                        android:padding="12dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <TextView
 | 
				
			||||||
 | 
					                            android:id="@+id/tv_response_data"
 | 
				
			||||||
                            android:layout_width="match_parent"
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
                            android:layout_height="wrap_content"
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
                            android:text="Loading card data..."
 | 
					                            android:text="Loading response data..."
 | 
				
			||||||
                            style="@style/MonospaceTextStyle"
 | 
					                            style="@style/MonospaceTextStyle"
 | 
				
			||||||
                            android:scrollbars="vertical" />
 | 
					                            android:lineSpacingExtra="4dp"
 | 
				
			||||||
 | 
					                            android:textIsSelectable="true" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    </ScrollView>
 | 
					                    </ScrollView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                </LinearLayout>
 | 
					                </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </androidx.cardview.widget.CardView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- Technical Details Card -->
 | 
				
			||||||
 | 
					            <androidx.cardview.widget.CardView
 | 
				
			||||||
 | 
					                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_marginBottom="16dp"
 | 
				
			||||||
 | 
					                app:cardCornerRadius="8dp"
 | 
				
			||||||
 | 
					                app:cardElevation="4dp"
 | 
				
			||||||
 | 
					                app:cardBackgroundColor="@color/card_background">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <LinearLayout
 | 
				
			||||||
 | 
					                    android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                    android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                    android:orientation="vertical"
 | 
				
			||||||
 | 
					                    android:padding="20dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <TextView
 | 
				
			||||||
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                        android:text="Technical Details"
 | 
				
			||||||
 | 
					                        style="@style/CardTitleStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <LinearLayout
 | 
				
			||||||
 | 
					                        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                        android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                        android:orientation="vertical">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <LinearLayout
 | 
				
			||||||
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:orientation="horizontal"
 | 
				
			||||||
 | 
					                            android:layout_marginBottom="8dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:layout_width="0dp"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_weight="1"
 | 
				
			||||||
 | 
					                                android:text="Payment Method:"
 | 
				
			||||||
 | 
					                                style="@style/HintTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:id="@+id/tv_payment_method"
 | 
				
			||||||
 | 
					                                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:text="EMV_MIDTRANS"
 | 
				
			||||||
 | 
					                                style="@style/BodyTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <LinearLayout
 | 
				
			||||||
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:orientation="horizontal"
 | 
				
			||||||
 | 
					                            android:layout_marginBottom="8dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:layout_width="0dp"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_weight="1"
 | 
				
			||||||
 | 
					                                android:text="Transaction ID:"
 | 
				
			||||||
 | 
					                                style="@style/HintTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:id="@+id/tv_transaction_id"
 | 
				
			||||||
 | 
					                                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:text="N/A"
 | 
				
			||||||
 | 
					                                style="@style/BodyTextStyle"
 | 
				
			||||||
 | 
					                                android:textIsSelectable="true" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <LinearLayout
 | 
				
			||||||
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:orientation="horizontal"
 | 
				
			||||||
 | 
					                            android:layout_marginBottom="8dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:layout_width="0dp"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_weight="1"
 | 
				
			||||||
 | 
					                                android:text="Order ID:"
 | 
				
			||||||
 | 
					                                style="@style/HintTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:id="@+id/tv_order_id"
 | 
				
			||||||
 | 
					                                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:text="N/A"
 | 
				
			||||||
 | 
					                                style="@style/BodyTextStyle"
 | 
				
			||||||
 | 
					                                android:textIsSelectable="true" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <LinearLayout
 | 
				
			||||||
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:orientation="horizontal"
 | 
				
			||||||
 | 
					                            android:layout_marginBottom="8dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:layout_width="0dp"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_weight="1"
 | 
				
			||||||
 | 
					                                android:text="Timestamp:"
 | 
				
			||||||
 | 
					                                style="@style/HintTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:id="@+id/tv_timestamp"
 | 
				
			||||||
 | 
					                                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:text="N/A"
 | 
				
			||||||
 | 
					                                style="@style/BodyTextStyle" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <LinearLayout
 | 
				
			||||||
 | 
					                            android:id="@+id/layout_error_details"
 | 
				
			||||||
 | 
					                            android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                            android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                            android:orientation="vertical"
 | 
				
			||||||
 | 
					                            android:visibility="gone">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:text="Error Details:"
 | 
				
			||||||
 | 
					                                style="@style/HintTextStyle"
 | 
				
			||||||
 | 
					                                android:layout_marginTop="8dp"
 | 
				
			||||||
 | 
					                                android:layout_marginBottom="4dp" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <TextView
 | 
				
			||||||
 | 
					                                android:id="@+id/tv_error_details"
 | 
				
			||||||
 | 
					                                android:layout_width="match_parent"
 | 
				
			||||||
 | 
					                                android:layout_height="wrap_content"
 | 
				
			||||||
 | 
					                                android:text=""
 | 
				
			||||||
 | 
					                                style="@style/BodyTextStyle"
 | 
				
			||||||
 | 
					                                android:textColor="@color/status_error"
 | 
				
			||||||
 | 
					                                android:background="@color/light_gray"
 | 
				
			||||||
 | 
					                                android:padding="8dp"
 | 
				
			||||||
 | 
					                                android:textIsSelectable="true" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            </androidx.cardview.widget.CardView>
 | 
					            </androidx.cardview.widget.CardView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </LinearLayout>
 | 
					        </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </ScrollView>
 | 
					    </ScrollView>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Action Buttons -->
 | 
					    <!-- Action Buttons -->
 | 
				
			||||||
    <LinearLayout
 | 
					    <LinearLayout
 | 
				
			||||||
        android:layout_width="match_parent"
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
        android:layout_height="wrap_content"
 | 
					        android:layout_height="wrap_content"
 | 
				
			||||||
        android:orientation="vertical"
 | 
					        android:orientation="horizontal"
 | 
				
			||||||
        android:padding="16dp"
 | 
					        android:padding="16dp"
 | 
				
			||||||
        android:background="@android:color/white"
 | 
					        android:background="@color/white"
 | 
				
			||||||
        android:elevation="8dp">
 | 
					        android:elevation="4dp">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <!-- Primary Actions Row -->
 | 
					 | 
				
			||||||
        <LinearLayout
 | 
					 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					 | 
				
			||||||
            android:layout_height="wrap_content"
 | 
					 | 
				
			||||||
            android:orientation="horizontal"
 | 
					 | 
				
			||||||
            android:layout_marginBottom="12dp">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <Button
 | 
					 | 
				
			||||||
                android:id="@+id/btn_copy_data"
 | 
					 | 
				
			||||||
                android:layout_width="0dp"
 | 
					 | 
				
			||||||
                android:layout_height="48dp"
 | 
					 | 
				
			||||||
                android:layout_weight="1"
 | 
					 | 
				
			||||||
                android:layout_marginEnd="8dp"
 | 
					 | 
				
			||||||
                android:text="COPY DATA"
 | 
					 | 
				
			||||||
                style="@style/OutlineButton"
 | 
					 | 
				
			||||||
                android:drawablePadding="8dp"
 | 
					 | 
				
			||||||
                android:gravity="center" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <Button
 | 
					 | 
				
			||||||
                android:id="@+id/btn_print_receipt"
 | 
					 | 
				
			||||||
                android:layout_width="0dp"
 | 
					 | 
				
			||||||
                android:layout_height="48dp"
 | 
					 | 
				
			||||||
                android:layout_weight="1"
 | 
					 | 
				
			||||||
                android:layout_marginStart="8dp"
 | 
					 | 
				
			||||||
                android:text="PRINT"
 | 
					 | 
				
			||||||
                style="@style/OutlineButton"
 | 
					 | 
				
			||||||
                android:drawablePadding="8dp"
 | 
					 | 
				
			||||||
                android:gravity="center" />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        </LinearLayout>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <!-- New Transaction Button -->
 | 
					 | 
				
			||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
            android:id="@+id/btn_new_transaction"
 | 
					            android:id="@+id/btn_new_transaction"
 | 
				
			||||||
            android:layout_width="match_parent"
 | 
					            android:layout_width="0dp"
 | 
				
			||||||
            android:layout_height="56dp"
 | 
					            android:layout_height="48dp"
 | 
				
			||||||
            android:text="TRANSAKSI BARU"
 | 
					            android:layout_weight="1"
 | 
				
			||||||
            style="@style/PrimaryButton"
 | 
					            android:layout_marginEnd="8dp"
 | 
				
			||||||
            android:drawablePadding="8dp"
 | 
					            android:text="New Transaction"
 | 
				
			||||||
            android:gravity="center" />
 | 
					            style="@style/PrimaryButton" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Button
 | 
				
			||||||
 | 
					            android:id="@+id/btn_retry"
 | 
				
			||||||
 | 
					            android:layout_width="0dp"
 | 
				
			||||||
 | 
					            android:layout_height="48dp"
 | 
				
			||||||
 | 
					            android:layout_weight="1"
 | 
				
			||||||
 | 
					            android:layout_marginStart="8dp"
 | 
				
			||||||
 | 
					            android:text="Retry Payment"
 | 
				
			||||||
 | 
					            style="@style/OutlineButton"
 | 
				
			||||||
 | 
					            android:visibility="gone" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    </LinearLayout>
 | 
					    </LinearLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user