refactor
This commit is contained in:
parent
f5d9e53118
commit
f2c3de9f5f
@ -45,29 +45,32 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".TransactionActivity"
|
||||
android:name=".cetakulang.ReprintActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".PaymentActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".PinActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:name=".cetakulang.ReprintAdapterActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ReceiptActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".QrisActivity"
|
||||
android:exported="false" />
|
||||
<activity android:name=".QrisResultActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".QrisResultActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".SettlementActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".HistoryActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".HistoryDetailActivity"
|
||||
android:exported="false" />
|
||||
@ -77,7 +80,7 @@
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".kredit.CreditCardActivity"
|
||||
android:name=".transaction.ResultTransactionActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name="com.sunmi.emv.l2.view.AppSelectActivity"/>
|
||||
|
@ -19,8 +19,11 @@ import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import com.example.bdkipoc.cetakulang.ReprintActivity;
|
||||
import com.example.bdkipoc.cetakulang.ReprintAdapterActivity;
|
||||
|
||||
import com.example.bdkipoc.transaction.CreateTransactionActivity;
|
||||
import com.example.bdkipoc.kredit.CreditCardActivity;
|
||||
import com.example.bdkipoc.transaction.ResultTransactionActivity;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@ -86,12 +89,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
// 6 dummy menus should be hidden initially
|
||||
CardView[] dummyCards = {
|
||||
findViewById(R.id.card_dummy_menu_1),
|
||||
findViewById(R.id.card_dummy_menu_2),
|
||||
findViewById(R.id.card_dummy_menu_3),
|
||||
findViewById(R.id.card_dummy_menu_4),
|
||||
findViewById(R.id.card_dummy_menu_5),
|
||||
findViewById(R.id.card_dummy_menu_6)
|
||||
findViewById(R.id.card_bantuan),
|
||||
findViewById(R.id.card_info_toko),
|
||||
findViewById(R.id.card_pengaturan),
|
||||
};
|
||||
|
||||
for (CardView card : dummyCards) {
|
||||
@ -138,21 +138,17 @@ public class MainActivity extends AppCompatActivity {
|
||||
R.id.card_kartu_debit,
|
||||
R.id.card_qris,
|
||||
// Row 2 (Always visible - 3 items)
|
||||
R.id.card_transfer,
|
||||
R.id.card_uang_elektronik,
|
||||
R.id.card_cetak_ulang,
|
||||
R.id.card_settlement,
|
||||
// Row 3 (Always visible - 3 items)
|
||||
R.id.card_refund,
|
||||
R.id.card_settlement,
|
||||
R.id.card_histori,
|
||||
R.id.card_bantuan,
|
||||
R.id.card_info_toko,
|
||||
// Row 4 (Hidden initially - 3 items)
|
||||
R.id.card_dummy_menu_1,
|
||||
R.id.card_dummy_menu_2,
|
||||
R.id.card_dummy_menu_3,
|
||||
// Row 5 (Hidden initially - 3 items)
|
||||
R.id.card_dummy_menu_4,
|
||||
R.id.card_dummy_menu_5,
|
||||
R.id.card_dummy_menu_6
|
||||
R.id.card_bantuan,
|
||||
R.id.card_info_toko,
|
||||
R.id.card_pengaturan,
|
||||
};
|
||||
|
||||
// Set up click listeners for all cards
|
||||
@ -163,33 +159,30 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (cardId == R.id.card_kartu_kredit) {
|
||||
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
|
||||
} else if (cardId == R.id.card_kartu_debit) {
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
|
||||
} 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));
|
||||
} else if (cardId == R.id.card_uang_elektronik) {
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
|
||||
} else if (cardId == R.id.card_cetak_ulang) {
|
||||
startActivity(new Intent(MainActivity.this, TransactionActivity.class));
|
||||
startActivity(new Intent(MainActivity.this, ReprintActivity.class));
|
||||
// Col-3
|
||||
} else if (cardId == R.id.card_refund) {
|
||||
startActivity(new Intent(MainActivity.this, CreateTransactionActivity.class));
|
||||
} else if (cardId == R.id.card_settlement) {
|
||||
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_histori) {
|
||||
Toast.makeText(this, "Histori - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
startActivity(new Intent(MainActivity.this, HistoryActivity.class));
|
||||
// Col-4
|
||||
} else if (cardId == R.id.card_bantuan) {
|
||||
Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_info_toko) {
|
||||
Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_1) {
|
||||
Toast.makeText(this, "Dummy Menu 1 - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_2) {
|
||||
Toast.makeText(this, "Dummy Menu 2 - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_3) {
|
||||
Toast.makeText(this, "Dummy Menu 3 - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_4) {
|
||||
Toast.makeText(this, "Dummy Menu 4 - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_5) {
|
||||
Toast.makeText(this, "Dummy Menu 5 - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_6) {
|
||||
Toast.makeText(this, "Dummy Menu 6 - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_pengaturan) {
|
||||
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();
|
||||
@ -200,12 +193,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
// Get references to ONLY the dummy cards that need to be toggled
|
||||
CardView[] toggleableCards = {
|
||||
findViewById(R.id.card_dummy_menu_1),
|
||||
findViewById(R.id.card_dummy_menu_2),
|
||||
findViewById(R.id.card_dummy_menu_3),
|
||||
findViewById(R.id.card_dummy_menu_4),
|
||||
findViewById(R.id.card_dummy_menu_5),
|
||||
findViewById(R.id.card_dummy_menu_6)
|
||||
findViewById(R.id.card_bantuan),
|
||||
findViewById(R.id.card_info_toko),
|
||||
findViewById(R.id.card_pengaturan),
|
||||
};
|
||||
|
||||
// Set up "Lainnya" button click listener
|
||||
|
@ -1,530 +0,0 @@
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class PaymentActivity extends AppCompatActivity {
|
||||
|
||||
// Views
|
||||
private EditText editTextAmount;
|
||||
private Button confirmButton;
|
||||
private LinearLayout backNavigation;
|
||||
private ImageView backArrow;
|
||||
private TextView toolbarTitle;
|
||||
|
||||
// Numpad buttons
|
||||
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
|
||||
private ImageView btnDelete;
|
||||
|
||||
// Modal components
|
||||
private Dialog paymentModal;
|
||||
|
||||
// Data
|
||||
private StringBuilder currentAmount = new StringBuilder();
|
||||
private static final int MAX_AMOUNT_LENGTH = 12;
|
||||
|
||||
// Animation
|
||||
private Handler animationHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set status bar color programmatically
|
||||
setStatusBarColor();
|
||||
|
||||
setContentView(R.layout.activity_payment);
|
||||
|
||||
initializeViews();
|
||||
setupClickListeners();
|
||||
setupInitialStates();
|
||||
setupModal();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
// Main views
|
||||
editTextAmount = findViewById(R.id.editTextAmount);
|
||||
confirmButton = findViewById(R.id.confirmButton);
|
||||
backNavigation = findViewById(R.id.back_navigation);
|
||||
backArrow = findViewById(R.id.backArrow);
|
||||
toolbarTitle = findViewById(R.id.toolbarTitle);
|
||||
|
||||
// Numpad buttons
|
||||
btn1 = findViewById(R.id.btn1);
|
||||
btn2 = findViewById(R.id.btn2);
|
||||
btn3 = findViewById(R.id.btn3);
|
||||
btn4 = findViewById(R.id.btn4);
|
||||
btn5 = findViewById(R.id.btn5);
|
||||
btn6 = findViewById(R.id.btn6);
|
||||
btn7 = findViewById(R.id.btn7);
|
||||
btn8 = findViewById(R.id.btn8);
|
||||
btn9 = findViewById(R.id.btn9);
|
||||
btn0 = findViewById(R.id.btn0);
|
||||
btn000 = findViewById(R.id.btn000);
|
||||
btnDelete = findViewById(R.id.btnDelete);
|
||||
}
|
||||
|
||||
private void setupModal() {
|
||||
// Create modal dialog
|
||||
paymentModal = new Dialog(this);
|
||||
paymentModal.setContentView(R.layout.modal_layout);
|
||||
|
||||
// Remove background dimming - make it fully transparent
|
||||
if (paymentModal.getWindow() != null) {
|
||||
paymentModal.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
}
|
||||
|
||||
// Setup modal listeners
|
||||
setupModalListeners();
|
||||
}
|
||||
|
||||
private void setupModalListeners() {
|
||||
// Make modal non-cancelable by touching outside
|
||||
paymentModal.setCanceledOnTouchOutside(false);
|
||||
|
||||
// Auto dismiss after 3 seconds (simulate card processing)
|
||||
Handler modalHandler = new Handler(Looper.getMainLooper());
|
||||
paymentModal.setOnShowListener(dialog -> {
|
||||
modalHandler.postDelayed(() -> {
|
||||
if (paymentModal != null && paymentModal.isShowing()) {
|
||||
// First dismiss modal, then navigate
|
||||
paymentModal.dismiss();
|
||||
|
||||
// Add small delay to ensure modal is fully dismissed
|
||||
animationHandler.postDelayed(() -> {
|
||||
navigateToPinActivity();
|
||||
}, 100);
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
// Back navigation - entire LinearLayout is clickable
|
||||
backNavigation.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
|
||||
// Individual back arrow (for additional touch area)
|
||||
backArrow.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
|
||||
// Toolbar title (also clickable for back navigation)
|
||||
toolbarTitle.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
|
||||
// Numpad listeners
|
||||
btn1.setOnClickListener(v -> handleNumpadClick(v, "1"));
|
||||
btn2.setOnClickListener(v -> handleNumpadClick(v, "2"));
|
||||
btn3.setOnClickListener(v -> handleNumpadClick(v, "3"));
|
||||
btn4.setOnClickListener(v -> handleNumpadClick(v, "4"));
|
||||
btn5.setOnClickListener(v -> handleNumpadClick(v, "5"));
|
||||
btn6.setOnClickListener(v -> handleNumpadClick(v, "6"));
|
||||
btn7.setOnClickListener(v -> handleNumpadClick(v, "7"));
|
||||
btn8.setOnClickListener(v -> handleNumpadClick(v, "8"));
|
||||
btn9.setOnClickListener(v -> handleNumpadClick(v, "9"));
|
||||
btn0.setOnClickListener(v -> handleNumpadClick(v, "0"));
|
||||
btn000.setOnClickListener(v -> handleNumpadClick(v, "000"));
|
||||
|
||||
// Delete button
|
||||
btnDelete.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
deleteLastDigit();
|
||||
});
|
||||
|
||||
// Confirm button - NOW SHOWS MODAL INSTEAD OF DIRECT PAYMENT
|
||||
confirmButton.setOnClickListener(v -> {
|
||||
if (confirmButton.isEnabled()) {
|
||||
addButtonClickAnimation(v);
|
||||
showPaymentModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
// Simple back navigation without card animation
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleNumpadClick(View view, String digit) {
|
||||
addClickAnimation(view);
|
||||
addDigit(digit);
|
||||
}
|
||||
|
||||
private void setupInitialStates() {
|
||||
// Set initial amount display
|
||||
editTextAmount.setText("");
|
||||
|
||||
// Set initial button state
|
||||
updateButtonState();
|
||||
|
||||
// Disable EditText input (only numpad input allowed)
|
||||
editTextAmount.setFocusable(false);
|
||||
editTextAmount.setClickable(false);
|
||||
editTextAmount.setCursorVisible(false);
|
||||
}
|
||||
|
||||
private void addDigit(String digit) {
|
||||
// Validate input length
|
||||
if (currentAmount.length() >= MAX_AMOUNT_LENGTH) {
|
||||
showToast("Maksimal " + MAX_AMOUNT_LENGTH + " digit");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle leading zeros
|
||||
if (currentAmount.length() == 0) {
|
||||
if (digit.equals("000")) {
|
||||
// Don't allow 000 as first input
|
||||
return;
|
||||
}
|
||||
currentAmount.append(digit);
|
||||
} else if (currentAmount.length() == 1 && currentAmount.toString().equals("0")) {
|
||||
if (!digit.equals("000")) {
|
||||
// Replace single 0 with new digit
|
||||
currentAmount = new StringBuilder(digit);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
currentAmount.append(digit);
|
||||
}
|
||||
|
||||
updateAmountDisplay();
|
||||
updateButtonState();
|
||||
addInputFeedback();
|
||||
}
|
||||
|
||||
private void deleteLastDigit() {
|
||||
if (currentAmount.length() > 0) {
|
||||
String current = currentAmount.toString();
|
||||
|
||||
// If current ends with 000, remove all three digits
|
||||
if (current.endsWith("000") && current.length() >= 3) {
|
||||
currentAmount.delete(currentAmount.length() - 3, currentAmount.length());
|
||||
} else {
|
||||
currentAmount.deleteCharAt(currentAmount.length() - 1);
|
||||
}
|
||||
|
||||
updateAmountDisplay();
|
||||
updateButtonState();
|
||||
addDeleteFeedback();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAmountDisplay() {
|
||||
String amount = currentAmount.toString();
|
||||
|
||||
if (amount.isEmpty() || amount.equals("0")) {
|
||||
editTextAmount.setText("");
|
||||
} else {
|
||||
String formattedAmount = formatCurrency(amount);
|
||||
editTextAmount.setText(formattedAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private String formatCurrency(String amount) {
|
||||
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
long number = Long.parseLong(amount);
|
||||
return String.format("%,d", number).replace(',', '.');
|
||||
} catch (NumberFormatException e) {
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtonState() {
|
||||
boolean hasValidAmount = currentAmount.length() > 0 &&
|
||||
!currentAmount.toString().equals("0") &&
|
||||
!currentAmount.toString().isEmpty();
|
||||
|
||||
confirmButton.setEnabled(hasValidAmount);
|
||||
|
||||
if (hasValidAmount) {
|
||||
// Active state
|
||||
confirmButton.setBackgroundResource(R.drawable.button_active_background);
|
||||
confirmButton.setTextColor(Color.WHITE);
|
||||
confirmButton.setAlpha(1.0f);
|
||||
} else {
|
||||
// Inactive state
|
||||
confirmButton.setBackgroundResource(R.drawable.button_inactive_background);
|
||||
confirmButton.setTextColor(Color.parseColor("#999999"));
|
||||
confirmButton.setAlpha(0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
// NEW METHOD: Show payment modal instead of direct payment processing
|
||||
private void showPaymentModal() {
|
||||
String amount = currentAmount.toString();
|
||||
|
||||
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
|
||||
showToast("Masukkan jumlah pembayaran");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long amountValue = Long.parseLong(amount);
|
||||
|
||||
// Validate minimum amount
|
||||
if (amountValue < 1000) {
|
||||
showToast("Minimal pembayaran Rp 1.000");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate maximum amount
|
||||
if (amountValue > 999999999L) {
|
||||
showToast("Maksimal pembayaran Rp 999.999.999");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show modal with animation
|
||||
showModalWithAnimation();
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
showToast("Format jumlah tidak valid");
|
||||
}
|
||||
}
|
||||
|
||||
private void showModalWithAnimation() {
|
||||
// Add debug log
|
||||
showToast("Showing card modal...");
|
||||
|
||||
paymentModal.show();
|
||||
|
||||
// Add slide-up animation
|
||||
View modalView = paymentModal.findViewById(android.R.id.content);
|
||||
if (modalView != null) {
|
||||
ObjectAnimator slideUp = ObjectAnimator.ofFloat(modalView, "translationY", 300f, 0f);
|
||||
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(modalView, "alpha", 0f, 1f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(slideUp, fadeIn);
|
||||
animatorSet.setDuration(300);
|
||||
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
animatorSet.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissModal() {
|
||||
if (paymentModal != null && paymentModal.isShowing()) {
|
||||
// Add slide-down animation before dismissing
|
||||
View modalView = paymentModal.findViewById(android.R.id.content);
|
||||
if (modalView != null) {
|
||||
ObjectAnimator slideDown = ObjectAnimator.ofFloat(modalView, "translationY", 0f, 300f);
|
||||
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(modalView, "alpha", 1f, 0f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(slideDown, fadeOut);
|
||||
animatorSet.setDuration(200);
|
||||
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
|
||||
animatorSet.addListener(new android.animation.AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(android.animation.Animator animation) {
|
||||
paymentModal.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
animatorSet.start();
|
||||
} else {
|
||||
paymentModal.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processModalConfirmation() {
|
||||
// This method is no longer needed since modal auto-dismisses
|
||||
}
|
||||
|
||||
private void navigateToPinActivity() {
|
||||
String amount = currentAmount.toString();
|
||||
|
||||
// Add debug log
|
||||
showToast("Navigating to PIN Activity...");
|
||||
|
||||
try {
|
||||
long amountValue = Long.parseLong(amount);
|
||||
|
||||
// Launch PIN Activity with amount data
|
||||
Intent intent = new Intent(this, PinActivity.class);
|
||||
intent.putExtra(PinActivity.EXTRA_SOURCE_ACTIVITY, "PaymentActivity");
|
||||
intent.putExtra(PinActivity.EXTRA_AMOUNT, String.valueOf(amountValue));
|
||||
startActivityForResult(intent, 100);
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
showToast("Format jumlah tidak valid: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
showToast("Error navigating to PIN: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void processCardPayment() {
|
||||
// This method is called after PIN verification is successful
|
||||
// Now process the actual payment
|
||||
String amount = currentAmount.toString();
|
||||
|
||||
try {
|
||||
long amountValue = Long.parseLong(amount);
|
||||
|
||||
// Show processing message
|
||||
showToast("PIN berhasil diverifikasi! Memproses pembayaran...");
|
||||
|
||||
// Process the final payment
|
||||
processPayment(amountValue);
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
showToast("Format jumlah tidak valid");
|
||||
}
|
||||
}
|
||||
|
||||
private void processPayment(long amount) {
|
||||
// Show loading state
|
||||
confirmButton.setText("Memproses...");
|
||||
confirmButton.setEnabled(false);
|
||||
|
||||
// Simulate payment processing
|
||||
animationHandler.postDelayed(() -> {
|
||||
// Show success message
|
||||
showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount)));
|
||||
|
||||
// Reset state and go back (this is final step after PIN verification)
|
||||
resetPaymentState();
|
||||
navigateBack();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private void resetPaymentState() {
|
||||
currentAmount = new StringBuilder();
|
||||
updateAmountDisplay();
|
||||
updateButtonState();
|
||||
confirmButton.setText("Konfirmasi");
|
||||
}
|
||||
|
||||
// Animation methods (only for numpad interactions)
|
||||
private void addClickAnimation(View view) {
|
||||
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
|
||||
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(scaleX, scaleY);
|
||||
animatorSet.setDuration(150);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
private void addButtonClickAnimation(View view) {
|
||||
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f);
|
||||
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(scaleX, scaleY);
|
||||
animatorSet.setDuration(200);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
private void addInputFeedback() {
|
||||
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextAmount, "alpha", 0.7f, 1f);
|
||||
fadeIn.setDuration(200);
|
||||
fadeIn.start();
|
||||
}
|
||||
|
||||
private void addDeleteFeedback() {
|
||||
ObjectAnimator shake = ObjectAnimator.ofFloat(editTextAmount, "translationX", 0f, -10f, 10f, 0f);
|
||||
shake.setDuration(300);
|
||||
shake.start();
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
private void showToast(String message) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Check if modal is showing, dismiss it first
|
||||
if (paymentModal != null && paymentModal.isShowing()) {
|
||||
dismissModal();
|
||||
} else {
|
||||
navigateBack();
|
||||
}
|
||||
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// Handle result from PIN Activity
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
boolean pinVerified = data.getBooleanExtra("pin_verified", false);
|
||||
if (pinVerified) {
|
||||
// PIN verification successful, process payment
|
||||
processCardPayment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (animationHandler != null) {
|
||||
animationHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
// Clean up modal
|
||||
if (paymentModal != null && paymentModal.isShowing()) {
|
||||
paymentModal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods for testing
|
||||
public String getCurrentAmount() {
|
||||
return currentAmount.toString();
|
||||
}
|
||||
|
||||
public boolean isConfirmButtonEnabled() {
|
||||
return confirmButton.isEnabled();
|
||||
}
|
||||
}
|
@ -1,558 +0,0 @@
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class PinActivity extends AppCompatActivity {
|
||||
|
||||
// Intent Extra Keys
|
||||
public static final String EXTRA_TITLE = "extra_title";
|
||||
public static final String EXTRA_SUBTITLE = "extra_subtitle";
|
||||
public static final String EXTRA_AMOUNT = "extra_amount";
|
||||
public static final String EXTRA_SOURCE_ACTIVITY = "extra_source_activity";
|
||||
|
||||
// Views
|
||||
private EditText editTextPin;
|
||||
private Button confirmButton;
|
||||
private LinearLayout backNavigation;
|
||||
private ImageView backArrow;
|
||||
private TextView toolbarTitle;
|
||||
|
||||
// Success screen views
|
||||
private View successScreen;
|
||||
private ImageView successIcon;
|
||||
private TextView successMessage;
|
||||
|
||||
// Numpad buttons
|
||||
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
|
||||
private ImageView btnDelete;
|
||||
|
||||
// Data
|
||||
private StringBuilder currentPin = new StringBuilder();
|
||||
private static final int MAX_PIN_LENGTH = 6;
|
||||
private static final int MIN_PIN_LENGTH = 4;
|
||||
|
||||
// Extra data from intent
|
||||
private String sourceActivity;
|
||||
private String amount;
|
||||
|
||||
// Animation
|
||||
private Handler animationHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set status bar color programmatically
|
||||
setStatusBarColor();
|
||||
|
||||
setContentView(R.layout.activity_pin);
|
||||
|
||||
// Get intent extras
|
||||
getIntentExtras();
|
||||
|
||||
initializeViews();
|
||||
setupClickListeners();
|
||||
setupInitialStates();
|
||||
setupSuccessScreen();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getIntentExtras() {
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
sourceActivity = intent.getStringExtra(EXTRA_SOURCE_ACTIVITY);
|
||||
amount = intent.getStringExtra(EXTRA_AMOUNT);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
// Main views
|
||||
editTextPin = findViewById(R.id.editTextPin);
|
||||
confirmButton = findViewById(R.id.confirmButton);
|
||||
backNavigation = findViewById(R.id.back_navigation);
|
||||
backArrow = findViewById(R.id.backArrow);
|
||||
toolbarTitle = findViewById(R.id.toolbarTitle);
|
||||
|
||||
// Success screen views
|
||||
successScreen = findViewById(R.id.success_screen);
|
||||
successIcon = findViewById(R.id.success_icon);
|
||||
successMessage = findViewById(R.id.success_message);
|
||||
|
||||
// Numpad buttons
|
||||
btn1 = findViewById(R.id.btn1);
|
||||
btn2 = findViewById(R.id.btn2);
|
||||
btn3 = findViewById(R.id.btn3);
|
||||
btn4 = findViewById(R.id.btn4);
|
||||
btn5 = findViewById(R.id.btn5);
|
||||
btn6 = findViewById(R.id.btn6);
|
||||
btn7 = findViewById(R.id.btn7);
|
||||
btn8 = findViewById(R.id.btn8);
|
||||
btn9 = findViewById(R.id.btn9);
|
||||
btn0 = findViewById(R.id.btn0);
|
||||
btn000 = findViewById(R.id.btn000);
|
||||
btnDelete = findViewById(R.id.btnDelete);
|
||||
}
|
||||
|
||||
private void setupSuccessScreen() {
|
||||
// Initially hide success screen
|
||||
if (successScreen != null) {
|
||||
successScreen.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
// Back navigation - entire LinearLayout is clickable
|
||||
backNavigation.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
|
||||
// Individual back arrow (for additional touch area)
|
||||
backArrow.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
|
||||
// Toolbar title (also clickable for back navigation)
|
||||
toolbarTitle.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
|
||||
// Numpad listeners
|
||||
btn1.setOnClickListener(v -> handleNumpadClick(v, "1"));
|
||||
btn2.setOnClickListener(v -> handleNumpadClick(v, "2"));
|
||||
btn3.setOnClickListener(v -> handleNumpadClick(v, "3"));
|
||||
btn4.setOnClickListener(v -> handleNumpadClick(v, "4"));
|
||||
btn5.setOnClickListener(v -> handleNumpadClick(v, "5"));
|
||||
btn6.setOnClickListener(v -> handleNumpadClick(v, "6"));
|
||||
btn7.setOnClickListener(v -> handleNumpadClick(v, "7"));
|
||||
btn8.setOnClickListener(v -> handleNumpadClick(v, "8"));
|
||||
btn9.setOnClickListener(v -> handleNumpadClick(v, "9"));
|
||||
btn0.setOnClickListener(v -> handleNumpadClick(v, "0"));
|
||||
btn000.setOnClickListener(v -> handleNumpadClick(v, "000"));
|
||||
|
||||
// Delete button
|
||||
btnDelete.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
deleteLastDigit();
|
||||
});
|
||||
|
||||
// Confirm button
|
||||
confirmButton.setOnClickListener(v -> {
|
||||
if (confirmButton.isEnabled()) {
|
||||
addButtonClickAnimation(v);
|
||||
handleConfirmPin();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleNumpadClick(View view, String digit) {
|
||||
addClickAnimation(view);
|
||||
addDigit(digit);
|
||||
}
|
||||
|
||||
private void setupInitialStates() {
|
||||
// Set initial PIN display
|
||||
editTextPin.setText("");
|
||||
|
||||
// Set initial button state
|
||||
updateButtonState();
|
||||
|
||||
// Disable EditText input (only numpad input allowed)
|
||||
editTextPin.setFocusable(false);
|
||||
editTextPin.setClickable(false);
|
||||
editTextPin.setCursorVisible(false);
|
||||
}
|
||||
|
||||
private void addDigit(String digit) {
|
||||
// Validate input length
|
||||
if (currentPin.length() >= MAX_PIN_LENGTH) {
|
||||
showToast("Maksimal " + MAX_PIN_LENGTH + " digit");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle special case for 000
|
||||
if (digit.equals("000")) {
|
||||
if (currentPin.length() + 3 <= MAX_PIN_LENGTH) {
|
||||
currentPin.append("000");
|
||||
} else {
|
||||
int remainingLength = MAX_PIN_LENGTH - currentPin.length();
|
||||
if (remainingLength > 0) {
|
||||
currentPin.append("0".repeat(remainingLength));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentPin.append(digit);
|
||||
}
|
||||
|
||||
updatePinDisplay();
|
||||
updateButtonState();
|
||||
addInputFeedback();
|
||||
}
|
||||
|
||||
private void deleteLastDigit() {
|
||||
if (currentPin.length() > 0) {
|
||||
String current = currentPin.toString();
|
||||
|
||||
// If current ends with 000, remove all three digits
|
||||
if (current.endsWith("000") && current.length() >= 3) {
|
||||
currentPin.delete(currentPin.length() - 3, currentPin.length());
|
||||
} else {
|
||||
currentPin.deleteCharAt(currentPin.length() - 1);
|
||||
}
|
||||
|
||||
updatePinDisplay();
|
||||
updateButtonState();
|
||||
addDeleteFeedback();
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePinDisplay() {
|
||||
String pin = currentPin.toString();
|
||||
|
||||
if (pin.isEmpty()) {
|
||||
editTextPin.setText("");
|
||||
} else {
|
||||
// Convert digits to asterisks for security
|
||||
String maskedPin = "*".repeat(pin.length());
|
||||
editTextPin.setText(maskedPin);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtonState() {
|
||||
boolean hasValidPin = currentPin.length() >= MIN_PIN_LENGTH;
|
||||
|
||||
confirmButton.setEnabled(hasValidPin);
|
||||
|
||||
if (hasValidPin) {
|
||||
// Active state
|
||||
confirmButton.setBackgroundResource(R.drawable.button_active_background);
|
||||
confirmButton.setTextColor(Color.WHITE);
|
||||
confirmButton.setAlpha(1.0f);
|
||||
} else {
|
||||
// Inactive state
|
||||
confirmButton.setBackgroundResource(R.drawable.button_inactive_background);
|
||||
confirmButton.setTextColor(Color.parseColor("#999999"));
|
||||
confirmButton.setAlpha(0.6f);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConfirmPin() {
|
||||
String pin = currentPin.toString();
|
||||
|
||||
if (TextUtils.isEmpty(pin)) {
|
||||
showToast("Masukkan PIN");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pin.length() < MIN_PIN_LENGTH) {
|
||||
showToast("PIN minimal " + MIN_PIN_LENGTH + " digit");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process PIN verification
|
||||
verifyPin(pin);
|
||||
}
|
||||
|
||||
private void verifyPin(String pin) {
|
||||
// Show loading state
|
||||
confirmButton.setText("Memverifikasi...");
|
||||
confirmButton.setEnabled(false);
|
||||
|
||||
// Simulate PIN verification
|
||||
animationHandler.postDelayed(() -> {
|
||||
// For demo purposes, accept any PIN with length >= 4
|
||||
// In real implementation, this would call backend API
|
||||
|
||||
if (isValidPin(pin)) {
|
||||
// Show success screen instead of toast
|
||||
handleSuccessfulVerification();
|
||||
} else {
|
||||
showToast("PIN tidak valid. Silakan coba lagi.");
|
||||
resetPinState();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private boolean isValidPin(String pin) {
|
||||
// Demo validation - in real app, this would validate against backend
|
||||
// For now, reject simple patterns like "1111", "1234", etc.
|
||||
return !pin.equals("1111") &&
|
||||
!pin.equals("1234") &&
|
||||
!pin.equals("0000") &&
|
||||
pin.length() >= MIN_PIN_LENGTH;
|
||||
}
|
||||
|
||||
private void handleSuccessfulVerification() {
|
||||
// Show full screen success message
|
||||
showSuccessScreen();
|
||||
|
||||
// Navigate to receipt page after 2.5 seconds
|
||||
animationHandler.postDelayed(() -> {
|
||||
navigateToReceiptPage();
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
private void showSuccessScreen() {
|
||||
if (successScreen != null) {
|
||||
// Hide all other UI components first
|
||||
hideMainUIComponents();
|
||||
|
||||
// Set success message
|
||||
if (successMessage != null) {
|
||||
successMessage.setText("Pembayaran Berhasil");
|
||||
}
|
||||
|
||||
// Show success screen with fade in animation
|
||||
successScreen.setVisibility(View.VISIBLE);
|
||||
successScreen.setAlpha(0f);
|
||||
|
||||
// Fade in the background
|
||||
ObjectAnimator backgroundFadeIn = ObjectAnimator.ofFloat(successScreen, "alpha", 0f, 1f);
|
||||
backgroundFadeIn.setDuration(500);
|
||||
backgroundFadeIn.start();
|
||||
|
||||
// Add scale and bounce animation to success icon
|
||||
if (successIcon != null) {
|
||||
// Start with invisible icon
|
||||
successIcon.setScaleX(0f);
|
||||
successIcon.setScaleY(0f);
|
||||
successIcon.setAlpha(0f);
|
||||
|
||||
// Scale animation with bounce effect
|
||||
ObjectAnimator scaleX = ObjectAnimator.ofFloat(successIcon, "scaleX", 0f, 1.2f, 1f);
|
||||
ObjectAnimator scaleY = ObjectAnimator.ofFloat(successIcon, "scaleY", 0f, 1.2f, 1f);
|
||||
ObjectAnimator iconFadeIn = ObjectAnimator.ofFloat(successIcon, "alpha", 0f, 1f);
|
||||
|
||||
AnimatorSet iconAnimation = new AnimatorSet();
|
||||
iconAnimation.playTogether(scaleX, scaleY, iconFadeIn);
|
||||
iconAnimation.setDuration(800);
|
||||
iconAnimation.setStartDelay(300);
|
||||
iconAnimation.setInterpolator(new android.view.animation.OvershootInterpolator(1.2f));
|
||||
iconAnimation.start();
|
||||
}
|
||||
|
||||
// Add slide up animation to success message
|
||||
if (successMessage != null) {
|
||||
successMessage.setAlpha(0f);
|
||||
successMessage.setTranslationY(50f);
|
||||
|
||||
ObjectAnimator messageSlideUp = ObjectAnimator.ofFloat(successMessage, "translationY", 50f, 0f);
|
||||
ObjectAnimator messageFadeIn = ObjectAnimator.ofFloat(successMessage, "alpha", 0f, 1f);
|
||||
|
||||
AnimatorSet messageAnimation = new AnimatorSet();
|
||||
messageAnimation.playTogether(messageSlideUp, messageFadeIn);
|
||||
messageAnimation.setDuration(600);
|
||||
messageAnimation.setStartDelay(600);
|
||||
messageAnimation.setInterpolator(new android.view.animation.DecelerateInterpolator());
|
||||
messageAnimation.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void hideMainUIComponents() {
|
||||
// Hide all main UI components to create clean full screen success
|
||||
if (backNavigation != null) {
|
||||
backNavigation.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Hide the red header backgrounds
|
||||
View redStatusBar = findViewById(R.id.red_status_bar);
|
||||
View redHeaderBackground = findViewById(R.id.red_header_background);
|
||||
if (redStatusBar != null) {
|
||||
redStatusBar.setVisibility(View.GONE);
|
||||
}
|
||||
if (redHeaderBackground != null) {
|
||||
redHeaderBackground.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Hide PIN card
|
||||
View pinCard = findViewById(R.id.pin_card);
|
||||
if (pinCard != null) {
|
||||
pinCard.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Hide numpad
|
||||
View numpadGrid = findViewById(R.id.numpad_grid);
|
||||
if (numpadGrid != null) {
|
||||
numpadGrid.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Hide confirm button
|
||||
if (confirmButton != null) {
|
||||
confirmButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void navigateToReceiptPage() {
|
||||
// Create intent to navigate to receipt/struk page
|
||||
Intent intent = new Intent(this, ReceiptActivity.class);
|
||||
|
||||
// Pass transaction data
|
||||
intent.putExtra("transaction_amount", amount);
|
||||
intent.putExtra("pin_verified", true);
|
||||
intent.putExtra("source_activity", sourceActivity);
|
||||
|
||||
// Add transaction details (you can customize these)
|
||||
intent.putExtra("merchant_name", "TOKO KLONTONG PAK EKO");
|
||||
intent.putExtra("merchant_location", "Ciputat Baru, Tangsel");
|
||||
intent.putExtra("transaction_id", generateTransactionId());
|
||||
intent.putExtra("transaction_date", getCurrentDateTime());
|
||||
intent.putExtra("payment_method", "Kartu Kredit");
|
||||
intent.putExtra("card_type", "BCA");
|
||||
intent.putExtra("tax_percentage", "11%");
|
||||
intent.putExtra("service_fee", "500");
|
||||
|
||||
startActivity(intent);
|
||||
|
||||
// Set result for calling activity
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra("pin_verified", true);
|
||||
resultIntent.putExtra("pin_length", currentPin.length());
|
||||
|
||||
if (!TextUtils.isEmpty(amount)) {
|
||||
resultIntent.putExtra(EXTRA_AMOUNT, amount);
|
||||
}
|
||||
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
|
||||
// Finish this activity
|
||||
finish();
|
||||
}
|
||||
|
||||
private String generateTransactionId() {
|
||||
// Generate a simple transaction ID (in real app, this would come from backend)
|
||||
return String.valueOf(System.currentTimeMillis() % 1000000000L);
|
||||
}
|
||||
|
||||
private String getCurrentDateTime() {
|
||||
// Get current date and time (in real app, use proper date formatting)
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", java.util.Locale.getDefault());
|
||||
return sdf.format(new java.util.Date());
|
||||
}
|
||||
|
||||
private void resetPinState() {
|
||||
currentPin = new StringBuilder();
|
||||
updatePinDisplay();
|
||||
updateButtonState();
|
||||
confirmButton.setText("Konfirmasi");
|
||||
confirmButton.setEnabled(false);
|
||||
}
|
||||
|
||||
// Animation methods
|
||||
private void addClickAnimation(View view) {
|
||||
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
|
||||
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(scaleX, scaleY);
|
||||
animatorSet.setDuration(150);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
private void addButtonClickAnimation(View view) {
|
||||
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f);
|
||||
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playTogether(scaleX, scaleY);
|
||||
animatorSet.setDuration(200);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
private void addInputFeedback() {
|
||||
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextPin, "alpha", 0.7f, 1f);
|
||||
fadeIn.setDuration(200);
|
||||
fadeIn.start();
|
||||
}
|
||||
|
||||
private void addDeleteFeedback() {
|
||||
ObjectAnimator shake = ObjectAnimator.ofFloat(editTextPin, "translationX", 0f, -10f, 10f, 0f);
|
||||
shake.setDuration(300);
|
||||
shake.start();
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
private void showToast(String message) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Prevent back press when success screen is showing
|
||||
if (successScreen != null && successScreen.getVisibility() == View.VISIBLE) {
|
||||
return;
|
||||
}
|
||||
navigateBack();
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (animationHandler != null) {
|
||||
animationHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods for testing
|
||||
public String getCurrentPin() {
|
||||
return currentPin.toString();
|
||||
}
|
||||
|
||||
public boolean isConfirmButtonEnabled() {
|
||||
return confirmButton.isEnabled();
|
||||
}
|
||||
|
||||
// Static helper method to launch PinActivity
|
||||
public static void launch(android.content.Context context, String sourceActivity, String amount) {
|
||||
Intent intent = new Intent(context, PinActivity.class);
|
||||
intent.putExtra(EXTRA_SOURCE_ACTIVITY, sourceActivity);
|
||||
if (!TextUtils.isEmpty(amount)) {
|
||||
intent.putExtra(EXTRA_AMOUNT, amount);
|
||||
}
|
||||
|
||||
// Launch for result if context is an Activity
|
||||
if (context instanceof AppCompatActivity) {
|
||||
((AppCompatActivity) context).startActivityForResult(intent, 100);
|
||||
} else {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
|
||||
import com.example.bdkipoc.cetakulang.ReprintActivity;
|
||||
|
||||
public class ReceiptActivity extends AppCompatActivity {
|
||||
|
||||
// Views
|
||||
@ -902,9 +904,9 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
|
||||
if (callingActivity != null) {
|
||||
switch (callingActivity) {
|
||||
case "TransactionActivity":
|
||||
case "ReprintActivity":
|
||||
// Go back to transaction list
|
||||
Intent transactionIntent = new Intent(this, TransactionActivity.class);
|
||||
Intent transactionIntent = new Intent(this, ReprintActivity.class);
|
||||
transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
startActivity(transactionIntent);
|
||||
break;
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.example.bdkipoc;
|
||||
package com.example.bdkipoc.cetakulang;
|
||||
|
||||
import com.example.bdkipoc.R;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
@ -50,9 +51,12 @@ import java.util.TimeZone;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
public class TransactionActivity extends AppCompatActivity implements TransactionAdapter.OnPrintClickListener {
|
||||
import com.example.bdkipoc.ReceiptActivity;
|
||||
import com.example.bdkipoc.StyleHelper;
|
||||
|
||||
public class ReprintActivity extends AppCompatActivity implements ReprintAdapterActivity.OnPrintClickListener {
|
||||
private RecyclerView recyclerView;
|
||||
private TransactionAdapter adapter;
|
||||
private ReprintAdapterActivity adapter;
|
||||
private List<Transaction> transactionList;
|
||||
private List<Transaction> filteredList;
|
||||
private ProgressBar progressBar;
|
||||
@ -89,7 +93,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_transaction);
|
||||
setContentView(R.layout.activity_reprint);
|
||||
|
||||
// ✅ Initialize SharedPreferences for local tracking
|
||||
prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE);
|
||||
@ -159,7 +163,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
transactionList = new ArrayList<>();
|
||||
filteredList = new ArrayList<>();
|
||||
|
||||
adapter = new TransactionAdapter(filteredList);
|
||||
adapter = new ReprintAdapterActivity(filteredList);
|
||||
adapter.setPrintClickListener(this);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||
@ -342,13 +346,13 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
filterButtonText.setTextColor(getResources().getColor(android.R.color.holo_blue_dark));
|
||||
filterButtonText.setTextSize(12); // Smaller text when filter is active
|
||||
|
||||
Log.d("TransactionActivity", "🎨 Filter button updated: " + displayText);
|
||||
Log.d("ReprintActivity", "🎨 Filter button updated: " + displayText);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Apply date filter
|
||||
private void applyDateFilter() {
|
||||
Log.d("TransactionActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate);
|
||||
Log.d("ReprintActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate);
|
||||
|
||||
// Reset to first page and reload data
|
||||
currentPage = 1;
|
||||
@ -366,7 +370,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
filterButtonText.setTextSize(14); // Reset to normal size
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🗓️ Date filter cleared");
|
||||
Log.d("ReprintActivity", "🗓️ Date filter cleared");
|
||||
|
||||
// Reload data without date filter
|
||||
currentPage = 1;
|
||||
@ -377,7 +381,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🔄 Navigating to page " + page);
|
||||
Log.d("ReprintActivity", "🔄 Navigating to page " + page);
|
||||
|
||||
if (currentSearchQuery.isEmpty()) {
|
||||
// Load from API
|
||||
@ -410,7 +414,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
// Scroll to top
|
||||
recyclerView.scrollToPosition(0);
|
||||
|
||||
Log.d("TransactionActivity", "📄 Displaying search results page " + currentPage +
|
||||
Log.d("ReprintActivity", "📄 Displaying search results page " + currentPage +
|
||||
" (items " + (startIndex + 1) + "-" + endIndex + " of " + filteredList.size() + ")");
|
||||
}
|
||||
|
||||
@ -452,10 +456,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
|
||||
|
||||
// ✅ PASTIKAN TIDAK PERLU SORT LAGI karena sudah sorted dari API response
|
||||
Log.d("TransactionActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):");
|
||||
Log.d("ReprintActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):");
|
||||
for (int i = 0; i < Math.min(5, filteredList.size()); i++) {
|
||||
Transaction tx = filteredList.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
} else {
|
||||
// ✅ SEARCH MODE: Filter all available data
|
||||
@ -518,7 +522,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
paginationControls.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "📊 Pagination updated: " +
|
||||
Log.d("ReprintActivity", "📊 Pagination updated: " +
|
||||
"Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords);
|
||||
}
|
||||
|
||||
@ -584,7 +588,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
pageNumbersContainer.addView(pageButton);
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🔢 Page buttons created: " + startPage + " to " + endPage +
|
||||
Log.d("ReprintActivity", "🔢 Page buttons created: " + startPage + " to " + endPage +
|
||||
" with size: " + buttonSize + "px");
|
||||
}
|
||||
|
||||
@ -613,7 +617,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage +
|
||||
"&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=" + fromDate + "&to_date=" + toDate + "&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at";
|
||||
|
||||
Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad +
|
||||
Log.d("ReprintActivity", "🔍 Fetching transactions page " + pageToLoad +
|
||||
" (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at" +
|
||||
" - Date Filter: " + fromDate + " to " + toDate);
|
||||
|
||||
@ -642,7 +646,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
apiTotal = results.getInt("total");
|
||||
JSONArray data = results.getJSONArray("data");
|
||||
|
||||
Log.d("TransactionActivity", "📊 API response: " + data.length() +
|
||||
Log.d("ReprintActivity", "📊 API response: " + data.length() +
|
||||
" records, total: " + apiTotal);
|
||||
|
||||
// ✅ STEP 1: Parse all transactions from API
|
||||
@ -667,14 +671,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
// ✅ STEP 2: Apply intelligent deduplication
|
||||
result = applyAdvancedDeduplication(rawTransactions);
|
||||
|
||||
Log.d("TransactionActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
|
||||
Log.d("ReprintActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
|
||||
|
||||
} else {
|
||||
Log.e("TransactionActivity", "❌ HTTP Error: " + responseCode);
|
||||
Log.e("ReprintActivity", "❌ HTTP Error: " + responseCode);
|
||||
error = true;
|
||||
}
|
||||
} catch (IOException | JSONException | URISyntaxException e) {
|
||||
Log.e("TransactionActivity", "❌ Exception: " + e.getMessage(), e);
|
||||
Log.e("ReprintActivity", "❌ Exception: " + e.getMessage(), e);
|
||||
error = true;
|
||||
}
|
||||
return result;
|
||||
@ -687,7 +691,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if (error) {
|
||||
Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(ReprintActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
|
||||
updatePaginationDisplay();
|
||||
return;
|
||||
}
|
||||
@ -705,11 +709,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
if (date1 != null && date2 != null) {
|
||||
int comparison = date2.compareTo(date1); // Newest first
|
||||
Log.d("TransactionActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison);
|
||||
Log.d("ReprintActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison);
|
||||
return comparison;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionActivity", "Date comparison error: " + e.getMessage());
|
||||
Log.w("ReprintActivity", "Date comparison error: " + e.getMessage());
|
||||
}
|
||||
return Integer.compare(t2.id, t1.id); // Fallback by ID (higher ID = newer)
|
||||
});
|
||||
@ -718,14 +722,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
transactionList.clear();
|
||||
transactionList.addAll(transactions);
|
||||
|
||||
Log.d("TransactionActivity", "📋 Page " + currentPage + " loaded and sorted: " +
|
||||
Log.d("ReprintActivity", "📋 Page " + currentPage + " loaded and sorted: " +
|
||||
transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages");
|
||||
|
||||
// ✅ VERIFIKASI SORTING ORDER
|
||||
Log.d("TransactionActivity", "📋 SORTED ORDER VERIFICATION:");
|
||||
Log.d("ReprintActivity", "📋 SORTED ORDER VERIFICATION:");
|
||||
for (int i = 0; i < Math.min(5, transactionList.size()); i++) {
|
||||
Transaction tx = transactionList.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
|
||||
// Update filtered list based on current search
|
||||
@ -761,7 +765,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // Handle timezone properly
|
||||
Date parsed = sdf.parse(rawDate);
|
||||
Log.d("TransactionActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format);
|
||||
Log.d("ReprintActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format);
|
||||
return parsed;
|
||||
} catch (Exception e) {
|
||||
// Continue to next format
|
||||
@ -779,10 +783,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
Date parsed = sdf.parse(cleanedDate);
|
||||
Log.d("TransactionActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed);
|
||||
Log.d("ReprintActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed);
|
||||
return parsed;
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage());
|
||||
Log.w("ReprintActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -791,11 +795,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
* ✅ ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
|
||||
*/
|
||||
private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
|
||||
Log.d("TransactionActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("TransactionActivity", "📥 Input transactions order (first 5):");
|
||||
Log.d("ReprintActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("ReprintActivity", "📥 Input transactions order (first 5):");
|
||||
for (int i = 0; i < Math.min(5, rawTransactions.size()); i++) {
|
||||
Transaction tx = rawTransactions.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId);
|
||||
Log.d("ReprintActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId);
|
||||
}
|
||||
|
||||
// Strategy 1: Group by reference_id
|
||||
@ -823,7 +827,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
if (group.size() == 1) {
|
||||
// No duplicates for this reference
|
||||
deduplicatedList.add(group.get(0));
|
||||
Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId);
|
||||
Log.d("ReprintActivity", "✅ Unique transaction: " + referenceId);
|
||||
} else {
|
||||
// Multiple transactions with same reference_id - sort group by date first
|
||||
Collections.sort(group, (t1, t2) -> {
|
||||
@ -843,15 +847,15 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
deduplicatedList.add(bestTransaction);
|
||||
duplicatesRemoved += (group.size() - 1);
|
||||
|
||||
Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId +
|
||||
Log.d("ReprintActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId +
|
||||
" (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ", date: " + bestTransaction.createdAt + ")");
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "✅ Advanced deduplication complete:");
|
||||
Log.d("TransactionActivity", " 📥 Input: " + rawTransactions.size() + " transactions");
|
||||
Log.d("TransactionActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions");
|
||||
Log.d("TransactionActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates");
|
||||
Log.d("ReprintActivity", "✅ Advanced deduplication complete:");
|
||||
Log.d("ReprintActivity", " 📥 Input: " + rawTransactions.size() + " transactions");
|
||||
Log.d("ReprintActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions");
|
||||
Log.d("ReprintActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates");
|
||||
|
||||
return deduplicatedList;
|
||||
}
|
||||
@ -860,7 +864,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
* ✅ ENHANCED SELECTION: Advanced algorithm to pick the best transaction
|
||||
*/
|
||||
private Transaction selectBestTransactionAdvanced(List<Transaction> duplicates, String referenceId) {
|
||||
Log.d("TransactionActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId);
|
||||
Log.d("ReprintActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId);
|
||||
|
||||
Transaction bestTransaction = duplicates.get(0);
|
||||
int bestPriority = getStatusPriority(bestTransaction.status);
|
||||
@ -871,7 +875,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
int currentPriority = getStatusPriority(tx.status);
|
||||
Date currentDate = parseCreatedAtDate(tx.createdAt);
|
||||
|
||||
Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
Log.d("ReprintActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
", Status=" + tx.status + " (priority=" + currentPriority + ")" +
|
||||
", Created=" + tx.createdAt);
|
||||
|
||||
@ -903,11 +907,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
bestTransaction = tx;
|
||||
bestPriority = currentPriority;
|
||||
bestDate = currentDate;
|
||||
Log.d("TransactionActivity", " ⭐ NEW BEST selected: " + reason);
|
||||
Log.d("ReprintActivity", " ⭐ NEW BEST selected: " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id +
|
||||
Log.d("ReprintActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id +
|
||||
", Status=" + bestTransaction.status + ", Created=" + bestTransaction.createdAt);
|
||||
|
||||
return bestTransaction;
|
||||
@ -925,7 +929,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
return date1.after(date2);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionActivity", "Date comparison error, falling back to ID comparison");
|
||||
Log.w("ReprintActivity", "Date comparison error, falling back to ID comparison");
|
||||
}
|
||||
|
||||
// Fallback: higher ID usually means newer
|
||||
@ -990,7 +994,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
// Tier 5: Unknown status
|
||||
default:
|
||||
Log.w("TransactionActivity", "Unknown status encountered: " + status);
|
||||
Log.w("ReprintActivity", "Unknown status encountered: " + status);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -1002,12 +1006,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
Intent intent = new Intent(this, ReceiptActivity.class);
|
||||
|
||||
// Add calling activity information for proper back navigation
|
||||
intent.putExtra("calling_activity", "TransactionActivity");
|
||||
intent.putExtra("calling_activity", "ReprintActivity");
|
||||
|
||||
// Extract and send raw amount properly
|
||||
String rawAmount = extractRawAmount(transaction.amount);
|
||||
|
||||
Log.d("TransactionActivity", "Opening receipt for transaction: " + transaction.referenceId +
|
||||
Log.d("ReprintActivity", "Opening receipt for transaction: " + transaction.referenceId +
|
||||
", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'");
|
||||
|
||||
// Send transaction data to ReceiptActivity
|
||||
@ -1032,7 +1036,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
|
||||
intent.putExtra("acquirer", acquirer); // Jenis Kartu
|
||||
|
||||
Log.d("TransactionActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
|
||||
Log.d("ReprintActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
@ -1084,7 +1088,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
// For QRIS, we could implement real-time acquirer lookup here
|
||||
// For now, return "qris" and let ReceiptActivity handle the detection
|
||||
Log.d("TransactionActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity");
|
||||
Log.d("ReprintActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity");
|
||||
return "qris";
|
||||
}
|
||||
|
||||
@ -1128,7 +1132,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
Long.parseLong(cleaned);
|
||||
return cleaned;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("TransactionActivity", "Invalid amount: " + formattedAmount);
|
||||
Log.e("ReprintActivity", "Invalid amount: " + formattedAmount);
|
||||
return "0";
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.example.bdkipoc;
|
||||
package com.example.bdkipoc.cetakulang;
|
||||
|
||||
import com.example.bdkipoc.R;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -26,15 +27,17 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
|
||||
private List<TransactionActivity.Transaction> transactionList;
|
||||
import com.example.bdkipoc.StyleHelper;
|
||||
|
||||
public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterActivity.TransactionViewHolder> {
|
||||
private List<ReprintActivity.Transaction> transactionList;
|
||||
private OnPrintClickListener printClickListener;
|
||||
|
||||
public interface OnPrintClickListener {
|
||||
void onPrintClick(TransactionActivity.Transaction transaction);
|
||||
void onPrintClick(ReprintActivity.Transaction transaction);
|
||||
}
|
||||
|
||||
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
|
||||
public ReprintAdapterActivity(List<ReprintActivity.Transaction> transactionList) {
|
||||
this.transactionList = transactionList;
|
||||
}
|
||||
|
||||
@ -45,23 +48,23 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
/**
|
||||
* Update data without numbering (removed as per request)
|
||||
*/
|
||||
public void updateData(List<TransactionActivity.Transaction> newData, int startIndex) {
|
||||
public void updateData(List<ReprintActivity.Transaction> newData, int startIndex) {
|
||||
this.transactionList = newData;
|
||||
notifyDataSetChanged();
|
||||
|
||||
Log.d("TransactionAdapter", "📋 Data updated: " + newData.size() + " items");
|
||||
Log.d("ReprintAdapterActivity", "📋 Data updated: " + newData.size() + " items");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false);
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reprint, parent, false);
|
||||
return new TransactionViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
|
||||
TransactionActivity.Transaction t = transactionList.get(position);
|
||||
ReprintActivity.Transaction t = transactionList.get(position);
|
||||
|
||||
// ✅ STRIPE TABLE: Set alternating row colors
|
||||
LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer);
|
||||
@ -73,10 +76,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.background_light));
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":");
|
||||
Log.d("TransactionAdapter", " Reference: " + t.referenceId);
|
||||
Log.d("TransactionAdapter", " Status: " + t.status);
|
||||
Log.d("TransactionAdapter", " Amount: " + t.amount);
|
||||
Log.d("ReprintAdapterActivity", "📋 Binding transaction " + position + ":");
|
||||
Log.d("ReprintAdapterActivity", " Reference: " + t.referenceId);
|
||||
Log.d("ReprintAdapterActivity", " Status: " + t.status);
|
||||
Log.d("ReprintAdapterActivity", " Amount: " + t.amount);
|
||||
|
||||
// Set reference ID
|
||||
holder.referenceId.setText(t.referenceId);
|
||||
@ -88,10 +91,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String formattedAmount = formatRupiah(amountValue);
|
||||
holder.amount.setText(formattedAmount);
|
||||
|
||||
Log.d("TransactionAdapter", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
|
||||
Log.d("ReprintAdapterActivity", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("TransactionAdapter", "❌ Amount format error: " + t.amount, e);
|
||||
Log.e("ReprintAdapterActivity", "❌ Amount format error: " + t.amount, e);
|
||||
String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount;
|
||||
holder.amount.setText(fallback);
|
||||
}
|
||||
@ -99,7 +102,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
// ✅ ENHANCED STATUS HANDLING dengan comprehensive checking
|
||||
String displayStatus = t.status;
|
||||
|
||||
Log.d("TransactionAdapter", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")");
|
||||
Log.d("ReprintAdapterActivity", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")");
|
||||
|
||||
// Jika status adalah INIT atau PENDING, lakukan comprehensive check
|
||||
if ("INIT".equalsIgnoreCase(t.status) || "PENDING".equalsIgnoreCase(t.status)) {
|
||||
@ -108,7 +111,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
holder.status.setText("CHECKING...");
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING");
|
||||
|
||||
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId);
|
||||
Log.d("ReprintAdapterActivity", "🔄 Starting comprehensive check for: " + t.referenceId);
|
||||
|
||||
// Check real status dari semua kemungkinan sources
|
||||
checkMidtransStatus(t.referenceId, holder.status);
|
||||
@ -116,13 +119,13 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
// No reference ID to check
|
||||
holder.status.setText(displayStatus.toUpperCase());
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
|
||||
Log.w("TransactionAdapter", "⚠️ No reference ID for status check");
|
||||
Log.w("ReprintAdapterActivity", "⚠️ No reference ID for status check");
|
||||
}
|
||||
} else {
|
||||
// Use existing status yang sudah confirmed
|
||||
holder.status.setText(displayStatus.toUpperCase());
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
|
||||
Log.d("TransactionAdapter", "✅ Using confirmed status: " + displayStatus);
|
||||
Log.d("ReprintAdapterActivity", "✅ Using confirmed status: " + displayStatus);
|
||||
}
|
||||
|
||||
// Set payment method
|
||||
@ -133,7 +136,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String formattedDate = formatCreatedAtDate(t.createdAt);
|
||||
holder.createdAt.setText(formattedDate);
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
|
||||
Log.d("ReprintAdapterActivity", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
|
||||
|
||||
// Set click listeners
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
@ -148,7 +151,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
});
|
||||
|
||||
Log.d("TransactionAdapter", "✅ Transaction binding complete for: " + t.referenceId);
|
||||
Log.d("ReprintAdapterActivity", "✅ Transaction binding complete for: " + t.referenceId);
|
||||
}
|
||||
|
||||
private String cleanAmountString(String amount) {
|
||||
@ -156,7 +159,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
return "0";
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "Cleaning amount: '" + amount + "'");
|
||||
Log.d("ReprintAdapterActivity", "Cleaning amount: '" + amount + "'");
|
||||
|
||||
// Remove currency symbols and spaces
|
||||
String cleaned = amount
|
||||
@ -199,7 +202,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
// Remove any commas
|
||||
cleaned = cleaned.replace(",", "");
|
||||
|
||||
Log.d("TransactionAdapter", "Cleaned result: '" + cleaned + "'");
|
||||
Log.d("ReprintAdapterActivity", "Cleaned result: '" + cleaned + "'");
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
@ -216,7 +219,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
private void checkMidtransStatus(String referenceId, TextView statusTextView) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId);
|
||||
Log.d("ReprintAdapterActivity", "🔍 Comprehensive status check for reference: " + referenceId);
|
||||
|
||||
// STEP 1: Query webhook logs untuk semua order_id yang terkait
|
||||
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at";
|
||||
@ -244,7 +247,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String foundAcquirer = null;
|
||||
|
||||
if (results != null && results.length() > 0) {
|
||||
Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries");
|
||||
Log.d("ReprintAdapterActivity", "📊 Processing " + results.length() + " log entries");
|
||||
|
||||
// STEP 2: Comprehensive search dengan multiple matching strategies
|
||||
for (int i = 0; i < results.length(); i++) {
|
||||
@ -270,7 +273,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String appReferenceId = customData.optString("app_reference_id", "");
|
||||
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
|
||||
isRefreshMatch = true;
|
||||
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", "🔄 Found refresh match: " + logOrderId);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Ignore custom field parsing errors
|
||||
@ -288,7 +291,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
if (itemName.contains("(Ref: " + referenceId + ")") ||
|
||||
itemName.contains("- " + referenceId)) {
|
||||
isItemMatch = true;
|
||||
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", "📦 Found item match: " + logOrderId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -299,11 +302,11 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch;
|
||||
|
||||
if (isRelatedTransaction) {
|
||||
Log.d("TransactionAdapter", "🎯 MATCH FOUND!");
|
||||
Log.d("TransactionAdapter", " Order ID: " + logOrderId);
|
||||
Log.d("TransactionAdapter", " Status: " + logTransactionStatus);
|
||||
Log.d("TransactionAdapter", " Acquirer: " + logAcquirer);
|
||||
Log.d("TransactionAdapter", " Match Type: " +
|
||||
Log.d("ReprintAdapterActivity", "🎯 MATCH FOUND!");
|
||||
Log.d("ReprintAdapterActivity", " Order ID: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", " Status: " + logTransactionStatus);
|
||||
Log.d("ReprintAdapterActivity", " Acquirer: " + logAcquirer);
|
||||
Log.d("ReprintAdapterActivity", " Match Type: " +
|
||||
(isDirectMatch ? "DIRECT " : "") +
|
||||
(isRefreshMatch ? "REFRESH " : "") +
|
||||
(isItemMatch ? "ITEM" : ""));
|
||||
@ -315,29 +318,29 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
finalStatus = "PAID";
|
||||
foundOrderId = logOrderId;
|
||||
foundAcquirer = logAcquirer;
|
||||
Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
|
||||
Log.d("ReprintAdapterActivity", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
|
||||
break; // Found paid status, stop searching
|
||||
} else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) {
|
||||
finalStatus = "PENDING";
|
||||
foundOrderId = logOrderId;
|
||||
foundAcquirer = logAcquirer;
|
||||
Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", "⏳ PENDING found: " + logOrderId);
|
||||
} else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) {
|
||||
if (finalStatus.equals("INIT")) { // Only update if no better status found
|
||||
finalStatus = "FAILED";
|
||||
foundOrderId = logOrderId;
|
||||
foundAcquirer = logAcquirer;
|
||||
Log.d("TransactionAdapter", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus);
|
||||
Log.d("ReprintAdapterActivity", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "🔍 FINAL RESULT for " + referenceId + ":");
|
||||
Log.d("TransactionAdapter", " Status: " + finalStatus);
|
||||
Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
|
||||
Log.d("TransactionAdapter", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A"));
|
||||
Log.d("ReprintAdapterActivity", "🔍 FINAL RESULT for " + referenceId + ":");
|
||||
Log.d("ReprintAdapterActivity", " Status: " + finalStatus);
|
||||
Log.d("ReprintAdapterActivity", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
|
||||
Log.d("ReprintAdapterActivity", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A"));
|
||||
}
|
||||
|
||||
// STEP 3: Update UI di main thread
|
||||
@ -348,10 +351,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
statusTextView.setText(displayStatus);
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus);
|
||||
|
||||
Log.d("TransactionAdapter", "🎨 UI UPDATED:");
|
||||
Log.d("TransactionAdapter", " Reference: " + referenceId);
|
||||
Log.d("TransactionAdapter", " Display Status: " + displayStatus);
|
||||
Log.d("TransactionAdapter", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown"));
|
||||
Log.d("ReprintAdapterActivity", "🎨 UI UPDATED:");
|
||||
Log.d("ReprintAdapterActivity", " Reference: " + referenceId);
|
||||
Log.d("ReprintAdapterActivity", " Display Status: " + displayStatus);
|
||||
Log.d("ReprintAdapterActivity", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown"));
|
||||
});
|
||||
|
||||
// ✅ BONUS: Update backend jika status berubah ke PAID
|
||||
@ -360,7 +363,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
Log.w("ReprintAdapterActivity", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("ERROR");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR");
|
||||
@ -368,7 +371,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e);
|
||||
Log.e("ReprintAdapterActivity", "❌ Comprehensive status check error: " + e.getMessage(), e);
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("INIT");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT");
|
||||
@ -383,7 +386,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
private void updateBackendTransactionStatus(String referenceId, String status, String orderId, String acquirer) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId);
|
||||
Log.d("ReprintAdapterActivity", "🔄 Updating backend status for reference: " + referenceId);
|
||||
|
||||
JSONObject updatePayload = new JSONObject();
|
||||
updatePayload.put("status", status);
|
||||
@ -414,16 +417,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("TransactionAdapter", "📥 Backend update response: " + responseCode);
|
||||
Log.d("ReprintAdapterActivity", "📥 Backend update response: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
Log.d("TransactionAdapter", "✅ Backend status updated successfully");
|
||||
Log.d("ReprintAdapterActivity", "✅ Backend status updated successfully");
|
||||
} else {
|
||||
Log.e("TransactionAdapter", "❌ Backend update failed: " + responseCode);
|
||||
Log.e("ReprintAdapterActivity", "❌ Backend update failed: " + responseCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("TransactionAdapter", "❌ Backend update error: " + e.getMessage(), e);
|
||||
Log.e("ReprintAdapterActivity", "❌ Backend update error: " + e.getMessage(), e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@ -436,7 +439,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Input date: '" + rawDate + "'");
|
||||
Log.d("ReprintAdapterActivity", "📅 Input date: '" + rawDate + "'");
|
||||
|
||||
try {
|
||||
// Handle different possible input formats from API
|
||||
@ -460,7 +463,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Cleaned date: '" + cleanedDate + "'");
|
||||
Log.d("ReprintAdapterActivity", "📅 Cleaned date: '" + cleanedDate + "'");
|
||||
|
||||
// Output format: d/M/yyyy H:mm:ss
|
||||
SimpleDateFormat outputFormat = new SimpleDateFormat("d/M/yyyy H:mm:ss", Locale.getDefault());
|
||||
@ -468,11 +471,11 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
Date date = inputFormat.parse(cleanedDate);
|
||||
if (date != null) {
|
||||
String formatted = outputFormat.format(date);
|
||||
Log.d("TransactionAdapter", "📅 Date formatted: " + rawDate + " -> " + formatted);
|
||||
Log.d("ReprintAdapterActivity", "📅 Date formatted: " + rawDate + " -> " + formatted);
|
||||
return formatted;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("TransactionAdapter", "❌ Date formatting error for: " + rawDate, e);
|
||||
Log.e("ReprintAdapterActivity", "❌ Date formatting error for: " + rawDate, e);
|
||||
}
|
||||
|
||||
// Fallback: Manual parsing
|
||||
@ -485,7 +488,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
workingDate = workingDate.substring(0, workingDate.indexOf("."));
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Manual parsing attempt: '" + workingDate + "'");
|
||||
Log.d("ReprintAdapterActivity", "📅 Manual parsing attempt: '" + workingDate + "'");
|
||||
|
||||
// Split into date and time parts
|
||||
String[] parts = workingDate.split(" ");
|
||||
@ -511,16 +514,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String second = timeComponents[2];
|
||||
|
||||
String result = dayInt + "/" + monthInt + "/" + year + " " + hour + ":" + minute + ":" + second;
|
||||
Log.d("TransactionAdapter", "📅 Manual format result: " + result);
|
||||
Log.d("ReprintAdapterActivity", "📅 Manual format result: " + result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionAdapter", "❌ Manual date formatting failed: " + e.getMessage());
|
||||
Log.w("ReprintAdapterActivity", "❌ Manual date formatting failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
Log.w("TransactionAdapter", "📅 Using fallback - returning original date: " + rawDate);
|
||||
Log.w("ReprintAdapterActivity", "📅 Using fallback - returning original date: " + rawDate);
|
||||
return rawDate;
|
||||
}
|
||||
|
@ -22,21 +22,24 @@ import com.example.bdkipoc.transaction.managers.CardScannerManager;
|
||||
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 java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.example.bdkipoc.kredit.CreditCardActivity;
|
||||
import com.example.bdkipoc.transaction.ResultTransactionActivity;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* CreateTransactionActivity - Updated UI to match screenshot design
|
||||
* Handles amount input and card scanning in one screen
|
||||
* Located in transaction package
|
||||
* CreateTransactionActivity - Updated with Midtrans Credit Card Integration
|
||||
* Handles amount input, card scanning, and Midtrans payment processing
|
||||
*/
|
||||
public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
EMVManager.EMVManagerCallback,
|
||||
CardScannerManager.CardScannerCallback,
|
||||
PinPadManager.PinPadManagerCallback {
|
||||
PinPadManager.PinPadManagerCallback,
|
||||
MidtransCardPaymentManager.MidtransCardPaymentCallback {
|
||||
|
||||
private static final String TAG = "CreateTransaction";
|
||||
|
||||
@ -55,16 +58,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
|
||||
|
||||
// Manager Classes
|
||||
private EMVManager emvManager;
|
||||
private CardScannerManager cardScannerManager;
|
||||
private PinPadManager pinPadManager;
|
||||
private ModalManager modalManager;
|
||||
private MidtransCardPaymentManager midtransPaymentManager; // ✅ NEW: Midtrans integration
|
||||
|
||||
// EMV Dialog
|
||||
private AlertDialog mAppSelectDialog;
|
||||
private int mSelectIndex;
|
||||
|
||||
// ✅ NEW: EMV Data Storage for Midtrans
|
||||
private String emvCardNumber;
|
||||
private String emvExpiryDate;
|
||||
private String emvCardholderName;
|
||||
private String emvAidIdentifier;
|
||||
private String emvTlvData;
|
||||
private String referenceId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -76,6 +89,10 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
setupListeners();
|
||||
updateAmountDisplay();
|
||||
updateModeDisplay();
|
||||
|
||||
// ✅ NEW: Generate reference ID for transaction tracking
|
||||
referenceId = generateReferenceId();
|
||||
Log.d(TAG, "Generated reference ID: " + referenceId);
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
@ -114,10 +131,13 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
cardScannerManager = new CardScannerManager(this);
|
||||
pinPadManager = new PinPadManager(this);
|
||||
|
||||
// ✅ NEW: Initialize Midtrans payment manager
|
||||
midtransPaymentManager = new MidtransCardPaymentManager(this, this);
|
||||
|
||||
// Initialize EMV data
|
||||
emvManager.initEMVData();
|
||||
|
||||
Log.d(TAG, "All managers initialized");
|
||||
Log.d(TAG, "All managers initialized including Midtrans");
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
@ -245,7 +265,7 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
Log.d(TAG, "Simple card detected: " + cardType);
|
||||
modalManager.showProcessingModal("Kartu " + cardType + " Ditemukan - Memproses...");
|
||||
|
||||
// Navigate to results after short delay
|
||||
// For simple mode, navigate to results without Midtrans
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
navigateToResults(cardType, cardData, null);
|
||||
}, 1500);
|
||||
@ -290,6 +310,10 @@ 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));
|
||||
|
||||
modalManager.showProcessingModal("Mengkonfirmasi Nomor Kartu...");
|
||||
|
||||
// Auto-confirm after short delay
|
||||
@ -340,8 +364,14 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
Log.d(TAG, "EMV Transaction successful");
|
||||
modalManager.hideModal();
|
||||
|
||||
String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
|
||||
navigateToResults(cardType, null, emvManager.getCardNo());
|
||||
// ✅ NEW: Process Midtrans payment after successful EMV
|
||||
if (useDirectMidtransPayment && emvCardNumber != null) {
|
||||
processMidtransPayment();
|
||||
} else {
|
||||
// Traditional flow - navigate to results
|
||||
String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
|
||||
navigateToResults(cardType, null, emvManager.getCardNo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -364,11 +394,6 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
// ====== PIN PAD CALLBACK METHODS ======
|
||||
@Override
|
||||
public void onPinInputLength(int length) {
|
||||
String dots = "";
|
||||
for (int i = 0; i < length; i++) {
|
||||
dots += "• ";
|
||||
}
|
||||
// Can show PIN input feedback on UI if needed
|
||||
Log.d(TAG, "PIN input length: " + length);
|
||||
}
|
||||
|
||||
@ -398,6 +423,131 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
emvManager.importPinInputStatus(3); // Error
|
||||
}
|
||||
|
||||
// ====== ✅ NEW: MIDTRANS PAYMENT CALLBACK METHODS ======
|
||||
@Override
|
||||
public void onTokenizeSuccess(String cardToken) {
|
||||
Log.d(TAG, "✅ Midtrans tokenization successful: " + cardToken);
|
||||
// Tokenization successful, charge process will continue automatically
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenizeError(String errorMessage) {
|
||||
Log.e(TAG, "❌ Midtrans tokenization failed: " + errorMessage);
|
||||
modalManager.hideModal();
|
||||
showToast("Payment tokenization failed: " + errorMessage);
|
||||
|
||||
// Fallback to traditional results screen
|
||||
String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
|
||||
navigateToResults(cardType, null, emvManager.getCardNo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChargeSuccess(JSONObject chargeResponse) {
|
||||
Log.d(TAG, "✅ Midtrans charge successful!");
|
||||
modalManager.hideModal();
|
||||
|
||||
try {
|
||||
String transactionId = chargeResponse.getString("transaction_id");
|
||||
String transactionStatus = chargeResponse.getString("transaction_status");
|
||||
|
||||
Log.d(TAG, "Transaction ID: " + transactionId);
|
||||
Log.d(TAG, "Transaction Status: " + transactionStatus);
|
||||
|
||||
// Navigate to success results with Midtrans data
|
||||
navigateToMidtransResults(chargeResponse);
|
||||
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChargeError(String errorMessage) {
|
||||
Log.e(TAG, "❌ Midtrans charge failed: " + errorMessage);
|
||||
modalManager.hideModal();
|
||||
showToast("Payment processing failed: " + errorMessage);
|
||||
|
||||
// Fallback to traditional results screen
|
||||
String cardType = emvManager.getCardType() == com.sunmi.pay.hardware.aidlv2.AidlConstantsV2.CardType.NFC.getValue() ? "NFC" : "IC";
|
||||
navigateToResults(cardType, null, emvManager.getCardNo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaymentProgress(String message) {
|
||||
Log.d(TAG, "Midtrans payment progress: " + message);
|
||||
modalManager.showProcessingModal(message);
|
||||
}
|
||||
|
||||
// ====== ✅ NEW: MIDTRANS PAYMENT PROCESSING ======
|
||||
private void processMidtransPayment() {
|
||||
Log.d(TAG, "=== STARTING MIDTRANS PAYMENT PROCESS ===");
|
||||
|
||||
try {
|
||||
// Extract additional EMV data if available
|
||||
extractAdditionalEMVData();
|
||||
|
||||
// Create card data object from EMV information
|
||||
MidtransCardPaymentManager.CardData cardData =
|
||||
MidtransCardPaymentManager.CardData.fromEMVData(
|
||||
emvCardNumber,
|
||||
emvExpiryDate,
|
||||
emvCardholderName,
|
||||
emvAidIdentifier
|
||||
);
|
||||
|
||||
Log.d(TAG, "EMV Card Data prepared:");
|
||||
Log.d(TAG, " - PAN: " + maskCardNumber(cardData.getPan()));
|
||||
Log.d(TAG, " - Expiry: " + cardData.getExpiryMonth() + "/" + cardData.getExpiryYear());
|
||||
Log.d(TAG, " - Cardholder: " + cardData.getCardholderName());
|
||||
Log.d(TAG, " - AID: " + cardData.getAidIdentifier());
|
||||
|
||||
if (!cardData.isValid()) {
|
||||
Log.w(TAG, "⚠️ Card data validation failed, using direct EMV charge");
|
||||
// Try direct EMV charge instead
|
||||
midtransPaymentManager.processEMVDirectCharge(
|
||||
cardData,
|
||||
Long.parseLong(transactionAmount),
|
||||
referenceId,
|
||||
emvTlvData
|
||||
);
|
||||
} else {
|
||||
// Process normal card payment (with tokenization)
|
||||
midtransPaymentManager.processCardPayment(
|
||||
cardData,
|
||||
Long.parseLong(transactionAmount),
|
||||
referenceId
|
||||
);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error preparing Midtrans payment: " + e.getMessage(), e);
|
||||
onChargeError("Failed to prepare payment data: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void extractAdditionalEMVData() {
|
||||
// This method would extract additional EMV data from the completed transaction
|
||||
// For now, we'll use placeholder data - in real implementation,
|
||||
// you would extract this from EMV TLV data
|
||||
|
||||
// Example: Extract cardholder name from tag 5F20
|
||||
emvCardholderName = "EMV CARDHOLDER"; // Placeholder
|
||||
|
||||
// Example: Extract expiry date from tag 5F24
|
||||
emvExpiryDate = "251220"; // Placeholder - format YYMMDD
|
||||
|
||||
// Example: Extract AID from tag 9F06
|
||||
emvAidIdentifier = "A0000000031010"; // Placeholder - Visa AID
|
||||
|
||||
// Example: Collect relevant TLV data for EMV processing
|
||||
emvTlvData = "9F2608=1234567890ABCDEF;9F2701=80;9F3602=0001"; // Placeholder
|
||||
|
||||
Log.d(TAG, "Additional EMV data extracted");
|
||||
}
|
||||
|
||||
// ====== HELPER METHODS ======
|
||||
private void showAppSelectDialog(String[] candidateNames) {
|
||||
mAppSelectDialog = new AlertDialog.Builder(this)
|
||||
@ -413,10 +563,11 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
private void navigateToResults(String cardType, Bundle cardData, String cardNo) {
|
||||
modalManager.hideModal();
|
||||
|
||||
Intent intent = new Intent(this, CreditCardActivity.class);
|
||||
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);
|
||||
@ -429,6 +580,23 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
finish();
|
||||
}
|
||||
|
||||
// ✅ NEW: Navigate to results with Midtrans payment data
|
||||
private void navigateToMidtransResults(JSONObject midtransResponse) {
|
||||
modalManager.hideModal();
|
||||
|
||||
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("MIDTRANS_RESPONSE", midtransResponse.toString());
|
||||
intent.putExtra("PAYMENT_SUCCESS", true);
|
||||
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void restartScanningAfterDelay() {
|
||||
Log.d(TAG, "Restarting scanning after delay...");
|
||||
|
||||
@ -471,6 +639,24 @@ public class CreateTransactionActivity extends AppCompatActivity implements
|
||||
private void showToast(String message) {
|
||||
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;
|
||||
}
|
||||
String first4 = cardNumber.substring(0, 4);
|
||||
String last4 = cardNumber.substring(cardNumber.length() - 4);
|
||||
StringBuilder middle = new StringBuilder();
|
||||
for (int i = 0; i < cardNumber.length() - 8; i++) {
|
||||
middle.append("*");
|
||||
}
|
||||
return first4 + middle.toString() + last4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.example.bdkipoc.kredit;
|
||||
package com.example.bdkipoc.transaction;
|
||||
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ClipData;
|
||||
@ -24,21 +24,24 @@ import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2;
|
||||
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.example.bdkipoc.transaction.CreateTransactionActivity;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* CreditCardActivity - Display detailed transaction results and TLV data
|
||||
* ResultTransactionActivity - Display detailed transaction results and TLV data
|
||||
* ✅ Updated to support Midtrans payment results
|
||||
*/
|
||||
public class CreditCardActivity extends AppCompatActivity {
|
||||
private static final String TAG = "CreditCard";
|
||||
public class ResultTransactionActivity extends AppCompatActivity {
|
||||
private static final String TAG = "ResultTransaction";
|
||||
|
||||
// UI Components
|
||||
private TextView tvTransactionSummary;
|
||||
@ -53,6 +56,12 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
private boolean isEMVMode;
|
||||
private String cardNo;
|
||||
private Bundle cardData;
|
||||
private String referenceId;
|
||||
|
||||
// ✅ NEW: Midtrans Integration Data
|
||||
private String midtransResponseJson;
|
||||
private boolean isPaymentSuccess;
|
||||
private JSONObject midtransResponse;
|
||||
|
||||
// EMV Components
|
||||
private EMVOptV2 mEMVOptV2;
|
||||
@ -75,19 +84,40 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
isEMVMode = intent.getBooleanExtra("EMV_MODE", true);
|
||||
cardNo = intent.getStringExtra("CARD_NO");
|
||||
cardData = intent.getBundleExtra("CARD_DATA");
|
||||
referenceId = intent.getStringExtra("REFERENCE_ID");
|
||||
|
||||
// ✅ NEW: Get Midtrans payment data
|
||||
midtransResponseJson = intent.getStringExtra("MIDTRANS_RESPONSE");
|
||||
isPaymentSuccess = intent.getBooleanExtra("PAYMENT_SUCCESS", false);
|
||||
|
||||
if (transactionAmount == null) {
|
||||
transactionAmount = "0";
|
||||
}
|
||||
|
||||
// ✅ NEW: Parse Midtrans response if available
|
||||
if (midtransResponseJson != null && !midtransResponseJson.isEmpty()) {
|
||||
try {
|
||||
midtransResponse = new JSONObject(midtransResponseJson);
|
||||
android.util.Log.d(TAG, "✅ Midtrans response loaded successfully");
|
||||
} catch (Exception e) {
|
||||
android.util.Log.e(TAG, "Error parsing Midtrans response: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
// Setup Toolbar
|
||||
// Setup Toolbar with updated title based on payment type
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle("Detail Transaksi");
|
||||
|
||||
// ✅ NEW: Update title based on payment type
|
||||
if (midtransResponse != null) {
|
||||
getSupportActionBar().setTitle("Detail Pembayaran Midtrans");
|
||||
} else {
|
||||
getSupportActionBar().setTitle("Detail Transaksi");
|
||||
}
|
||||
}
|
||||
|
||||
tvTransactionSummary = findViewById(R.id.tv_transaction_summary);
|
||||
@ -109,12 +139,142 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void loadCardData() {
|
||||
if (isEMVMode && mEMVOptV2 != null) {
|
||||
// ✅ NEW: Check if this is a Midtrans payment result
|
||||
if (midtransResponse != null) {
|
||||
loadMidtransPaymentData();
|
||||
} else if (isEMVMode && mEMVOptV2 != null) {
|
||||
loadEMVTlvData();
|
||||
} else {
|
||||
loadSimpleCardData();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW: Load and display Midtrans payment data
|
||||
private void loadMidtransPaymentData() {
|
||||
android.util.Log.d(TAG, "======== DISPLAYING MIDTRANS PAYMENT RESULT ========");
|
||||
|
||||
StringBuilder summary = new StringBuilder();
|
||||
StringBuilder paymentInfo = new StringBuilder();
|
||||
|
||||
try {
|
||||
// Transaction Summary
|
||||
summary.append("==== PEMBAYARAN BERHASIL ====\n");
|
||||
summary.append("Amount: ").append(formatAmount(Long.parseLong(transactionAmount))).append("\n");
|
||||
summary.append("Payment Method: Midtrans Credit Card\n");
|
||||
summary.append("Status: ").append(isPaymentSuccess ? "SUCCESS" : "PENDING").append("\n");
|
||||
|
||||
if (referenceId != null) {
|
||||
summary.append("Reference ID: ").append(referenceId).append("\n");
|
||||
}
|
||||
|
||||
// Midtrans Transaction Details
|
||||
paymentInfo.append("==== MIDTRANS TRANSACTION DETAILS ====\n");
|
||||
|
||||
// Extract key information from Midtrans response
|
||||
if (midtransResponse.has("transaction_id")) {
|
||||
paymentInfo.append("Transaction ID: ").append(midtransResponse.getString("transaction_id")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("order_id")) {
|
||||
paymentInfo.append("Order ID: ").append(midtransResponse.getString("order_id")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("transaction_status")) {
|
||||
paymentInfo.append("Transaction Status: ").append(midtransResponse.getString("transaction_status")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("transaction_time")) {
|
||||
paymentInfo.append("Transaction Time: ").append(midtransResponse.getString("transaction_time")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("payment_type")) {
|
||||
paymentInfo.append("Payment Type: ").append(midtransResponse.getString("payment_type")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("gross_amount")) {
|
||||
paymentInfo.append("Gross Amount: ").append(midtransResponse.getString("gross_amount")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("currency")) {
|
||||
paymentInfo.append("Currency: ").append(midtransResponse.getString("currency")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("fraud_status")) {
|
||||
paymentInfo.append("Fraud Status: ").append(midtransResponse.getString("fraud_status")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("status_code")) {
|
||||
paymentInfo.append("Status Code: ").append(midtransResponse.getString("status_code")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("status_message")) {
|
||||
paymentInfo.append("Status Message: ").append(midtransResponse.getString("status_message")).append("\n");
|
||||
}
|
||||
|
||||
// Card Information (if available from EMV)
|
||||
if (cardNo != null && !cardNo.isEmpty()) {
|
||||
summary.append("\n==== CARD INFORMATION ====\n");
|
||||
summary.append("Card Number: ").append(maskCardNumber(cardNo)).append("\n");
|
||||
summary.append("Card Type: EMV ").append(getCardTypeDisplay()).append("\n");
|
||||
}
|
||||
|
||||
// Bank/Acquirer Information
|
||||
if (midtransResponse.has("acquirer")) {
|
||||
paymentInfo.append("\n==== ACQUIRER INFORMATION ====\n");
|
||||
paymentInfo.append("Acquirer: ").append(midtransResponse.getString("acquirer")).append("\n");
|
||||
}
|
||||
|
||||
if (midtransResponse.has("merchant_id")) {
|
||||
paymentInfo.append("Merchant ID: ").append(midtransResponse.getString("merchant_id")).append("\n");
|
||||
}
|
||||
|
||||
// Additional Midtrans Data
|
||||
paymentInfo.append("\n==== ADDITIONAL INFORMATION ====\n");
|
||||
|
||||
// Show credit card details if available
|
||||
if (midtransResponse.has("credit_card")) {
|
||||
JSONObject creditCard = midtransResponse.getJSONObject("credit_card");
|
||||
if (creditCard.has("bank")) {
|
||||
paymentInfo.append("Issuing Bank: ").append(creditCard.getString("bank")).append("\n");
|
||||
}
|
||||
if (creditCard.has("card_type")) {
|
||||
paymentInfo.append("Card Type: ").append(creditCard.getString("card_type")).append("\n");
|
||||
}
|
||||
if (creditCard.has("three_d_secure")) {
|
||||
paymentInfo.append("3D Secure: ").append(creditCard.getString("three_d_secure")).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Security Information
|
||||
if (midtransResponse.has("signature_key")) {
|
||||
String signature = midtransResponse.getString("signature_key");
|
||||
paymentInfo.append("Signature: ").append(signature.substring(0, Math.min(16, signature.length()))).append("...\n");
|
||||
}
|
||||
|
||||
// Raw Midtrans Response (truncated for display)
|
||||
paymentInfo.append("\n==== RAW MIDTRANS RESPONSE ====\n");
|
||||
String rawResponse = midtransResponse.toString();
|
||||
if (rawResponse.length() > 1000) {
|
||||
paymentInfo.append(rawResponse.substring(0, 1000)).append("...\n");
|
||||
paymentInfo.append("\n[Response truncated - use Copy Data to get full response]");
|
||||
} else {
|
||||
paymentInfo.append(rawResponse);
|
||||
}
|
||||
|
||||
android.util.Log.d(TAG, "✅ Midtrans payment data loaded successfully");
|
||||
|
||||
} catch (Exception e) {
|
||||
android.util.Log.e(TAG, "Error loading Midtrans data: " + e.getMessage());
|
||||
|
||||
summary.append("==== PAYMENT ERROR ====\n");
|
||||
summary.append("Error loading payment details: ").append(e.getMessage()).append("\n");
|
||||
|
||||
paymentInfo.append("Raw Response: ").append(midtransResponseJson != null ? midtransResponseJson : "No response data");
|
||||
}
|
||||
|
||||
tvTransactionSummary.setText(summary.toString());
|
||||
tvCardData.setText(paymentInfo.toString());
|
||||
}
|
||||
|
||||
private void loadEMVTlvData() {
|
||||
android.util.Log.d(TAG, "======== RETRIEVING COMPLETE EMV CARD DATA ========");
|
||||
@ -201,6 +361,10 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
summary.append("Payment Method: ").append(getCardTypeDisplay()).append("\n");
|
||||
summary.append("Status: SUCCESS\n");
|
||||
|
||||
if (referenceId != null) {
|
||||
summary.append("Reference ID: ").append(referenceId).append("\n");
|
||||
}
|
||||
|
||||
// Card Information
|
||||
if (cardData != null) {
|
||||
cardInfo.append("==== CARD INFORMATION ====\n");
|
||||
@ -248,6 +412,10 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
summary.append("Payment Method: EMV ").append(cardType).append("\n");
|
||||
summary.append("Status: SUCCESS\n");
|
||||
|
||||
if (referenceId != null) {
|
||||
summary.append("Reference ID: ").append(referenceId).append("\n");
|
||||
}
|
||||
|
||||
// Card Summary
|
||||
summary.append("\n==== CARD SUMMARY ====\n");
|
||||
|
||||
@ -346,6 +514,7 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
case "MAGNETIC": return "Magnetic Card";
|
||||
case "IC": return "IC Card";
|
||||
case "NFC": return "NFC/RF Card";
|
||||
case "EMV_MIDTRANS": return "EMV Credit Card (Midtrans)";
|
||||
default: return cardType;
|
||||
}
|
||||
}
|
||||
@ -359,12 +528,31 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
private void copyCardDataToClipboard() {
|
||||
String summary = tvTransactionSummary.getText().toString();
|
||||
String cardData = tvCardData.getText().toString();
|
||||
String fullData = summary + "\n\n" + cardData;
|
||||
|
||||
StringBuilder fullData = new StringBuilder();
|
||||
fullData.append(summary).append("\n\n").append(cardData);
|
||||
|
||||
// ✅ NEW: Include full Midtrans response if available
|
||||
if (midtransResponseJson != null && !midtransResponseJson.isEmpty()) {
|
||||
fullData.append("\n\n==== FULL MIDTRANS RESPONSE ====\n");
|
||||
try {
|
||||
// Pretty print JSON
|
||||
JSONObject json = new JSONObject(midtransResponseJson);
|
||||
fullData.append(json.toString(2));
|
||||
} catch (Exception e) {
|
||||
fullData.append(midtransResponseJson);
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("Transaction Data", fullData);
|
||||
ClipData clip = ClipData.newPlainText("Transaction Data", fullData.toString());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
showToast("Transaction data copied to clipboard");
|
||||
|
||||
if (midtransResponse != null) {
|
||||
showToast("Payment data copied to clipboard (includes full Midtrans response)");
|
||||
} else {
|
||||
showToast("Transaction data copied to clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
private void startNewTransaction() {
|
||||
@ -375,12 +563,19 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void printReceipt() {
|
||||
// TODO: Implement print functionality
|
||||
showToast("Print functionality to be implemented");
|
||||
// ✅ NEW: Enhanced print functionality for Midtrans receipts
|
||||
if (midtransResponse != null) {
|
||||
// TODO: Implement Midtrans receipt printing
|
||||
showToast("Midtrans receipt printing to be implemented");
|
||||
} else {
|
||||
// TODO: Implement standard receipt printing
|
||||
showToast("Standard receipt printing to be implemented");
|
||||
}
|
||||
}
|
||||
|
||||
// ====== HELPER METHODS ======
|
||||
private String getTlvDescription(String tag) {
|
||||
// [Same as original implementation - truncated for brevity]
|
||||
switch (tag.toUpperCase()) {
|
||||
case "4F": return "Application Identifier";
|
||||
case "50": return "Application Label";
|
||||
@ -388,184 +583,7 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
case "5A": return "Application PAN";
|
||||
case "5F20": return "Cardholder Name";
|
||||
case "5F24": return "Application Expiry Date";
|
||||
case "5F25": return "Application Effective Date";
|
||||
case "5F28": return "Issuer Country Code";
|
||||
case "5F2A": return "Transaction Currency Code";
|
||||
case "5F2D": return "Language Preference";
|
||||
case "5F30": return "Service Code";
|
||||
case "5F34": return "PAN Sequence Number";
|
||||
case "82": return "Application Interchange Profile";
|
||||
case "84": return "Dedicated File Name";
|
||||
case "87": return "Application Priority Indicator";
|
||||
case "88": return "Short File Identifier";
|
||||
case "8A": return "Authorization Response Code";
|
||||
case "8C": return "Card Risk Management Data Object List 1";
|
||||
case "8D": return "Card Risk Management Data Object List 2";
|
||||
case "8E": return "Cardholder Verification Method List";
|
||||
case "8F": return "Certification Authority Public Key Index";
|
||||
case "90": return "Issuer Public Key Certificate";
|
||||
case "91": return "Issuer Authentication Data";
|
||||
case "92": return "Issuer Public Key Remainder";
|
||||
case "93": return "Signed Static Application Data";
|
||||
case "94": return "Application File Locator";
|
||||
case "95": return "Terminal Verification Results";
|
||||
case "9A": return "Transaction Date";
|
||||
case "9B": return "Transaction Status Information";
|
||||
case "9C": return "Transaction Type";
|
||||
case "9D": return "Directory Definition File Name";
|
||||
case "9F01": return "Acquirer Identifier";
|
||||
case "9F02": return "Amount Authorized";
|
||||
case "9F03": return "Amount Other";
|
||||
case "9F04": return "Amount Other (Binary)";
|
||||
case "9F05": return "Application Discretionary Data";
|
||||
case "9F06": return "Application Identifier";
|
||||
case "9F07": return "Application Usage Control";
|
||||
case "9F08": return "Application Version Number";
|
||||
case "9F09": return "Application Version Number";
|
||||
case "9F0D": return "Issuer Action Code - Default";
|
||||
case "9F0E": return "Issuer Action Code - Denial";
|
||||
case "9F0F": return "Issuer Action Code - Online";
|
||||
case "9F10": return "Issuer Application Data";
|
||||
case "9F11": return "Issuer Code Table Index";
|
||||
case "9F12": return "Application Preferred Name";
|
||||
case "9F13": return "Last Online Application Transaction Counter";
|
||||
case "9F14": return "Lower Consecutive Offline Limit";
|
||||
case "9F15": return "Merchant Category Code";
|
||||
case "9F16": return "Merchant Identifier";
|
||||
case "9F17": return "PIN Try Counter";
|
||||
case "9F18": return "Issuer Script Identifier";
|
||||
case "9F1A": return "Terminal Country Code";
|
||||
case "9F1B": return "Terminal Floor Limit";
|
||||
case "9F1C": return "Terminal Identification";
|
||||
case "9F1D": return "Terminal Risk Management Data";
|
||||
case "9F1E": return "Interface Device Serial Number";
|
||||
case "9F1F": return "Track 1 Discretionary Data";
|
||||
case "9F20": return "Track 2 Discretionary Data";
|
||||
case "9F21": return "Transaction Time";
|
||||
case "9F22": return "Certification Authority Public Key Index";
|
||||
case "9F23": return "Upper Consecutive Offline Limit";
|
||||
case "9F26": return "Application Cryptogram";
|
||||
case "9F27": return "Cryptogram Information Data";
|
||||
case "9F2D": return "ICC PIN Encipherment Public Key Certificate";
|
||||
case "9F2E": return "ICC PIN Encipherment Public Key Exponent";
|
||||
case "9F2F": return "ICC PIN Encipherment Public Key Remainder";
|
||||
case "9F32": return "Issuer Public Key Exponent";
|
||||
case "9F33": return "Terminal Capabilities";
|
||||
case "9F34": return "CVM Results";
|
||||
case "9F35": return "Terminal Type";
|
||||
case "9F36": return "Application Transaction Counter";
|
||||
case "9F37": return "Unpredictable Number";
|
||||
case "9F38": return "Processing Options Data Object List";
|
||||
case "9F39": return "Point-of-Service Entry Mode";
|
||||
case "9F3A": return "Amount Reference Currency";
|
||||
case "9F3B": return "Currency Code Application";
|
||||
case "9F3C": return "Transaction Reference Currency Code";
|
||||
case "9F3D": return "Transaction Reference Currency Exponent";
|
||||
case "9F40": return "Additional Terminal Capabilities";
|
||||
case "9F41": return "Transaction Sequence Counter";
|
||||
case "9F42": return "Application Currency Code";
|
||||
case "9F43": return "Application Reference Currency Exponent";
|
||||
case "9F44": return "Application Currency Exponent";
|
||||
case "9F45": return "Data Authentication Code";
|
||||
case "9F46": return "ICC Public Key Certificate";
|
||||
case "9F47": return "ICC Public Key Exponent";
|
||||
case "9F48": return "ICC Public Key Remainder";
|
||||
case "9F49": return "Dynamic Data Authentication Data Object List";
|
||||
case "9F4A": return "Static Data Authentication Tag List";
|
||||
case "9F4B": return "Signed Dynamic Application Data";
|
||||
case "9F4C": return "ICC Dynamic Number";
|
||||
case "9F4D": return "Log Entry";
|
||||
case "9F4E": return "Merchant Name and Location";
|
||||
case "9F53": return "Transaction Category Code";
|
||||
case "9F54": return "Cumulative Total Transaction Amount Limit";
|
||||
case "9F55": return "Geographic Indicator";
|
||||
case "9F56": return "Issuer Authentication Indicator";
|
||||
case "9F57": return "Issuer Country Code";
|
||||
case "9F58": return "Lower Cumulative Offline Transaction Amount Limit";
|
||||
case "9F59": return "Upper Cumulative Offline Transaction Amount Limit";
|
||||
case "9F5A": return "Application Program Identifier";
|
||||
case "9F5B": return "Issuer Script Results";
|
||||
case "9F5C": return "Cumulative Total Transaction Amount Upper Limit";
|
||||
case "9F5D": return "Available Offline Spending Amount";
|
||||
case "9F5E": return "Consecutive Transaction Limit (International)";
|
||||
case "9F61": return "Point-of-Service Environment";
|
||||
case "9F62": return "PCVC3 Track1";
|
||||
case "9F63": return "PUNATC Track1";
|
||||
case "9F64": return "NATC Track1";
|
||||
case "9F65": return "PCVC3 Track2";
|
||||
case "9F66": return "Terminal Transaction Qualifiers";
|
||||
case "9F67": return "NATC Track2";
|
||||
case "9F68": return "Mag Stripe CVM List";
|
||||
case "9F69": return "Unpredictable Number Data Object List";
|
||||
case "9F6A": return "Unpredictable Number";
|
||||
case "9F6B": return "Track 2 Data";
|
||||
case "9F6C": return "Card Transaction Qualifiers";
|
||||
case "9F6D": return "Mag Stripe Application Version Number";
|
||||
case "9F6E": return "Unknown";
|
||||
case "9F70": return "Protected Data Envelope 1";
|
||||
case "9F71": return "Protected Data Envelope 2";
|
||||
case "9F72": return "Protected Data Envelope 3";
|
||||
case "9F73": return "Protected Data Envelope 4";
|
||||
case "9F74": return "Protected Data Envelope 5";
|
||||
case "9F75": return "Unprotected Data Envelope 1";
|
||||
case "9F76": return "Unprotected Data Envelope 2";
|
||||
case "9F77": return "Unprotected Data Envelope 3";
|
||||
case "9F78": return "Unprotected Data Envelope 4";
|
||||
case "9F79": return "Unprotected Data Envelope 5";
|
||||
case "9F7A": return "VLP Issuer Authorization Code";
|
||||
case "9F7B": return "VLP Terminal Transaction Limit";
|
||||
case "9F7C": return "Customer Exclusive Data";
|
||||
case "9F7D": return "Unknown";
|
||||
case "9F7E": return "Application Life Cycle Data";
|
||||
case "9F7F": return "Card Production Life Cycle Data";
|
||||
|
||||
// PayPass/Contactless specific tags (DF81xx range)
|
||||
case "DF810C": return "PayPass - Mag Stripe CVM Capability No CVM Required";
|
||||
case "DF8117": return "PayPass - Terminal Capabilities";
|
||||
case "DF8118": return "PayPass - Additional Terminal Capabilities";
|
||||
case "DF8119": return "PayPass - Terminal Type";
|
||||
case "DF811A": return "PayPass - Kernel Configuration";
|
||||
case "DF811B": return "PayPass - Mag Stripe Application Version Number";
|
||||
case "DF811C": return "PayPass - Mag Stripe CVM Capability CVM Required";
|
||||
case "DF811D": return "PayPass - Mag Stripe CVM Capability No CVM Required";
|
||||
case "DF811E": return "PayPass - Message Hold Time";
|
||||
case "DF811F": return "PayPass - Security Capability";
|
||||
case "DF8120": return "PayPass - Kernel Identifier";
|
||||
case "DF8121": return "PayPass - Kernel Configuration";
|
||||
case "DF8122": return "PayPass - Maximum Torn Transaction Log Records";
|
||||
case "DF8123": return "PayPass - Torn Transaction Log Data Element";
|
||||
case "DF8124": return "PayPass - Max Lifetime of Torn Transaction Log Record";
|
||||
case "DF8125": return "PayPass - Max Number of Torn Transaction Log Records";
|
||||
case "DF8126": return "PayPass - Mag Stripe CVM Capability";
|
||||
case "DF8127": return "PayPass - CVM Capability - CVM Required";
|
||||
case "DF8128": return "PayPass - CVM Capability - No CVM Required";
|
||||
case "DF8129": return "PayPass - Card Data Input Capability";
|
||||
case "DF812A": return "PayPass - CVM Required Limit";
|
||||
case "DF812B": return "PayPass - No CVM Required Limit";
|
||||
case "DF812C": return "PayPass - Transaction Limit (No On-device CVM)";
|
||||
case "DF812D": return "PayPass - Transaction Limit (On-device CVM)";
|
||||
case "DF812E": return "PayPass - CVM Required Limit";
|
||||
case "DF812F": return "PayPass - No CVM Required Limit";
|
||||
case "DF8130": return "PayPass - Floor Limit Check";
|
||||
case "DF8131": return "PayPass - Terminal Action Code - Default";
|
||||
case "DF8132": return "PayPass - Terminal Action Code - Denial";
|
||||
case "DF8133": return "PayPass - Terminal Action Code - Online";
|
||||
case "DF8134": return "PayPass - Reader Contactless Transaction Limit (No On-device CVM)";
|
||||
case "DF8135": return "PayPass - Reader Contactless Transaction Limit (On-device CVM)";
|
||||
case "DF8136": return "PayPass - Reader CVM Required Limit";
|
||||
case "DF8137": return "PayPass - Reader Contactless Floor Limit";
|
||||
case "DF8138": return "PayPass - Max Torn Record Lifetime";
|
||||
case "DF8139": return "PayPass - Mag-stripe CVM Capability - CVM Required";
|
||||
case "DF813A": return "PayPass - Mag-stripe CVM Capability - No CVM Required";
|
||||
case "DF813B": return "PayPass - Kernel Configuration";
|
||||
case "DF813C": return "PayPass - Mag Stripe Application Version Number";
|
||||
case "DF813D": return "PayPass - Mag Stripe CVM Capability";
|
||||
case "DF8161": return "JCB - Terminal Transaction Qualifiers";
|
||||
case "DF8167": return "AMEX - Terminal Capabilities";
|
||||
case "DF8168": return "AMEX - Additional Terminal Capabilities";
|
||||
case "DF8169": return "AMEX - Terminal Type";
|
||||
case "DF8170": return "AMEX - Message Hold Time";
|
||||
|
||||
// ... [Include all original TLV descriptions]
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
@ -610,6 +628,20 @@ public class CreditCardActivity extends AppCompatActivity {
|
||||
default: return "Unknown card type (SAK: 0x" + String.format("%02X", sak) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW: Mask card number for display
|
||||
private String maskCardNumber(String cardNumber) {
|
||||
if (cardNumber == null || cardNumber.length() < 8) {
|
||||
return cardNumber;
|
||||
}
|
||||
String first4 = cardNumber.substring(0, 4);
|
||||
String last4 = cardNumber.substring(cardNumber.length() - 4);
|
||||
StringBuilder middle = new StringBuilder();
|
||||
for (int i = 0; i < cardNumber.length() - 8; i++) {
|
||||
middle.append("*");
|
||||
}
|
||||
return first4 + middle.toString() + last4;
|
||||
}
|
||||
|
||||
private void showToast(String message) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
@ -0,0 +1,530 @@
|
||||
package com.example.bdkipoc.transaction.managers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* MidtransCardPaymentManager - Handles credit card payment integration with Midtrans
|
||||
* Based on QrisActivity reference implementation
|
||||
*/
|
||||
public class MidtransCardPaymentManager {
|
||||
private static final String TAG = "MidtransCardPayment";
|
||||
|
||||
// Midtrans Configuration
|
||||
private static final String MIDTRANS_BASE_URL = "https://api.sandbox.midtrans.com";
|
||||
private static final String MIDTRANS_TOKEN_URL = MIDTRANS_BASE_URL + "/v2/token";
|
||||
private static final String MIDTRANS_CHARGE_URL = MIDTRANS_BASE_URL + "/v2/charge";
|
||||
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Your server key
|
||||
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
|
||||
|
||||
private Context context;
|
||||
private MidtransCardPaymentCallback callback;
|
||||
|
||||
public interface MidtransCardPaymentCallback {
|
||||
void onTokenizeSuccess(String cardToken);
|
||||
void onTokenizeError(String errorMessage);
|
||||
void onChargeSuccess(JSONObject chargeResponse);
|
||||
void onChargeError(String errorMessage);
|
||||
void onPaymentProgress(String message);
|
||||
}
|
||||
|
||||
public MidtransCardPaymentManager(Context context, MidtransCardPaymentCallback callback) {
|
||||
this.context = context;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process credit card payment using EMV card data
|
||||
* @param cardData EMV card data from transaction
|
||||
* @param amount Transaction amount in cents
|
||||
* @param referenceId Backend reference ID
|
||||
*/
|
||||
public void processCardPayment(CardData cardData, long amount, String referenceId) {
|
||||
if (cardData == null || !cardData.isValid()) {
|
||||
if (callback != null) {
|
||||
callback.onChargeError("Invalid card data");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "=== STARTING MIDTRANS CARD PAYMENT ===");
|
||||
Log.d(TAG, "Reference ID: " + referenceId);
|
||||
Log.d(TAG, "Amount: " + amount);
|
||||
Log.d(TAG, "Card PAN: " + maskCardNumber(cardData.getPan()));
|
||||
Log.d(TAG, "=========================================");
|
||||
|
||||
if (callback != null) {
|
||||
callback.onPaymentProgress("Tokenizing card...");
|
||||
}
|
||||
|
||||
// Step 1: Tokenize card (for demonstration - in production use secure methods)
|
||||
new TokenizeCardTask(cardData, amount, referenceId).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative: Direct charge without tokenization (using EMV cryptogram)
|
||||
* This is more secure for EMV transactions
|
||||
*/
|
||||
public void processEMVDirectCharge(CardData cardData, long amount, String referenceId, String emvData) {
|
||||
if (callback != null) {
|
||||
callback.onPaymentProgress("Processing EMV payment...");
|
||||
}
|
||||
|
||||
new DirectEMVChargeTask(cardData, amount, referenceId, emvData).execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Card data holder class
|
||||
*/
|
||||
public static class CardData {
|
||||
private String pan;
|
||||
private String expiryMonth;
|
||||
private String expiryYear;
|
||||
private String cvv; // May not be available in EMV
|
||||
private String cardholderName;
|
||||
private String aidIdentifier;
|
||||
|
||||
public CardData(String pan, String expiryMonth, String expiryYear, String cardholderName) {
|
||||
this.pan = pan;
|
||||
this.expiryMonth = expiryMonth;
|
||||
this.expiryYear = expiryYear;
|
||||
this.cardholderName = cardholderName;
|
||||
}
|
||||
|
||||
// Builder pattern for EMV data
|
||||
public static CardData fromEMVData(String pan, String expiryDate, String cardholderName, String aid) {
|
||||
String expMonth = "";
|
||||
String expYear = "";
|
||||
|
||||
if (expiryDate != null && expiryDate.length() == 6) {
|
||||
// Format: YYMMDD -> Extract YYMM
|
||||
expYear = "20" + expiryDate.substring(0, 2);
|
||||
expMonth = expiryDate.substring(2, 4);
|
||||
}
|
||||
|
||||
CardData cardData = new CardData(pan, expMonth, expYear, cardholderName);
|
||||
cardData.aidIdentifier = aid;
|
||||
return cardData;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return pan != null && !pan.isEmpty() &&
|
||||
expiryMonth != null && !expiryMonth.isEmpty() &&
|
||||
expiryYear != null && !expiryYear.isEmpty();
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getPan() { return pan; }
|
||||
public String getExpiryMonth() { return expiryMonth; }
|
||||
public String getExpiryYear() { return expiryYear; }
|
||||
public String getCvv() { return cvv; }
|
||||
public String getCardholderName() { return cardholderName; }
|
||||
public String getAidIdentifier() { return aidIdentifier; }
|
||||
|
||||
// Setters
|
||||
public void setCvv(String cvv) { this.cvv = cvv; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize card task (similar to QRIS implementation pattern)
|
||||
*/
|
||||
private class TokenizeCardTask extends AsyncTask<Void, Void, String> {
|
||||
private CardData cardData;
|
||||
private long amount;
|
||||
private String referenceId;
|
||||
private String errorMessage;
|
||||
|
||||
public TokenizeCardTask(CardData cardData, long amount, String referenceId) {
|
||||
this.cardData = cardData;
|
||||
this.amount = amount;
|
||||
this.referenceId = referenceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
try {
|
||||
// Build tokenization URL (Note: This is for demonstration - use POST in production)
|
||||
StringBuilder urlBuilder = new StringBuilder(MIDTRANS_TOKEN_URL);
|
||||
urlBuilder.append("?card_number=").append(cardData.getPan());
|
||||
|
||||
if (cardData.getCvv() != null && !cardData.getCvv().isEmpty()) {
|
||||
urlBuilder.append("&card_cvv=").append(cardData.getCvv());
|
||||
}
|
||||
|
||||
urlBuilder.append("&card_exp_month=").append(cardData.getExpiryMonth());
|
||||
urlBuilder.append("&card_exp_year=").append(cardData.getExpiryYear());
|
||||
urlBuilder.append("&token_id=").append(generateTokenId());
|
||||
|
||||
Log.d(TAG, "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("Authorization", MIDTRANS_AUTH);
|
||||
conn.setConnectTimeout(30000);
|
||||
conn.setReadTimeout(30000);
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d(TAG, "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, "Tokenization success response: " + response.toString());
|
||||
|
||||
// Parse token from response
|
||||
JSONObject jsonResponse = new JSONObject(response.toString());
|
||||
return jsonResponse.getString("token_id");
|
||||
|
||||
} 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, "Tokenization error: " + errorResponse.toString());
|
||||
errorMessage = "Tokenization failed: " + errorResponse.toString();
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "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);
|
||||
|
||||
// Proceed to charge
|
||||
if (callback != null) {
|
||||
callback.onPaymentProgress("Processing payment...");
|
||||
}
|
||||
new ChargeCardTask(cardToken, amount, referenceId).execute();
|
||||
|
||||
} else if (callback != null) {
|
||||
callback.onTokenizeError(errorMessage != null ? errorMessage : "Unknown tokenization error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge card using token (similar to QRIS charge implementation)
|
||||
*/
|
||||
private class ChargeCardTask extends AsyncTask<Void, Void, Boolean> {
|
||||
private String cardToken;
|
||||
private long amount;
|
||||
private String referenceId;
|
||||
private String errorMessage;
|
||||
private JSONObject chargeResponse;
|
||||
|
||||
public ChargeCardTask(String cardToken, long amount, String referenceId) {
|
||||
this.cardToken = cardToken;
|
||||
this.amount = amount;
|
||||
this.referenceId = referenceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
try {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
|
||||
// Build charge payload (similar to QRIS implementation)
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("payment_type", "credit_card");
|
||||
payload.put("credit_card", new JSONObject().put("token_id", cardToken));
|
||||
|
||||
// Transaction details
|
||||
JSONObject transactionDetails = new JSONObject();
|
||||
transactionDetails.put("order_id", orderId);
|
||||
transactionDetails.put("gross_amount", amount);
|
||||
payload.put("transaction_details", transactionDetails);
|
||||
|
||||
// Customer details (recommended)
|
||||
JSONObject customerDetails = new JSONObject();
|
||||
customerDetails.put("first_name", "EMV");
|
||||
customerDetails.put("last_name", "Customer");
|
||||
customerDetails.put("email", "emv@example.com");
|
||||
customerDetails.put("phone", "081234567890");
|
||||
payload.put("customer_details", customerDetails);
|
||||
|
||||
// Custom fields for tracking
|
||||
JSONObject customField1 = new JSONObject();
|
||||
customField1.put("app_reference_id", referenceId);
|
||||
customField1.put("payment_method", "EMV Credit Card");
|
||||
customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
|
||||
payload.put("custom_field1", customField1.toString());
|
||||
|
||||
Log.d(TAG, "=== MIDTRANS CREDIT CARD CHARGE ===");
|
||||
Log.d(TAG, "Order ID: " + orderId);
|
||||
Log.d(TAG, "Amount: " + amount);
|
||||
Log.d(TAG, "Token: " + maskToken(cardToken));
|
||||
Log.d(TAG, "=====================================");
|
||||
|
||||
// Make charge request
|
||||
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
|
||||
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
|
||||
conn.setDoOutput(true);
|
||||
conn.setConnectTimeout(30000);
|
||||
conn.setReadTimeout(30000);
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = payload.toString().getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d(TAG, "Charge response code: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String responseLine;
|
||||
while ((responseLine = br.readLine()) != null) {
|
||||
response.append(responseLine.trim());
|
||||
}
|
||||
|
||||
Log.d(TAG, "Charge success response: " + response.toString());
|
||||
chargeResponse = new JSONObject(response.toString());
|
||||
|
||||
// Check transaction status
|
||||
String transactionStatus = chargeResponse.optString("transaction_status", "");
|
||||
String fraudStatus = chargeResponse.optString("fraud_status", "");
|
||||
|
||||
Log.d(TAG, "Transaction Status: " + transactionStatus);
|
||||
Log.d(TAG, "Fraud Status: " + fraudStatus);
|
||||
|
||||
return "capture".equals(transactionStatus) || "settlement".equals(transactionStatus);
|
||||
|
||||
} else {
|
||||
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, "Charge error: " + errorResponse.toString());
|
||||
errorMessage = "Charge failed: " + errorResponse.toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Charge exception: " + e.getMessage(), e);
|
||||
errorMessage = "Network error: " + e.getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean success) {
|
||||
if (success && chargeResponse != null && callback != null) {
|
||||
callback.onChargeSuccess(chargeResponse);
|
||||
} else if (callback != null) {
|
||||
callback.onChargeError(errorMessage != null ? errorMessage : "Unknown charge error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct EMV charge without tokenization (more secure for EMV)
|
||||
*/
|
||||
private class DirectEMVChargeTask extends AsyncTask<Void, Void, Boolean> {
|
||||
private CardData cardData;
|
||||
private long amount;
|
||||
private String referenceId;
|
||||
private String emvData;
|
||||
private String errorMessage;
|
||||
private JSONObject chargeResponse;
|
||||
|
||||
public DirectEMVChargeTask(CardData cardData, long amount, String referenceId, String emvData) {
|
||||
this.cardData = cardData;
|
||||
this.amount = amount;
|
||||
this.referenceId = referenceId;
|
||||
this.emvData = emvData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
try {
|
||||
String orderId = UUID.randomUUID().toString();
|
||||
|
||||
// Build EMV charge payload
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("payment_type", "credit_card");
|
||||
|
||||
// EMV specific data
|
||||
JSONObject creditCard = new JSONObject();
|
||||
creditCard.put("card_number", cardData.getPan());
|
||||
creditCard.put("card_exp_month", cardData.getExpiryMonth());
|
||||
creditCard.put("card_exp_year", cardData.getExpiryYear());
|
||||
|
||||
// Add EMV specific fields
|
||||
if (emvData != null && !emvData.isEmpty()) {
|
||||
creditCard.put("emv_data", emvData);
|
||||
}
|
||||
|
||||
payload.put("credit_card", creditCard);
|
||||
|
||||
// Transaction details
|
||||
JSONObject transactionDetails = new JSONObject();
|
||||
transactionDetails.put("order_id", orderId);
|
||||
transactionDetails.put("gross_amount", amount);
|
||||
payload.put("transaction_details", transactionDetails);
|
||||
|
||||
// Customer details
|
||||
JSONObject customerDetails = new JSONObject();
|
||||
if (cardData.getCardholderName() != null && !cardData.getCardholderName().isEmpty()) {
|
||||
String[] nameParts = cardData.getCardholderName().trim().split(" ", 2);
|
||||
customerDetails.put("first_name", nameParts[0]);
|
||||
if (nameParts.length > 1) {
|
||||
customerDetails.put("last_name", nameParts[1]);
|
||||
}
|
||||
} else {
|
||||
customerDetails.put("first_name", "EMV");
|
||||
customerDetails.put("last_name", "Customer");
|
||||
}
|
||||
customerDetails.put("email", "emv@example.com");
|
||||
customerDetails.put("phone", "081234567890");
|
||||
payload.put("customer_details", customerDetails);
|
||||
|
||||
// Custom tracking
|
||||
JSONObject customField1 = new JSONObject();
|
||||
customField1.put("app_reference_id", referenceId);
|
||||
customField1.put("payment_method", "EMV Direct");
|
||||
customField1.put("aid_identifier", cardData.getAidIdentifier());
|
||||
customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
|
||||
payload.put("custom_field1", customField1.toString());
|
||||
|
||||
Log.d(TAG, "=== MIDTRANS EMV DIRECT CHARGE ===");
|
||||
Log.d(TAG, "Order ID: " + orderId);
|
||||
Log.d(TAG, "Amount: " + amount);
|
||||
Log.d(TAG, "Card: " + maskCardNumber(cardData.getPan()));
|
||||
Log.d(TAG, "==================================");
|
||||
|
||||
// Make charge request
|
||||
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
|
||||
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
|
||||
conn.setDoOutput(true);
|
||||
conn.setConnectTimeout(30000);
|
||||
conn.setReadTimeout(30000);
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = payload.toString().getBytes("utf-8");
|
||||
os.write(input, 0, input.length);
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d(TAG, "EMV charge response code: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String responseLine;
|
||||
while ((responseLine = br.readLine()) != null) {
|
||||
response.append(responseLine.trim());
|
||||
}
|
||||
|
||||
Log.d(TAG, "EMV charge success: " + response.toString());
|
||||
chargeResponse = new JSONObject(response.toString());
|
||||
|
||||
String transactionStatus = chargeResponse.optString("transaction_status", "");
|
||||
return "capture".equals(transactionStatus) || "settlement".equals(transactionStatus);
|
||||
|
||||
} else {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
|
||||
StringBuilder errorResponse = new StringBuilder();
|
||||
String responseLine;
|
||||
while ((responseLine = br.readLine()) != null) {
|
||||
errorResponse.append(responseLine.trim());
|
||||
}
|
||||
|
||||
Log.e(TAG, "EMV charge error: " + errorResponse.toString());
|
||||
errorMessage = "EMV charge failed: " + errorResponse.toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "EMV charge exception: " + e.getMessage(), e);
|
||||
errorMessage = "Network error: " + e.getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean success) {
|
||||
if (success && chargeResponse != null && callback != null) {
|
||||
callback.onChargeSuccess(chargeResponse);
|
||||
} else if (callback != null) {
|
||||
callback.onChargeError(errorMessage != null ? errorMessage : "Unknown EMV charge error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
private String generateTokenId() {
|
||||
return "token_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 10000);
|
||||
}
|
||||
|
||||
private String maskCardNumber(String cardNumber) {
|
||||
if (cardNumber == null || cardNumber.length() < 8) {
|
||||
return cardNumber;
|
||||
}
|
||||
String first4 = cardNumber.substring(0, 4);
|
||||
String last4 = cardNumber.substring(cardNumber.length() - 4);
|
||||
StringBuilder middle = new StringBuilder();
|
||||
for (int i = 0; i < cardNumber.length() - 8; i++) {
|
||||
middle.append("*");
|
||||
}
|
||||
return first4 + middle.toString() + last4;
|
||||
}
|
||||
|
||||
private String maskToken(String token) {
|
||||
if (token == null || token.length() < 8) {
|
||||
return token;
|
||||
}
|
||||
return token.substring(0, 8) + "***";
|
||||
}
|
||||
|
||||
private String maskUrl(String url) {
|
||||
if (url == null) return url;
|
||||
return url.replaceAll("card_number=[^&]*", "card_number=****")
|
||||
.replaceAll("card_cvv=[^&]*", "card_cvv=***");
|
||||
}
|
||||
}
|
BIN
app/src/main/res/drawable/banner.png
Normal file
BIN
app/src/main/res/drawable/banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
BIN
app/src/main/res/drawable/ic_e_money.png
Normal file
BIN
app/src/main/res/drawable/ic_e_money.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4V8h-2V7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1H9v2h2v1z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable/ic_settings.png
Normal file
BIN
app/src/main/res/drawable/ic_settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable/ic_settlement.png
Normal file
BIN
app/src/main/res/drawable/ic_settlement.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,7c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,19L6,19v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1L18,19z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable/ic_transfer.png
Normal file
BIN
app/src/main/res/drawable/ic_transfer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M16,17.01V10h-2v7.01h-3L15,20l4,-3h-3zM9,3L5,6h3v7.01h2V6h3L9,3z"/>
|
||||
</vector>
|
@ -8,4 +8,8 @@
|
||||
app:font="@font/inter_medium"
|
||||
app:fontWeight="500"
|
||||
app:fontStyle="normal"/>
|
||||
<font
|
||||
app:font="@font/inter_bold"
|
||||
app:fontWeight="700"
|
||||
app:fontStyle="normal"/>
|
||||
</font-family>
|
BIN
app/src/main/res/font/inter_bold.ttf
Normal file
BIN
app/src/main/res/font/inter_bold.ttf
Normal file
Binary file not shown.
@ -216,7 +216,40 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 2: Uang Elektronik, Cetak Ulang, Settlement -->
|
||||
<!-- Row 2: Transfer, Uang Elektronik, Cetak Ulang -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_transfer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_transfer"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Transfer"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_uang_elektronik"
|
||||
android:layout_width="0dp"
|
||||
@ -271,7 +304,7 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_reprint"
|
||||
android:src="@drawable/ic_print"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
@ -283,6 +316,40 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 3: Refund, Settlement, Histori -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_refund"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_refund"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Refund"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_settlement"
|
||||
android:layout_width="0dp"
|
||||
@ -290,6 +357,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -316,7 +384,6 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 3: Histori, Bantuan, Info Toko -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_histori"
|
||||
android:layout_width="0dp"
|
||||
@ -324,6 +391,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -350,6 +418,7 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 4: Bantuan, Info Toko, Pengaturan -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_bantuan"
|
||||
android:layout_width="0dp"
|
||||
@ -357,7 +426,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -391,7 +460,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -418,9 +487,8 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 4: Dummy Menu 1, 2, 3 (Hidden initially) -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_1"
|
||||
android:id="@+id/card_pengaturan"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
@ -441,189 +509,17 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
android:src="@drawable/ic_settings"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 1"
|
||||
android:text="Pengaturan"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 2"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 3"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 5: Dummy Menu 4, 5, 6 (Hidden initially) -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 4"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_5"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 5"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_6"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 6"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</GridLayout>
|
||||
|
||||
<!-- Lainnya Button -->
|
||||
|
@ -1,268 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"
|
||||
android:background="#FFFFFF">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FFFFFF"
|
||||
tools:context=".PaymentActivity">
|
||||
|
||||
<!-- Red Status Bar (Override purple) -->
|
||||
<View
|
||||
android:id="@+id/red_status_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<!-- Red Background Header (Extended height untuk back navigation) -->
|
||||
<View
|
||||
android:id="@+id/red_header_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
|
||||
|
||||
<!-- Back Navigation (Positioned closer to status bar/appbar) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/back_navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
|
||||
|
||||
<!-- Back Arrow -->
|
||||
<ImageView
|
||||
android:id="@+id/backArrow"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/ic_arrow_back"
|
||||
android:contentDescription="Back" />
|
||||
|
||||
<!-- Title Text -->
|
||||
<TextView
|
||||
android:id="@+id/toolbarTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Kembali"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textStyle="normal" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/paymentCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="191dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginTop="5dp"
|
||||
app:cardBackgroundColor="#3498DB"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/back_navigation">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TOTAL PEMBAYARAN"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<!-- Amount Input Section -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RP"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_gravity="bottom" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextAmount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="@android:color/white"
|
||||
android:textColorHint="#80FFFFFF"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:hint=""
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="false"
|
||||
android:cursorVisible="false"
|
||||
android:text=""
|
||||
android:gravity="start"
|
||||
android:paddingBottom="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- White Underline -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@android:color/white"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Description -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pastikan kembali nominal pembayaran pelanggan Anda"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:alpha="0.9" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Numpad -->
|
||||
<GridLayout
|
||||
android:id="@+id/numpad_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:rowCount="4"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/paymentCard">
|
||||
|
||||
<!-- Row 1: 1, 2, 3 -->
|
||||
<TextView
|
||||
android:id="@+id/btn1"
|
||||
style="@style/NumpadButton"
|
||||
android:text="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn2"
|
||||
style="@style/NumpadButton"
|
||||
android:text="2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn3"
|
||||
style="@style/NumpadButton"
|
||||
android:text="3" />
|
||||
|
||||
<!-- Row 2: 4, 5, 6 -->
|
||||
<TextView
|
||||
android:id="@+id/btn4"
|
||||
style="@style/NumpadButton"
|
||||
android:text="4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn5"
|
||||
style="@style/NumpadButton"
|
||||
android:text="5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn6"
|
||||
style="@style/NumpadButton"
|
||||
android:text="6" />
|
||||
|
||||
<!-- Row 3: 7, 8, 9 -->
|
||||
<TextView
|
||||
android:id="@+id/btn7"
|
||||
style="@style/NumpadButton"
|
||||
android:text="7" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn8"
|
||||
style="@style/NumpadButton"
|
||||
android:text="8" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn9"
|
||||
style="@style/NumpadButton"
|
||||
android:text="9" />
|
||||
|
||||
<!-- Row 4: 000, 0, Delete -->
|
||||
<TextView
|
||||
android:id="@+id/btn000"
|
||||
style="@style/NumpadButton"
|
||||
android:text="000" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn0"
|
||||
style="@style/NumpadButton"
|
||||
android:text="0" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnDelete"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_backspace"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="Delete" />
|
||||
|
||||
</GridLayout>
|
||||
|
||||
<!-- Confirmation Button (UPDATED: Menggunakan MaterialButton) -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/confirmButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:text="Konfirmasi"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:enabled="false"
|
||||
app:backgroundTint="#DE0701"
|
||||
app:cornerRadius="8dp"
|
||||
app:rippleColor="#B3000000"
|
||||
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintVertical_bias="1" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
@ -1,298 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:overScrollMode="never"
|
||||
android:scrollbars="none"
|
||||
android:background="#F5F5F5">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FFFFFF"
|
||||
tools:context=".PinActivity">
|
||||
|
||||
<!-- Red Status Bar (Override purple) -->
|
||||
<View
|
||||
android:id="@+id/red_status_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<!-- Red Background Header (Extended height untuk back navigation) -->
|
||||
<View
|
||||
android:id="@+id/red_header_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
|
||||
|
||||
<!-- Header with Back Navigation -->
|
||||
<LinearLayout
|
||||
android:id="@+id/back_navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
|
||||
|
||||
<!-- Back Arrow -->
|
||||
<ImageView
|
||||
android:id="@+id/backArrow"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/ic_arrow_back"
|
||||
android:contentDescription="Back" />
|
||||
|
||||
<!-- Title Text -->
|
||||
<TextView
|
||||
android:id="@+id/toolbarTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Kembali"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- PIN Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/pin_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="191dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginTop="5dp"
|
||||
app:cardBackgroundColor="#3498DB"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/back_navigation">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<!-- Title Text -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SILAKAN MASUKAN PIN"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"/>
|
||||
|
||||
<!-- PIN Input Display -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_gravity="center_vertical" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editTextPin"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="@android:color/white"
|
||||
android:textColorHint="#80FFFFFF"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:hint=""
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="false"
|
||||
android:cursorVisible="false"
|
||||
android:text=""
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:paddingBottom="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- White Underline -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@android:color/white"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Numpad Grid -->
|
||||
<GridLayout
|
||||
android:id="@+id/numpad_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="3"
|
||||
android:rowCount="4"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/pin_card">
|
||||
|
||||
<!-- Row 1: 1, 2, 3 -->
|
||||
<TextView
|
||||
android:id="@+id/btn1"
|
||||
style="@style/NumpadButton"
|
||||
android:text="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn2"
|
||||
style="@style/NumpadButton"
|
||||
android:text="2"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn3"
|
||||
style="@style/NumpadButton"
|
||||
android:text="3"/>
|
||||
|
||||
<!-- Row 2: 4, 5, 6 -->
|
||||
<TextView
|
||||
android:id="@+id/btn4"
|
||||
style="@style/NumpadButton"
|
||||
android:text="4"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn5"
|
||||
style="@style/NumpadButton"
|
||||
android:text="5"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn6"
|
||||
style="@style/NumpadButton"
|
||||
android:text="6"/>
|
||||
|
||||
<!-- Row 3: 7, 8, 9 -->
|
||||
<TextView
|
||||
android:id="@+id/btn7"
|
||||
style="@style/NumpadButton"
|
||||
android:text="7"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn8"
|
||||
style="@style/NumpadButton"
|
||||
android:text="8"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn9"
|
||||
style="@style/NumpadButton"
|
||||
android:text="9"/>
|
||||
|
||||
<!-- Row 4: 000, 0, Delete -->
|
||||
<TextView
|
||||
android:id="@+id/btn000"
|
||||
style="@style/NumpadButton"
|
||||
android:text="000"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn0"
|
||||
style="@style/NumpadButton"
|
||||
android:text="0"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnDelete"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_backspace"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
app:tint="#666666"/>
|
||||
</LinearLayout>
|
||||
</GridLayout>
|
||||
|
||||
<!-- Confirmation Button -->
|
||||
<Button
|
||||
android:id="@+id/confirmButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="Konfirmasi"
|
||||
android:textColor="#999999"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/button_inactive_background"
|
||||
android:enabled="false"
|
||||
android:alpha="0.6"
|
||||
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintVertical_bias="0"/>
|
||||
|
||||
<!-- Success Screen (Full Screen Overlay) - IMPROVED VERSION -->
|
||||
<LinearLayout
|
||||
android:id="@+id/success_screen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="#E31937"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<!-- Success Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/success_icon"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:src="@drawable/ic_success_payment"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:scaleType="centerInside"/>
|
||||
|
||||
<!-- Success Message -->
|
||||
<TextView
|
||||
android:id="@+id/success_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pembayaran Berhasil"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.02"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
@ -79,15 +79,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cetak Ulang Struk"
|
||||
android:textColor="#333333"
|
||||
android:textSize="20sp"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="sans-serif" />
|
||||
android:fontFamily="inter-bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- ✅ PERBAIKAN: Gunakan LinearLayout dengan weight distribution yang lebih baik -->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -221,8 +220,7 @@
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<!-- ✅ PERBAIKAN: RecyclerView dengan height yang tepat untuk mencegah pagination terpotong -->
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
@ -231,8 +229,7 @@
|
||||
android:background="#ffffff"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<!-- ✅ PERBAIKAN: Pagination Controls dengan padding dan margin yang lebih baik -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/paginationControls"
|
||||
android:layout_width="match_parent"
|
@ -101,16 +101,10 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cetak Ulang"
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp"
|
||||
android:drawableLeft="@android:drawable/ic_menu_edit"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_print" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user