proeses transaction post backend and database

This commit is contained in:
riz081 2025-06-26 15:48:12 +07:00
parent 7a2ddc3f15
commit b66ef4bb00
4 changed files with 1349 additions and 406 deletions

View File

@ -22,6 +22,7 @@ import com.google.android.material.button.MaterialButton;
import com.example.bdkipoc.cetakulang.ReprintActivity;
import com.example.bdkipoc.cetakulang.ReprintAdapterActivity;
import com.example.bdkipoc.R;
import com.example.bdkipoc.transaction.CreateTransactionActivity;
import com.example.bdkipoc.transaction.ResultTransactionActivity;
@ -156,22 +157,23 @@ public class MainActivity extends AppCompatActivity {
CardView cardView = findViewById(cardId);
if (cardView != null) {
cardView.setOnClickListener(v -> {
// ENHANCED: Navigate with payment type information
if (cardId == R.id.card_kartu_kredit) {
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
navigateToCreateTransaction("credit_card", cardId, "Kartu Kredit");
} else if (cardId == R.id.card_kartu_debit) {
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
navigateToCreateTransaction("debit_card", cardId, "Kartu Debit");
} else if (cardId == R.id.card_qris) {
startActivity(new Intent(MainActivity.this, QrisActivity.class));
// Col-2
} else if (cardId == R.id.card_transfer) {
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
navigateToCreateTransaction("transfer", cardId, "Transfer");
} else if (cardId == R.id.card_uang_elektronik) {
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
navigateToCreateTransaction("e_money", cardId, "Uang Elektronik");
} else if (cardId == R.id.card_cetak_ulang) {
startActivity(new Intent(MainActivity.this, ReprintActivity.class));
// Col-3
} else if (cardId == R.id.card_refund) {
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
navigateToCreateTransaction("refund", cardId, "Refund");
} else if (cardId == R.id.card_settlement) {
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_histori) {
@ -185,7 +187,7 @@ public class MainActivity extends AppCompatActivity {
Toast.makeText(this, "Pengaturan - Coming Soon", Toast.LENGTH_SHORT).show();
} else {
// Fallback for any other cards
Toast.makeText(this, "Menu Diklik: " + getResources().getResourceEntryName(cardId), Toast.LENGTH_SHORT).show();
navigateToCreateTransaction("credit_card", cardId, "Unknown");
}
});
}
@ -242,6 +244,125 @@ public class MainActivity extends AppCompatActivity {
}
}
// NEW: Enhanced navigation method with payment type information
private void navigateToCreateTransaction(String paymentType, int cardMenuId, String cardName) {
try {
Intent intent = new Intent(MainActivity.this, CreateTransactionActivity.class);
// ENHANCED: Pass comprehensive payment information
intent.putExtra("PAYMENT_TYPE", paymentType);
intent.putExtra("CARD_MENU_ID", cardMenuId);
intent.putExtra("CARD_NAME", cardName);
intent.putExtra("CALLING_ACTIVITY", "MainActivity");
// DEBUG: Log navigation details
android.util.Log.d("MainActivity", "=== NAVIGATING TO CREATE TRANSACTION ===");
android.util.Log.d("MainActivity", "Payment Type: " + paymentType);
android.util.Log.d("MainActivity", "Card Menu ID: " + cardMenuId);
android.util.Log.d("MainActivity", "Card Name: " + cardName);
android.util.Log.d("MainActivity", "========================================");
startActivity(intent);
} catch (Exception e) {
android.util.Log.e("MainActivity", "Error navigating to CreateTransaction: " + e.getMessage(), e);
Toast.makeText(this, "Error opening transaction: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
// NEW: Helper method to get payment type from card ID (for backward compatibility)
private String getPaymentTypeFromCardId(int cardId) {
if (cardId == R.id.card_kartu_kredit) {
return "credit_card";
} else if (cardId == R.id.card_kartu_debit) {
return "debit_card";
} else if (cardId == R.id.card_qris) {
return "qris";
} else if (cardId == R.id.card_transfer) {
return "transfer";
} else if (cardId == R.id.card_uang_elektronik) {
return "e_money";
} else if (cardId == R.id.card_refund) {
return "refund";
} else {
android.util.Log.w("MainActivity", "Unknown card ID: " + cardId + ", defaulting to credit_card");
return "credit_card";
}
}
// NEW: Helper method to get card name from card ID
private String getCardNameFromCardId(int cardId) {
if (cardId == R.id.card_kartu_kredit) {
return "Kartu Kredit";
} else if (cardId == R.id.card_kartu_debit) {
return "Kartu Debit";
} else if (cardId == R.id.card_qris) {
return "QRIS";
} else if (cardId == R.id.card_transfer) {
return "Transfer";
} else if (cardId == R.id.card_uang_elektronik) {
return "Uang Elektronik";
} else if (cardId == R.id.card_refund) {
return "Refund";
} else if (cardId == R.id.card_settlement) {
return "Settlement";
} else if (cardId == R.id.card_histori) {
return "Histori";
} else if (cardId == R.id.card_cetak_ulang) {
return "Cetak Ulang";
} else if (cardId == R.id.card_bantuan) {
return "Bantuan";
} else if (cardId == R.id.card_info_toko) {
return "Info Toko";
} else if (cardId == R.id.card_pengaturan) {
return "Pengaturan";
} else {
return "Unknown";
}
}
// NEW: Method to validate payment type compatibility
private boolean isPaymentTypeSupported(String paymentType) {
String[] supportedTypes = {
"credit_card", "debit_card", "e_money", "qris",
"transfer", "refund"
};
for (String supportedType : supportedTypes) {
if (supportedType.equals(paymentType)) {
return true;
}
}
return false;
}
// NEW: Method to show payment type selection dialog (for future use)
private void showPaymentTypeDialog() {
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
builder.setTitle("Pilih Jenis Pembayaran");
String[] paymentTypes = {
"Kartu Kredit", "Kartu Debit", "Uang Elektronik",
"QRIS", "Transfer", "Refund"
};
String[] paymentTypeCodes = {
"credit_card", "debit_card", "e_money",
"qris", "transfer", "refund"
};
builder.setItems(paymentTypes, (dialog, which) -> {
String selectedType = paymentTypeCodes[which];
String selectedName = paymentTypes[which];
// Use a generic card ID for dialog selection
navigateToCreateTransaction(selectedType, -1, selectedName);
});
builder.setNegativeButton("Batal", null);
builder.show();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@ -256,5 +377,74 @@ public class MainActivity extends AppCompatActivity {
// Clear any transaction completion flags to avoid repeated messages
getIntent().removeExtra("transaction_completed");
getIntent().removeExtra("transaction_amount");
// NEW: Log resume for debugging
android.util.Log.d("MainActivity", "MainActivity resumed");
}
@Override
protected void onPause() {
super.onPause();
android.util.Log.d("MainActivity", "MainActivity paused");
}
@Override
protected void onDestroy() {
super.onDestroy();
android.util.Log.d("MainActivity", "MainActivity destroyed");
}
// NEW: Method to handle direct payment type launch (for external calls)
public static Intent createTransactionIntent(android.content.Context context, String paymentType, String cardName) {
Intent intent = new Intent(context, CreateTransactionActivity.class);
intent.putExtra("PAYMENT_TYPE", paymentType);
intent.putExtra("CARD_NAME", cardName);
intent.putExtra("CALLING_ACTIVITY", "External");
return intent;
}
// NEW: Public method to simulate card click (for testing)
public void simulateCardClick(int cardId) {
CardView cardView = findViewById(cardId);
if (cardView != null) {
cardView.performClick();
} else {
android.util.Log.w("MainActivity", "Card not found for ID: " + cardId);
}
}
// NEW: Method to get all available payment types
public String[] getAvailablePaymentTypes() {
return new String[]{
"credit_card", "debit_card", "e_money",
"qris", "transfer", "refund"
};
}
// NEW: Method to get payment type display names
public String[] getPaymentTypeDisplayNames() {
return new String[]{
"Kartu Kredit", "Kartu Debit", "Uang Elektronik",
"QRIS", "Transfer", "Refund"
};
}
// NEW: Debug method to log all card IDs and their payment types
private void debugCardMappings() {
android.util.Log.d("MainActivity", "=== CARD PAYMENT TYPE MAPPINGS ===");
int[] cardIds = {
R.id.card_kartu_kredit, R.id.card_kartu_debit, R.id.card_qris,
R.id.card_transfer, R.id.card_uang_elektronik, R.id.card_refund
};
for (int cardId : cardIds) {
String paymentType = getPaymentTypeFromCardId(cardId);
String cardName = getCardNameFromCardId(cardId);
android.util.Log.d("MainActivity",
"Card ID: " + cardId + " -> Payment Type: " + paymentType + " -> Name: " + cardName);
}
android.util.Log.d("MainActivity", "==================================");
}
}

View File

@ -24,6 +24,7 @@ import com.example.bdkipoc.transaction.managers.EMVManager;
import com.example.bdkipoc.transaction.managers.ModalManager;
import com.example.bdkipoc.transaction.managers.PinPadManager;
import com.example.bdkipoc.transaction.managers.MidtransCardPaymentManager;
import com.example.bdkipoc.transaction.managers.PostTransactionBackendManager;
import java.text.NumberFormat;
import java.util.Locale;
@ -34,14 +35,17 @@ import org.json.JSONObject;
import org.json.JSONException;
/**
* CreateTransactionActivity - Updated with Enhanced Midtrans Credit Card Integration
* Handles amount input, card scanning, and Midtrans payment processing with improved bank detection
* CreateTransactionActivity - Enhanced with Backend Integration
*
* Flow: Backend Post Transaction => EMV/Card Processing => Midtrans Charge => Results
* The transaction_uuid from backend is used as order_id in Midtrans
*/
public class CreateTransactionActivity extends AppCompatActivity implements
EMVManager.EMVManagerCallback,
CardScannerManager.CardScannerCallback,
PinPadManager.PinPadManagerCallback,
MidtransCardPaymentManager.MidtransCardPaymentCallback {
MidtransCardPaymentManager.MidtransCardPaymentCallback,
PostTransactionBackendManager.PostTransactionCallback {
private static final String TAG = "CreateTransaction";
@ -60,20 +64,26 @@ public class CreateTransactionActivity extends AppCompatActivity implements
// State Management
private String transactionAmount = "0";
private boolean isEMVMode = true;
private boolean useDirectMidtransPayment = true; // Toggle for Midtrans integration
private boolean useDirectMidtransPayment = true;
// NEW: Payment type and backend integration
private String paymentType = "credit_card"; // Default
private String transactionUuid; // From backend response
private String backendTransactionStatus = "INIT";
// Manager Classes
private EMVManager emvManager;
private CardScannerManager cardScannerManager;
private PinPadManager pinPadManager;
private ModalManager modalManager;
private MidtransCardPaymentManager midtransPaymentManager; // NEW: Midtrans integration
private MidtransCardPaymentManager midtransPaymentManager;
private PostTransactionBackendManager backendManager; // NEW: Backend manager
// EMV Dialog
private AlertDialog mAppSelectDialog;
private int mSelectIndex;
// NEW: EMV Data Storage for Midtrans
// EMV Data Storage for Midtrans
private String emvCardNumber;
private String emvExpiryDate;
private String emvCardholderName;
@ -81,10 +91,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements
private String emvTlvData;
private String referenceId;
// ENHANCED: Store last response for better debugging
// Enhanced response storage
private JSONObject lastMidtransResponse;
private JSONObject lastBackendResponse; // NEW: Store backend response
// deklarasi variabel success screen
// Success screen components
private LinearLayout successScreen;
private ImageView successIcon;
private TextView successMessage;
@ -95,14 +106,16 @@ public class CreateTransactionActivity extends AppCompatActivity implements
setContentView(R.layout.activity_create_transaction);
initViews();
extractPaymentTypeFromIntent(); // NEW: Determine payment type
initManagers();
setupListeners();
updateAmountDisplay();
updateModeDisplay();
// NEW: Generate reference ID for transaction tracking
referenceId = generateReferenceId();
// Generate reference ID for transaction tracking
referenceId = PostTransactionBackendManager.generateReferenceId();
Log.d(TAG, "Generated reference ID: " + referenceId);
Log.d(TAG, "Payment type determined: " + paymentType);
}
private void initViews() {
@ -133,6 +146,38 @@ public class CreateTransactionActivity extends AppCompatActivity implements
successMessage = findViewById(R.id.success_message);
}
// NEW: Extract payment type from intent (based on MainActivity card selection)
private void extractPaymentTypeFromIntent() {
Intent intent = getIntent();
// Check if payment type was passed directly
String intentPaymentType = intent.getStringExtra("PAYMENT_TYPE");
if (intentPaymentType != null) {
paymentType = intentPaymentType;
Log.d(TAG, "Payment type from intent: " + paymentType);
return;
}
// Check if card menu ID was passed
int cardMenuId = intent.getIntExtra("CARD_MENU_ID", -1);
if (cardMenuId != -1) {
paymentType = PostTransactionBackendManager.mapCardMenuToPaymentType(cardMenuId);
Log.d(TAG, "Payment type from card menu ID " + cardMenuId + ": " + paymentType);
return;
}
// Fallback: try to determine from calling activity
String callingActivity = intent.getStringExtra("CALLING_ACTIVITY");
if (callingActivity != null) {
// Parse calling activity info if available
Log.d(TAG, "Calling activity: " + callingActivity);
}
// Default fallback
paymentType = "credit_card";
Log.d(TAG, "Using default payment type: " + paymentType);
}
private void initManagers() {
// Initialize Modal Manager
FrameLayout modalOverlay = findViewById(R.id.modal_overlay);
@ -145,13 +190,16 @@ public class CreateTransactionActivity extends AppCompatActivity implements
cardScannerManager = new CardScannerManager(this);
pinPadManager = new PinPadManager(this);
// NEW: Initialize Midtrans payment manager
// Initialize Midtrans payment manager
midtransPaymentManager = new MidtransCardPaymentManager(this, this);
// NEW: Initialize Backend manager
backendManager = new PostTransactionBackendManager(this, this);
// Initialize EMV data
emvManager.initEMVData();
Log.d(TAG, "All managers initialized including Midtrans");
Log.d(TAG, "All managers initialized including Backend and Midtrans");
}
private void setupListeners() {
@ -183,7 +231,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements
// Clear button (ImageView)
btnClear.setOnClickListener(v -> clearAmount());
// Confirm button - shows modal and starts scanning
// Confirm button - starts the enhanced flow
btnConfirm.setOnClickListener(v -> handleConfirmAmount());
// Toggle mode button (hidden but functional)
@ -205,7 +253,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
if (newAmount.length() <= 9) {
transactionAmount = newAmount;
updateAmountDisplay();
// Update status tombol
btnConfirm.setEnabled(!transactionAmount.equals("0"));
}
}
@ -217,7 +264,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
transactionAmount = "0";
}
updateAmountDisplay();
// Update status tombol
btnConfirm.setEnabled(!transactionAmount.equals("0"));
}
@ -225,19 +271,18 @@ public class CreateTransactionActivity extends AppCompatActivity implements
if (tvAmountDisplay != null) {
if (transactionAmount.equals("0")) {
tvAmountDisplay.setText("");
// Disable tombol dan akan otomatis pakai background inactive
btnConfirm.setEnabled(false);
} else {
long amountCents = Long.parseLong(transactionAmount);
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
String formattedAmount = formatter.format(amountCents);
tvAmountDisplay.setText(formattedAmount);
// Enable tombol dan akan otomatis pakai background active
btnConfirm.setEnabled(true);
}
}
}
// ENHANCED: New enhanced flow with backend integration
private void handleConfirmAmount() {
long amountCents = Long.parseLong(transactionAmount);
if (amountCents < 100) { // Minimum Rp 1.00
@ -245,15 +290,77 @@ public class CreateTransactionActivity extends AppCompatActivity implements
return;
}
// Show modal and start card scanning
showModalAndStartScanning();
Log.d(TAG, "=== STARTING ENHANCED TRANSACTION FLOW ===");
Log.d(TAG, "Payment Type: " + paymentType);
Log.d(TAG, "Amount: " + amountCents);
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "==========================================");
// Start enhanced flow: Backend => Card Processing => Midtrans
startEnhancedTransactionFlow();
}
// NEW: Enhanced transaction flow with backend integration
private void startEnhancedTransactionFlow() {
// Step 1: Post initial transaction to backend with INIT status
modalManager.showProcessingModal("Initializing transaction...");
private void showModalAndStartScanning() {
Log.d(TAG, "Starting card scanning with modal...");
// Debug transaction data
backendManager.debugTransactionData(paymentType, referenceId, Long.parseLong(transactionAmount), "INIT");
// Show modal first
// Post to backend first
backendManager.postInitTransaction(paymentType, referenceId, Long.parseLong(transactionAmount));
}
// ====== NEW: BACKEND CALLBACK METHODS ======
@Override
public void onPostTransactionSuccess(JSONObject response, String transactionUuid) {
Log.d(TAG, "✅ Backend transaction posted successfully!");
Log.d(TAG, "Transaction UUID: " + transactionUuid);
// Store backend response and transaction UUID
lastBackendResponse = response;
this.transactionUuid = transactionUuid;
// Step 2: Start card scanning after backend success
modalManager.showProcessingModal("Backend initialized - Scanning card...");
// Start card scanning after short delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
startCardScanningFlow();
}, 1000);
}
@Override
public void onPostTransactionError(String errorMessage) {
Log.e(TAG, "❌ Backend transaction failed: " + errorMessage);
modalManager.hideModal();
showToast("Backend initialization failed: " + errorMessage);
// Option 1: Retry backend call
// Option 2: Proceed without backend (fallback mode)
// For now, let's offer retry
new AlertDialog.Builder(this)
.setTitle("Backend Error")
.setMessage("Failed to initialize transaction with backend. Retry?")
.setPositiveButton("Retry", (dialog, which) -> {
startEnhancedTransactionFlow();
})
.setNegativeButton("Cancel", (dialog, which) -> {
// Could implement fallback mode here
})
.show();
}
@Override
public void onPostTransactionProgress(String message) {
Log.d(TAG, "Backend progress: " + message);
modalManager.showProcessingModal(message);
}
// NEW: Start card scanning flow (after backend success)
private void startCardScanningFlow() {
Log.d(TAG, "Starting card scanning flow...");
modalManager.showScanCardModal();
// Start scanning after short delay
@ -284,10 +391,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements
Log.d(TAG, "Simple card detected: " + cardType);
modalManager.showProcessingModal("Kartu " + cardType + " Ditemukan - Memproses...");
// For simple mode, navigate to results without Midtrans
new Handler(Looper.getMainLooper()).postDelayed(() -> {
navigateToResults(cardType, cardData, null);
}, 1500);
// For simple mode, update backend status and navigate to results
updateBackendToSuccessAndNavigate(cardType, cardData, null);
}
@Override
@ -312,7 +417,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
@Override
public void onScanProgress(String message) {
Log.d(TAG, "Scan progress: " + message);
// Can update UI with progress if needed
}
// ====== EMV MANAGER CALLBACK METHODS ======
@ -329,7 +433,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
@Override
public void onConfirmCardNo(String cardNo) {
// NEW: Store EMV card data for Midtrans
emvCardNumber = cardNo;
Log.d(TAG, "EMV Card Number extracted: " + maskCardNumber(cardNo));
@ -382,13 +485,13 @@ public class CreateTransactionActivity extends AppCompatActivity implements
public void onTransactionSuccess(int code, String desc) {
Log.d(TAG, "EMV Transaction successful");
// NEW: Process Midtrans payment after successful EMV
if (useDirectMidtransPayment && emvCardNumber != null) {
// Process Midtrans payment after successful EMV
if (useDirectMidtransPayment && emvCardNumber != null && transactionUuid != null) {
processMidtransPayment();
} else {
// Traditional flow - navigate to results
// Fallback - navigate to results
String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
navigateToResults(cardType, null, emvManager.getCardNo());
updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo());
}
}
@ -437,6 +540,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements
})
.start();
}
// ====== PIN PAD CALLBACK METHODS ======
@Override
public void onPinInputLength(int length) {
@ -473,7 +577,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
@Override
public void onTokenizeSuccess(String cardToken) {
Log.d(TAG, "✅ Midtrans tokenization successful: " + cardToken);
// Tokenization successful, charge process will continue automatically
}
@Override
@ -482,14 +585,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements
modalManager.hideModal();
showToast("Payment tokenization failed: " + errorMessage);
// IMPROVED: Better fallback handling
showToast("Tokenization failed, trying alternative method...");
// Try fallback to traditional results screen after delay
// Fallback to traditional results screen after delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
String cardType = emvManager.getCardType() ==
com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
navigateToResults(cardType, null, emvManager.getCardNo());
updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo());
}, 2000);
}
@ -497,7 +597,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements
public void onChargeSuccess(JSONObject chargeResponse) {
Log.d(TAG, "✅ Midtrans charge successful!");
// ENHANCED: Store response for debugging
// Store response for debugging
lastMidtransResponse = chargeResponse;
try {
@ -505,30 +605,29 @@ public class CreateTransactionActivity extends AppCompatActivity implements
String transactionStatus = chargeResponse.getString("transaction_status");
String statusCode = chargeResponse.optString("status_code", "");
// ENHANCED: Extract and log bank information
String bankInfo = extractBankFromResponse(chargeResponse);
Log.d(TAG, "✅ Payment Details:");
Log.d(TAG, " - Transaction ID: " + transactionId);
Log.d(TAG, " - Transaction Status: " + transactionStatus);
Log.d(TAG, " - Status Code: " + statusCode);
Log.d(TAG, " - Bank Info: " + bankInfo);
Log.d(TAG, " - Full Response: " + chargeResponse.toString());
Log.d(TAG, " - Transaction UUID: " + transactionUuid);
// Check transaction status and navigate accordingly
// Update backend status to SUCCESS
backendTransactionStatus = "SUCCESS";
backendManager.postSuccessTransaction(paymentType, referenceId, Long.parseLong(transactionAmount));
// Navigate to results with all data
boolean isSuccess = "capture".equals(transactionStatus) ||
"settlement".equals(transactionStatus) ||
"pending".equals(transactionStatus);
// Navigate to results with Midtrans data
navigateToMidtransResults(chargeResponse, isSuccess);
navigateToEnhancedResults(chargeResponse, isSuccess);
} catch (Exception e) {
Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage());
// Fallback to traditional results
String cardType = emvManager.getCardType() ==
com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
navigateToResults(cardType, null, emvManager.getCardNo());
updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo());
}
}
@ -536,23 +635,17 @@ public class CreateTransactionActivity extends AppCompatActivity implements
public void onChargeError(String errorMessage) {
Log.e(TAG, "❌ Midtrans charge failed: " + errorMessage);
// ENHANCED: Try to get response even in error case
JSONObject errorResponse = midtransPaymentManager.getLastResponse();
lastMidtransResponse = errorResponse;
if (errorResponse != null) {
Log.d(TAG, "✅ Got Midtrans error response with data, using it for display");
// ENHANCED: Extract bank even from error response
String bankInfo = extractBankFromResponse(errorResponse);
Log.d(TAG, "Bank info from error response: " + bankInfo);
Log.d(TAG, "Got Midtrans error response with data, using it for display");
modalManager.hideModal();
String userMessage = getUserFriendlyErrorMessage(errorMessage);
showToast(userMessage);
// Use Midtrans results even for failed transactions
navigateToMidtransResults(errorResponse, false);
navigateToEnhancedResults(errorResponse, false);
} else {
Log.d(TAG, "No Midtrans response data available, using fallback");
@ -565,75 +658,21 @@ public class CreateTransactionActivity extends AppCompatActivity implements
new Handler(Looper.getMainLooper()).postDelayed(() -> {
String cardType = emvManager.getCardType() ==
com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
navigateToResults(cardType, null, emvManager.getCardNo());
updateBackendToSuccessAndNavigate(cardType, null, emvManager.getCardNo());
}, 3000);
}
}
// NEW: Method to extract bank information from Midtrans response
private String extractBankFromResponse(JSONObject response) {
if (response == null) {
return "Unknown";
}
try {
// Try different possible bank field names
String[] possibleBankFields = {"bank", "issuer", "acquiring_bank", "card_type"};
for (String field : possibleBankFields) {
if (response.has(field)) {
String value = response.getString(field);
if (value != null && !value.trim().isEmpty()) {
Log.d(TAG, "Found bank info in field '" + field + "': " + value);
return value;
}
}
}
// If no bank field found, try to extract from card details
if (response.has("payment_method")) {
String paymentMethod = response.getString("payment_method");
Log.d(TAG, "Payment method: " + paymentMethod);
}
return "Unknown";
} catch (JSONException e) {
Log.e(TAG, "Error extracting bank from response: " + e.getMessage());
return "Unknown";
}
}
private String getUserFriendlyErrorMessage(String errorMessage) {
if (errorMessage == null) {
return "Payment processing failed";
}
String lowerError = errorMessage.toLowerCase();
if (lowerError.contains("cvv") || lowerError.contains("cvv2")) {
return "Card verification failed";
} else if (lowerError.contains("token expired")) {
return "Card session expired, please try again";
} else if (lowerError.contains("network") || lowerError.contains("timeout")) {
return "Network connection issue, please try again";
} else if (lowerError.contains("decline") || lowerError.contains("deny")) {
return "Transaction declined by bank";
} else if (lowerError.contains("invalid")) {
return "Invalid card information";
} else {
return "Payment processing failed, please try again";
}
}
@Override
public void onPaymentProgress(String message) {
Log.d(TAG, "Midtrans payment progress: " + message);
modalManager.showProcessingModal(message);
}
// NEW: Enhanced Midtrans payment processing with transaction UUID
private void processMidtransPayment() {
Log.d(TAG, "=== STARTING ENHANCED MIDTRANS PAYMENT PROCESS ===");
Log.d(TAG, "=== STARTING ENHANCED MIDTRANS PAYMENT ===");
Log.d(TAG, "Using transaction UUID as order_id: " + transactionUuid);
try {
// Extract additional EMV data if available
@ -661,19 +700,18 @@ public class CreateTransactionActivity extends AppCompatActivity implements
return;
}
// NEW: Use EMV-specific payment processing
modalManager.showProcessingModal("Processing EMV Payment...");
modalManager.showProcessingModal("Processing EMV Payment with Backend UUID...");
// Process as EMV card payment (no CVV required)
midtransPaymentManager.processEMVCardPayment(
// ENHANCED: Process with transaction UUID as order_id
midtransPaymentManager.processEMVCardPaymentWithOrderId(
cardData,
Long.parseLong(transactionAmount),
referenceId,
transactionUuid, // Use transaction UUID as order_id
emvTlvData
);
} catch (Exception e) {
Log.e(TAG, "Error preparing Midtrans payment: " + e.getMessage(), e);
Log.e(TAG, "Error preparing enhanced Midtrans payment: " + e.getMessage(), e);
onChargeError("Failed to prepare payment data: " + e.getMessage());
}
}
@ -684,7 +722,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements
String rawExpiryDate = extractEMVTag("5F24", null);
if (rawExpiryDate != null && rawExpiryDate.length() >= 4) {
emvExpiryDate = rawExpiryDate.substring(0, 4) + "01"; // Add day for YYMMDD format
emvExpiryDate = rawExpiryDate.substring(0, 4) + "01";
} else {
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.add(java.util.Calendar.YEAR, 2);
@ -692,10 +730,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements
cal.get(java.util.Calendar.YEAR) % 100,
cal.get(java.util.Calendar.MONTH) + 1);
}
// Extract AID from EMV tag 9F06 (Application Identifier - Terminal)
emvAidIdentifier = extractEMVTag("9F06", "A0000000031010"); // Default to Visa AID
// NEW: Build comprehensive TLV data for EMV processing
emvAidIdentifier = extractEMVTag("9F06", "A0000000031010");
emvTlvData = buildEMVTLVData();
Log.d(TAG, "✅ Enhanced EMV data extracted:");
@ -708,8 +744,8 @@ public class CreateTransactionActivity extends AppCompatActivity implements
Log.e(TAG, "Error extracting EMV data: " + e.getMessage(), e);
// Set fallback values
emvCardholderName = "EMV CARDHOLDER";
emvExpiryDate = "251201"; // Dec 2025
emvAidIdentifier = "A0000000031010"; // Visa AID
emvExpiryDate = "251201";
emvAidIdentifier = "A0000000031010";
emvTlvData = "";
}
}
@ -717,16 +753,12 @@ public class CreateTransactionActivity extends AppCompatActivity implements
private String extractEMVTag(String tag, String defaultValue) {
try {
switch (tag) {
case "5F20": // Cardholder Name
case "5F20":
return defaultValue != null ? defaultValue : "EMV CARDHOLDER";
case "5F24": // Application Expiration Date
// Return null to trigger date generation logic
case "5F24":
return null;
case "9F06": // Application Identifier (Terminal)
case "9F06":
return defaultValue != null ? defaultValue : "A0000000031010";
default:
return defaultValue;
}
@ -741,29 +773,13 @@ public class CreateTransactionActivity extends AppCompatActivity implements
try {
StringBuilder tlvBuilder = new StringBuilder();
// Add key EMV tags that might be useful for Midtrans
// Format: TAG=VALUE;TAG=VALUE;...
// Application Transaction Counter (9F36)
tlvBuilder.append("9F36=").append(String.format("%04X",
(int)(Math.random() * 65535))).append(";");
// Terminal Verification Results (95)
tlvBuilder.append("95=0000000000;");
// Transaction Status Information (9B)
tlvBuilder.append("9B=E800;");
// Application Interchange Profile (82)
tlvBuilder.append("82=1C00;");
// Cryptogram Information Data (9F27)
tlvBuilder.append("9F27=80;");
// Application Cryptogram (9F26)
tlvBuilder.append("9F26=").append(generateRandomHex(16)).append(";");
// Unpredictable Number (9F37)
tlvBuilder.append("9F37=").append(generateRandomHex(8)).append(";");
String tlvData = tlvBuilder.toString();
@ -785,6 +801,84 @@ public class CreateTransactionActivity extends AppCompatActivity implements
return hex.toString();
}
// NEW: Update backend to SUCCESS and navigate
private void updateBackendToSuccessAndNavigate(String cardType, Bundle cardData, String cardNo) {
// Update backend status to SUCCESS
backendTransactionStatus = "SUCCESS";
modalManager.showProcessingModal("Finalizing transaction...");
// Update backend status
backendManager.postSuccessTransaction(paymentType, referenceId, Long.parseLong(transactionAmount));
// Navigate after delay
new Handler(Looper.getMainLooper()).postDelayed(() -> {
navigateToResults(cardType, cardData, cardNo);
}, 1500);
}
// ====== NAVIGATION METHODS ======
private void navigateToResults(String cardType, Bundle cardData, String cardNo) {
showSuccessScreen(() -> {
Intent intent = new Intent(this, ResultTransactionActivity.class);
intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
intent.putExtra("CARD_TYPE", cardType);
intent.putExtra("EMV_MODE", isEMVMode);
intent.putExtra("REFERENCE_ID", referenceId);
intent.putExtra("PAYMENT_TYPE", paymentType); // NEW: Include payment type
intent.putExtra("TRANSACTION_UUID", transactionUuid); // NEW: Include transaction UUID
if (cardData != null) {
intent.putExtra("CARD_DATA", cardData);
}
if (cardNo != null) {
intent.putExtra("CARD_NO", cardNo);
}
// NEW: Include backend response
if (lastBackendResponse != null) {
intent.putExtra("BACKEND_RESPONSE", lastBackendResponse.toString());
}
startActivity(intent);
finish();
});
}
// NEW: Enhanced navigation with comprehensive data
private void navigateToEnhancedResults(JSONObject midtransResponse, boolean paymentSuccess) {
Log.d(TAG, "=== NAVIGATING TO ENHANCED RESULTS ===");
Log.d(TAG, "Payment Success: " + paymentSuccess);
Log.d(TAG, "Transaction UUID: " + transactionUuid);
showSuccessScreen(() -> {
Intent intent = new Intent(this, ResultTransactionActivity.class);
intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
intent.putExtra("CARD_TYPE", "EMV_MIDTRANS_BACKEND");
intent.putExtra("EMV_MODE", true);
intent.putExtra("REFERENCE_ID", referenceId);
intent.putExtra("CARD_NO", emvCardNumber);
intent.putExtra("PAYMENT_SUCCESS", paymentSuccess);
intent.putExtra("PAYMENT_TYPE", paymentType); // NEW
intent.putExtra("TRANSACTION_UUID", transactionUuid); // NEW
// Enhanced data
if (midtransResponse != null) {
intent.putExtra("MIDTRANS_RESPONSE", midtransResponse.toString());
}
if (lastBackendResponse != null) {
intent.putExtra("BACKEND_RESPONSE", lastBackendResponse.toString());
}
// Add additional EMV data
intent.putExtra("EMV_CARDHOLDER_NAME", emvCardholderName);
intent.putExtra("EMV_AID", emvAidIdentifier);
intent.putExtra("EMV_EXPIRY", emvExpiryDate);
startActivity(intent);
finish();
});
}
// ====== HELPER METHODS ======
private void showAppSelectDialog(String[] candidateNames) {
mAppSelectDialog = new AlertDialog.Builder(this)
@ -797,241 +891,34 @@ public class CreateTransactionActivity extends AppCompatActivity implements
mAppSelectDialog.show();
}
private void navigateToResults(String cardType, Bundle cardData, String cardNo) {
// modalManager.hideModal();
showSuccessScreen(() -> {
Intent intent = new Intent(this, ResultTransactionActivity.class);
intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
intent.putExtra("CARD_TYPE", cardType);
intent.putExtra("EMV_MODE", isEMVMode);
intent.putExtra("REFERENCE_ID", referenceId);
if (cardData != null) {
intent.putExtra("CARD_DATA", cardData);
}
if (cardNo != null) {
intent.putExtra("CARD_NO", cardNo);
}
startActivity(intent);
finish();
});
}
// ENHANCED: Better navigation with comprehensive data
private void navigateToMidtransResults(JSONObject midtransResponse, boolean paymentSuccess) {
Log.d(TAG, "=== NAVIGATING TO MIDTRANS RESULTS ===");
Log.d(TAG, "Payment Success: " + paymentSuccess);
Log.d(TAG, "Response Length: " + (midtransResponse != null ? midtransResponse.length() : 0));
// ENHANCED: Validate and enhance response data
JSONObject enhancedResponse = enhanceMidtransResponse(midtransResponse);
showSuccessScreen(() -> {
Intent intent = new Intent(this, ResultTransactionActivity.class);
intent.putExtra("TRANSACTION_AMOUNT", transactionAmount);
intent.putExtra("CARD_TYPE", "EMV_MIDTRANS");
intent.putExtra("EMV_MODE", true);
intent.putExtra("REFERENCE_ID", referenceId);
intent.putExtra("CARD_NO", emvCardNumber);
intent.putExtra("PAYMENT_SUCCESS", paymentSuccess);
// ENHANCED: Send enhanced response
if (enhancedResponse != null) {
String responseString = enhancedResponse.toString();
intent.putExtra("MIDTRANS_RESPONSE", responseString);
Log.d(TAG, "✅ Sending Midtrans response: " + responseString);
} else {
Log.w(TAG, "⚠️ No Midtrans response to send");
}
// Add additional EMV data for receipt
intent.putExtra("EMV_CARDHOLDER_NAME", emvCardholderName);
intent.putExtra("EMV_AID", emvAidIdentifier);
intent.putExtra("EMV_EXPIRY", emvExpiryDate);
// ENHANCED: Add debugging extras
intent.putExtra("DEBUG_BANK_SOURCE", "midtrans_response");
startActivity(intent);
finish();
});
}
// NEW: Method to enhance Midtrans response with additional data
private JSONObject enhanceMidtransResponse(JSONObject originalResponse) {
if (originalResponse == null) {
Log.w(TAG, "Original response is null, creating fallback response");
return createFallbackMidtransResponse();
private String getUserFriendlyErrorMessage(String errorMessage) {
if (errorMessage == null) {
return "Payment processing failed";
}
try {
// Clone the original response
JSONObject enhanced = new JSONObject(originalResponse.toString());
String lowerError = errorMessage.toLowerCase();
// ENHANCED: Add bank information if missing
if (!enhanced.has("bank") || enhanced.getString("bank").trim().isEmpty()) {
String bankFromCard = determineBankFromCard();
if (bankFromCard != null) {
enhanced.put("bank", bankFromCard);
Log.d(TAG, "✅ Added bank to response: " + bankFromCard);
}
}
// ENHANCED: Add EMV data if available
if (emvCardNumber != null) {
enhanced.put("emv_card_number", maskCardNumber(emvCardNumber));
}
if (emvCardholderName != null) {
enhanced.put("emv_cardholder_name", emvCardholderName);
}
if (emvAidIdentifier != null) {
enhanced.put("emv_aid", emvAidIdentifier);
}
// Add transaction metadata
enhanced.put("processing_timestamp", System.currentTimeMillis());
enhanced.put("emv_mode", true);
Log.d(TAG, "✅ Enhanced response created with " + enhanced.length() + " fields");
return enhanced;
} catch (JSONException e) {
Log.e(TAG, "Error enhancing response: " + e.getMessage());
return originalResponse;
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";
}
}
// NEW: Create fallback response when Midtrans response is unavailable
private JSONObject createFallbackMidtransResponse() {
try {
JSONObject fallback = new JSONObject();
// Basic transaction info
fallback.put("transaction_id", "FALLBACK_" + System.currentTimeMillis());
fallback.put("order_id", "ORDER_" + System.currentTimeMillis());
fallback.put("gross_amount", transactionAmount);
fallback.put("transaction_status", "processed");
fallback.put("status_code", "200");
fallback.put("status_message", "Success");
// ENHANCED: Determine bank from available data
String bankFromCard = determineBankFromCard();
if (bankFromCard != null) {
fallback.put("bank", bankFromCard);
} else {
fallback.put("bank", "BCA"); // Default
}
// EMV data
if (emvCardNumber != null) {
fallback.put("emv_card_number", maskCardNumber(emvCardNumber));
}
if (emvCardholderName != null) {
fallback.put("emv_cardholder_name", emvCardholderName);
}
fallback.put("payment_type", "credit_card");
fallback.put("transaction_time", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
fallback.put("is_fallback", true);
Log.d(TAG, "✅ Created fallback response: " + fallback.toString());
return fallback;
} catch (JSONException e) {
Log.e(TAG, "Error creating fallback response: " + e.getMessage());
return null;
}
}
// NEW: Determine bank from card number or EMV data
private String determineBankFromCard() {
// Priority 1: Use card BIN
if (emvCardNumber != null && emvCardNumber.length() >= 6) {
String bin = emvCardNumber.substring(0, 6);
return getBankFromBin(bin);
}
// Priority 2: Use EMV AID
if (emvAidIdentifier != null) {
return getBankFromAid(emvAidIdentifier);
}
return null;
}
// ENHANCED: Better BIN to bank mapping
private String getBankFromBin(String bin) {
if (bin == null || bin.length() < 4) {
return "BCA"; // Default
}
// Get first 4 digits for matching
String bin4 = bin.substring(0, 4);
// Indonesian Bank BIN mapping
switch (bin4) {
// BCA
case "4621": case "4699": case "5221": case "6277":
return "BCA";
// MANDIRI
case "4313": case "5573": case "6011": case "6234":
case "5406": case "4097":
return "MANDIRI";
// BNI
case "4603": case "1946": case "5264":
return "BNI";
// BRI
case "4578": case "4479": case "5208": case "4486":
return "BRI";
// CIMB NIAGA
case "4599": case "5249": case "4543":
return "CIMB NIAGA";
// DANAMON
case "4055": case "5108": case "4631":
return "DANAMON";
default:
// Try 6-digit BIN patterns
if (bin.length() >= 6) {
String bin6 = bin.substring(0, 6);
// Add more specific 6-digit patterns here if needed
Log.d(TAG, "Unknown BIN pattern: " + bin6);
}
return "BCA"; // Default fallback
}
}
private String getBankFromAid(String aid) {
if (aid == null) return "BCA";
// AID patterns for Indonesian banks
if (aid.contains("A0000000031010")) {
return "BCA"; // Common for VISA
} else if (aid.contains("A0000000041010")) {
return "MANDIRI"; // Common for Mastercard
}
return "BCA"; // Default
}
// NEW: Add getter for last response (for debugging)
public JSONObject getLastMidtransResponse() {
return lastMidtransResponse;
}
private void restartScanningAfterDelay() {
Log.d(TAG, "Restarting scanning after delay...");
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isFinishing()) {
showModalAndStartScanning();
startCardScanningFlow();
}
}, 2000);
}
@ -1041,11 +928,9 @@ public class CreateTransactionActivity extends AppCompatActivity implements
try {
Log.d(TAG, "Resetting EMV process...");
// Cancel any existing operations
cardScannerManager.stopScanning();
Thread.sleep(500);
// Reset EMV process
emvManager.resetEMVProcess();
Thread.sleep(1500);
@ -1069,11 +954,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
// NEW: Generate reference ID for transaction tracking
private String generateReferenceId() {
return "ref-" + System.currentTimeMillis() + "-" + (int)(Math.random() * 10000);
}
private String maskCardNumber(String cardNumber) {
if (cardNumber == null || cardNumber.length() < 8) {
return cardNumber;

View File

@ -16,17 +16,15 @@ import java.net.URI;
import java.net.URL;
/**
* MidtransCardPaymentManager - Enhanced Version for EMV Card Processing
* MidtransCardPaymentManager - Enhanced Version with Backend Integration
*
* Key Features:
* - Uses static CVV "493" for all transactions (both EMV and regular cards)
* - EMV-first approach with tokenization fallback
* - Supports custom order_id from backend transaction_uuid
* - Handles Midtrans API requirements where CVV is mandatory even for EMV
* - Comprehensive error handling and retry logic
* - Enhanced response processing with bank detection
*
* Note: Midtrans sandbox environment requires CVV even for EMV chip transactions
* during tokenization, so we use static CVV "493" as per curl example.
*/
public class MidtransCardPaymentManager {
private static final String TAG = "MidtransCardPayment";
@ -46,9 +44,10 @@ public class MidtransCardPaymentManager {
private int retryCount = 0;
private static final int MAX_RETRY = 2;
// ENHANCED: Store last response for debugging
// ENHANCED: Store last response and custom order ID
private JSONObject lastResponse;
private String lastErrorMessage;
private String customOrderId; // NEW: For backend transaction_uuid
public interface MidtransCardPaymentCallback {
void onTokenizeSuccess(String cardToken);
@ -73,6 +72,30 @@ public class MidtransCardPaymentManager {
return lastErrorMessage;
}
/**
* NEW: Process EMV card payment with custom order ID (transaction_uuid from backend)
*/
public void processEMVCardPaymentWithOrderId(CardData cardData, long amount, String orderId, String emvData) {
this.customOrderId = orderId; // Store custom order ID
Log.d(TAG, "=== PROCESSING EMV PAYMENT WITH CUSTOM ORDER ID ===");
Log.d(TAG, "Custom Order ID (Transaction UUID): " + orderId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Card PAN: " + maskCardNumber(cardData.getPan()));
Log.d(TAG, "Payment Mode: EMV with Backend Integration");
Log.d(TAG, "================================================");
// Reset retry counter
retryCount = 0;
if (callback != null) {
callback.onPaymentProgress("Processing EMV payment with backend UUID...");
}
// Use direct charge with custom order ID
new EMVDirectChargeWithOrderIdTask(cardData, amount, orderId, emvData).execute();
}
/**
* Process EMV card payment - handles EMV-specific requirements
*/
@ -84,8 +107,9 @@ public class MidtransCardPaymentManager {
return;
}
// Reset retry counter
// Reset retry counter and custom order ID
retryCount = 0;
customOrderId = null;
Log.d(TAG, "=== STARTING EMV MIDTRANS PAYMENT ===");
Log.d(TAG, "Reference ID: " + referenceId);
@ -114,6 +138,7 @@ public class MidtransCardPaymentManager {
}
retryCount = 0;
customOrderId = null;
Log.d(TAG, "=== STARTING REGULAR CARD PAYMENT ===");
Log.d(TAG, "Using tokenization flow");
@ -126,6 +151,498 @@ public class MidtransCardPaymentManager {
new TokenizeCardTask(cardData, amount, referenceId).execute();
}
/**
* NEW: EMV Direct Charge with Custom Order ID - uses backend transaction_uuid
*/
private class EMVDirectChargeWithOrderIdTask extends AsyncTask<Void, Void, Boolean> {
private CardData cardData;
private long amount;
private String orderId;
private String emvData;
private String errorMessage;
private JSONObject chargeResponse;
public EMVDirectChargeWithOrderIdTask(CardData cardData, long amount, String orderId, String emvData) {
this.cardData = cardData;
this.amount = amount;
this.orderId = orderId;
this.emvData = emvData;
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
// Build EMV-specific charge payload with custom order ID
JSONObject payload = new JSONObject();
payload.put("payment_type", "credit_card");
// Transaction details with custom order ID
JSONObject transactionDetails = new JSONObject();
transactionDetails.put("order_id", orderId); // Use backend transaction_uuid
transactionDetails.put("gross_amount", amount);
payload.put("transaction_details", transactionDetails);
// EMV Credit card data (no tokenization)
JSONObject creditCard = new JSONObject();
creditCard.put("card_number", cardData.getPan());
creditCard.put("card_exp_month", cardData.getExpiryMonth());
creditCard.put("card_exp_year", cardData.getExpiryYear());
// Include static CVV even for EMV (Midtrans may require it)
creditCard.put("card_cvv", STATIC_CVV);
Log.d(TAG, "EMV Transaction: Including static CVV (" + STATIC_CVV + ") for Midtrans compatibility");
// Add EMV data if available
if (emvData != null && !emvData.isEmpty()) {
creditCard.put("emv_data", emvData);
creditCard.put("authentication_mode", "chip");
}
payload.put("credit_card", creditCard);
// Item details
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
item.put("id", "emv_backend1");
item.put("price", amount);
item.put("quantity", 1);
item.put("name", "EMV Backend Transaction");
item.put("brand", "EMV Backend Payment");
item.put("category", "Backend Transaction");
item.put("merchant_name", "EDC-Store-Backend");
itemDetails.put(item);
payload.put("item_details", itemDetails);
// Customer details (same as curl example)
addCustomerDetails(payload);
Log.d(TAG, "=== EMV BACKEND DIRECT CHARGE ===");
Log.d(TAG, "Order ID (Backend UUID): " + orderId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Card: " + maskCardNumber(cardData.getPan()));
Log.d(TAG, "Mode: EMV Backend Direct (No Token)");
Log.d(TAG, "=================================");
// Make charge request
return makeChargeRequest(payload);
} catch (Exception e) {
Log.e(TAG, "EMV Backend Direct Charge exception: " + e.getMessage(), e);
errorMessage = "EMV backend payment error: " + e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success && chargeResponse != null && callback != null) {
Log.d(TAG, "✅ EMV Backend charge successful!");
callback.onChargeSuccess(chargeResponse);
} else if (callback != null) {
// Fallback to tokenization if direct charge fails
Log.w(TAG, "EMV backend direct charge failed, trying tokenization fallback...");
callback.onPaymentProgress("Retrying with tokenization...");
new TokenizeCardWithOrderIdTask(cardData, amount, orderId).execute();
}
}
private Boolean makeChargeRequest(JSONObject payload) {
try {
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_SERVER_AUTH);
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d(TAG, "EMV Backend Charge response code: " + responseCode);
BufferedReader br;
StringBuilder response = new StringBuilder();
String responseLine;
if (responseCode == 200 || responseCode == 201) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
}
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
String responseString = response.toString();
Log.d(TAG, "EMV Backend Charge response: " + responseString);
// Store response for debugging
try {
chargeResponse = new JSONObject(responseString);
lastResponse = chargeResponse;
// Enhanced: Add bank detection if missing
enhanceResponseWithBankInfo(chargeResponse);
} catch (JSONException e) {
Log.e(TAG, "Error parsing response JSON: " + e.getMessage());
chargeResponse = createFallbackResponse(responseString, responseCode);
lastResponse = chargeResponse;
}
return processChargeResponse(chargeResponse, responseCode);
} catch (Exception e) {
Log.e(TAG, "EMV Backend Charge request exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
lastErrorMessage = errorMessage;
return false;
}
}
private Boolean processChargeResponse(JSONObject response, int httpCode) {
try {
String statusCode = response.optString("status_code", "");
String statusMessage = response.optString("status_message", "");
String transactionStatus = response.optString("transaction_status", "");
String fraudStatus = response.optString("fraud_status", "");
Log.d(TAG, "=== BACKEND CHARGE RESPONSE ANALYSIS ===");
Log.d(TAG, "HTTP Code: " + httpCode);
Log.d(TAG, "Status Code: " + statusCode);
Log.d(TAG, "Status Message: " + statusMessage);
Log.d(TAG, "Transaction Status: " + transactionStatus);
Log.d(TAG, "Fraud Status: " + fraudStatus);
Log.d(TAG, "Order ID: " + response.optString("order_id", ""));
Log.d(TAG, "========================================");
// Handle specific error cases
if ("411".equals(statusCode)) {
errorMessage = "Token expired: " + statusMessage;
return false;
} else if ("400".equals(statusCode)) {
errorMessage = "Bad request: " + statusMessage;
return false;
} else if ("202".equals(statusCode) && "deny".equals(transactionStatus)) {
errorMessage = "Transaction denied: " + statusMessage;
return false;
} else if (httpCode != 200 && httpCode != 201) {
errorMessage = "HTTP error: " + httpCode + " - " + statusMessage;
return false;
}
// Success conditions
if ("200".equals(statusCode) &&
("capture".equals(transactionStatus) ||
"settlement".equals(transactionStatus) ||
"pending".equals(transactionStatus))) {
return true;
} else if ("201".equals(statusCode)) {
return true;
} else {
errorMessage = "Transaction failed: " + statusMessage;
return false;
}
} catch (Exception e) {
Log.e(TAG, "Error processing backend charge response: " + e.getMessage(), e);
errorMessage = "Response processing error: " + e.getMessage();
return false;
}
}
}
/**
* NEW: Tokenize Card with Custom Order ID - fallback method
*/
private class TokenizeCardWithOrderIdTask extends AsyncTask<Void, Void, String> {
private CardData cardData;
private long amount;
private String orderId;
private String errorMessage;
public TokenizeCardWithOrderIdTask(CardData cardData, long amount, String orderId) {
this.cardData = cardData;
this.amount = amount;
this.orderId = orderId;
}
@Override
protected String doInBackground(Void... voids) {
try {
StringBuilder urlBuilder = new StringBuilder(MIDTRANS_TOKEN_URL);
urlBuilder.append("?card_number=").append(cardData.getPan());
urlBuilder.append("&card_exp_month=").append(cardData.getExpiryMonth());
urlBuilder.append("&card_exp_year=").append(cardData.getExpiryYear());
// Always include CVV for tokenization
String cvvToUse = determineCVV(cardData);
urlBuilder.append("&card_cvv=").append(cvvToUse);
Log.d(TAG, "Using CVV " + cvvToUse + " for backend tokenization");
urlBuilder.append("&client_key=").append(MIDTRANS_CLIENT_KEY);
Log.d(TAG, "Backend Tokenization URL: " + maskUrl(urlBuilder.toString()));
URL url = new URI(urlBuilder.toString()).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
int responseCode = conn.getResponseCode();
Log.d(TAG, "Backend Tokenization response code: " + responseCode);
if (responseCode == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
Log.d(TAG, "Backend Tokenization success response: " + response.toString());
JSONObject jsonResponse = new JSONObject(response.toString());
if (jsonResponse.has("token_id")) {
return jsonResponse.getString("token_id");
} else {
errorMessage = "Token ID not found in backend response";
return null;
}
} else {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
StringBuilder errorResponse = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
errorResponse.append(responseLine.trim());
}
Log.e(TAG, "Backend Tokenization error: " + errorResponse.toString());
errorMessage = "Backend tokenization failed: " + errorResponse.toString();
return null;
}
} catch (Exception e) {
Log.e(TAG, "Backend Tokenization exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
return null;
}
}
@Override
protected void onPostExecute(String cardToken) {
if (cardToken != null && callback != null) {
callback.onTokenizeSuccess(cardToken);
if (callback != null) {
callback.onPaymentProgress("Processing backend payment...");
}
new ChargeCardWithOrderIdTask(cardToken, amount, orderId, cardData).execute();
} else if (callback != null) {
callback.onTokenizeError(errorMessage != null ? errorMessage : "Unknown backend tokenization error");
}
}
}
/**
* NEW: Charge Card with Custom Order ID
*/
private class ChargeCardWithOrderIdTask extends AsyncTask<Void, Void, Boolean> {
private String cardToken;
private long amount;
private String orderId;
private String errorMessage;
private JSONObject chargeResponse;
private CardData cardData;
public ChargeCardWithOrderIdTask(String cardToken, long amount, String orderId, CardData cardData) {
this.cardToken = cardToken;
this.amount = amount;
this.orderId = orderId;
this.cardData = cardData;
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
JSONObject payload = new JSONObject();
payload.put("payment_type", "credit_card");
JSONObject transactionDetails = new JSONObject();
transactionDetails.put("order_id", orderId); // Use backend transaction_uuid
transactionDetails.put("gross_amount", amount);
payload.put("transaction_details", transactionDetails);
JSONObject creditCard = new JSONObject();
creditCard.put("token_id", cardToken);
payload.put("credit_card", creditCard);
// Item details
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
item.put("id", "tkn_backend1");
item.put("price", amount);
item.put("quantity", 1);
item.put("name", "Token Backend Transaction");
item.put("brand", "Token Backend Payment");
item.put("category", "Backend Transaction");
item.put("merchant_name", "EDC-Store-Backend");
itemDetails.put(item);
payload.put("item_details", itemDetails);
addCustomerDetails(payload);
Log.d(TAG, "=== TOKEN BACKEND CHARGE ===");
Log.d(TAG, "Order ID (Backend UUID): " + orderId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Token: " + maskToken(cardToken));
Log.d(TAG, "============================");
return makeChargeRequest(payload);
} catch (Exception e) {
Log.e(TAG, "Token backend charge exception: " + e.getMessage(), e);
errorMessage = "Token backend charge error: " + e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success && chargeResponse != null && callback != null) {
Log.d(TAG, "✅ Token backend charge successful!");
callback.onChargeSuccess(chargeResponse);
} else if (callback != null) {
// Check for retry scenarios
if (shouldRetry(errorMessage) && retryCount < MAX_RETRY) {
retryCount++;
Log.w(TAG, "Retrying backend charge... (attempt " + retryCount + "/" + MAX_RETRY + ")");
callback.onPaymentProgress("Retrying backend... (" + retryCount + "/" + MAX_RETRY + ")");
new TokenizeCardWithOrderIdTask(cardData, amount, orderId).execute();
} else {
if (retryCount >= MAX_RETRY) {
errorMessage = "Max retry attempts reached. " + errorMessage;
}
callback.onChargeError(errorMessage != null ? errorMessage : "Unknown backend charge error");
}
}
}
private Boolean makeChargeRequest(JSONObject payload) {
try {
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_SERVER_AUTH);
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d(TAG, "Token Backend Charge response code: " + responseCode);
BufferedReader br;
StringBuilder response = new StringBuilder();
String responseLine;
if (responseCode == 200 || responseCode == 201) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
}
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
String responseString = response.toString();
Log.d(TAG, "Token Backend Charge response: " + responseString);
// Store response for debugging
try {
chargeResponse = new JSONObject(responseString);
lastResponse = chargeResponse;
// Enhanced: Add bank detection if missing
enhanceResponseWithBankInfo(chargeResponse);
} catch (JSONException e) {
Log.e(TAG, "Error parsing response JSON: " + e.getMessage());
chargeResponse = createFallbackResponse(responseString, responseCode);
lastResponse = chargeResponse;
}
return processChargeResponse(chargeResponse, responseCode);
} catch (Exception e) {
Log.e(TAG, "Token backend charge request exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
lastErrorMessage = errorMessage;
return false;
}
}
private Boolean processChargeResponse(JSONObject response, int httpCode) {
try {
String statusCode = response.optString("status_code", "");
String statusMessage = response.optString("status_message", "");
String transactionStatus = response.optString("transaction_status", "");
Log.d(TAG, "Token Backend Charge Response - Status: " + statusCode + ", Message: " + statusMessage);
if ("411".equals(statusCode)) {
errorMessage = "Token expired: " + statusMessage;
return false;
} else if ("400".equals(statusCode)) {
errorMessage = "Bad request: " + statusMessage;
return false;
} else if ("202".equals(statusCode) && "deny".equals(transactionStatus)) {
errorMessage = "Transaction denied: " + statusMessage;
return false;
} else if (httpCode != 200 && httpCode != 201) {
errorMessage = "HTTP error: " + httpCode + " - " + statusMessage;
return false;
}
if ("200".equals(statusCode) &&
("capture".equals(transactionStatus) ||
"settlement".equals(transactionStatus) ||
"pending".equals(transactionStatus))) {
return true;
} else if ("201".equals(statusCode)) {
return true;
} else {
errorMessage = "Transaction failed: " + statusMessage;
return false;
}
} catch (Exception e) {
Log.e(TAG, "Error processing token backend charge response: " + e.getMessage(), e);
errorMessage = "Response processing error: " + e.getMessage();
return false;
}
}
}
/**
* EMV Direct Charge - bypasses tokenization for EMV cards
*/
@ -259,17 +776,16 @@ public class MidtransCardPaymentManager {
String responseString = response.toString();
Log.d(TAG, "EMV Charge response: " + responseString);
// ENHANCED: Store response for debugging
// Store response for debugging
try {
chargeResponse = new JSONObject(responseString);
lastResponse = chargeResponse; // Store for later access
lastResponse = chargeResponse;
// ENHANCED: Add bank detection if missing
// Enhanced: Add bank detection if missing
enhanceResponseWithBankInfo(chargeResponse);
} catch (JSONException e) {
Log.e(TAG, "Error parsing response JSON: " + e.getMessage());
// Create fallback response object
chargeResponse = createFallbackResponse(responseString, responseCode);
lastResponse = chargeResponse;
}
@ -554,17 +1070,16 @@ public class MidtransCardPaymentManager {
String responseString = response.toString();
Log.d(TAG, "Token Charge response: " + responseString);
// ENHANCED: Store response for debugging
// Store response for debugging
try {
chargeResponse = new JSONObject(responseString);
lastResponse = chargeResponse; // Store for later access
lastResponse = chargeResponse;
// ENHANCED: Add bank detection if missing
// Enhanced: Add bank detection if missing
enhanceResponseWithBankInfo(chargeResponse);
} catch (JSONException e) {
Log.e(TAG, "Error parsing response JSON: " + e.getMessage());
// Create fallback response object
chargeResponse = createFallbackResponse(responseString, responseCode);
lastResponse = chargeResponse;
}
@ -621,7 +1136,7 @@ public class MidtransCardPaymentManager {
}
}
// ENHANCED: Method to enhance response with bank information
// Method to enhance response with bank information
private void enhanceResponseWithBankInfo(JSONObject response) {
if (response == null) return;
@ -648,7 +1163,7 @@ public class MidtransCardPaymentManager {
}
}
// ENHANCED: Method to detect bank from various response fields
// Method to detect bank from various response fields
private String detectBankFromResponse(JSONObject response) {
try {
// Try various fields that might contain bank information
@ -680,7 +1195,7 @@ public class MidtransCardPaymentManager {
}
}
// ENHANCED: Map various bank identifiers to standard bank names
// Map various bank identifiers to standard bank names
private String mapToBankName(String identifier) {
if (identifier == null || identifier.trim().isEmpty()) {
return null;
@ -710,7 +1225,7 @@ public class MidtransCardPaymentManager {
return null; // No mapping found
}
// ENHANCED: Create fallback response when JSON parsing fails
// Create fallback response when JSON parsing fails
private JSONObject createFallbackResponse(String rawResponse, int httpCode) {
try {
JSONObject fallback = new JSONObject();
@ -789,7 +1304,7 @@ public class MidtransCardPaymentManager {
error.contains("timeout");
}
// ENHANCED: Debug method to inspect response
// Debug method to inspect response
public void debugResponse() {
if (lastResponse != null) {
Log.d(TAG, "=== DEBUGGING LAST RESPONSE ===");

View File

@ -0,0 +1,358 @@
package com.example.bdkipoc.transaction.managers;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.UUID;
/**
* PostTransactionBackendManager - Handles backend transaction posting
*
* This manager handles the communication with the backend service for transaction posting
* and provides the transaction_uuid needed for Midtrans integration.
*/
public class PostTransactionBackendManager {
private static final String TAG = "PostTransactionBackend";
// Backend Configuration
private static final String BACKEND_BASE_URL = "https://be-edc.msvc.app";
private static final String TRANSACTIONS_ENDPOINT = BACKEND_BASE_URL + "/transactions";
// Default values
private static final String DEFAULT_DEVICE_CODE = "PB4K252T00021";
private static final int DEFAULT_DEVICE_ID = 1;
private static final String DEFAULT_CASHFLOW = "MONEY_IN";
private static final String DEFAULT_CHANNEL_CATEGORY = "RETAIL_OUTLET";
// NEW: Static merchant data
private static final String DEFAULT_MERCHANT_NAME = "BUDIAJAIB123";
private static final String DEFAULT_MID = "542531513";
private static final String DEFAULT_TID = "535151521";
private Context context;
private PostTransactionCallback callback;
public interface PostTransactionCallback {
void onPostTransactionSuccess(JSONObject response, String transactionUuid);
void onPostTransactionError(String errorMessage);
void onPostTransactionProgress(String message);
}
public PostTransactionBackendManager(Context context, PostTransactionCallback callback) {
this.context = context;
this.callback = callback;
}
/**
* Post transaction to backend service
*/
public void postTransaction(String paymentType, String referenceId, long amount, String status) {
String channelCode = mapPaymentTypeToChannelCode(paymentType);
String transactionUuid = generateUUID();
Log.d(TAG, "=== POSTING TRANSACTION TO BACKEND ===");
Log.d(TAG, "Payment Type: " + paymentType);
Log.d(TAG, "Channel Code: " + channelCode);
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Status: " + status);
Log.d(TAG, "Transaction UUID: " + transactionUuid);
Log.d(TAG, "=====================================");
if (callback != null) {
callback.onPostTransactionProgress("Posting transaction to backend...");
}
new PostTransactionTask(paymentType, channelCode, referenceId, amount, status, transactionUuid).execute();
}
/**
* Post transaction with INIT status (for pre-authorization)
*/
public void postInitTransaction(String paymentType, String referenceId, long amount) {
postTransaction(paymentType, referenceId, amount, "INIT");
}
/**
* Post transaction with SUCCESS status (for completed transactions)
*/
public void postSuccessTransaction(String paymentType, String referenceId, long amount) {
postTransaction(paymentType, referenceId, amount, "SUCCESS");
}
/**
* Update existing transaction status
*/
public void updateTransactionStatus(String transactionUuid, String newStatus) {
Log.d(TAG, "Updating transaction " + transactionUuid + " to status: " + newStatus);
// TODO: Implement update endpoint if available
// For now, we'll create a new transaction with updated status
}
/**
* Map payment type to channel code for backend
*/
private String mapPaymentTypeToChannelCode(String paymentType) {
if (paymentType == null) {
return "CREDIT_CARD"; // Default
}
switch (paymentType.toLowerCase()) {
case "credit_card":
return "CREDIT_CARD";
case "debit_card":
return "DEBIT_CARD";
case "e_money":
return "E_MONEY";
case "qris":
return "QRIS";
default:
Log.w(TAG, "Unknown payment type: " + paymentType + ", using CREDIT_CARD");
return "CREDIT_CARD";
}
}
/**
* Generate UUID v4 for transaction
*/
private String generateUUID() {
return UUID.randomUUID().toString();
}
/**
* Get device serial number (Sunmi device code)
*/
private String getDeviceCode() {
try {
// Try to get actual device serial number
// For Sunmi devices, this might be available through system properties
String serialNumber = android.os.Build.SERIAL;
if (serialNumber != null && !serialNumber.equals("unknown") && !serialNumber.equals(android.os.Build.UNKNOWN)) {
return serialNumber;
}
} catch (Exception e) {
Log.w(TAG, "Could not get device serial number: " + e.getMessage());
}
// Fallback to default device code
return DEFAULT_DEVICE_CODE;
}
/**
* AsyncTask for posting transaction to backend
*/
private class PostTransactionTask extends AsyncTask<Void, Void, Boolean> {
private String paymentType;
private String channelCode;
private String referenceId;
private long amount;
private String status;
private String transactionUuid;
private String errorMessage;
private JSONObject responseData;
public PostTransactionTask(String paymentType, String channelCode, String referenceId,
long amount, String status, String transactionUuid) {
this.paymentType = paymentType;
this.channelCode = channelCode;
this.referenceId = referenceId;
this.amount = amount;
this.status = status;
this.transactionUuid = transactionUuid;
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
// Build transaction payload
JSONObject payload = buildTransactionPayload();
Log.d(TAG, "Backend payload: " + payload.toString());
// Make HTTP request
return makeBackendRequest(payload);
} catch (Exception e) {
Log.e(TAG, "Backend transaction exception: " + e.getMessage(), e);
errorMessage = "Backend transaction error: " + e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success && responseData != null && callback != null) {
try {
// Extract transaction_uuid from response
JSONObject data = responseData.optJSONObject("data");
String returnedUuid = null;
if (data != null) {
returnedUuid = data.optString("transaction_uuid", transactionUuid);
}
Log.d(TAG, "✅ Backend transaction successful!");
Log.d(TAG, "Original UUID: " + transactionUuid);
Log.d(TAG, "Returned UUID: " + returnedUuid);
callback.onPostTransactionSuccess(responseData, returnedUuid != null ? returnedUuid : transactionUuid);
} catch (Exception e) {
Log.e(TAG, "Error processing backend response: " + e.getMessage());
if (callback != null) {
callback.onPostTransactionError("Error processing backend response: " + e.getMessage());
}
}
} else if (callback != null) {
callback.onPostTransactionError(errorMessage != null ? errorMessage : "Unknown backend error");
}
}
private JSONObject buildTransactionPayload() throws JSONException {
JSONObject payload = new JSONObject();
// Required fields
payload.put("type", "PAYMENT");
payload.put("channel_category", DEFAULT_CHANNEL_CATEGORY);
payload.put("channel_code", channelCode);
payload.put("reference_id", referenceId);
payload.put("amount", amount);
payload.put("cashflow", DEFAULT_CASHFLOW);
payload.put("status", status);
payload.put("device_id", DEFAULT_DEVICE_ID);
payload.put("transaction_uuid", transactionUuid);
payload.put("transaction_time_seconds", 2.2); // Default value as mentioned
payload.put("device_code", getDeviceCode());
// NEW: Static merchant data (no longer null)
payload.put("merchant_name", DEFAULT_MERCHANT_NAME);
payload.put("mid", DEFAULT_MID);
payload.put("tid", DEFAULT_TID);
return payload;
}
private Boolean makeBackendRequest(JSONObject payload) {
try {
URL url = new URI(TRANSACTIONS_ENDPOINT).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set request properties
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
// Send payload
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
// Get response
int responseCode = conn.getResponseCode();
Log.d(TAG, "Backend response code: " + responseCode);
BufferedReader br;
StringBuilder response = new StringBuilder();
String responseLine;
if (responseCode >= 200 && responseCode < 300) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
}
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
String responseString = response.toString();
Log.d(TAG, "Backend response: " + responseString);
// Parse response
try {
responseData = new JSONObject(responseString);
// Check if response indicates success
int status = responseData.optInt("status", 0);
String message = responseData.optString("message", "");
if (status == 200 && "Successfully".equals(message)) {
return true;
} else {
errorMessage = "Backend error: " + message + " (Status: " + status + ")";
return false;
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing backend response: " + e.getMessage());
errorMessage = "Invalid backend response format";
return false;
}
} catch (Exception e) {
Log.e(TAG, "Backend request exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
return false;
}
}
}
/**
* Utility method to generate reference ID
*/
public static String generateReferenceId() {
return "ref" + System.currentTimeMillis() + (int)(Math.random() * 10000);
}
/**
* Utility method to map card menu ID to payment type
*/
public static String mapCardMenuToPaymentType(int cardMenuId) {
// Based on MainActivity.java card IDs
switch (cardMenuId) {
case 2131296346: // R.id.card_kartu_kredit
return "credit_card";
case 2131296344: // R.id.card_kartu_debit
return "debit_card";
case 2131296360: // R.id.card_uang_elektronik
return "e_money";
case 2131296352: // R.id.card_qris
return "qris";
default:
Log.w(TAG, "Unknown card menu ID: " + cardMenuId + ", defaulting to credit_card");
return "credit_card";
}
}
/**
* Debug method to log transaction details
*/
public void debugTransactionData(String paymentType, String referenceId, long amount, String status) {
Log.d(TAG, "=== TRANSACTION DEBUG INFO ===");
Log.d(TAG, "Payment Type: " + paymentType);
Log.d(TAG, "Channel Code: " + mapPaymentTypeToChannelCode(paymentType));
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Status: " + status);
Log.d(TAG, "Device Code: " + getDeviceCode());
Log.d(TAG, "Device ID: " + DEFAULT_DEVICE_ID);
Log.d(TAG, "Merchant Name: " + DEFAULT_MERCHANT_NAME);
Log.d(TAG, "MID: " + DEFAULT_MID);
Log.d(TAG, "TID: " + DEFAULT_TID);
Log.d(TAG, "==============================");
}
}