midtrans solve
This commit is contained in:
parent
edca7f92ec
commit
a1f536b03e
@ -1,22 +1,13 @@
|
|||||||
package com.example.bdkipoc;
|
package com.example.bdkipoc;
|
||||||
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@ -28,6 +19,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@ -46,34 +38,24 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public class QrisActivity extends AppCompatActivity {
|
public class QrisActivity extends AppCompatActivity {
|
||||||
|
|
||||||
// Views
|
private ProgressBar progressBar;
|
||||||
private EditText editTextAmount;
|
|
||||||
private Button initiatePaymentButton;
|
private Button initiatePaymentButton;
|
||||||
|
private TextView statusTextView;
|
||||||
|
private EditText editTextAmount;
|
||||||
|
private TextView referenceIdTextView;
|
||||||
private LinearLayout backNavigation;
|
private LinearLayout backNavigation;
|
||||||
private TextView toolbarTitle;
|
|
||||||
private TextView descriptionText;
|
|
||||||
|
|
||||||
// Numpad buttons
|
// Numpad buttons
|
||||||
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
|
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000, btnDelete;
|
||||||
private TextView btnDelete; // Changed from ImageView to TextView
|
private TextView descriptionText;
|
||||||
|
|
||||||
// Only needed views for loading state
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private TextView statusTextView;
|
|
||||||
private TextView referenceIdTextView;
|
|
||||||
private LinearLayout initialPaymentLayout;
|
|
||||||
|
|
||||||
// Data
|
|
||||||
private StringBuilder currentAmount = new StringBuilder();
|
|
||||||
private static final int MAX_AMOUNT_LENGTH = 12;
|
|
||||||
private String transactionId;
|
private String transactionId;
|
||||||
private String transactionUuid;
|
private String transactionUuid;
|
||||||
private String referenceId;
|
private String referenceId;
|
||||||
private int amount;
|
private int amount;
|
||||||
private JSONObject midtransResponse;
|
private JSONObject midtransResponse;
|
||||||
|
|
||||||
// Animation
|
private StringBuilder currentAmount = new StringBuilder();
|
||||||
private Handler animationHandler = new Handler(Looper.getMainLooper());
|
|
||||||
|
|
||||||
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
|
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
|
||||||
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
|
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
|
||||||
@ -83,49 +65,18 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Set status bar color programmatically
|
|
||||||
setStatusBarColor();
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_qris);
|
setContentView(R.layout.activity_qris);
|
||||||
|
|
||||||
initializeViews();
|
// Initialize views
|
||||||
setupClickListeners();
|
progressBar = findViewById(R.id.progressBar);
|
||||||
setupInitialStates();
|
|
||||||
|
|
||||||
// Generate reference ID
|
|
||||||
referenceId = "ref-" + generateRandomString(8);
|
|
||||||
referenceIdTextView.setText(referenceId);
|
|
||||||
|
|
||||||
// Initially hide the progress and status views
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
statusTextView.setVisibility(View.GONE);
|
|
||||||
initialPaymentLayout.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
// New UI components (similar to PaymentActivity)
|
|
||||||
editTextAmount = findViewById(R.id.editTextAmount);
|
|
||||||
initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
|
initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
|
||||||
|
statusTextView = findViewById(R.id.statusTextView);
|
||||||
|
editTextAmount = findViewById(R.id.editTextAmount);
|
||||||
|
referenceIdTextView = findViewById(R.id.referenceIdTextView);
|
||||||
backNavigation = findViewById(R.id.back_navigation);
|
backNavigation = findViewById(R.id.back_navigation);
|
||||||
toolbarTitle = findViewById(R.id.toolbarTitle);
|
|
||||||
descriptionText = findViewById(R.id.descriptionText);
|
descriptionText = findViewById(R.id.descriptionText);
|
||||||
|
|
||||||
// Numpad buttons
|
// Initialize numpad buttons
|
||||||
btn1 = findViewById(R.id.btn1);
|
btn1 = findViewById(R.id.btn1);
|
||||||
btn2 = findViewById(R.id.btn2);
|
btn2 = findViewById(R.id.btn2);
|
||||||
btn3 = findViewById(R.id.btn3);
|
btn3 = findViewById(R.id.btn3);
|
||||||
@ -137,232 +88,102 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
btn9 = findViewById(R.id.btn9);
|
btn9 = findViewById(R.id.btn9);
|
||||||
btn0 = findViewById(R.id.btn0);
|
btn0 = findViewById(R.id.btn0);
|
||||||
btn000 = findViewById(R.id.btn000);
|
btn000 = findViewById(R.id.btn000);
|
||||||
btnDelete = findViewById(R.id.btnDelete); // Now TextView
|
btnDelete = findViewById(R.id.btnDelete);
|
||||||
|
|
||||||
// Only needed views for loading state
|
// Generate reference ID
|
||||||
progressBar = findViewById(R.id.progressBar);
|
referenceId = "ref-" + generateRandomString(8);
|
||||||
statusTextView = findViewById(R.id.statusTextView);
|
referenceIdTextView.setText(referenceId);
|
||||||
referenceIdTextView = findViewById(R.id.referenceIdTextView);
|
|
||||||
|
// Set up click listeners
|
||||||
|
initiatePaymentButton.setOnClickListener(v -> createTransaction());
|
||||||
|
backNavigation.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
// Main content layout (replaces initialPaymentLayout)
|
// Set up numpad listeners
|
||||||
initialPaymentLayout = findViewById(R.id.mainContentLayout);
|
setupNumpadListeners();
|
||||||
}
|
|
||||||
|
|
||||||
private void setupClickListeners() {
|
|
||||||
// Back navigation
|
|
||||||
backNavigation.setOnClickListener(v -> {
|
|
||||||
addClickAnimation(v);
|
|
||||||
navigateBack();
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Original QRIS buttons - only initiate payment needed
|
|
||||||
initiatePaymentButton.setOnClickListener(v -> {
|
|
||||||
if (initiatePaymentButton.isEnabled()) {
|
|
||||||
addButtonClickAnimation(v);
|
|
||||||
createTransaction();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupInitialStates() {
|
|
||||||
// Initially hide amount input and show description
|
|
||||||
editTextAmount.setVisibility(View.GONE);
|
|
||||||
descriptionText.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
// Set initial button state
|
// Initially disable the button
|
||||||
updateButtonState();
|
initiatePaymentButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNumpadListeners() {
|
||||||
|
View.OnClickListener numberClickListener = v -> {
|
||||||
|
TextView button = (TextView) v;
|
||||||
|
String number = button.getText().toString();
|
||||||
|
appendNumber(number);
|
||||||
|
};
|
||||||
|
|
||||||
|
btn1.setOnClickListener(numberClickListener);
|
||||||
|
btn2.setOnClickListener(numberClickListener);
|
||||||
|
btn3.setOnClickListener(numberClickListener);
|
||||||
|
btn4.setOnClickListener(numberClickListener);
|
||||||
|
btn5.setOnClickListener(numberClickListener);
|
||||||
|
btn6.setOnClickListener(numberClickListener);
|
||||||
|
btn7.setOnClickListener(numberClickListener);
|
||||||
|
btn8.setOnClickListener(numberClickListener);
|
||||||
|
btn9.setOnClickListener(numberClickListener);
|
||||||
|
btn0.setOnClickListener(numberClickListener);
|
||||||
|
btn000.setOnClickListener(numberClickListener);
|
||||||
|
|
||||||
// Disable EditText input (only numpad input allowed)
|
btnDelete.setOnClickListener(v -> deleteLastDigit());
|
||||||
editTextAmount.setFocusable(false);
|
|
||||||
editTextAmount.setClickable(false);
|
|
||||||
editTextAmount.setCursorVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void appendNumber(String number) {
|
||||||
finish();
|
currentAmount.append(number);
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNumpadClick(View view, String digit) {
|
|
||||||
addClickAnimation(view);
|
|
||||||
addDigit(digit);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
updateAmountDisplay();
|
||||||
updateButtonState();
|
|
||||||
addInputFeedback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteLastDigit() {
|
private void deleteLastDigit() {
|
||||||
if (currentAmount.length() > 0) {
|
if (currentAmount.length() > 0) {
|
||||||
String current = currentAmount.toString();
|
currentAmount.deleteCharAt(currentAmount.length() - 1);
|
||||||
|
|
||||||
// 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();
|
updateAmountDisplay();
|
||||||
updateButtonState();
|
|
||||||
addDeleteFeedback();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAmountDisplay() {
|
private void updateAmountDisplay() {
|
||||||
String amount = currentAmount.toString();
|
String amountStr = currentAmount.toString();
|
||||||
|
|
||||||
if (amount.isEmpty() || amount.equals("0")) {
|
if (amountStr.isEmpty()) {
|
||||||
// Show description text, hide amount input
|
|
||||||
editTextAmount.setVisibility(View.GONE);
|
editTextAmount.setVisibility(View.GONE);
|
||||||
descriptionText.setVisibility(View.VISIBLE);
|
descriptionText.setText("Pastikan kembali nominal pembayaran pelanggan Anda");
|
||||||
|
initiatePaymentButton.setEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
// Show amount input, hide description text
|
|
||||||
String formattedAmount = formatCurrency(amount);
|
|
||||||
editTextAmount.setText(formattedAmount);
|
|
||||||
editTextAmount.setVisibility(View.VISIBLE);
|
editTextAmount.setVisibility(View.VISIBLE);
|
||||||
descriptionText.setVisibility(View.GONE);
|
editTextAmount.setText(formatAmount(amountStr));
|
||||||
|
descriptionText.setText("Tekan Konfirmasi untuk melanjutkan");
|
||||||
|
|
||||||
|
// Enable button if amount is valid
|
||||||
|
try {
|
||||||
|
int amt = Integer.parseInt(amountStr);
|
||||||
|
initiatePaymentButton.setEnabled(amt >= 1000);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
initiatePaymentButton.setEnabled(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatCurrency(String amount) {
|
private String formatAmount(String amount) {
|
||||||
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
|
if (amount.isEmpty()) return "";
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
long number = Long.parseLong(amount);
|
long num = Long.parseLong(amount);
|
||||||
return String.format("%,d", number).replace(',', '.');
|
return String.format("%,d", num);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateButtonState() {
|
|
||||||
boolean hasValidAmount = currentAmount.length() > 0 &&
|
|
||||||
!currentAmount.toString().equals("0") &&
|
|
||||||
!currentAmount.toString().isEmpty();
|
|
||||||
|
|
||||||
initiatePaymentButton.setEnabled(hasValidAmount);
|
|
||||||
|
|
||||||
// Use MaterialButton's backgroundTint property
|
|
||||||
com.google.android.material.button.MaterialButton materialButton =
|
|
||||||
(com.google.android.material.button.MaterialButton) initiatePaymentButton;
|
|
||||||
|
|
||||||
if (hasValidAmount) {
|
|
||||||
// Active state - red background like in the XML
|
|
||||||
materialButton.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#DE0701")));
|
|
||||||
materialButton.setTextColor(Color.WHITE);
|
|
||||||
materialButton.setAlpha(1.0f);
|
|
||||||
} else {
|
|
||||||
// Inactive state - gray background
|
|
||||||
materialButton.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#E8E8E8")));
|
|
||||||
materialButton.setTextColor(Color.parseColor("#999999"));
|
|
||||||
materialButton.setAlpha(0.6f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTransaction() {
|
private void createTransaction() {
|
||||||
String amountText = currentAmount.toString();
|
if (currentAmount.length() == 0) {
|
||||||
|
Toast.makeText(this, "Masukkan jumlah pembayaran", Toast.LENGTH_SHORT).show();
|
||||||
if (TextUtils.isEmpty(amountText) || amountText.equals("0")) {
|
|
||||||
showToast("Masukkan jumlah pembayaran");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
long amountValue = Long.parseLong(amountText);
|
initiatePaymentButton.setEnabled(false);
|
||||||
|
statusTextView.setVisibility(View.VISIBLE);
|
||||||
// Validate minimum amount
|
statusTextView.setText("Creating transaction...");
|
||||||
if (amountValue < 1000) {
|
|
||||||
showToast("Minimal pembayaran Rp 1.000");
|
new CreateTransactionTask().execute();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate maximum amount
|
|
||||||
if (amountValue > 999999999L) {
|
|
||||||
showToast("Maksimal pembayaran Rp 999.999.999");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set amount for transaction
|
|
||||||
amount = (int) amountValue;
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
statusTextView.setText("Creating transaction...");
|
|
||||||
statusTextView.setVisibility(View.VISIBLE);
|
|
||||||
initiatePaymentButton.setEnabled(false);
|
|
||||||
|
|
||||||
new CreateTransactionTask().execute();
|
|
||||||
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
showToast("Format jumlah tidak valid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayQrCode(String qrImageUrl) {
|
|
||||||
// This method is no longer needed since we navigate to QrisResultActivity
|
|
||||||
// Keeping it for compatibility but it won't be called
|
|
||||||
}
|
|
||||||
|
|
||||||
private void simulateWebhook() {
|
|
||||||
// This method is no longer needed since we navigate to QrisResultActivity
|
|
||||||
// QrisResultActivity handles webhook simulation
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSuccessScreen() {
|
|
||||||
// This method is no longer needed since we navigate to QrisResultActivity
|
|
||||||
// QrisResultActivity handles success display
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateRandomString(int length) {
|
private String generateRandomString(int length) {
|
||||||
@ -377,11 +198,23 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getServerKey() {
|
private String getServerKey() {
|
||||||
// MIDTRANS_AUTH = 'Basic base64string'
|
try {
|
||||||
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
|
// MIDTRANS_AUTH = 'Basic base64string'
|
||||||
String decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT).toString();
|
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
|
||||||
// Format is usually 'SB-Mid-server-xxxx:'. Remove trailing colon if present.
|
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
|
||||||
return decoded.replace(":\n", "");
|
String decodedString = new String(decoded);
|
||||||
|
// Format is usually 'SB-Mid-server-xxxx:'. Remove trailing colon if present.
|
||||||
|
return decodedString.replace(":", "");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("MidtransCharge", "Error decoding server key: " + e.getMessage());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidServerKey(String serverKey) {
|
||||||
|
return serverKey != null &&
|
||||||
|
serverKey.startsWith("SB-Mid-server-") &&
|
||||||
|
serverKey.length() > 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
|
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
|
||||||
@ -397,52 +230,18 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
return hexString.toString();
|
return hexString.toString();
|
||||||
} catch (java.security.NoSuchAlgorithmException e) {
|
} catch (java.security.NoSuchAlgorithmException e) {
|
||||||
|
Log.e("MidtransCharge", "Error generating signature: " + e.getMessage());
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(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
|
@Override
|
||||||
public void onBackPressed() {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
// Simple back navigation
|
if (item.getItemId() == android.R.id.home) {
|
||||||
navigateBack();
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CreateTransactionTask extends AsyncTask<Void, Void, Boolean> {
|
private class CreateTransactionTask extends AsyncTask<Void, Void, Boolean> {
|
||||||
@ -461,7 +260,32 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
payload.put("channel_code", "QRIS");
|
payload.put("channel_code", "QRIS");
|
||||||
payload.put("reference_id", referenceId);
|
payload.put("reference_id", referenceId);
|
||||||
|
|
||||||
Log.d("MidtransCharge", "Amount for transaction: " + amount);
|
// Get amount from current input
|
||||||
|
String amountText = currentAmount.toString();
|
||||||
|
Log.d("MidtransCharge", "Raw amount text: " + amountText);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse amount - expecting integer in lowest denomination (Indonesian Rupiah)
|
||||||
|
amount = Integer.parseInt(amountText);
|
||||||
|
|
||||||
|
// Validate minimum amount
|
||||||
|
if (amount < 1000) {
|
||||||
|
errorMessage = "Minimum amount is IDR 1,000";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate maximum amount for testing
|
||||||
|
if (amount > 10000000) {
|
||||||
|
errorMessage = "Maximum amount is IDR 10,000,000";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "Parsed amount: " + amount);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e("MidtransCharge", "Amount parsing error: " + e.getMessage());
|
||||||
|
errorMessage = "Invalid amount format. Please enter numbers only.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
payload.put("amount", amount);
|
payload.put("amount", amount);
|
||||||
payload.put("cashflow", "MONEY_IN");
|
payload.put("cashflow", "MONEY_IN");
|
||||||
@ -474,12 +298,15 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
payload.put("mid", "71000026521");
|
payload.put("mid", "71000026521");
|
||||||
payload.put("tid", "73001500");
|
payload.put("tid", "73001500");
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "Backend transaction payload: " + payload.toString());
|
||||||
|
|
||||||
// Make the API call
|
// Make the API call
|
||||||
URL url = new URI(BACKEND_BASE + "/transactions").toURL();
|
URL url = new URI(BACKEND_BASE + "/transactions").toURL();
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setRequestMethod("POST");
|
conn.setRequestMethod("POST");
|
||||||
conn.setRequestProperty("Content-Type", "application/json");
|
conn.setRequestProperty("Content-Type", "application/json");
|
||||||
conn.setRequestProperty("Accept", "application/json");
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||||
conn.setDoOutput(true);
|
conn.setDoOutput(true);
|
||||||
|
|
||||||
try (OutputStream os = conn.getOutputStream()) {
|
try (OutputStream os = conn.getOutputStream()) {
|
||||||
@ -488,6 +315,8 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
int responseCode = conn.getResponseCode();
|
||||||
|
Log.d("MidtransCharge", "Backend response code: " + responseCode);
|
||||||
|
|
||||||
if (responseCode == 200 || responseCode == 201) {
|
if (responseCode == 200 || responseCode == 201) {
|
||||||
// Read the response
|
// Read the response
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
|
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
|
||||||
@ -497,11 +326,15 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
response.append(responseLine.trim());
|
response.append(responseLine.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "Backend response: " + response.toString());
|
||||||
|
|
||||||
// Parse the response to get transaction ID
|
// Parse the response to get transaction ID
|
||||||
JSONObject jsonResponse = new JSONObject(response.toString());
|
JSONObject jsonResponse = new JSONObject(response.toString());
|
||||||
JSONObject data = jsonResponse.getJSONObject("data");
|
JSONObject data = jsonResponse.getJSONObject("data");
|
||||||
transactionId = String.valueOf(data.getInt("id"));
|
transactionId = String.valueOf(data.getInt("id"));
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "Created transaction ID: " + transactionId);
|
||||||
|
|
||||||
// Now generate QRIS via Midtrans
|
// Now generate QRIS via Midtrans
|
||||||
return generateQris(amount);
|
return generateQris(amount);
|
||||||
} else {
|
} else {
|
||||||
@ -512,18 +345,29 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
while ((responseLine = br.readLine()) != null) {
|
while ((responseLine = br.readLine()) != null) {
|
||||||
response.append(responseLine.trim());
|
response.append(responseLine.trim());
|
||||||
}
|
}
|
||||||
errorMessage = "Error creating transaction: " + response.toString();
|
Log.e("MidtransCharge", "Backend error response: " + response.toString());
|
||||||
|
errorMessage = "Error creating backend transaction: " + response.toString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("MidtransCharge", "Exception: " + e.getMessage(), e);
|
Log.e("MidtransCharge", "Backend transaction exception: " + e.getMessage(), e);
|
||||||
errorMessage = "Unexpected error: " + e.getMessage();
|
errorMessage = "Backend transaction error: " + e.getMessage();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean generateQris(int amount) {
|
private boolean generateQris(int amount) {
|
||||||
try {
|
try {
|
||||||
|
// Validate server key first
|
||||||
|
String serverKey = getServerKey();
|
||||||
|
if (!isValidServerKey(serverKey)) {
|
||||||
|
Log.e("MidtransCharge", "Invalid server key format");
|
||||||
|
errorMessage = "Invalid server key configuration";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "Using server key: " + serverKey.substring(0, Math.min(20, serverKey.length())) + "...");
|
||||||
|
|
||||||
// Create QRIS charge JSON payload
|
// Create QRIS charge JSON payload
|
||||||
JSONObject payload = new JSONObject();
|
JSONObject payload = new JSONObject();
|
||||||
payload.put("payment_type", "qris");
|
payload.put("payment_type", "qris");
|
||||||
@ -533,13 +377,31 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
transactionDetails.put("gross_amount", amount);
|
transactionDetails.put("gross_amount", amount);
|
||||||
payload.put("transaction_details", transactionDetails);
|
payload.put("transaction_details", transactionDetails);
|
||||||
|
|
||||||
|
// Add customer details (recommended for better success rate)
|
||||||
|
JSONObject customerDetails = new JSONObject();
|
||||||
|
customerDetails.put("first_name", "Test");
|
||||||
|
customerDetails.put("last_name", "Customer");
|
||||||
|
customerDetails.put("email", "test@example.com");
|
||||||
|
customerDetails.put("phone", "081234567890");
|
||||||
|
payload.put("customer_details", customerDetails);
|
||||||
|
|
||||||
|
// Add item details (optional but recommended)
|
||||||
|
org.json.JSONArray itemDetails = new org.json.JSONArray();
|
||||||
|
JSONObject item = new JSONObject();
|
||||||
|
item.put("id", "item1");
|
||||||
|
item.put("price", amount);
|
||||||
|
item.put("quantity", 1);
|
||||||
|
item.put("name", "QRIS Payment");
|
||||||
|
itemDetails.put(item);
|
||||||
|
payload.put("item_details", itemDetails);
|
||||||
|
|
||||||
// Log the request details
|
// Log the request details
|
||||||
|
Log.d("MidtransCharge", "=== MIDTRANS QRIS REQUEST ===");
|
||||||
Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
|
Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
|
||||||
Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
|
Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
|
||||||
Log.d("MidtransCharge", "Accept: application/json");
|
|
||||||
Log.d("MidtransCharge", "Content-Type: application/json");
|
|
||||||
Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
|
Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
|
||||||
Log.d("MidtransCharge", "Payload: " + payload.toString());
|
Log.d("MidtransCharge", "Payload: " + payload.toString());
|
||||||
|
Log.d("MidtransCharge", "================================");
|
||||||
|
|
||||||
// Make the API call to Midtrans
|
// Make the API call to Midtrans
|
||||||
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
|
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
|
||||||
@ -549,7 +411,10 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
conn.setRequestProperty("Content-Type", "application/json");
|
conn.setRequestProperty("Content-Type", "application/json");
|
||||||
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
|
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
|
||||||
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
|
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
|
||||||
|
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||||
conn.setDoOutput(true);
|
conn.setDoOutput(true);
|
||||||
|
conn.setConnectTimeout(30000); // 30 seconds
|
||||||
|
conn.setReadTimeout(30000); // 30 seconds
|
||||||
|
|
||||||
try (OutputStream os = conn.getOutputStream()) {
|
try (OutputStream os = conn.getOutputStream()) {
|
||||||
byte[] input = payload.toString().getBytes("utf-8");
|
byte[] input = payload.toString().getBytes("utf-8");
|
||||||
@ -557,6 +422,8 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
int responseCode = conn.getResponseCode();
|
||||||
|
Log.d("MidtransCharge", "Midtrans HTTP Response Code: " + responseCode);
|
||||||
|
|
||||||
if (responseCode == 200 || responseCode == 201) {
|
if (responseCode == 200 || responseCode == 201) {
|
||||||
InputStream inputStream = conn.getInputStream();
|
InputStream inputStream = conn.getInputStream();
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
@ -567,8 +434,32 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
response.append(responseLine.trim());
|
response.append(responseLine.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "Midtrans Success Response: " + response.toString());
|
||||||
|
|
||||||
// Parse the response
|
// Parse the response
|
||||||
midtransResponse = new JSONObject(response.toString());
|
midtransResponse = new JSONObject(response.toString());
|
||||||
|
|
||||||
|
// Check if response contains error within success response
|
||||||
|
if (midtransResponse.has("status_code")) {
|
||||||
|
String statusCode = midtransResponse.getString("status_code");
|
||||||
|
if (!statusCode.equals("201")) {
|
||||||
|
String statusMessage = midtransResponse.optString("status_message", "Unknown error");
|
||||||
|
Log.e("MidtransCharge", "Midtrans Error in response: " + statusCode + " - " + statusMessage);
|
||||||
|
errorMessage = "Midtrans Error: " + statusMessage + " (Code: " + statusCode + ")";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate response has required fields
|
||||||
|
if (!midtransResponse.has("actions") ||
|
||||||
|
!midtransResponse.has("transaction_id") ||
|
||||||
|
!midtransResponse.has("gross_amount")) {
|
||||||
|
Log.e("MidtransCharge", "Missing required fields in Midtrans response");
|
||||||
|
errorMessage = "Invalid response from Midtrans - missing required fields";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d("MidtransCharge", "QRIS generation successful!");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available");
|
Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available");
|
||||||
@ -584,17 +475,38 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
while ((responseLine = br.readLine()) != null) {
|
while ((responseLine = br.readLine()) != null) {
|
||||||
errorResponse.append(responseLine.trim());
|
errorResponse.append(responseLine.trim());
|
||||||
}
|
}
|
||||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": " + errorResponse.toString());
|
|
||||||
errorMessage = "Error generating QRIS: HTTP " + responseCode + ": " + errorResponse.toString();
|
Log.e("MidtransCharge", "Midtrans HTTP " + responseCode + ": " + errorResponse.toString());
|
||||||
|
|
||||||
|
// Try to parse error JSON for better error message
|
||||||
|
try {
|
||||||
|
JSONObject errorJson = new JSONObject(errorResponse.toString());
|
||||||
|
|
||||||
|
// Handle different error response formats
|
||||||
|
String errorMessage = "";
|
||||||
|
if (errorJson.has("error_messages")) {
|
||||||
|
errorMessage = errorJson.optString("error_messages", "Unknown error");
|
||||||
|
} else if (errorJson.has("status_message")) {
|
||||||
|
errorMessage = errorJson.optString("status_message", "Unknown error");
|
||||||
|
} else if (errorJson.has("message")) {
|
||||||
|
errorMessage = errorJson.optString("message", "Unknown error");
|
||||||
|
} else {
|
||||||
|
errorMessage = errorResponse.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorMessage = "Midtrans Error: " + errorMessage;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
this.errorMessage = "HTTP " + responseCode + ": " + errorResponse.toString();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": No error stream available");
|
Log.e("MidtransCharge", "HTTP " + responseCode + ": No error stream available");
|
||||||
errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No error stream available";
|
this.errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No error stream available";
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("MidtransCharge", "Exception: " + e.getMessage(), e);
|
Log.e("MidtransCharge", "Midtrans QRIS generation exception: " + e.getMessage(), e);
|
||||||
errorMessage = "Unexpected error: " + e.getMessage();
|
errorMessage = "Network error: " + e.getMessage();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -604,7 +516,17 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
if (success && midtransResponse != null) {
|
if (success && midtransResponse != null) {
|
||||||
try {
|
try {
|
||||||
// Extract needed values from midtransResponse
|
// Extract needed values from midtransResponse
|
||||||
JSONObject actions = midtransResponse.getJSONArray("actions").getJSONObject(0);
|
org.json.JSONArray actionsArray = midtransResponse.getJSONArray("actions");
|
||||||
|
if (actionsArray.length() == 0) {
|
||||||
|
Log.e("MidtransCharge", "No actions found in Midtrans response");
|
||||||
|
Toast.makeText(QrisActivity.this, "Error: No QR code URL found in response", Toast.LENGTH_LONG).show();
|
||||||
|
initiatePaymentButton.setEnabled(true);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
statusTextView.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject actions = actionsArray.getJSONObject(0);
|
||||||
String qrImageUrl = actions.getString("url");
|
String qrImageUrl = actions.getString("url");
|
||||||
|
|
||||||
// Extract transaction_id
|
// Extract transaction_id
|
||||||
@ -615,15 +537,19 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
String exactGrossAmount = midtransResponse.getString("gross_amount");
|
String exactGrossAmount = midtransResponse.getString("gross_amount");
|
||||||
|
|
||||||
// Log everything before launching activity
|
// Log everything before launching activity
|
||||||
Log.d("MidtransCharge", "Creating QrisResultActivity intent with:");
|
Log.d("MidtransCharge", "=== LAUNCHING QRIS RESULT ACTIVITY ===");
|
||||||
Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl);
|
Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl);
|
||||||
Log.d("MidtransCharge", "amount: " + amount);
|
Log.d("MidtransCharge", "amount: " + amount);
|
||||||
Log.d("MidtransCharge", "referenceId: " + referenceId);
|
Log.d("MidtransCharge", "referenceId: " + referenceId);
|
||||||
Log.d("MidtransCharge", "transactionUuid (orderId): " + transactionUuid);
|
Log.d("MidtransCharge", "transactionUuid (orderId): " + transactionUuid);
|
||||||
Log.d("MidtransCharge", "transaction_id: " + transactionId);
|
Log.d("MidtransCharge", "transaction_id: " + transactionId);
|
||||||
Log.d("MidtransCharge", "exactGrossAmount: " + exactGrossAmount);
|
Log.d("MidtransCharge", "exactGrossAmount: " + exactGrossAmount);
|
||||||
|
Log.d("MidtransCharge", "transactionTime: " + transactionTime);
|
||||||
|
Log.d("MidtransCharge", "acquirer: " + acquirer);
|
||||||
|
Log.d("MidtransCharge", "merchantId: " + merchantId);
|
||||||
|
Log.d("MidtransCharge", "========================================");
|
||||||
|
|
||||||
// Launch QrisResultActivity instead of showing QR inline
|
// Launch QrisResultActivity
|
||||||
Intent intent = new Intent(QrisActivity.this, QrisResultActivity.class);
|
Intent intent = new Intent(QrisActivity.this, QrisResultActivity.class);
|
||||||
intent.putExtra("qrImageUrl", qrImageUrl);
|
intent.putExtra("qrImageUrl", qrImageUrl);
|
||||||
intent.putExtra("amount", amount);
|
intent.putExtra("amount", amount);
|
||||||
@ -637,54 +563,24 @@ public class QrisActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
// Finish this activity so user can't go back to input form
|
finish(); // Close QrisActivity
|
||||||
finish();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e);
|
Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e);
|
||||||
Toast.makeText(QrisActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
Toast.makeText(QrisActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
resetToInitialState();
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
|
Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
|
||||||
Toast.makeText(QrisActivity.this, "Error processing QRIS response", Toast.LENGTH_LONG).show();
|
Toast.makeText(QrisActivity.this, "Error processing QRIS response: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
resetToInitialState();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String message = (errorMessage != null && !errorMessage.isEmpty()) ? errorMessage : "Unknown error occurred. Please check Logcat for details.";
|
String message = (errorMessage != null && !errorMessage.isEmpty()) ?
|
||||||
|
errorMessage : "Unknown error occurred. Please check Logcat for details.";
|
||||||
Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
|
||||||
resetToInitialState();
|
initiatePaymentButton.setEnabled(true);
|
||||||
}
|
}
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
statusTextView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetToInitialState() {
|
|
||||||
// Reset to show the input form again
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
statusTextView.setVisibility(View.GONE);
|
|
||||||
initiatePaymentButton.setEnabled(true);
|
|
||||||
updateButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove DownloadImageTask - no longer needed
|
|
||||||
|
|
||||||
// Remove SimulateWebhookTask - no longer needed
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (animationHandler != null) {
|
|
||||||
animationHandler.removeCallbacksAndMessages(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods for testing
|
|
||||||
public String getCurrentAmount() {
|
|
||||||
return currentAmount.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInitiateButtonEnabled() {
|
|
||||||
return initiatePaymentButton.isEnabled();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,26 +3,21 @@ package com.example.bdkipoc;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.CountDownTimer;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -35,28 +30,18 @@ import java.io.OutputStream;
|
|||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class QrisResultActivity extends AppCompatActivity {
|
public class QrisResultActivity extends AppCompatActivity {
|
||||||
private ImageView qrImageView;
|
private ImageView qrImageView;
|
||||||
private TextView amountTextView;
|
private TextView amountTextView;
|
||||||
private TextView referenceTextView;
|
private TextView referenceTextView;
|
||||||
private TextView timerTextView;
|
|
||||||
private TextView qrStatusTextView;
|
|
||||||
private Button downloadQrisButton;
|
private Button downloadQrisButton;
|
||||||
private Button checkStatusButton;
|
private Button checkStatusButton;
|
||||||
private Button cancelButton;
|
|
||||||
private TextView statusTextView;
|
private TextView statusTextView;
|
||||||
private Button returnMainButton;
|
private Button returnMainButton;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private LinearLayout backNavigation;
|
|
||||||
|
|
||||||
// Timer and QR refresh
|
|
||||||
private CountDownTimer countDownTimer;
|
|
||||||
private long timeLeftInMillis = 60000; // 60 seconds
|
|
||||||
private int qrRefreshCount = 0;
|
|
||||||
private final int MAX_QR_REFRESH = 5; // Maximum 5 refreshes
|
|
||||||
|
|
||||||
// Data variables
|
|
||||||
private String orderId;
|
private String orderId;
|
||||||
private String grossAmount;
|
private String grossAmount;
|
||||||
private String referenceId;
|
private String referenceId;
|
||||||
@ -64,102 +49,42 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
private String transactionTime;
|
private String transactionTime;
|
||||||
private String acquirer;
|
private String acquirer;
|
||||||
private String merchantId;
|
private String merchantId;
|
||||||
private String originalQrImageUrl;
|
|
||||||
private int amount;
|
|
||||||
private String backendBase = "https://be-edc.msvc.app";
|
private String backendBase = "https://be-edc.msvc.app";
|
||||||
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
|
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
|
||||||
|
|
||||||
|
// Server key for signature generation
|
||||||
|
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM=";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Set status bar color programmatically
|
|
||||||
setStatusBarColor();
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_qris_result);
|
setContentView(R.layout.activity_qris_result);
|
||||||
|
|
||||||
initializeViews();
|
// Set up the toolbar
|
||||||
setupClickListeners();
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
setupData();
|
if (toolbar != null) {
|
||||||
startTimer();
|
setSupportActionBar(toolbar);
|
||||||
}
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
private void setStatusBarColor() {
|
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
getSupportActionBar().setTitle("QRIS Payment");
|
||||||
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() {
|
// Initialize views
|
||||||
qrImageView = findViewById(R.id.qrImageView);
|
qrImageView = findViewById(R.id.qrImageView);
|
||||||
amountTextView = findViewById(R.id.amountTextView);
|
amountTextView = findViewById(R.id.amountTextView);
|
||||||
referenceTextView = findViewById(R.id.referenceTextView);
|
referenceTextView = findViewById(R.id.referenceTextView);
|
||||||
timerTextView = findViewById(R.id.timerTextView);
|
|
||||||
qrStatusTextView = findViewById(R.id.qrStatusTextView);
|
|
||||||
downloadQrisButton = findViewById(R.id.downloadQrisButton);
|
downloadQrisButton = findViewById(R.id.downloadQrisButton);
|
||||||
checkStatusButton = findViewById(R.id.checkStatusButton);
|
checkStatusButton = findViewById(R.id.checkStatusButton);
|
||||||
cancelButton = findViewById(R.id.cancelButton);
|
|
||||||
statusTextView = findViewById(R.id.statusTextView);
|
statusTextView = findViewById(R.id.statusTextView);
|
||||||
returnMainButton = findViewById(R.id.returnMainButton);
|
returnMainButton = findViewById(R.id.returnMainButton);
|
||||||
progressBar = findViewById(R.id.progressBar);
|
progressBar = findViewById(R.id.progressBar);
|
||||||
backNavigation = findViewById(R.id.back_navigation);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupClickListeners() {
|
// Get intent data
|
||||||
// Back navigation
|
|
||||||
if (backNavigation != null) {
|
|
||||||
backNavigation.setOnClickListener(v -> finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel button
|
|
||||||
cancelButton.setOnClickListener(v -> {
|
|
||||||
if (countDownTimer != null) {
|
|
||||||
countDownTimer.cancel();
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Download QRIS button (now visible and functional)
|
|
||||||
downloadQrisButton.setOnClickListener(v -> {
|
|
||||||
qrImageView.setDrawingCacheEnabled(true);
|
|
||||||
Bitmap bitmap = qrImageView.getDrawingCache();
|
|
||||||
if (bitmap != null) {
|
|
||||||
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
qrImageView.setDrawingCacheEnabled(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check Payment Status button (now visible and functional)
|
|
||||||
checkStatusButton.setOnClickListener(v -> {
|
|
||||||
Toast.makeText(this, "Checking payment status...", Toast.LENGTH_SHORT).show();
|
|
||||||
simulateWebhook();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return to Main Screen button (now visible and functional)
|
|
||||||
returnMainButton.setOnClickListener(v -> {
|
|
||||||
if (countDownTimer != null) {
|
|
||||||
countDownTimer.cancel();
|
|
||||||
}
|
|
||||||
Intent intent = new Intent(QrisResultActivity.this, MainActivity.class);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
startActivity(intent);
|
|
||||||
finishAffinity();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupData() {
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String qrImageUrl = intent.getStringExtra("qrImageUrl");
|
String qrImageUrl = intent.getStringExtra("qrImageUrl");
|
||||||
originalQrImageUrl = qrImageUrl; // Store original URL for refresh
|
int amount = intent.getIntExtra("amount", 0);
|
||||||
amount = intent.getIntExtra("amount", 0);
|
|
||||||
referenceId = intent.getStringExtra("referenceId");
|
referenceId = intent.getStringExtra("referenceId");
|
||||||
orderId = intent.getStringExtra("orderId");
|
orderId = intent.getStringExtra("orderId");
|
||||||
grossAmount = intent.getStringExtra("grossAmount");
|
grossAmount = intent.getStringExtra("grossAmount");
|
||||||
@ -168,152 +93,176 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
acquirer = intent.getStringExtra("acquirer");
|
acquirer = intent.getStringExtra("acquirer");
|
||||||
merchantId = intent.getStringExtra("merchantId");
|
merchantId = intent.getStringExtra("merchantId");
|
||||||
|
|
||||||
|
// Enhanced logging for debugging
|
||||||
|
Log.d("QrisResultFlow", "=== QRIS RESULT ACTIVITY STARTED ===");
|
||||||
|
Log.d("QrisResultFlow", "QR Image URL: " + qrImageUrl);
|
||||||
|
Log.d("QrisResultFlow", "Amount (int): " + amount);
|
||||||
|
Log.d("QrisResultFlow", "Gross Amount (string): " + grossAmount);
|
||||||
|
Log.d("QrisResultFlow", "Reference ID: " + referenceId);
|
||||||
|
Log.d("QrisResultFlow", "Order ID: " + orderId);
|
||||||
|
Log.d("QrisResultFlow", "Transaction ID: " + transactionId);
|
||||||
|
Log.d("QrisResultFlow", "Transaction Time: " + transactionTime);
|
||||||
|
Log.d("QrisResultFlow", "Acquirer: " + acquirer);
|
||||||
|
Log.d("QrisResultFlow", "Merchant ID: " + merchantId);
|
||||||
|
Log.d("QrisResultFlow", "======================================");
|
||||||
|
|
||||||
|
// Validate required data
|
||||||
if (orderId == null || transactionId == null) {
|
if (orderId == null || transactionId == null) {
|
||||||
Log.e("QrisResultFlow", "orderId or transactionId is null! Intent extras: " + intent.getExtras());
|
Log.e("QrisResultFlow", "Critical error: orderId or transactionId is null!");
|
||||||
Toast.makeText(this, "Missing transaction details!", Toast.LENGTH_LONG).show();
|
Log.e("QrisResultFlow", "Intent extras: " + intent.getExtras());
|
||||||
|
Toast.makeText(this, "Missing transaction details! Cannot proceed.", Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format and display amount
|
if (qrImageUrl == null || qrImageUrl.isEmpty()) {
|
||||||
if (grossAmount != null) {
|
Log.e("QrisResultFlow", "Critical error: QR image URL is null or empty!");
|
||||||
String formattedAmount = "RP." + formatCurrency(grossAmount);
|
Toast.makeText(this, "Missing QR code URL! Cannot display QR code.", Toast.LENGTH_LONG).show();
|
||||||
amountTextView.setText(formattedAmount);
|
|
||||||
} else if (amount > 0) {
|
|
||||||
String formattedAmount = "RP." + formatCurrency(String.valueOf(amount));
|
|
||||||
amountTextView.setText(formattedAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set reference ID (hidden)
|
// Display formatted amount
|
||||||
if (referenceId != null) {
|
String formattedAmount = formatCurrency(grossAmount != null ? grossAmount : String.valueOf(amount));
|
||||||
referenceTextView.setText("Reference ID: " + referenceId);
|
amountTextView.setText("Amount: " + formattedAmount);
|
||||||
}
|
referenceTextView.setText("Reference ID: " + referenceId);
|
||||||
|
|
||||||
// Load initial QR image
|
// Load QR image if URL is available
|
||||||
if (qrImageUrl != null) {
|
if (qrImageUrl != null && !qrImageUrl.isEmpty()) {
|
||||||
loadQrCode(qrImageUrl);
|
Log.d("QrisResultFlow", "Loading QR image from: " + qrImageUrl);
|
||||||
|
new DownloadImageTask(qrImageView).execute(qrImageUrl);
|
||||||
|
} else {
|
||||||
|
Log.w("QrisResultFlow", "QR image URL is not available");
|
||||||
|
qrImageView.setVisibility(View.GONE);
|
||||||
|
downloadQrisButton.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize UI state
|
||||||
|
checkStatusButton.setEnabled(false);
|
||||||
|
statusTextView.setText("Waiting for payment...");
|
||||||
|
|
||||||
// Start polling for pending payment log
|
// Start polling for pending payment log
|
||||||
if (orderId != null) {
|
pollPendingPaymentLog(orderId);
|
||||||
pollPendingPaymentLog(orderId);
|
|
||||||
}
|
// Set up click listeners
|
||||||
|
setupClickListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupClickListeners() {
|
||||||
|
// Download QRIS button
|
||||||
|
downloadQrisButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
downloadQrCode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check Payment Status button
|
||||||
|
checkStatusButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Log.d("QrisResultFlow", "Check status button clicked");
|
||||||
|
simulateWebhook();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return to Main Screen button
|
||||||
|
returnMainButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
Intent intent = new Intent(QrisResultActivity.this, com.example.bdkipoc.MainActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
finishAffinity();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatCurrency(String amount) {
|
private String formatCurrency(String amount) {
|
||||||
try {
|
try {
|
||||||
long number = Long.parseLong(amount.replace(".00", ""));
|
double amountDouble = Double.parseDouble(amount);
|
||||||
return String.format("%,d", number).replace(',', '.');
|
NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
|
||||||
|
return formatter.format(amountDouble);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return amount;
|
Log.w("QrisResultFlow", "Error formatting currency: " + e.getMessage());
|
||||||
|
return "IDR " + amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startTimer() {
|
private void downloadQrCode() {
|
||||||
countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) {
|
try {
|
||||||
@Override
|
qrImageView.setDrawingCacheEnabled(true);
|
||||||
public void onTick(long millisUntilFinished) {
|
qrImageView.buildDrawingCache();
|
||||||
timeLeftInMillis = millisUntilFinished;
|
Bitmap bitmap = qrImageView.getDrawingCache();
|
||||||
int seconds = (int) (millisUntilFinished / 1000);
|
if (bitmap != null) {
|
||||||
timerTextView.setText(String.valueOf(seconds));
|
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
|
||||||
|
} else {
|
||||||
// Update status text based on remaining time
|
Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show();
|
||||||
if (seconds > 10) {
|
|
||||||
qrStatusTextView.setText("QR Code akan refresh dalam " + seconds + " detik");
|
|
||||||
qrStatusTextView.setTextColor(Color.parseColor("#666666"));
|
|
||||||
} else {
|
|
||||||
qrStatusTextView.setText("QR Code akan refresh dalam " + seconds + " detik");
|
|
||||||
qrStatusTextView.setTextColor(Color.parseColor("#E31937"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
@Override
|
Log.e("QrisResultFlow", "Error downloading QR code: " + e.getMessage(), e);
|
||||||
public void onFinish() {
|
Toast.makeText(this, "Error downloading QR code: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
timerTextView.setText("0");
|
} finally {
|
||||||
qrStatusTextView.setText("Refreshing QR Code...");
|
qrImageView.setDrawingCacheEnabled(false);
|
||||||
qrStatusTextView.setTextColor(Color.parseColor("#E31937"));
|
|
||||||
|
|
||||||
// Auto refresh QR code
|
|
||||||
refreshQrCode();
|
|
||||||
}
|
|
||||||
}.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshQrCode() {
|
|
||||||
qrRefreshCount++;
|
|
||||||
|
|
||||||
if (qrRefreshCount >= MAX_QR_REFRESH) {
|
|
||||||
// Max refresh reached, show expired message
|
|
||||||
timerTextView.setText("Expired");
|
|
||||||
qrStatusTextView.setText("QR Code telah expired. Silahkan generate ulang.");
|
|
||||||
qrStatusTextView.setTextColor(Color.parseColor("#E31937"));
|
|
||||||
Toast.makeText(this, "QR Code expired. Please generate new payment.", Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
qrStatusTextView.setText("Generating new QR Code... (" + qrRefreshCount + "/" + MAX_QR_REFRESH + ")");
|
|
||||||
|
|
||||||
// Simulate generating new QR code
|
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
|
||||||
// Simulate API call delay
|
|
||||||
Thread.sleep(2000);
|
|
||||||
|
|
||||||
// Generate new QR code (in real app, call Midtrans API again)
|
|
||||||
generateNewQrCode();
|
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateNewQrCode() {
|
|
||||||
new Handler(Looper.getMainLooper()).post(() -> {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// In real implementation, you would call Midtrans API again
|
|
||||||
// For demo, we'll reload the same QR with a timestamp to show refresh
|
|
||||||
String refreshedUrl = originalQrImageUrl + "?refresh=" + System.currentTimeMillis();
|
|
||||||
loadQrCode(refreshedUrl);
|
|
||||||
|
|
||||||
// Reset timer for new 60 seconds
|
|
||||||
timeLeftInMillis = 60000;
|
|
||||||
startTimer();
|
|
||||||
|
|
||||||
Toast.makeText(this, "QR Code refreshed! (" + qrRefreshCount + "/" + MAX_QR_REFRESH + ")", Toast.LENGTH_SHORT).show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadQrCode(String qrImageUrl) {
|
|
||||||
new DownloadImageTask(qrImageView).execute(qrImageUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
|
private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
|
||||||
ImageView bmImage;
|
private ImageView bmImage;
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
DownloadImageTask(ImageView bmImage) {
|
DownloadImageTask(ImageView bmImage) {
|
||||||
this.bmImage = bmImage;
|
this.bmImage = bmImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected Bitmap doInBackground(String... urls) {
|
protected Bitmap doInBackground(String... urls) {
|
||||||
String urlDisplay = urls[0];
|
String urlDisplay = urls[0];
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
try {
|
try {
|
||||||
|
Log.d("QrisResultFlow", "Downloading image from: " + urlDisplay);
|
||||||
URL url = new URI(urlDisplay).toURL();
|
URL url = new URI(urlDisplay).toURL();
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setDoInput(true);
|
connection.setDoInput(true);
|
||||||
|
connection.setConnectTimeout(10000); // 10 seconds
|
||||||
|
connection.setReadTimeout(10000); // 10 seconds
|
||||||
|
connection.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||||
connection.connect();
|
connection.connect();
|
||||||
InputStream input = connection.getInputStream();
|
|
||||||
bitmap = BitmapFactory.decodeStream(input);
|
int responseCode = connection.getResponseCode();
|
||||||
|
Log.d("QrisResultFlow", "Image download response code: " + responseCode);
|
||||||
|
|
||||||
|
if (responseCode == 200) {
|
||||||
|
InputStream input = connection.getInputStream();
|
||||||
|
bitmap = BitmapFactory.decodeStream(input);
|
||||||
|
if (bitmap != null) {
|
||||||
|
Log.d("QrisResultFlow", "Image downloaded successfully. Size: " +
|
||||||
|
bitmap.getWidth() + "x" + bitmap.getHeight());
|
||||||
|
} else {
|
||||||
|
Log.e("QrisResultFlow", "Failed to decode bitmap from stream");
|
||||||
|
errorMessage = "Failed to decode QR code image";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e("QrisResultFlow", "Failed to download image. HTTP code: " + responseCode);
|
||||||
|
errorMessage = "Failed to download QR code (HTTP " + responseCode + ")";
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
Log.e("QrisResultFlow", "Exception downloading image: " + e.getMessage(), e);
|
||||||
|
errorMessage = "Error downloading QR code: " + e.getMessage();
|
||||||
}
|
}
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void onPostExecute(Bitmap result) {
|
protected void onPostExecute(Bitmap result) {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
bmImage.setImageBitmap(result);
|
bmImage.setImageBitmap(result);
|
||||||
|
Log.d("QrisResultFlow", "QR code image displayed successfully");
|
||||||
|
} else {
|
||||||
|
Log.e("QrisResultFlow", "Failed to display QR code image");
|
||||||
|
bmImage.setImageResource(android.R.drawable.ic_menu_report_image);
|
||||||
|
// Show error message to user if available
|
||||||
|
if (errorMessage != null && bmImage.getContext() != null) {
|
||||||
|
Toast.makeText(bmImage.getContext(), errorMessage, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,31 +274,45 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
getContentResolver(), bitmap, fileName, "QRIS Payment QR Code");
|
getContentResolver(), bitmap, fileName, "QRIS Payment QR Code");
|
||||||
if (savedImageURL != null) {
|
if (savedImageURL != null) {
|
||||||
Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.d("QrisResultFlow", "QR code saved to gallery: " + savedImageURL);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.e("QrisResultFlow", "Failed to save QR code to gallery");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
Log.e("QrisResultFlow", "Error saving QR code: " + e.getMessage(), e);
|
||||||
Toast.makeText(this, "Error saving QRIS: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "Error saving QRIS: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pollPendingPaymentLog(final String orderId) {
|
private void pollPendingPaymentLog(final String orderId) {
|
||||||
Log.d("QrisResultFlow", "Polling for orderId (transaction_uuid): " + orderId);
|
Log.d("QrisResultFlow", "Starting polling for orderId: " + orderId);
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
statusTextView.setText("Checking payment status...");
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
int maxAttempts = 10;
|
int maxAttempts = 12; // Increased attempts
|
||||||
int intervalMs = 1500;
|
int intervalMs = 2000; // 2 seconds interval
|
||||||
int attempt = 0;
|
int attempt = 0;
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
|
|
||||||
while (attempt < maxAttempts && !found) {
|
while (attempt < maxAttempts && !found) {
|
||||||
try {
|
try {
|
||||||
String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + orderId + "\"}";
|
String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + orderId + "\"}";
|
||||||
|
Log.d("QrisResultFlow", "Polling attempt " + (attempt + 1) + "/" + maxAttempts);
|
||||||
Log.d("QrisResultFlow", "Polling URL: " + urlStr);
|
Log.d("QrisResultFlow", "Polling URL: " + urlStr);
|
||||||
|
|
||||||
URL url = new URL(urlStr);
|
URL url = new URL(urlStr);
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setRequestMethod("GET");
|
conn.setRequestMethod("GET");
|
||||||
conn.setRequestProperty("Accept", "application/json");
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||||
|
conn.setConnectTimeout(5000);
|
||||||
|
conn.setReadTimeout(5000);
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
int responseCode = conn.getResponseCode();
|
||||||
|
Log.d("QrisResultFlow", "Polling response code: " + responseCode);
|
||||||
|
|
||||||
if (responseCode == 200) {
|
if (responseCode == 200) {
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||||
StringBuilder response = new StringBuilder();
|
StringBuilder response = new StringBuilder();
|
||||||
@ -357,76 +320,160 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
response.append(line);
|
response.append(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("QrisResultFlow", "Polling response: " + response.toString());
|
||||||
|
|
||||||
JSONObject json = new JSONObject(response.toString());
|
JSONObject json = new JSONObject(response.toString());
|
||||||
JSONArray results = json.optJSONArray("results");
|
JSONArray results = json.optJSONArray("results");
|
||||||
|
|
||||||
if (results != null && results.length() > 0) {
|
if (results != null && results.length() > 0) {
|
||||||
|
Log.d("QrisResultFlow", "Found " + results.length() + " log entries");
|
||||||
|
|
||||||
for (int i = 0; i < results.length(); i++) {
|
for (int i = 0; i < results.length(); i++) {
|
||||||
JSONObject log = results.getJSONObject(i);
|
JSONObject log = results.getJSONObject(i);
|
||||||
JSONObject reqBody = log.optJSONObject("request_body");
|
JSONObject reqBody = log.optJSONObject("request_body");
|
||||||
if (reqBody != null && "pending".equals(reqBody.optString("transaction_status"))) {
|
|
||||||
found = true;
|
if (reqBody != null) {
|
||||||
break;
|
String transactionStatus = reqBody.optString("transaction_status");
|
||||||
|
String logOrderId = reqBody.optString("order_id");
|
||||||
|
|
||||||
|
Log.d("QrisResultFlow", "Log entry " + i + ": order_id=" + logOrderId +
|
||||||
|
", transaction_status=" + transactionStatus);
|
||||||
|
|
||||||
|
if ("pending".equals(transactionStatus) && orderId.equals(logOrderId)) {
|
||||||
|
found = true;
|
||||||
|
Log.d("QrisResultFlow", "Found matching pending payment log!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("QrisResultFlow", "No log entries found in response");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.w("QrisResultFlow", "Polling failed with HTTP code: " + responseCode);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("QrisResultFlow", "Polling error: " + e.getMessage(), e);
|
Log.e("QrisResultFlow", "Polling error on attempt " + (attempt + 1) + ": " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
attempt++;
|
attempt++;
|
||||||
try { Thread.sleep(intervalMs); } catch (InterruptedException ignored) {}
|
if (attempt < maxAttempts) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(intervalMs);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
Log.d("QrisResultFlow", "Polling interrupted");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean logFound = found;
|
final boolean logFound = found;
|
||||||
new Handler(Looper.getMainLooper()).post(() -> {
|
new Handler(Looper.getMainLooper()).post(() -> {
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
if (logFound) {
|
if (logFound) {
|
||||||
checkStatusButton.setEnabled(true);
|
checkStatusButton.setEnabled(true);
|
||||||
Toast.makeText(QrisResultActivity.this, "Pending payment log found!", Toast.LENGTH_SHORT).show();
|
statusTextView.setText("Ready to simulate payment");
|
||||||
|
Toast.makeText(QrisResultActivity.this, "Pending payment log found! You can now simulate payment.", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.d("QrisResultFlow", "Polling completed successfully - payment log found");
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(QrisResultActivity.this, "Pending payment log NOT found.", Toast.LENGTH_LONG).show();
|
statusTextView.setText("Payment log not found");
|
||||||
|
Toast.makeText(QrisResultActivity.this, "Pending payment log NOT found. You may still try to simulate payment.", Toast.LENGTH_LONG).show();
|
||||||
|
Log.w("QrisResultFlow", "Polling completed - payment log NOT found after " + maxAttempts + " attempts");
|
||||||
|
// Enable button anyway to allow manual testing
|
||||||
|
checkStatusButton.setEnabled(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getServerKey() {
|
||||||
|
try {
|
||||||
|
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
|
||||||
|
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
|
||||||
|
String decodedString = new String(decoded);
|
||||||
|
return decodedString.replace(":", "");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("QrisResultFlow", "Error decoding server key: " + e.getMessage());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
|
||||||
|
String input = orderId + statusCode + grossAmount + serverKey;
|
||||||
|
try {
|
||||||
|
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-512");
|
||||||
|
byte[] messageDigest = md.digest(input.getBytes());
|
||||||
|
StringBuilder hexString = new StringBuilder();
|
||||||
|
for (byte b : messageDigest) {
|
||||||
|
String hex = Integer.toHexString(0xff & b);
|
||||||
|
if (hex.length() == 1) hexString.append('0');
|
||||||
|
hexString.append(hex);
|
||||||
|
}
|
||||||
|
return hexString.toString();
|
||||||
|
} catch (java.security.NoSuchAlgorithmException e) {
|
||||||
|
Log.e("QrisResultFlow", "Error generating signature: " + e.getMessage());
|
||||||
|
return "dummy_signature";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simulate webhook callback
|
// Simulate webhook callback
|
||||||
private void simulateWebhook() {
|
private void simulateWebhook() {
|
||||||
|
Log.d("QrisResultFlow", "Starting webhook simulation");
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
statusTextView.setText("Simulating payment...");
|
||||||
|
checkStatusButton.setEnabled(false);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
|
// Generate proper signature
|
||||||
|
String serverKey = getServerKey();
|
||||||
|
String signatureKey = generateSignature(orderId, "200", grossAmount, serverKey);
|
||||||
|
|
||||||
JSONObject payload = new JSONObject();
|
JSONObject payload = new JSONObject();
|
||||||
payload.put("transaction_type", "on-us");
|
payload.put("transaction_type", "on-us");
|
||||||
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
||||||
payload.put("transaction_status", "settlement");
|
payload.put("transaction_status", "settlement");
|
||||||
payload.put("transaction_id", transactionId); // Use the actual transaction_id
|
payload.put("transaction_id", transactionId);
|
||||||
payload.put("status_message", "midtrans payment notification");
|
payload.put("status_message", "midtrans payment notification");
|
||||||
payload.put("status_code", "200");
|
payload.put("status_code", "200");
|
||||||
payload.put("signature_key", "dummy_signature");
|
payload.put("signature_key", signatureKey);
|
||||||
payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
||||||
payload.put("payment_type", "qris");
|
payload.put("payment_type", "qris");
|
||||||
payload.put("order_id", orderId); // Use order_id
|
payload.put("order_id", orderId);
|
||||||
payload.put("merchant_id", merchantId != null ? merchantId : "DUMMY_MERCHANT_ID");
|
payload.put("merchant_id", merchantId != null ? merchantId : "DUMMY_MERCHANT_ID");
|
||||||
payload.put("issuer", acquirer != null ? acquirer : "gopay");
|
payload.put("issuer", acquirer != null ? acquirer : "gopay");
|
||||||
payload.put("gross_amount", grossAmount); // Use exact gross amount
|
payload.put("gross_amount", grossAmount);
|
||||||
payload.put("fraud_status", "accept");
|
payload.put("fraud_status", "accept");
|
||||||
payload.put("currency", "IDR");
|
payload.put("currency", "IDR");
|
||||||
payload.put("acquirer", acquirer != null ? acquirer : "gopay");
|
payload.put("acquirer", acquirer != null ? acquirer : "gopay");
|
||||||
payload.put("shopeepay_reference_number", "");
|
payload.put("shopeepay_reference_number", "");
|
||||||
payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID");
|
payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID");
|
||||||
|
|
||||||
|
Log.d("QrisResultFlow", "=== WEBHOOK SIMULATION ===");
|
||||||
|
Log.d("QrisResultFlow", "Webhook URL: " + webhookUrl);
|
||||||
Log.d("QrisResultFlow", "Webhook payload: " + payload.toString());
|
Log.d("QrisResultFlow", "Webhook payload: " + payload.toString());
|
||||||
|
Log.d("QrisResultFlow", "==========================");
|
||||||
|
|
||||||
URL url = new URL(webhookUrl);
|
URL url = new URL(webhookUrl);
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.setRequestMethod("POST");
|
conn.setRequestMethod("POST");
|
||||||
conn.setRequestProperty("Content-Type", "application/json");
|
conn.setRequestProperty("Content-Type", "application/json");
|
||||||
|
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||||
conn.setDoOutput(true);
|
conn.setDoOutput(true);
|
||||||
|
conn.setConnectTimeout(15000);
|
||||||
|
conn.setReadTimeout(15000);
|
||||||
|
|
||||||
OutputStream os = conn.getOutputStream();
|
OutputStream os = conn.getOutputStream();
|
||||||
os.write(payload.toString().getBytes());
|
os.write(payload.toString().getBytes());
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
os.close();
|
||||||
|
|
||||||
int responseCode = conn.getResponseCode();
|
int responseCode = conn.getResponseCode();
|
||||||
|
Log.d("QrisResultFlow", "Webhook response code: " + responseCode);
|
||||||
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
BufferedReader br = new BufferedReader(new InputStreamReader(
|
||||||
responseCode < 400 ? conn.getInputStream() : conn.getErrorStream()));
|
responseCode < 400 ? conn.getInputStream() : conn.getErrorStream()));
|
||||||
StringBuilder response = new StringBuilder();
|
StringBuilder response = new StringBuilder();
|
||||||
@ -434,58 +481,57 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
response.append(line);
|
response.append(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("QrisResultFlow", "Webhook response: " + response.toString());
|
Log.d("QrisResultFlow", "Webhook response: " + response.toString());
|
||||||
|
|
||||||
|
// Wait a bit for processing
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("QrisResultFlow", "Webhook error: " + e.getMessage(), e);
|
Log.e("QrisResultFlow", "Webhook simulation error: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
new Handler(Looper.getMainLooper()).post(() -> {
|
new Handler(Looper.getMainLooper()).post(() -> {
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
// Show success state
|
showPaymentSuccess();
|
||||||
showSuccessState();
|
|
||||||
});
|
});
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSuccessState() {
|
private void showPaymentSuccess() {
|
||||||
// Stop timer
|
Log.d("QrisResultFlow", "Showing payment success screen");
|
||||||
if (countDownTimer != null) {
|
|
||||||
countDownTimer.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide QR section and show success
|
// Hide payment elements
|
||||||
qrImageView.setVisibility(View.GONE);
|
qrImageView.setVisibility(View.GONE);
|
||||||
amountTextView.setVisibility(View.GONE);
|
amountTextView.setVisibility(View.GONE);
|
||||||
timerTextView.setVisibility(View.GONE);
|
referenceTextView.setVisibility(View.GONE);
|
||||||
qrStatusTextView.setVisibility(View.GONE);
|
|
||||||
downloadQrisButton.setVisibility(View.GONE);
|
downloadQrisButton.setVisibility(View.GONE);
|
||||||
checkStatusButton.setVisibility(View.GONE);
|
checkStatusButton.setVisibility(View.GONE);
|
||||||
cancelButton.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// Show success message
|
// Show success elements
|
||||||
statusTextView.setText("Payment Successful!");
|
|
||||||
statusTextView.setTextColor(Color.parseColor("#4CAF50"));
|
|
||||||
statusTextView.setTextSize(24);
|
|
||||||
statusTextView.setVisibility(View.VISIBLE);
|
statusTextView.setVisibility(View.VISIBLE);
|
||||||
|
statusTextView.setText("✅ Payment Successful!\n\nTransaction ID: " + transactionId +
|
||||||
|
"\nReference: " + referenceId +
|
||||||
|
"\nAmount: " + formatCurrency(grossAmount));
|
||||||
returnMainButton.setVisibility(View.VISIBLE);
|
returnMainButton.setVisibility(View.VISIBLE);
|
||||||
returnMainButton.setText("Back to Main");
|
|
||||||
returnMainButton.setBackgroundColor(Color.parseColor("#4CAF50"));
|
|
||||||
|
|
||||||
Toast.makeText(this, "Payment completed successfully!", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "Payment simulation completed successfully!", Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
public boolean onOptionsItemSelected(android.view.MenuItem item) {
|
||||||
super.onDestroy();
|
if (item.getItemId() == android.R.id.home) {
|
||||||
if (countDownTimer != null) {
|
onBackPressed();
|
||||||
countDownTimer.cancel();
|
return true;
|
||||||
}
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (countDownTimer != null) {
|
Intent intent = new Intent(this, com.example.bdkipoc.MainActivity.class);
|
||||||
countDownTimer.cancel();
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
}
|
startActivity(intent);
|
||||||
super.onBackPressed();
|
finishAffinity();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import android.widget.LinearLayout;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
@ -52,6 +53,14 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
|||||||
holder.amount.setText("Rp. " + t.amount);
|
holder.amount.setText("Rp. " + t.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set status with appropriate color
|
||||||
|
holder.status.setText(t.status.toUpperCase());
|
||||||
|
setStatusColor(holder.status, t.status);
|
||||||
|
|
||||||
|
// Set payment method
|
||||||
|
String paymentMethod = getPaymentMethodName(t.channelCode, t.channelCategory);
|
||||||
|
holder.paymentMethod.setText(paymentMethod);
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(v -> {
|
holder.itemView.setOnClickListener(v -> {
|
||||||
if (printClickListener != null) {
|
if (printClickListener != null) {
|
||||||
printClickListener.onPrintClick(t);
|
printClickListener.onPrintClick(t);
|
||||||
@ -66,19 +75,77 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setStatusColor(TextView statusTextView, String status) {
|
||||||
|
String statusLower = status.toLowerCase();
|
||||||
|
int color;
|
||||||
|
|
||||||
|
if (statusLower.equals("failed") || statusLower.equals("failure") ||
|
||||||
|
statusLower.equals("error") || statusLower.equals("declined")) {
|
||||||
|
// Red for failed statuses
|
||||||
|
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_red_dark);
|
||||||
|
} else if (statusLower.equals("success") || statusLower.equals("paid") ||
|
||||||
|
statusLower.equals("settlement") || statusLower.equals("completed")) {
|
||||||
|
// Green for successful statuses
|
||||||
|
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_green_dark);
|
||||||
|
} else if (statusLower.equals("pending") || statusLower.equals("processing") ||
|
||||||
|
statusLower.equals("waiting") || statusLower.equals("init")) {
|
||||||
|
// Orange/Yellow for pending statuses
|
||||||
|
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_orange_dark);
|
||||||
|
} else {
|
||||||
|
// Default gray for unknown statuses
|
||||||
|
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.darker_gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
statusTextView.setTextColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPaymentMethodName(String channelCode, String channelCategory) {
|
||||||
|
// Convert channel code to readable payment method name
|
||||||
|
if (channelCode == null) return "Unknown";
|
||||||
|
|
||||||
|
switch (channelCode.toUpperCase()) {
|
||||||
|
case "QRIS":
|
||||||
|
return "QRIS";
|
||||||
|
case "DEBIT":
|
||||||
|
return "Kartu Debit";
|
||||||
|
case "CREDIT":
|
||||||
|
return "Kartu Kredit";
|
||||||
|
case "BCA":
|
||||||
|
return "BCA";
|
||||||
|
case "MANDIRI":
|
||||||
|
return "Mandiri";
|
||||||
|
case "BNI":
|
||||||
|
return "BNI";
|
||||||
|
case "BRI":
|
||||||
|
return "BRI";
|
||||||
|
case "CASH":
|
||||||
|
return "Tunai";
|
||||||
|
case "EDC":
|
||||||
|
return "EDC";
|
||||||
|
default:
|
||||||
|
// If channel category is available, use it as fallback
|
||||||
|
if (channelCategory != null && !channelCategory.isEmpty()) {
|
||||||
|
return channelCategory.toUpperCase();
|
||||||
|
}
|
||||||
|
return channelCode.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return transactionList.size();
|
return transactionList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TransactionViewHolder extends RecyclerView.ViewHolder {
|
static class TransactionViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView amount, referenceId;
|
TextView amount, referenceId, status, paymentMethod;
|
||||||
LinearLayout printSection;
|
LinearLayout printSection;
|
||||||
|
|
||||||
public TransactionViewHolder(@NonNull View itemView) {
|
public TransactionViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
amount = itemView.findViewById(R.id.textAmount);
|
amount = itemView.findViewById(R.id.textAmount);
|
||||||
referenceId = itemView.findViewById(R.id.textReferenceId);
|
referenceId = itemView.findViewById(R.id.textReferenceId);
|
||||||
|
status = itemView.findViewById(R.id.textStatus);
|
||||||
|
paymentMethod = itemView.findViewById(R.id.textPaymentMethod);
|
||||||
printSection = itemView.findViewById(R.id.printSection);
|
printSection = itemView.findViewById(R.id.printSection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,59 @@
|
|||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<!-- Kolom 1: Reference ID -->
|
<!-- Kolom 1: Transaction Info -->
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/textReferenceId"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="ref-eowu3pin"
|
android:orientation="vertical">
|
||||||
android:textColor="#333333"
|
|
||||||
android:textSize="16sp"
|
<!-- Reference ID -->
|
||||||
android:textStyle="bold" />
|
<TextView
|
||||||
|
android:id="@+id/textReferenceId"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="ref-eowu3pin"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- Status and Payment Method -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="SUCCESS"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#4CAF50" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text=" • "
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:layout_marginHorizontal="4dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textPaymentMethod"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="QRIS"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#333333" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Kolom 2: Amount -->
|
<!-- Kolom 2: Amount -->
|
||||||
<TextView
|
<TextView
|
||||||
|
Loading…
x
Reference in New Issue
Block a user