1463 lines
66 KiB
Java
1463 lines
66 KiB
Java
package com.example.bdkipoc;
|
|
|
|
import android.content.Intent;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.RemoteException;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.view.Window;
|
|
import android.view.WindowManager;
|
|
import android.widget.Button;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.cardview.widget.CardView;
|
|
|
|
import com.sunmi.peripheral.printer.InnerResultCallback;
|
|
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.Locale;
|
|
import java.io.BufferedReader;
|
|
import java.io.InputStreamReader;
|
|
import java.net.HttpURLConnection;
|
|
import java.io.OutputStream;
|
|
import java.net.URL;
|
|
import java.net.URI;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import com.example.bdkipoc.cetakulang.ReprintActivity;
|
|
|
|
public class ReceiptActivity extends AppCompatActivity {
|
|
private static final Map<String, String> ISSUER_DISPLAY_MAP = new HashMap<String, String>() {{
|
|
put("airpay shopee", "ShopeePay");
|
|
put("shopeepay", "ShopeePay");
|
|
put("shopee", "ShopeePay");
|
|
put("linkaja", "LinkAja");
|
|
put("link aja", "LinkAja");
|
|
put("dana", "DANA");
|
|
put("ovo", "OVO");
|
|
put("gopay", "GoPay");
|
|
put("jenius", "Jenius");
|
|
put("sakuku", "Sakuku");
|
|
put("bni", "BNI");
|
|
put("bca", "BCA");
|
|
put("mandiri", "Mandiri");
|
|
put("bri", "BRI");
|
|
put("cimb", "CIMB Niaga");
|
|
put("permata", "Permata");
|
|
put("maybank", "Maybank");
|
|
put("qris", "QRIS");
|
|
}};
|
|
|
|
// Views
|
|
private LinearLayout backNavigation;
|
|
private ImageView backArrow;
|
|
private TextView toolbarTitle;
|
|
private CardView receiptCard;
|
|
|
|
// Receipt details
|
|
private TextView merchantName;
|
|
private TextView merchantLocation;
|
|
private TextView midText;
|
|
private TextView tidText;
|
|
private TextView transactionNumber;
|
|
private TextView transactionDate;
|
|
private TextView paymentMethod;
|
|
private TextView cardType;
|
|
private TextView transactionTotal;
|
|
private TextView taxPercentage;
|
|
private TextView serviceFee;
|
|
private TextView finalTotal;
|
|
|
|
// Action buttons
|
|
private LinearLayout printButton;
|
|
private LinearLayout emailButton;
|
|
private Button finishButton;
|
|
|
|
// Printer callback
|
|
private final InnerResultCallback printCallback = new InnerResultCallback() {
|
|
@Override
|
|
public void onRunResult(boolean isSuccess) throws RemoteException {
|
|
runOnUiThread(() -> {
|
|
if (isSuccess) {
|
|
showToast("Struk berhasil dicetak");
|
|
} else {
|
|
showToast("Gagal mencetak struk");
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onReturnString(String result) throws RemoteException {
|
|
Log.d("ReceiptActivity", "Print result: " + result);
|
|
}
|
|
|
|
@Override
|
|
public void onRaiseException(int code, String msg) throws RemoteException {
|
|
runOnUiThread(() -> showToast("Printer error: " + msg));
|
|
Log.e("ReceiptActivity", "Printer exception: " + code + " - " + msg);
|
|
}
|
|
|
|
@Override
|
|
public void onPrintResult(int code, String msg) throws RemoteException {
|
|
Log.d("ReceiptActivity", "Print result code: " + code + ", msg: " + msg);
|
|
}
|
|
};
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
// Set status bar color
|
|
setStatusBarColor();
|
|
|
|
setContentView(R.layout.activity_receipt);
|
|
|
|
initializeViews();
|
|
setupClickListeners();
|
|
loadTransactionData();
|
|
}
|
|
|
|
private void initializeViews() {
|
|
// Navigation
|
|
backNavigation = findViewById(R.id.back_navigation);
|
|
backArrow = findViewById(R.id.backArrow);
|
|
toolbarTitle = findViewById(R.id.toolbarTitle);
|
|
|
|
// Receipt card view that will be printed
|
|
receiptCard = findViewById(R.id.receipt_card);
|
|
|
|
// Receipt details
|
|
merchantName = findViewById(R.id.merchant_name);
|
|
merchantLocation = findViewById(R.id.merchant_location);
|
|
midText = findViewById(R.id.mid_text);
|
|
tidText = findViewById(R.id.tid_text);
|
|
transactionNumber = findViewById(R.id.transaction_number);
|
|
transactionDate = findViewById(R.id.transaction_date);
|
|
paymentMethod = findViewById(R.id.payment_method);
|
|
cardType = findViewById(R.id.card_type);
|
|
transactionTotal = findViewById(R.id.transaction_total);
|
|
taxPercentage = findViewById(R.id.tax_percentage);
|
|
serviceFee = findViewById(R.id.service_fee);
|
|
finalTotal = findViewById(R.id.final_total);
|
|
|
|
// Action buttons
|
|
printButton = findViewById(R.id.print_button);
|
|
emailButton = findViewById(R.id.email_button);
|
|
finishButton = findViewById(R.id.finish_button);
|
|
}
|
|
|
|
private void handlePrintReceipt() {
|
|
try {
|
|
// Check if printer service is available
|
|
if (MyApplication.app.sunmiPrinterService == null) {
|
|
showToast("Printer tidak tersedia");
|
|
return;
|
|
}
|
|
|
|
// Get all the data from the views
|
|
String merchantNameText = merchantName.getText().toString();
|
|
String merchantLocationText = merchantLocation.getText().toString();
|
|
String midTextValue = midText.getText().toString();
|
|
String tidTextValue = tidText.getText().toString();
|
|
String transactionNumberText = transactionNumber.getText().toString();
|
|
String transactionDateText = transactionDate.getText().toString();
|
|
String paymentMethodText = paymentMethod.getText().toString();
|
|
String cardTypeText = cardType.getText().toString();
|
|
String transactionTotalText = transactionTotal.getText().toString();
|
|
String taxPercentageText = taxPercentage.getText().toString();
|
|
String serviceFeeText = serviceFee.getText().toString();
|
|
String finalTotalText = finalTotal.getText().toString();
|
|
|
|
showToast("Mencetak struk...");
|
|
|
|
try {
|
|
// Start printing
|
|
MyApplication.app.sunmiPrinterService.enterPrinterBuffer(true);
|
|
|
|
// Set alignment to center
|
|
MyApplication.app.sunmiPrinterService.setAlignment(1, null);
|
|
|
|
// Print header
|
|
MyApplication.app.sunmiPrinterService.printText("# Payvora PRO\n\n", null);
|
|
|
|
// Set alignment to left
|
|
MyApplication.app.sunmiPrinterService.setAlignment(0, null);
|
|
|
|
// Print merchant info
|
|
MyApplication.app.sunmiPrinterService.printText(merchantNameText + "\n", null);
|
|
MyApplication.app.sunmiPrinterService.printText(merchantLocationText + "\n\n", null);
|
|
|
|
// Print MID/TID
|
|
MyApplication.app.sunmiPrinterService.printText(midTextValue + " | " + tidTextValue + "\n\n", null);
|
|
|
|
// Print transaction details
|
|
MyApplication.app.sunmiPrinterService.printText("Nomor transaksi " + transactionNumberText + "\n", null);
|
|
MyApplication.app.sunmiPrinterService.printText("Tanggal transaksi " + transactionDateText + "\n", null);
|
|
MyApplication.app.sunmiPrinterService.printText("Metode pembayaran " + paymentMethodText + "\n", null);
|
|
MyApplication.app.sunmiPrinterService.printText("Jenis Kartu " + cardTypeText + "\n\n", null);
|
|
|
|
// Print amounts
|
|
MyApplication.app.sunmiPrinterService.printText("Total transaksi " + transactionTotalText + "\n", null);
|
|
MyApplication.app.sunmiPrinterService.printText("Pajak (%) " + taxPercentageText + "\n", null);
|
|
MyApplication.app.sunmiPrinterService.printText("Biaya Layanan " + serviceFeeText + "\n\n", null);
|
|
|
|
// Print total in bold
|
|
MyApplication.app.sunmiPrinterService.printText("**TOTAL** **" + finalTotalText + "**\n", null);
|
|
|
|
// Add some line feeds
|
|
MyApplication.app.sunmiPrinterService.lineWrap(4, null);
|
|
|
|
// Exit buffer mode
|
|
MyApplication.app.sunmiPrinterService.exitPrinterBuffer(true);
|
|
|
|
} catch (RemoteException e) {
|
|
e.printStackTrace();
|
|
showToast("Error printer: " + e.getMessage());
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "Print error", e);
|
|
showToast("Error saat mencetak: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
private void setStatusBarColor() {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
Window window = getWindow();
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
|
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
|
|
|
|
// Make status bar icons white (for dark red background)
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
View decorView = window.getDecorView();
|
|
decorView.setSystemUiVisibility(0); // Clear light status bar flag
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Create Bitmap from View */
|
|
private Bitmap createBitmapFromView(View view) {
|
|
try {
|
|
// Enable drawing cache
|
|
view.setDrawingCacheEnabled(true);
|
|
view.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
|
|
|
|
// Measure and layout the view to ensure it has correct dimensions
|
|
view.measure(
|
|
View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
|
|
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
);
|
|
|
|
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
|
|
|
|
// Create bitmap
|
|
Bitmap bitmap = Bitmap.createBitmap(
|
|
view.getWidth(),
|
|
view.getHeight(),
|
|
Bitmap.Config.ARGB_8888
|
|
);
|
|
|
|
Canvas canvas = new Canvas(bitmap);
|
|
view.draw(canvas);
|
|
|
|
return bitmap;
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "Error creating bitmap", e);
|
|
return null;
|
|
} finally {
|
|
view.setDrawingCacheEnabled(false);
|
|
}
|
|
}
|
|
|
|
private void setupClickListeners() {
|
|
// Back navigation - Goes back to previous activity
|
|
backNavigation.setOnClickListener(v -> handleBackNavigation());
|
|
backArrow.setOnClickListener(v -> handleBackNavigation());
|
|
toolbarTitle.setOnClickListener(v -> handleBackNavigation());
|
|
|
|
// Action buttons
|
|
printButton.setOnClickListener(v -> handlePrintReceipt());
|
|
emailButton.setOnClickListener(v -> handleEmailReceipt());
|
|
finishButton.setOnClickListener(v -> handleFinish());
|
|
}
|
|
|
|
/**
|
|
* ✅ ENHANCED: Load transaction data with support for both EMV/Card and QRIS
|
|
*/
|
|
private void loadTransactionData() {
|
|
Intent intent = getIntent();
|
|
if (intent != null) {
|
|
Log.d("ReceiptActivity", "=== LOADING ENHANCED TRANSACTION DATA ===");
|
|
|
|
// ✅ DETECT TRANSACTION TYPE
|
|
String callingActivity = intent.getStringExtra("calling_activity");
|
|
String channelCategory = intent.getStringExtra("channel_category");
|
|
String channelCode = intent.getStringExtra("channel_code");
|
|
boolean isEmvTransaction = "EMV_CARD".equals(channelCategory) || "ResultTransactionActivity".equals(callingActivity);
|
|
boolean isQrisTransaction = "QRIS".equalsIgnoreCase(channelCode) || "QrisResultActivity".equals(callingActivity);
|
|
|
|
Log.d("ReceiptActivity", "🔍 TRANSACTION TYPE DETECTION:");
|
|
Log.d("ReceiptActivity", " Calling Activity: " + callingActivity);
|
|
Log.d("ReceiptActivity", " Channel Category: " + channelCategory);
|
|
Log.d("ReceiptActivity", " Channel Code: " + channelCode);
|
|
Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction);
|
|
Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction);
|
|
|
|
// Get all available data from intent
|
|
String amount = intent.getStringExtra("transaction_amount");
|
|
String grossAmount = intent.getStringExtra("gross_amount");
|
|
String merchantNameStr = intent.getStringExtra("merchant_name");
|
|
String merchantLocationStr = intent.getStringExtra("merchant_location");
|
|
String transactionId = intent.getStringExtra("transaction_id");
|
|
String referenceId = intent.getStringExtra("reference_id");
|
|
String orderId = intent.getStringExtra("order_id");
|
|
String transactionDateStr = intent.getStringExtra("transaction_date");
|
|
String createdAt = intent.getStringExtra("created_at");
|
|
String paymentMethodStr = intent.getStringExtra("payment_method");
|
|
String cardTypeStr = intent.getStringExtra("card_type");
|
|
String acquirer = intent.getStringExtra("acquirer");
|
|
String mid = intent.getStringExtra("mid");
|
|
String tid = intent.getStringExtra("tid");
|
|
|
|
// ✅ EMV SPECIFIC DATA
|
|
String emvCardholderName = intent.getStringExtra("emv_cardholder_name");
|
|
String emvAid = intent.getStringExtra("emv_aid");
|
|
String emvExpiry = intent.getStringExtra("emv_expiry");
|
|
String cardNumber = intent.getStringExtra("card_number");
|
|
String midtransResponse = intent.getStringExtra("midtrans_response");
|
|
boolean emvMode = intent.getBooleanExtra("emv_mode", false);
|
|
|
|
// ✅ QRIS SPECIFIC DATA
|
|
String qrString = intent.getStringExtra("qr_string");
|
|
|
|
// Log received data for debugging
|
|
Log.d("ReceiptActivity", "🔍 RECEIVED DATA:");
|
|
Log.d("ReceiptActivity", " amount: " + amount);
|
|
Log.d("ReceiptActivity", " referenceId: " + referenceId);
|
|
Log.d("ReceiptActivity", " orderId: " + orderId);
|
|
Log.d("ReceiptActivity", " acquirer (from intent): " + acquirer);
|
|
Log.d("ReceiptActivity", " createdAt: " + createdAt);
|
|
Log.d("ReceiptActivity", " EMV Mode: " + emvMode);
|
|
Log.d("ReceiptActivity", " EMV Cardholder: " + emvCardholderName);
|
|
Log.d("ReceiptActivity", " Card Number: " + (cardNumber != null ? cardNumber : "N/A"));
|
|
Log.d("ReceiptActivity", " QR String Available: " + (qrString != null && !qrString.isEmpty()));
|
|
|
|
// 1. Set merchant data with defaults
|
|
merchantName.setText(merchantNameStr != null ? merchantNameStr : "TOKO KLONTONG PAK EKO");
|
|
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Ciputat Baru, Tangsel");
|
|
|
|
// 2. Set MID and TID
|
|
midText.setText("MID: " + (mid != null ? mid : "123456789901"));
|
|
tidText.setText("TID: " + (tid != null ? tid : "123456789901"));
|
|
|
|
// 3. Set transaction number
|
|
String displayTransactionNumber = getDisplayTransactionNumber(referenceId, transactionId, orderId);
|
|
transactionNumber.setText(displayTransactionNumber);
|
|
|
|
// 4. Set transaction date
|
|
String displayDate = getDisplayTransactionDate(createdAt, transactionDateStr, isEmvTransaction);
|
|
transactionDate.setText(displayDate);
|
|
|
|
// 5. ✅ ENHANCED: Set payment method based on transaction type
|
|
String displayPaymentMethod = getDisplayPaymentMethod(channelCode, paymentMethodStr, isEmvTransaction, emvMode);
|
|
paymentMethod.setText(displayPaymentMethod);
|
|
|
|
// 6. ✅ ENHANCED: Set card type with EMV and QRIS priority
|
|
String displayCardType = getDisplayCardType(cardTypeStr, acquirer, channelCode, isEmvTransaction,
|
|
isQrisTransaction, midtransResponse, referenceId);
|
|
cardType.setText(displayCardType);
|
|
|
|
// 7. ✅ Format and set amounts with proper calculation
|
|
setAmountDataEnhanced(amount, grossAmount, isEmvTransaction, isQrisTransaction);
|
|
|
|
Log.d("ReceiptActivity", "💳 FINAL DISPLAY VALUES:");
|
|
Log.d("ReceiptActivity", " Payment Method: " + displayPaymentMethod);
|
|
Log.d("ReceiptActivity", " Card Type: " + displayCardType);
|
|
Log.d("ReceiptActivity", " Transaction Number: " + displayTransactionNumber);
|
|
Log.d("ReceiptActivity", "=== ENHANCED TRANSACTION DATA LOADED ===");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get display transaction number with priority
|
|
*/
|
|
private String getDisplayTransactionNumber(String referenceId, String transactionId, String orderId) {
|
|
if (referenceId != null && !referenceId.isEmpty()) {
|
|
return referenceId;
|
|
} else if (transactionId != null && !transactionId.isEmpty()) {
|
|
// For long transaction IDs, show last 10 characters
|
|
return transactionId.length() > 10 ?
|
|
transactionId.substring(transactionId.length() - 10) : transactionId;
|
|
} else if (orderId != null && !orderId.isEmpty()) {
|
|
return orderId.length() > 10 ?
|
|
orderId.substring(orderId.length() - 10) : orderId;
|
|
}
|
|
return String.valueOf(System.currentTimeMillis() % 10000000000L);
|
|
}
|
|
|
|
/**
|
|
* Get display transaction date with proper formatting
|
|
*/
|
|
private String getDisplayTransactionDate(String createdAt, String transactionDateStr, boolean isEmvTransaction) {
|
|
String dateToFormat = null;
|
|
|
|
if (createdAt != null && !createdAt.isEmpty()) {
|
|
dateToFormat = createdAt;
|
|
} else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
|
|
dateToFormat = transactionDateStr;
|
|
}
|
|
|
|
if (dateToFormat != null) {
|
|
if (isEmvTransaction) {
|
|
return formatDateForEmvTransaction(dateToFormat);
|
|
} else {
|
|
return formatDateFromCreatedAt(dateToFormat);
|
|
}
|
|
}
|
|
|
|
return getCurrentDateTime();
|
|
}
|
|
|
|
private String formatDateForEmvTransaction(String dateString) {
|
|
try {
|
|
// EMV transactions might use different date formats
|
|
String[] inputFormats = {
|
|
"dd MMMM yyyy HH:mm",
|
|
"yyyy-MM-dd HH:mm:ss",
|
|
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
|
|
"dd/MM/yyyy HH:mm"
|
|
};
|
|
|
|
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss", new Locale("id", "ID"));
|
|
|
|
for (String format : inputFormats) {
|
|
try {
|
|
SimpleDateFormat inputFormat = new SimpleDateFormat(format,
|
|
format.contains("MMMM") ? new Locale("id", "ID") : Locale.getDefault());
|
|
Date date = inputFormat.parse(dateString);
|
|
String formatted = outputFormat.format(date);
|
|
Log.d("ReceiptActivity", "EMV Date formatting: '" + dateString + "' -> '" + formatted + "'");
|
|
return formatted;
|
|
} catch (Exception ignored) {
|
|
// Try next format
|
|
}
|
|
}
|
|
|
|
// If all formats fail, return as-is
|
|
Log.w("ReceiptActivity", "Could not format EMV date: " + dateString);
|
|
return dateString;
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "Error formatting EMV date: " + dateString, e);
|
|
return dateString;
|
|
}
|
|
}
|
|
|
|
private String formatDateFromCreatedAt(String createdAt) {
|
|
try {
|
|
// Input format from database: "yyyy-MM-dd HH:mm:ss" atau "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
|
|
SimpleDateFormat inputFormat;
|
|
if (createdAt.contains("T")) {
|
|
inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
|
|
} else {
|
|
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
|
}
|
|
|
|
// Output format for receipt: "dd/MM/yy HH:mm:ss"
|
|
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yy HH:mm:ss", new Locale("id", "ID"));
|
|
|
|
Date date = inputFormat.parse(createdAt);
|
|
String formatted = outputFormat.format(date);
|
|
|
|
Log.d("ReceiptActivity", "Date formatting: '" + createdAt + "' -> '" + formatted + "'");
|
|
return formatted;
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "Error formatting date: " + createdAt, e);
|
|
// Fallback: return original string if parsing fails
|
|
return createdAt;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get display payment method with EMV support
|
|
*/
|
|
private String getDisplayPaymentMethod(String channelCode, String fallbackPaymentMethod,
|
|
boolean isEmvTransaction, boolean emvMode) {
|
|
|
|
// ✅ NEW: Check if this is from ReprintActivity
|
|
String callingActivity = getIntent().getStringExtra("calling_activity");
|
|
boolean isFromReprintActivity = "ReprintActivity".equals(callingActivity);
|
|
|
|
if (isFromReprintActivity) {
|
|
// For ReprintActivity, use the payment_method that was already processed
|
|
if (fallbackPaymentMethod != null && !fallbackPaymentMethod.isEmpty()) {
|
|
Log.d("ReceiptActivity", "✅ REPRINT: Using processed payment method: " + fallbackPaymentMethod);
|
|
return fallbackPaymentMethod;
|
|
}
|
|
}
|
|
|
|
// Continue with existing logic for other sources
|
|
if (isEmvTransaction) {
|
|
// For EMV transactions, be more specific
|
|
if (emvMode) {
|
|
if (channelCode != null) {
|
|
switch (channelCode.toUpperCase()) {
|
|
case "CREDIT": return "Kartu Kredit (EMV)";
|
|
case "DEBIT": return "Kartu Debit (EMV)";
|
|
default: return "Kartu Kredit (EMV)";
|
|
}
|
|
}
|
|
return "Kartu Kredit (EMV)";
|
|
} else {
|
|
// Magnetic stripe
|
|
if (channelCode != null) {
|
|
switch (channelCode.toUpperCase()) {
|
|
case "CREDIT": return "Kartu Kredit";
|
|
case "DEBIT": return "Kartu Debit";
|
|
default: return "Kartu Kredit";
|
|
}
|
|
}
|
|
return "Kartu Kredit";
|
|
}
|
|
} else {
|
|
// For QRIS and other transactions, use existing logic
|
|
return getPaymentMethodFromChannelCode(channelCode, fallbackPaymentMethod);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get payment method name from channel_code with comprehensive mapping
|
|
*/
|
|
private String getPaymentMethodFromChannelCode(String channelCode, String fallbackPaymentMethod) {
|
|
if (channelCode != null && !channelCode.isEmpty()) {
|
|
String code = channelCode.toUpperCase();
|
|
|
|
switch (code) {
|
|
case "QRIS":
|
|
return "QRIS";
|
|
case "DEBIT":
|
|
return "Kartu Debit";
|
|
case "CREDIT":
|
|
return "Kartu Kredit";
|
|
case "BCA":
|
|
return "BCA";
|
|
case "MANDIRI":
|
|
return "Bank Mandiri";
|
|
case "BNI":
|
|
return "Bank BNI";
|
|
case "BRI":
|
|
return "Bank BRI";
|
|
case "PERMATA":
|
|
return "Bank Permata";
|
|
case "CIMB":
|
|
return "CIMB Niaga";
|
|
case "DANAMON":
|
|
return "Bank Danamon";
|
|
case "BSI":
|
|
return "Bank Syariah Indonesia";
|
|
case "CASH":
|
|
return "Tunai";
|
|
case "EDC":
|
|
return "EDC";
|
|
case "ALFAMART":
|
|
return "Alfamart";
|
|
case "INDOMARET":
|
|
return "Indomaret";
|
|
case "AKULAKU":
|
|
return "Akulaku";
|
|
default:
|
|
return code; // Return the code as-is if not mapped
|
|
}
|
|
}
|
|
|
|
// Fallback to provided payment method or default
|
|
return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS";
|
|
}
|
|
|
|
/**
|
|
* ✅ ENHANCED: Card type detection with EMV and QRIS priority
|
|
*/
|
|
private String getDisplayCardType(String cardTypeStr, String acquirer, String channelCode,
|
|
boolean isEmvTransaction, boolean isQrisTransaction,
|
|
String midtransResponse, String referenceId) {
|
|
|
|
Log.d("ReceiptActivity", "🔍 ENHANCED CARD TYPE DETECTION:");
|
|
Log.d("ReceiptActivity", " Input Card Type: " + cardTypeStr);
|
|
Log.d("ReceiptActivity", " Input Acquirer: " + acquirer);
|
|
Log.d("ReceiptActivity", " Channel Code: " + channelCode);
|
|
Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction);
|
|
Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction);
|
|
|
|
// ✅ NEW: Check if this is from ReprintActivity
|
|
String callingActivity = getIntent().getStringExtra("calling_activity");
|
|
boolean isFromReprintActivity = "ReprintActivity".equals(callingActivity);
|
|
Log.d("ReceiptActivity", " Is from ReprintActivity: " + isFromReprintActivity);
|
|
|
|
if (isFromReprintActivity) {
|
|
return handleReprintActivityCardType(cardTypeStr, acquirer, channelCode, referenceId);
|
|
}
|
|
|
|
// Continue with existing logic for other sources
|
|
if (isEmvTransaction) {
|
|
// ✅ FOR EMV TRANSACTIONS: Priority to cardTypeStr from ResultTransactionActivity
|
|
if (cardTypeStr != null && !cardTypeStr.isEmpty() && !cardTypeStr.equalsIgnoreCase("unknown")) {
|
|
Log.d("ReceiptActivity", "✅ EMV: Using provided card type: " + cardTypeStr);
|
|
return cardTypeStr;
|
|
}
|
|
|
|
// ✅ FALLBACK: Try to extract from Midtrans response
|
|
if (midtransResponse != null && !midtransResponse.isEmpty()) {
|
|
String bankFromMidtrans = extractBankFromMidtransResponse(midtransResponse);
|
|
if (bankFromMidtrans != null && !bankFromMidtrans.isEmpty()) {
|
|
Log.d("ReceiptActivity", "✅ EMV: Using bank from Midtrans response: " + bankFromMidtrans);
|
|
return bankFromMidtrans;
|
|
}
|
|
}
|
|
|
|
// ✅ FALLBACK: Use acquirer
|
|
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
|
|
String mappedAcquirer = getCardTypeFromAcquirer(acquirer, channelCode, null);
|
|
Log.d("ReceiptActivity", "✅ EMV: Using mapped acquirer: " + mappedAcquirer);
|
|
return mappedAcquirer;
|
|
}
|
|
|
|
Log.d("ReceiptActivity", "⚠️ EMV: Using default fallback: BCA");
|
|
return "BCA"; // Default for EMV
|
|
|
|
} else if (isQrisTransaction) {
|
|
// ✅ FOR QRIS TRANSACTIONS: Enhanced detection
|
|
Log.d("ReceiptActivity", "🔍 QRIS transaction detected - searching for real acquirer");
|
|
|
|
// Priority 1: Use provided cardTypeStr if it's not generic
|
|
if (cardTypeStr != null && !cardTypeStr.isEmpty() &&
|
|
!cardTypeStr.equalsIgnoreCase("qris") && !cardTypeStr.equalsIgnoreCase("unknown")) {
|
|
Log.d("ReceiptActivity", "✅ QRIS: Using provided specific card type: " + cardTypeStr);
|
|
return cardTypeStr;
|
|
}
|
|
|
|
// Priority 2: Search webhook logs for real acquirer
|
|
if (referenceId != null && !referenceId.isEmpty()) {
|
|
String realAcquirer = fetchRealAcquirerSync(referenceId);
|
|
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
|
|
String mappedQrisAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
|
|
Log.d("ReceiptActivity", "✅ QRIS real acquirer found: " + realAcquirer + " -> " + mappedQrisAcquirer);
|
|
return mappedQrisAcquirer;
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ QRIS real acquirer not found, using generic QRIS");
|
|
// Start async search for better results
|
|
fetchRealAcquirerFromWebhook(referenceId);
|
|
return "QRIS";
|
|
}
|
|
} else {
|
|
return "QRIS";
|
|
}
|
|
} else {
|
|
// ✅ FOR OTHER TRANSACTIONS: Use standard logic
|
|
return getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr);
|
|
}
|
|
}
|
|
|
|
private String handleReprintActivityCardType(String cardTypeStr, String acquirer, String channelCode, String referenceId) {
|
|
Log.d("ReceiptActivity", "🔍 HANDLING REPRINT ACTIVITY CARD TYPE:");
|
|
Log.d("ReceiptActivity", " Provided Card Type: " + cardTypeStr);
|
|
Log.d("ReceiptActivity", " Channel Code: " + channelCode);
|
|
Log.d("ReceiptActivity", " Acquirer: " + acquirer);
|
|
|
|
// Priority 1: If channelCode indicates QRIS, search for real acquirer
|
|
if ("QRIS".equalsIgnoreCase(channelCode) || "RETAIL_OUTLET".equalsIgnoreCase(channelCode)) {
|
|
Log.d("ReceiptActivity", "🔍 QRIS detected from ReprintActivity");
|
|
|
|
if (referenceId != null && !referenceId.isEmpty()) {
|
|
String realAcquirer = fetchRealAcquirerSync(referenceId);
|
|
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
|
|
String mappedAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
|
|
Log.d("ReceiptActivity", "✅ REPRINT QRIS: Found real acquirer: " + realAcquirer + " -> " + mappedAcquirer);
|
|
return mappedAcquirer;
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ REPRINT QRIS: No real acquirer found, using generic QRIS");
|
|
// Start async search for better results
|
|
fetchRealAcquirerFromWebhook(referenceId);
|
|
return "QRIS";
|
|
}
|
|
} else {
|
|
return "QRIS";
|
|
}
|
|
}
|
|
|
|
// Priority 2: Use cardTypeStr if it's meaningful
|
|
if (cardTypeStr != null && !cardTypeStr.isEmpty() &&
|
|
!cardTypeStr.equalsIgnoreCase("unknown") &&
|
|
!cardTypeStr.equalsIgnoreCase("retail_outlet")) {
|
|
Log.d("ReceiptActivity", "✅ REPRINT: Using provided card type: " + cardTypeStr);
|
|
return cardTypeStr;
|
|
}
|
|
|
|
// Priority 3: Map from channelCode
|
|
if (channelCode != null && !channelCode.isEmpty()) {
|
|
String mappedFromChannel = mapChannelCodeToCardType(channelCode);
|
|
Log.d("ReceiptActivity", "✅ REPRINT: Mapped from channel code: " + channelCode + " -> " + mappedFromChannel);
|
|
return mappedFromChannel;
|
|
}
|
|
|
|
// Priority 4: Use acquirer if available
|
|
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
|
|
String mappedAcquirer = getCardTypeFromAcquirer(acquirer, channelCode, null);
|
|
Log.d("ReceiptActivity", "✅ REPRINT: Using mapped acquirer: " + mappedAcquirer);
|
|
return mappedAcquirer;
|
|
}
|
|
|
|
// Final fallback
|
|
Log.w("ReceiptActivity", "⚠️ REPRINT: Using final fallback: Unknown");
|
|
return "Unknown";
|
|
}
|
|
|
|
private String mapChannelCodeToCardType(String channelCode) {
|
|
if (channelCode == null || channelCode.isEmpty()) {
|
|
return "Unknown";
|
|
}
|
|
|
|
String code = channelCode.toUpperCase().trim();
|
|
|
|
switch (code) {
|
|
case "QRIS":
|
|
case "RETAIL_OUTLET":
|
|
return "QRIS";
|
|
case "DEBIT":
|
|
case "DEBIT_CARD":
|
|
return "Visa"; // Default for debit
|
|
case "CREDIT":
|
|
case "CREDIT_CARD":
|
|
return "Mastercard"; // Default for credit
|
|
case "BCA":
|
|
return "BCA";
|
|
case "MANDIRI":
|
|
return "Mandiri";
|
|
case "BNI":
|
|
return "BNI";
|
|
case "BRI":
|
|
return "BRI";
|
|
case "PERMATA":
|
|
return "Permata";
|
|
case "CIMB":
|
|
return "CIMB Niaga";
|
|
case "DANAMON":
|
|
return "Danamon";
|
|
case "BSI":
|
|
return "BSI";
|
|
default:
|
|
return capitalizeFirstLetter(channelCode.replace("_", " "));
|
|
}
|
|
}
|
|
|
|
private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) {
|
|
// STEP 1: If we have a valid acquirer that's not generic "qris", use it
|
|
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
|
|
String acq = acquirer.toLowerCase().trim();
|
|
|
|
Log.d("ReceiptActivity", "🔍 Mapping acquirer: '" + acquirer + "' -> '" + acq + "'");
|
|
|
|
// ✅ COMPREHENSIVE acquirer mapping (case-insensitive)
|
|
String mappedName = ISSUER_DISPLAY_MAP.get(acq);
|
|
if (mappedName != null) {
|
|
Log.d("ReceiptActivity", "✅ Mapped acquirer: " + acquirer + " -> " + mappedName);
|
|
return mappedName;
|
|
}
|
|
|
|
// Additional mapping for variations not in the map
|
|
switch (acq) {
|
|
// Credit card acquirers
|
|
case "visa": return "Visa";
|
|
case "mastercard":
|
|
case "master_card": return "Mastercard";
|
|
case "jcb": return "JCB";
|
|
case "amex":
|
|
case "american_express": return "American Express";
|
|
case "discover": return "Discover";
|
|
case "unionpay":
|
|
case "union_pay": return "UnionPay";
|
|
|
|
// Over-the-counter
|
|
case "alfamart":
|
|
case "alfa_mart": return "Alfamart";
|
|
case "indomaret":
|
|
case "indo_maret": return "Indomaret";
|
|
case "pos_indonesia": return "Pos Indonesia";
|
|
|
|
// Buy now pay later
|
|
case "akulaku": return "Akulaku";
|
|
case "kredivo": return "Kredivo";
|
|
case "indodana":
|
|
case "indo_dana": return "Indodana";
|
|
case "traveloka_paylater":
|
|
case "traveloka": return "Traveloka PayLater";
|
|
case "atome": return "Atome";
|
|
|
|
default:
|
|
// Return capitalized version of acquirer if not in mapping
|
|
String capitalized = capitalizeFirstLetter(acquirer);
|
|
Log.d("ReceiptActivity", "🔤 Using capitalized acquirer: " + capitalized);
|
|
return capitalized;
|
|
}
|
|
}
|
|
|
|
// STEP 2: Fallback based on channel code
|
|
if (channelCode != null && !channelCode.isEmpty()) {
|
|
String code = channelCode.toUpperCase();
|
|
Log.d("ReceiptActivity", "🔍 Using channel code fallback: " + code);
|
|
|
|
switch (code) {
|
|
case "QRIS": return "QRIS"; // Generic QRIS
|
|
case "DEBIT": return "Debit";
|
|
case "CREDIT": return "Credit";
|
|
case "BCA": return "BCA";
|
|
case "MANDIRI": return "Mandiri";
|
|
case "BNI": return "BNI";
|
|
case "BRI": return "BRI";
|
|
default: return code;
|
|
}
|
|
}
|
|
|
|
// STEP 3: Final fallback
|
|
String result = fallbackCardType != null ? fallbackCardType : "Unknown";
|
|
Log.d("ReceiptActivity", "🔍 Using final fallback: " + result);
|
|
return result;
|
|
}
|
|
|
|
private String fetchRealAcquirerSync(String referenceId) {
|
|
try {
|
|
Log.d("ReceiptActivity", "🔍 Sync search for acquirer: " + referenceId);
|
|
|
|
// ✅ IMPROVED: Search with broader scope for better matches
|
|
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=100&sortOrder=DESC&sortColumn=created_at";
|
|
|
|
URL url = new URL(queryUrl);
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
conn.setRequestMethod("GET");
|
|
conn.setRequestProperty("Accept", "application/json");
|
|
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
|
conn.setConnectTimeout(8000); // Slightly longer timeout for better results
|
|
conn.setReadTimeout(8000);
|
|
|
|
if (conn.getResponseCode() == 200) {
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
|
StringBuilder response = new StringBuilder();
|
|
String line;
|
|
while ((line = br.readLine()) != null) {
|
|
response.append(line);
|
|
}
|
|
|
|
org.json.JSONObject json = new org.json.JSONObject(response.toString());
|
|
org.json.JSONArray results = json.optJSONArray("results");
|
|
|
|
if (results != null && results.length() > 0) {
|
|
Log.d("ReceiptActivity", "📊 Sync analyzing " + results.length() + " webhook logs");
|
|
|
|
// ✅ PRIORITY SEARCH: Look for settlement first, then pending, then any
|
|
String[] searchPriority = {"settlement", "capture", "success", "pending", ""};
|
|
|
|
for (String status : searchPriority) {
|
|
String[] targetStatuses = status.isEmpty() ? new String[]{} : new String[]{status};
|
|
String foundAcquirer = searchLogsByStatus(results, referenceId, targetStatuses);
|
|
|
|
if (foundAcquirer != null && !foundAcquirer.isEmpty() && !foundAcquirer.equalsIgnoreCase("qris")) {
|
|
Log.d("ReceiptActivity", "🎯 Sync found acquirer: " + foundAcquirer +
|
|
" (priority: " + (status.isEmpty() ? "any" : status) + ")");
|
|
return foundAcquirer;
|
|
}
|
|
}
|
|
|
|
Log.w("ReceiptActivity", "⚠️ Sync search completed but no valid acquirer found");
|
|
}
|
|
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ Sync API call failed with code: " + conn.getResponseCode());
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "❌ Sync acquirer fetch error: " + e.getMessage());
|
|
}
|
|
|
|
return null; // No acquirer found
|
|
}
|
|
|
|
private void fetchRealAcquirerFromWebhook(String referenceId) {
|
|
new Thread(() -> {
|
|
try {
|
|
Log.d("ReceiptActivity", "🔍 Async search for real acquirer: " + referenceId);
|
|
|
|
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at";
|
|
|
|
URL url = new URL(queryUrl);
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
conn.setRequestMethod("GET");
|
|
conn.setRequestProperty("Accept", "application/json");
|
|
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
|
conn.setConnectTimeout(15000);
|
|
conn.setReadTimeout(15000);
|
|
|
|
if (conn.getResponseCode() == 200) {
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
|
StringBuilder response = new StringBuilder();
|
|
String line;
|
|
while ((line = br.readLine()) != null) {
|
|
response.append(line);
|
|
}
|
|
|
|
org.json.JSONObject json = new org.json.JSONObject(response.toString());
|
|
org.json.JSONArray results = json.optJSONArray("results");
|
|
|
|
if (results != null && results.length() > 0) {
|
|
Log.d("ReceiptActivity", "📊 Async analyzing " + results.length() + " webhook logs");
|
|
|
|
String realAcquirer = searchForRealAcquirer(results, referenceId);
|
|
|
|
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
|
|
Log.d("ReceiptActivity", "✅ Async found real acquirer: " + realAcquirer);
|
|
|
|
// Update UI on main thread
|
|
final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
|
|
runOnUiThread(() -> {
|
|
if (cardType != null) {
|
|
cardType.setText(displayAcquirer);
|
|
Log.d("ReceiptActivity", "🎨 UI UPDATED: QRIS -> " + displayAcquirer);
|
|
|
|
// Show a subtle indication that the data was updated
|
|
showToast("Card type updated: " + displayAcquirer);
|
|
}
|
|
});
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ Async search found no valid acquirer for: " + referenceId);
|
|
}
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ Async search returned no results");
|
|
}
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ Async API call failed with code: " + conn.getResponseCode());
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "❌ Async acquirer fetch error: " + e.getMessage(), e);
|
|
}
|
|
}).start();
|
|
}
|
|
|
|
private String searchForRealAcquirer(org.json.JSONArray results, String referenceId) {
|
|
try {
|
|
Log.d("ReceiptActivity", "🔍 Analyzing " + results.length() + " webhook logs for acquirer");
|
|
Log.d("ReceiptActivity", "🎯 Target reference ID: " + referenceId);
|
|
|
|
// Get order_id from intent for additional matching
|
|
String orderId = getIntent().getStringExtra("order_id");
|
|
Log.d("ReceiptActivity", "🎯 Target order ID: " + orderId);
|
|
|
|
// Strategy 1: Look for settlement/success transactions first (highest priority)
|
|
Log.d("ReceiptActivity", "🔍 Strategy 1: Searching for settlement/success status");
|
|
String acquirerFromSettlement = searchLogsByStatus(results, referenceId, new String[]{"settlement", "capture", "success"});
|
|
if (acquirerFromSettlement != null) {
|
|
Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from settlement: " + acquirerFromSettlement);
|
|
return acquirerFromSettlement;
|
|
}
|
|
|
|
// Strategy 2: Look for pending transactions
|
|
Log.d("ReceiptActivity", "🔍 Strategy 2: Searching for pending status");
|
|
String acquirerFromPending = searchLogsByStatus(results, referenceId, new String[]{"pending"});
|
|
if (acquirerFromPending != null) {
|
|
Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from pending: " + acquirerFromPending);
|
|
return acquirerFromPending;
|
|
}
|
|
|
|
// Strategy 3: Look for any transaction with this reference (broadest search)
|
|
Log.d("ReceiptActivity", "🔍 Strategy 3: Searching for any status");
|
|
String acquirerFromAny = searchLogsByStatus(results, referenceId, new String[]{});
|
|
if (acquirerFromAny != null) {
|
|
Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from any status: " + acquirerFromAny);
|
|
return acquirerFromAny;
|
|
}
|
|
|
|
Log.w("ReceiptActivity", "❌ FAILED: No acquirer found in webhook logs for reference: " + referenceId);
|
|
|
|
// Strategy 4: Debug logging - show what we actually found
|
|
Log.d("ReceiptActivity", "🔍 DEBUG: Showing first 5 log entries for analysis:");
|
|
for (int i = 0; i < Math.min(5, results.length()); i++) {
|
|
try {
|
|
org.json.JSONObject log = results.getJSONObject(i);
|
|
org.json.JSONObject reqBody = log.optJSONObject("request_body");
|
|
if (reqBody != null) {
|
|
String logOrderId = reqBody.optString("order_id", "");
|
|
String logRefId = reqBody.optString("reference_id", "");
|
|
String logStatus = reqBody.optString("transaction_status", "");
|
|
String logAcquirer = reqBody.optString("acquirer", "");
|
|
String logIssuer = reqBody.optString("issuer", "");
|
|
|
|
Log.d("ReceiptActivity", " Log " + (i+1) + ": " +
|
|
"order_id=" + logOrderId +
|
|
", ref_id=" + logRefId +
|
|
", status=" + logStatus +
|
|
", acquirer=" + logAcquirer +
|
|
", issuer=" + logIssuer);
|
|
}
|
|
} catch (Exception e) {
|
|
Log.w("ReceiptActivity", "Error parsing debug log " + i + ": " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
return null;
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "❌ Error analyzing webhook logs: " + e.getMessage(), e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private String searchLogsByStatus(org.json.JSONArray results, String referenceId, String[] targetStatuses) {
|
|
try {
|
|
for (int i = 0; i < results.length(); i++) {
|
|
org.json.JSONObject log = results.getJSONObject(i);
|
|
org.json.JSONObject reqBody = log.optJSONObject("request_body");
|
|
|
|
if (reqBody != null) {
|
|
String logReferenceId = reqBody.optString("reference_id", "");
|
|
String logTransactionStatus = reqBody.optString("transaction_status", "");
|
|
String logAcquirer = reqBody.optString("acquirer", "");
|
|
String logIssuer = reqBody.optString("issuer", "");
|
|
String logOrderId = reqBody.optString("order_id", "");
|
|
|
|
Log.d("ReceiptActivity", "🔍 Checking log: order_id=" + logOrderId +
|
|
", status=" + logTransactionStatus +
|
|
", acquirer=" + logAcquirer +
|
|
", issuer=" + logIssuer);
|
|
|
|
// Check for direct reference match
|
|
boolean isDirectMatch = referenceId.equals(logReferenceId);
|
|
|
|
// ✅ IMPROVED: Also check order_id match from QrisResultActivity
|
|
boolean isOrderMatch = false;
|
|
String orderId = getIntent().getStringExtra("order_id");
|
|
if (orderId != null && !orderId.isEmpty() && orderId.equals(logOrderId)) {
|
|
isOrderMatch = true;
|
|
Log.d("ReceiptActivity", "✅ Found order_id match: " + orderId);
|
|
}
|
|
|
|
// Check custom_field1 for refresh tracking
|
|
boolean isRefreshMatch = false;
|
|
String customField1 = reqBody.optString("custom_field1", "");
|
|
if (!customField1.isEmpty()) {
|
|
try {
|
|
org.json.JSONObject customData = new org.json.JSONObject(customField1);
|
|
String originalReference = customData.optString("original_reference", "");
|
|
String appReferenceId = customData.optString("app_reference_id", "");
|
|
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
|
|
isRefreshMatch = true;
|
|
}
|
|
} catch (org.json.JSONException e) {
|
|
// Ignore parsing errors
|
|
}
|
|
}
|
|
|
|
// Check if this log matches our reference
|
|
if (isDirectMatch || isRefreshMatch || isOrderMatch) {
|
|
Log.d("ReceiptActivity", "🎯 TRANSACTION MATCH FOUND!");
|
|
Log.d("ReceiptActivity", " Match type: " +
|
|
(isDirectMatch ? "DIRECT " : "") +
|
|
(isRefreshMatch ? "REFRESH " : "") +
|
|
(isOrderMatch ? "ORDER" : ""));
|
|
|
|
// If target statuses specified, check status
|
|
if (targetStatuses.length > 0) {
|
|
boolean statusMatches = false;
|
|
for (String targetStatus : targetStatuses) {
|
|
if (logTransactionStatus.equalsIgnoreCase(targetStatus)) {
|
|
statusMatches = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!statusMatches) {
|
|
Log.d("ReceiptActivity", " Status doesn't match target: " + logTransactionStatus);
|
|
continue; // Skip if status doesn't match
|
|
}
|
|
}
|
|
|
|
// ✅ CRITICAL FIX: For QRIS transactions, prioritize issuer over acquirer
|
|
String foundAcquirer = null;
|
|
|
|
// Check if this is a QRIS transaction by payment_type
|
|
String paymentType = reqBody.optString("payment_type", "");
|
|
boolean isQrisTransaction = "qris".equalsIgnoreCase(paymentType);
|
|
|
|
if (isQrisTransaction) {
|
|
// For QRIS: issuer contains the actual payment provider (LinkAja, DANA, etc.)
|
|
// acquirer is usually just "gopay" (the QRIS aggregator)
|
|
if (!logIssuer.isEmpty() && !logIssuer.equalsIgnoreCase("qris")) {
|
|
foundAcquirer = logIssuer;
|
|
Log.d("ReceiptActivity", "📱 QRIS: Using issuer as acquirer: " + foundAcquirer);
|
|
} else if (!logAcquirer.isEmpty() && !logAcquirer.equalsIgnoreCase("qris")) {
|
|
foundAcquirer = logAcquirer;
|
|
Log.d("ReceiptActivity", "📱 QRIS: Fallback to acquirer: " + foundAcquirer);
|
|
}
|
|
} else {
|
|
// For non-QRIS: prefer acquirer over issuer (traditional behavior)
|
|
if (!logAcquirer.isEmpty() && !logAcquirer.equalsIgnoreCase("qris")) {
|
|
foundAcquirer = logAcquirer;
|
|
Log.d("ReceiptActivity", "💳 Non-QRIS: Using acquirer: " + foundAcquirer);
|
|
} else if (!logIssuer.isEmpty() && !logIssuer.equalsIgnoreCase("qris")) {
|
|
foundAcquirer = logIssuer;
|
|
Log.d("ReceiptActivity", "💳 Non-QRIS: Fallback to issuer: " + foundAcquirer);
|
|
}
|
|
}
|
|
|
|
if (foundAcquirer != null && !foundAcquirer.isEmpty()) {
|
|
Log.d("ReceiptActivity", "✅ FINAL ACQUIRER FOUND: " + foundAcquirer +
|
|
" (status: " + logTransactionStatus +
|
|
", payment_type: " + paymentType + ")");
|
|
return foundAcquirer;
|
|
} else {
|
|
Log.w("ReceiptActivity", "⚠️ Transaction matched but no valid acquirer found");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "❌ Error searching logs by status: " + e.getMessage(), e);
|
|
}
|
|
|
|
return null; // No acquirer found for specified criteria
|
|
}
|
|
|
|
/**
|
|
* Extract bank from Midtrans response JSON string
|
|
*/
|
|
private String extractBankFromMidtransResponse(String midtransResponse) {
|
|
try {
|
|
org.json.JSONObject response = new org.json.JSONObject(midtransResponse);
|
|
|
|
// Try different possible bank fields
|
|
String[] bankFields = {"bank", "issuer", "acquiring_bank", "issuer_bank"};
|
|
|
|
for (String field : bankFields) {
|
|
if (response.has(field)) {
|
|
String bankValue = response.getString(field);
|
|
if (bankValue != null && !bankValue.trim().isEmpty() && !bankValue.equalsIgnoreCase("qris")) {
|
|
Log.d("ReceiptActivity", "Found bank in Midtrans response (" + field + "): " + bankValue);
|
|
return formatBankNameForReceipt(bankValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
Log.w("ReceiptActivity", "No valid bank found in Midtrans response");
|
|
return null;
|
|
|
|
} catch (Exception e) {
|
|
Log.e("ReceiptActivity", "Error extracting bank from Midtrans response: " + e.getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format bank name specifically for receipt display
|
|
*/
|
|
private String formatBankNameForReceipt(String bankName) {
|
|
if (bankName == null || bankName.trim().isEmpty()) {
|
|
return "BCA"; // Default
|
|
}
|
|
|
|
String formatted = bankName.trim();
|
|
|
|
// Common bank name mappings for receipt display
|
|
switch (formatted.toUpperCase()) {
|
|
case "BCA":
|
|
case "BANK BCA":
|
|
case "BANK CENTRAL ASIA": return "BCA";
|
|
|
|
case "MANDIRI":
|
|
case "BANK MANDIRI": return "Mandiri";
|
|
|
|
case "BNI":
|
|
case "BANK BNI":
|
|
case "BANK NEGARA INDONESIA": return "BNI";
|
|
|
|
case "BRI":
|
|
case "BANK BRI":
|
|
case "BANK RAKYAT INDONESIA": return "BRI";
|
|
|
|
case "CIMB":
|
|
case "CIMB NIAGA":
|
|
case "BANK CIMB NIAGA": return "CIMB Niaga";
|
|
|
|
case "DANAMON":
|
|
case "BANK DANAMON": return "Danamon";
|
|
|
|
case "PERMATA":
|
|
case "BANK PERMATA": return "Permata";
|
|
|
|
default:
|
|
// Return capitalized version
|
|
return capitalizeFirstLetter(formatted);
|
|
}
|
|
}
|
|
|
|
private String capitalizeFirstLetter(String input) {
|
|
if (input == null || input.isEmpty()) {
|
|
return input;
|
|
}
|
|
|
|
// Handle special cases
|
|
String cleaned = input.trim();
|
|
|
|
// Special handling for common patterns
|
|
if (cleaned.toLowerCase().contains("pay")) {
|
|
// Keep "Pay" capitalized in payment methods
|
|
return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase()
|
|
.replace("pay", "Pay");
|
|
}
|
|
|
|
return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* ✅ ENHANCED: Set amount data with EMV and QRIS support
|
|
*/
|
|
private void setAmountDataEnhanced(String amount, String grossAmount, boolean isEmvTransaction, boolean isQrisTransaction) {
|
|
String amountToUse = amount != null ? amount : grossAmount;
|
|
|
|
Log.d("ReceiptActivity", "Setting enhanced amount data - amount: " + amount +
|
|
", grossAmount: " + grossAmount + ", using: " + amountToUse +
|
|
", isEMV: " + isEmvTransaction + ", isQRIS: " + isQrisTransaction);
|
|
|
|
if (amountToUse != null) {
|
|
try {
|
|
String cleanAmount = cleanAmountString(amountToUse);
|
|
Log.d("ReceiptActivity", "Cleaned amount: " + cleanAmount);
|
|
|
|
long amountLong = Long.parseLong(cleanAmount);
|
|
|
|
// Set transaction total
|
|
transactionTotal.setText(formatCurrency(amountLong));
|
|
|
|
// ✅ CALCULATE FEES BASED ON TRANSACTION TYPE
|
|
long tax = 0;
|
|
long serviceFeeValue = 0;
|
|
long total = amountLong;
|
|
|
|
if (isEmvTransaction) {
|
|
// For EMV transactions, check if gross amount includes additional fees
|
|
if (grossAmount != null && !grossAmount.equals(amount)) {
|
|
try {
|
|
long grossAmountLong = Long.parseLong(cleanAmountString(grossAmount));
|
|
long difference = grossAmountLong - amountLong;
|
|
|
|
if (difference > 0) {
|
|
// Assume 11% tax and 500 service fee (adjust based on your business logic)
|
|
tax = Math.round(amountLong * 0.11);
|
|
serviceFeeValue = 500;
|
|
total = grossAmountLong;
|
|
|
|
Log.d("ReceiptActivity", "EMV: Calculated tax=" + tax + ", service=" + serviceFeeValue + ", total=" + total);
|
|
}
|
|
} catch (Exception e) {
|
|
Log.w("ReceiptActivity", "Could not parse gross amount for EMV: " + grossAmount);
|
|
}
|
|
}
|
|
} else if (isQrisTransaction) {
|
|
// For QRIS, typically no additional fees (tax=0, service=0)
|
|
tax = 0;
|
|
serviceFeeValue = 0;
|
|
total = amountLong;
|
|
Log.d("ReceiptActivity", "QRIS: No additional fees - total=" + total);
|
|
}
|
|
|
|
// Set calculated values
|
|
taxPercentage.setText(tax > 0 ? "11%" : "0%");
|
|
serviceFee.setText(formatCurrency(serviceFeeValue));
|
|
finalTotal.setText(formatCurrency(total));
|
|
|
|
Log.d("ReceiptActivity", "Enhanced amount formatting successful: " + amountLong + " -> " + formatCurrency(total));
|
|
|
|
} catch (NumberFormatException e) {
|
|
Log.e("ReceiptActivity", "Error parsing enhanced amount: " + amountToUse, e);
|
|
// Fallback if parsing fails
|
|
transactionTotal.setText(amountToUse);
|
|
taxPercentage.setText("0%");
|
|
serviceFee.setText("0");
|
|
finalTotal.setText(amountToUse);
|
|
}
|
|
} else {
|
|
// Default values if no amount provided
|
|
transactionTotal.setText("0");
|
|
taxPercentage.setText("0%");
|
|
serviceFee.setText("0");
|
|
finalTotal.setText("0");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean amount string to extract raw number correctly
|
|
* Input examples: "1000", "1000.00", "Rp 1.000", "1.000"
|
|
* Output: "1000"
|
|
*/
|
|
private String cleanAmountString(String amount) {
|
|
if (amount == null || amount.isEmpty()) {
|
|
return "0";
|
|
}
|
|
|
|
// Remove currency symbols and spaces first
|
|
String cleaned = amount
|
|
.replace("Rp. ", "")
|
|
.replace("Rp ", "")
|
|
.replace("IDR ", "")
|
|
.replace(" ", "")
|
|
.trim();
|
|
|
|
Log.d("ReceiptActivity", "After currency removal: '" + cleaned + "'");
|
|
|
|
// Handle decimal cases properly
|
|
if (cleaned.contains(".")) {
|
|
// Check if it contains decimal cents (like "1000.00") or thousand separator (like "1.000")
|
|
String[] parts = cleaned.split("\\.");
|
|
|
|
if (parts.length == 2) {
|
|
String beforeDot = parts[0];
|
|
String afterDot = parts[1];
|
|
|
|
// If after dot is "00" or "0", it's decimal cents - remove it
|
|
if (afterDot.equals("00") || afterDot.equals("0")) {
|
|
cleaned = beforeDot;
|
|
Log.d("ReceiptActivity", "Removed decimal cents: '" + cleaned + "'");
|
|
}
|
|
// If after dot has 3 digits, it's thousand separator - combine them
|
|
else if (afterDot.length() == 3) {
|
|
cleaned = beforeDot + afterDot;
|
|
Log.d("ReceiptActivity", "Combined thousand separator: '" + cleaned + "'");
|
|
}
|
|
// For other cases, try to determine based on length
|
|
else {
|
|
// If beforeDot is short (1-3 digits) and afterDot is 3 digits, it's thousand separator
|
|
if (beforeDot.length() <= 3 && afterDot.length() == 3) {
|
|
cleaned = beforeDot + afterDot;
|
|
} else {
|
|
// Otherwise, it's likely decimal - remove the decimal part
|
|
cleaned = beforeDot;
|
|
}
|
|
Log.d("ReceiptActivity", "Processed mixed format: '" + cleaned + "'");
|
|
}
|
|
} else {
|
|
// Multiple dots - remove all dots (treat as thousand separators)
|
|
cleaned = cleaned.replace(".", "");
|
|
Log.d("ReceiptActivity", "Removed multiple dots: '" + cleaned + "'");
|
|
}
|
|
}
|
|
|
|
// Remove any remaining commas (some locales use comma as thousand separator)
|
|
cleaned = cleaned.replace(",", "");
|
|
|
|
Log.d("ReceiptActivity", "Final cleaned amount: '" + amount + "' -> '" + cleaned + "'");
|
|
return cleaned;
|
|
}
|
|
|
|
private String formatCurrency(long amount) {
|
|
// Use Indonesian locale formatting with dots as thousand separators
|
|
return String.format("%,d", amount).replace(',', '.');
|
|
}
|
|
|
|
private String getCurrentDateTime() {
|
|
SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
|
|
return sdf.format(new Date());
|
|
}
|
|
|
|
private void handleEmailReceipt() {
|
|
// Handle email receipt action
|
|
// In real app, this would open email intent
|
|
showToast("Mengirim email...");
|
|
}
|
|
|
|
private void handleFinish() {
|
|
// Navigate to MainActivity/Home Page when "Selesai" button is pressed
|
|
navigateToHomePage();
|
|
}
|
|
|
|
private void handleBackNavigation() {
|
|
// Smart back navigation - go to the actual previous activity
|
|
// Check if we have a calling activity in the intent
|
|
String callingActivity = getIntent().getStringExtra("calling_activity");
|
|
|
|
if (callingActivity != null) {
|
|
switch (callingActivity) {
|
|
case "ReprintActivity":
|
|
// Go back to transaction list
|
|
Intent transactionIntent = new Intent(this, ReprintActivity.class);
|
|
transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
startActivity(transactionIntent);
|
|
break;
|
|
|
|
case "QrisResultActivity":
|
|
// Go back to main menu since QrisResultActivity is typically finished
|
|
navigateToHomePage();
|
|
break;
|
|
|
|
case "ResultTransactionActivity":
|
|
// ✅ NEW: Handle back from ResultTransactionActivity
|
|
navigateToHomePage();
|
|
break;
|
|
|
|
case "PaymentActivity":
|
|
case "QrisActivity":
|
|
// Go back to payment/qris activity
|
|
Intent paymentIntent = new Intent(this, QrisActivity.class);
|
|
paymentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
|
startActivity(paymentIntent);
|
|
break;
|
|
|
|
default:
|
|
// Default: use system back navigation
|
|
super.onBackPressed();
|
|
break;
|
|
}
|
|
} else {
|
|
// No calling activity specified, use system back navigation
|
|
super.onBackPressed();
|
|
}
|
|
|
|
finish();
|
|
}
|
|
|
|
private void navigateToHomePage() {
|
|
// Navigate to MainActivity/Home Page
|
|
Intent intent = new Intent(this, MainActivity.class);
|
|
|
|
// Clear all previous activities from the stack and start fresh
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
// Optional: Add success message to show in MainActivity
|
|
intent.putExtra("transaction_completed", true);
|
|
intent.putExtra("transaction_amount", getIntent().getStringExtra("transaction_amount"));
|
|
|
|
startActivity(intent);
|
|
finish();
|
|
|
|
// Show success message
|
|
showToast("Transaksi berhasil diselesaikan!");
|
|
}
|
|
|
|
private void showToast(String message) {
|
|
android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show();
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
// Use the smart back navigation
|
|
handleBackNavigation();
|
|
super.onBackPressed();
|
|
}
|
|
} |