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