midtrans solve
This commit is contained in:
parent
edca7f92ec
commit
a1f536b03e
@ -1,22 +1,13 @@
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
@ -28,6 +19,7 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -46,34 +38,24 @@ import java.util.UUID;
|
||||
|
||||
public class QrisActivity extends AppCompatActivity {
|
||||
|
||||
// Views
|
||||
private EditText editTextAmount;
|
||||
private ProgressBar progressBar;
|
||||
private Button initiatePaymentButton;
|
||||
private TextView statusTextView;
|
||||
private EditText editTextAmount;
|
||||
private TextView referenceIdTextView;
|
||||
private LinearLayout backNavigation;
|
||||
private TextView toolbarTitle;
|
||||
private TextView descriptionText;
|
||||
|
||||
// Numpad buttons
|
||||
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
|
||||
private TextView btnDelete; // Changed from ImageView to TextView
|
||||
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000, btnDelete;
|
||||
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 transactionUuid;
|
||||
private String referenceId;
|
||||
private int amount;
|
||||
private JSONObject midtransResponse;
|
||||
|
||||
// Animation
|
||||
private Handler animationHandler = new Handler(Looper.getMainLooper());
|
||||
private StringBuilder currentAmount = new StringBuilder();
|
||||
|
||||
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";
|
||||
@ -83,49 +65,18 @@ public class QrisActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set status bar color programmatically
|
||||
setStatusBarColor();
|
||||
|
||||
setContentView(R.layout.activity_qris);
|
||||
|
||||
initializeViews();
|
||||
setupClickListeners();
|
||||
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);
|
||||
// Initialize views
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
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);
|
||||
toolbarTitle = findViewById(R.id.toolbarTitle);
|
||||
descriptionText = findViewById(R.id.descriptionText);
|
||||
|
||||
// Numpad buttons
|
||||
// Initialize numpad buttons
|
||||
btn1 = findViewById(R.id.btn1);
|
||||
btn2 = findViewById(R.id.btn2);
|
||||
btn3 = findViewById(R.id.btn3);
|
||||
@ -137,232 +88,102 @@ public class QrisActivity extends AppCompatActivity {
|
||||
btn9 = findViewById(R.id.btn9);
|
||||
btn0 = findViewById(R.id.btn0);
|
||||
btn000 = findViewById(R.id.btn000);
|
||||
btnDelete = findViewById(R.id.btnDelete); // Now TextView
|
||||
btnDelete = findViewById(R.id.btnDelete);
|
||||
|
||||
// Only needed views for loading state
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
statusTextView = findViewById(R.id.statusTextView);
|
||||
referenceIdTextView = findViewById(R.id.referenceIdTextView);
|
||||
// Generate reference ID
|
||||
referenceId = "ref-" + generateRandomString(8);
|
||||
referenceIdTextView.setText(referenceId);
|
||||
|
||||
// Main content layout (replaces initialPaymentLayout)
|
||||
initialPaymentLayout = findViewById(R.id.mainContentLayout);
|
||||
// Set up click listeners
|
||||
initiatePaymentButton.setOnClickListener(v -> createTransaction());
|
||||
backNavigation.setOnClickListener(v -> finish());
|
||||
|
||||
// Set up numpad listeners
|
||||
setupNumpadListeners();
|
||||
|
||||
// Initially disable the button
|
||||
initiatePaymentButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
// Back navigation
|
||||
backNavigation.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
private void setupNumpadListeners() {
|
||||
View.OnClickListener numberClickListener = v -> {
|
||||
TextView button = (TextView) v;
|
||||
String number = button.getText().toString();
|
||||
appendNumber(number);
|
||||
};
|
||||
|
||||
toolbarTitle.setOnClickListener(v -> {
|
||||
addClickAnimation(v);
|
||||
navigateBack();
|
||||
});
|
||||
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);
|
||||
|
||||
// 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
|
||||
updateButtonState();
|
||||
|
||||
// Disable EditText input (only numpad input allowed)
|
||||
editTextAmount.setFocusable(false);
|
||||
editTextAmount.setClickable(false);
|
||||
editTextAmount.setCursorVisible(false);
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
finish();
|
||||
}
|
||||
|
||||
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);
|
||||
btnDelete.setOnClickListener(v -> deleteLastDigit());
|
||||
}
|
||||
|
||||
private void appendNumber(String number) {
|
||||
currentAmount.append(number);
|
||||
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();
|
||||
String amountStr = currentAmount.toString();
|
||||
|
||||
if (amount.isEmpty() || amount.equals("0")) {
|
||||
// Show description text, hide amount input
|
||||
if (amountStr.isEmpty()) {
|
||||
editTextAmount.setVisibility(View.GONE);
|
||||
descriptionText.setVisibility(View.VISIBLE);
|
||||
descriptionText.setText("Pastikan kembali nominal pembayaran pelanggan Anda");
|
||||
initiatePaymentButton.setEnabled(false);
|
||||
} else {
|
||||
// Show amount input, hide description text
|
||||
String formattedAmount = formatCurrency(amount);
|
||||
editTextAmount.setText(formattedAmount);
|
||||
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) {
|
||||
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
|
||||
return "";
|
||||
}
|
||||
private String formatAmount(String amount) {
|
||||
if (amount.isEmpty()) return "";
|
||||
|
||||
try {
|
||||
long number = Long.parseLong(amount);
|
||||
return String.format("%,d", number).replace(',', '.');
|
||||
long num = Long.parseLong(amount);
|
||||
return String.format("%,d", num);
|
||||
} catch (NumberFormatException e) {
|
||||
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() {
|
||||
String amountText = currentAmount.toString();
|
||||
|
||||
if (TextUtils.isEmpty(amountText) || amountText.equals("0")) {
|
||||
showToast("Masukkan jumlah pembayaran");
|
||||
if (currentAmount.length() == 0) {
|
||||
Toast.makeText(this, "Masukkan jumlah pembayaran", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long amountValue = Long.parseLong(amountText);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
statusTextView.setVisibility(View.VISIBLE);
|
||||
statusTextView.setText("Creating transaction...");
|
||||
|
||||
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) {
|
||||
@ -377,11 +198,23 @@ public class QrisActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private String getServerKey() {
|
||||
try {
|
||||
// MIDTRANS_AUTH = 'Basic base64string'
|
||||
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
|
||||
String decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT).toString();
|
||||
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
|
||||
String decodedString = new String(decoded);
|
||||
// Format is usually 'SB-Mid-server-xxxx:'. Remove trailing colon if present.
|
||||
return decoded.replace(":\n", "");
|
||||
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) {
|
||||
@ -397,52 +230,18 @@ public class QrisActivity extends AppCompatActivity {
|
||||
}
|
||||
return hexString.toString();
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
Log.e("MidtransCharge", "Error generating signature: " + e.getMessage());
|
||||
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
|
||||
public void onBackPressed() {
|
||||
// Simple back navigation
|
||||
navigateBack();
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private class CreateTransactionTask extends AsyncTask<Void, Void, Boolean> {
|
||||
@ -461,7 +260,32 @@ public class QrisActivity extends AppCompatActivity {
|
||||
payload.put("channel_code", "QRIS");
|
||||
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("cashflow", "MONEY_IN");
|
||||
@ -474,12 +298,15 @@ public class QrisActivity extends AppCompatActivity {
|
||||
payload.put("mid", "71000026521");
|
||||
payload.put("tid", "73001500");
|
||||
|
||||
Log.d("MidtransCharge", "Backend transaction payload: " + payload.toString());
|
||||
|
||||
// Make the API call
|
||||
URL url = new URI(BACKEND_BASE + "/transactions").toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||
conn.setDoOutput(true);
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
@ -488,6 +315,8 @@ public class QrisActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("MidtransCharge", "Backend response code: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
// Read the response
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
|
||||
@ -497,11 +326,15 @@ public class QrisActivity extends AppCompatActivity {
|
||||
response.append(responseLine.trim());
|
||||
}
|
||||
|
||||
Log.d("MidtransCharge", "Backend response: " + response.toString());
|
||||
|
||||
// Parse the response to get transaction ID
|
||||
JSONObject jsonResponse = new JSONObject(response.toString());
|
||||
JSONObject data = jsonResponse.getJSONObject("data");
|
||||
transactionId = String.valueOf(data.getInt("id"));
|
||||
|
||||
Log.d("MidtransCharge", "Created transaction ID: " + transactionId);
|
||||
|
||||
// Now generate QRIS via Midtrans
|
||||
return generateQris(amount);
|
||||
} else {
|
||||
@ -512,18 +345,29 @@ public class QrisActivity extends AppCompatActivity {
|
||||
while ((responseLine = br.readLine()) != null) {
|
||||
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;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("MidtransCharge", "Exception: " + e.getMessage(), e);
|
||||
errorMessage = "Unexpected error: " + e.getMessage();
|
||||
Log.e("MidtransCharge", "Backend transaction exception: " + e.getMessage(), e);
|
||||
errorMessage = "Backend transaction error: " + e.getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean generateQris(int amount) {
|
||||
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
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("payment_type", "qris");
|
||||
@ -533,13 +377,31 @@ public class QrisActivity extends AppCompatActivity {
|
||||
transactionDetails.put("gross_amount", amount);
|
||||
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.d("MidtransCharge", "=== MIDTRANS QRIS REQUEST ===");
|
||||
Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
|
||||
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", "Payload: " + payload.toString());
|
||||
Log.d("MidtransCharge", "================================");
|
||||
|
||||
// Make the API call to Midtrans
|
||||
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("Authorization", MIDTRANS_AUTH);
|
||||
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
|
||||
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||
conn.setDoOutput(true);
|
||||
conn.setConnectTimeout(30000); // 30 seconds
|
||||
conn.setReadTimeout(30000); // 30 seconds
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
byte[] input = payload.toString().getBytes("utf-8");
|
||||
@ -557,6 +422,8 @@ public class QrisActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("MidtransCharge", "Midtrans HTTP Response Code: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
InputStream inputStream = conn.getInputStream();
|
||||
if (inputStream != null) {
|
||||
@ -567,8 +434,32 @@ public class QrisActivity extends AppCompatActivity {
|
||||
response.append(responseLine.trim());
|
||||
}
|
||||
|
||||
Log.d("MidtransCharge", "Midtrans Success Response: " + response.toString());
|
||||
|
||||
// Parse the response
|
||||
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;
|
||||
} else {
|
||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available");
|
||||
@ -584,17 +475,38 @@ public class QrisActivity extends AppCompatActivity {
|
||||
while ((responseLine = br.readLine()) != null) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("MidtransCharge", "Exception: " + e.getMessage(), e);
|
||||
errorMessage = "Unexpected error: " + e.getMessage();
|
||||
Log.e("MidtransCharge", "Midtrans QRIS generation exception: " + e.getMessage(), e);
|
||||
errorMessage = "Network error: " + e.getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -604,7 +516,17 @@ public class QrisActivity extends AppCompatActivity {
|
||||
if (success && midtransResponse != null) {
|
||||
try {
|
||||
// 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");
|
||||
|
||||
// Extract transaction_id
|
||||
@ -615,15 +537,19 @@ public class QrisActivity extends AppCompatActivity {
|
||||
String exactGrossAmount = midtransResponse.getString("gross_amount");
|
||||
|
||||
// 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", "amount: " + amount);
|
||||
Log.d("MidtransCharge", "referenceId: " + referenceId);
|
||||
Log.d("MidtransCharge", "transactionUuid (orderId): " + transactionUuid);
|
||||
Log.d("MidtransCharge", "transaction_id: " + transactionId);
|
||||
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.putExtra("qrImageUrl", qrImageUrl);
|
||||
intent.putExtra("amount", amount);
|
||||
@ -637,54 +563,24 @@ public class QrisActivity extends AppCompatActivity {
|
||||
|
||||
try {
|
||||
startActivity(intent);
|
||||
// Finish this activity so user can't go back to input form
|
||||
finish();
|
||||
finish(); // Close QrisActivity
|
||||
} catch (Exception 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();
|
||||
resetToInitialState();
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (JSONException e) {
|
||||
Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
|
||||
Toast.makeText(QrisActivity.this, "Error processing QRIS response", Toast.LENGTH_LONG).show();
|
||||
resetToInitialState();
|
||||
Toast.makeText(QrisActivity.this, "Error processing QRIS response: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} 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();
|
||||
resetToInitialState();
|
||||
initiatePaymentButton.setEnabled(true);
|
||||
}
|
||||
progressBar.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.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -35,28 +30,18 @@ import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
public class QrisResultActivity extends AppCompatActivity {
|
||||
private ImageView qrImageView;
|
||||
private TextView amountTextView;
|
||||
private TextView referenceTextView;
|
||||
private TextView timerTextView;
|
||||
private TextView qrStatusTextView;
|
||||
private Button downloadQrisButton;
|
||||
private Button checkStatusButton;
|
||||
private Button cancelButton;
|
||||
private TextView statusTextView;
|
||||
private Button returnMainButton;
|
||||
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 grossAmount;
|
||||
private String referenceId;
|
||||
@ -64,102 +49,42 @@ public class QrisResultActivity extends AppCompatActivity {
|
||||
private String transactionTime;
|
||||
private String acquirer;
|
||||
private String merchantId;
|
||||
private String originalQrImageUrl;
|
||||
private int amount;
|
||||
private String backendBase = "https://be-edc.msvc.app";
|
||||
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
|
||||
|
||||
// Server key for signature generation
|
||||
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM=";
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Set status bar color programmatically
|
||||
setStatusBarColor();
|
||||
|
||||
setContentView(R.layout.activity_qris_result);
|
||||
|
||||
initializeViews();
|
||||
setupClickListeners();
|
||||
setupData();
|
||||
startTimer();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// Set up the toolbar
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
if (toolbar != null) {
|
||||
setSupportActionBar(toolbar);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
getSupportActionBar().setTitle("QRIS Payment");
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
// Initialize views
|
||||
qrImageView = findViewById(R.id.qrImageView);
|
||||
amountTextView = findViewById(R.id.amountTextView);
|
||||
referenceTextView = findViewById(R.id.referenceTextView);
|
||||
timerTextView = findViewById(R.id.timerTextView);
|
||||
qrStatusTextView = findViewById(R.id.qrStatusTextView);
|
||||
downloadQrisButton = findViewById(R.id.downloadQrisButton);
|
||||
checkStatusButton = findViewById(R.id.checkStatusButton);
|
||||
cancelButton = findViewById(R.id.cancelButton);
|
||||
statusTextView = findViewById(R.id.statusTextView);
|
||||
returnMainButton = findViewById(R.id.returnMainButton);
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
backNavigation = findViewById(R.id.back_navigation);
|
||||
}
|
||||
|
||||
private void setupClickListeners() {
|
||||
// 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() {
|
||||
// Get intent data
|
||||
Intent intent = getIntent();
|
||||
String qrImageUrl = intent.getStringExtra("qrImageUrl");
|
||||
originalQrImageUrl = qrImageUrl; // Store original URL for refresh
|
||||
amount = intent.getIntExtra("amount", 0);
|
||||
int amount = intent.getIntExtra("amount", 0);
|
||||
referenceId = intent.getStringExtra("referenceId");
|
||||
orderId = intent.getStringExtra("orderId");
|
||||
grossAmount = intent.getStringExtra("grossAmount");
|
||||
@ -168,152 +93,176 @@ public class QrisResultActivity extends AppCompatActivity {
|
||||
acquirer = intent.getStringExtra("acquirer");
|
||||
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) {
|
||||
Log.e("QrisResultFlow", "orderId or transactionId is null! Intent extras: " + intent.getExtras());
|
||||
Toast.makeText(this, "Missing transaction details!", Toast.LENGTH_LONG).show();
|
||||
Log.e("QrisResultFlow", "Critical error: orderId or transactionId is null!");
|
||||
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 (grossAmount != null) {
|
||||
String formattedAmount = "RP." + formatCurrency(grossAmount);
|
||||
amountTextView.setText(formattedAmount);
|
||||
} else if (amount > 0) {
|
||||
String formattedAmount = "RP." + formatCurrency(String.valueOf(amount));
|
||||
amountTextView.setText(formattedAmount);
|
||||
if (qrImageUrl == null || qrImageUrl.isEmpty()) {
|
||||
Log.e("QrisResultFlow", "Critical error: QR image URL is null or empty!");
|
||||
Toast.makeText(this, "Missing QR code URL! Cannot display QR code.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
// Set reference ID (hidden)
|
||||
if (referenceId != null) {
|
||||
// Display formatted amount
|
||||
String formattedAmount = formatCurrency(grossAmount != null ? grossAmount : String.valueOf(amount));
|
||||
amountTextView.setText("Amount: " + formattedAmount);
|
||||
referenceTextView.setText("Reference ID: " + referenceId);
|
||||
|
||||
// Load QR image if URL is available
|
||||
if (qrImageUrl != null && !qrImageUrl.isEmpty()) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Load initial QR image
|
||||
if (qrImageUrl != null) {
|
||||
loadQrCode(qrImageUrl);
|
||||
}
|
||||
// Initialize UI state
|
||||
checkStatusButton.setEnabled(false);
|
||||
statusTextView.setText("Waiting for payment...");
|
||||
|
||||
// Start polling for pending payment log
|
||||
if (orderId != null) {
|
||||
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) {
|
||||
try {
|
||||
long number = Long.parseLong(amount.replace(".00", ""));
|
||||
return String.format("%,d", number).replace(',', '.');
|
||||
double amountDouble = Double.parseDouble(amount);
|
||||
NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
|
||||
return formatter.format(amountDouble);
|
||||
} catch (NumberFormatException e) {
|
||||
return amount;
|
||||
Log.w("QrisResultFlow", "Error formatting currency: " + e.getMessage());
|
||||
return "IDR " + amount;
|
||||
}
|
||||
}
|
||||
|
||||
private void startTimer() {
|
||||
countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
timeLeftInMillis = millisUntilFinished;
|
||||
int seconds = (int) (millisUntilFinished / 1000);
|
||||
timerTextView.setText(String.valueOf(seconds));
|
||||
|
||||
// Update status text based on remaining time
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
timerTextView.setText("0");
|
||||
qrStatusTextView.setText("Refreshing QR Code...");
|
||||
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(() -> {
|
||||
private void downloadQrCode() {
|
||||
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();
|
||||
qrImageView.setDrawingCacheEnabled(true);
|
||||
qrImageView.buildDrawingCache();
|
||||
Bitmap bitmap = qrImageView.getDrawingCache();
|
||||
if (bitmap != null) {
|
||||
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
|
||||
} else {
|
||||
Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}).start();
|
||||
} catch (Exception e) {
|
||||
Log.e("QrisResultFlow", "Error downloading QR code: " + e.getMessage(), e);
|
||||
Toast.makeText(this, "Error downloading QR code: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||
} finally {
|
||||
qrImageView.setDrawingCacheEnabled(false);
|
||||
}
|
||||
|
||||
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> {
|
||||
ImageView bmImage;
|
||||
private ImageView bmImage;
|
||||
private String errorMessage;
|
||||
|
||||
DownloadImageTask(ImageView bmImage) {
|
||||
this.bmImage = bmImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap doInBackground(String... urls) {
|
||||
String urlDisplay = urls[0];
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
Log.d("QrisResultFlow", "Downloading image from: " + urlDisplay);
|
||||
URL url = new URI(urlDisplay).toURL();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.setConnectTimeout(10000); // 10 seconds
|
||||
connection.setReadTimeout(10000); // 10 seconds
|
||||
connection.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||
connection.connect();
|
||||
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
Log.e("QrisResultFlow", "Exception downloading image: " + e.getMessage(), e);
|
||||
errorMessage = "Error downloading QR code: " + e.getMessage();
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap result) {
|
||||
if (result != null) {
|
||||
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");
|
||||
if (savedImageURL != null) {
|
||||
Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show();
|
||||
Log.d("QrisResultFlow", "QR code saved to gallery: " + savedImageURL);
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show();
|
||||
Log.e("QrisResultFlow", "Failed to save QR code to gallery");
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
statusTextView.setText("Checking payment status...");
|
||||
|
||||
new Thread(() -> {
|
||||
int maxAttempts = 10;
|
||||
int intervalMs = 1500;
|
||||
int maxAttempts = 12; // Increased attempts
|
||||
int intervalMs = 2000; // 2 seconds interval
|
||||
int attempt = 0;
|
||||
boolean found = false;
|
||||
|
||||
while (attempt < maxAttempts && !found) {
|
||||
try {
|
||||
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);
|
||||
|
||||
URL url = new URL(urlStr);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("QrisResultFlow", "Polling response code: " + responseCode);
|
||||
|
||||
if (responseCode == 200) {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
@ -357,76 +320,160 @@ public class QrisResultActivity extends AppCompatActivity {
|
||||
while ((line = br.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
Log.d("QrisResultFlow", "Polling response: " + response.toString());
|
||||
|
||||
JSONObject json = new JSONObject(response.toString());
|
||||
JSONArray results = json.optJSONArray("results");
|
||||
|
||||
if (results != null && results.length() > 0) {
|
||||
Log.d("QrisResultFlow", "Found " + results.length() + " log entries");
|
||||
|
||||
for (int i = 0; i < results.length(); i++) {
|
||||
JSONObject log = results.getJSONObject(i);
|
||||
JSONObject reqBody = log.optJSONObject("request_body");
|
||||
if (reqBody != null && "pending".equals(reqBody.optString("transaction_status"))) {
|
||||
|
||||
if (reqBody != null) {
|
||||
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) {
|
||||
Log.e("QrisResultFlow", "Polling error on attempt " + (attempt + 1) + ": " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
attempt++;
|
||||
if (attempt < maxAttempts) {
|
||||
try {
|
||||
Thread.sleep(intervalMs);
|
||||
} catch (InterruptedException ignored) {
|
||||
Log.d("QrisResultFlow", "Polling interrupted");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("QrisResultFlow", "Polling error: " + e.getMessage(), e);
|
||||
}
|
||||
if (!found) {
|
||||
attempt++;
|
||||
try { Thread.sleep(intervalMs); } catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean logFound = found;
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (logFound) {
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
private void simulateWebhook() {
|
||||
Log.d("QrisResultFlow", "Starting webhook simulation");
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
statusTextView.setText("Simulating payment...");
|
||||
checkStatusButton.setEnabled(false);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Generate proper signature
|
||||
String serverKey = getServerKey();
|
||||
String signatureKey = generateSignature(orderId, "200", grossAmount, serverKey);
|
||||
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("transaction_type", "on-us");
|
||||
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
||||
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_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("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("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("currency", "IDR");
|
||||
payload.put("acquirer", acquirer != null ? acquirer : "gopay");
|
||||
payload.put("shopeepay_reference_number", "");
|
||||
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", "==========================");
|
||||
|
||||
URL url = new URL(webhookUrl);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
|
||||
conn.setDoOutput(true);
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(15000);
|
||||
|
||||
OutputStream os = conn.getOutputStream();
|
||||
os.write(payload.toString().getBytes());
|
||||
os.flush();
|
||||
os.close();
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("QrisResultFlow", "Webhook response code: " + responseCode);
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
||||
responseCode < 400 ? conn.getInputStream() : conn.getErrorStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
@ -434,58 +481,57 @@ public class QrisResultActivity extends AppCompatActivity {
|
||||
while ((line = br.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
Log.d("QrisResultFlow", "Webhook response: " + response.toString());
|
||||
|
||||
// Wait a bit for processing
|
||||
Thread.sleep(2000);
|
||||
|
||||
} 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(() -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
// Show success state
|
||||
showSuccessState();
|
||||
showPaymentSuccess();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void showSuccessState() {
|
||||
// Stop timer
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
}
|
||||
private void showPaymentSuccess() {
|
||||
Log.d("QrisResultFlow", "Showing payment success screen");
|
||||
|
||||
// Hide QR section and show success
|
||||
// Hide payment elements
|
||||
qrImageView.setVisibility(View.GONE);
|
||||
amountTextView.setVisibility(View.GONE);
|
||||
timerTextView.setVisibility(View.GONE);
|
||||
qrStatusTextView.setVisibility(View.GONE);
|
||||
referenceTextView.setVisibility(View.GONE);
|
||||
downloadQrisButton.setVisibility(View.GONE);
|
||||
checkStatusButton.setVisibility(View.GONE);
|
||||
cancelButton.setVisibility(View.GONE);
|
||||
|
||||
// Show success message
|
||||
statusTextView.setText("Payment Successful!");
|
||||
statusTextView.setTextColor(Color.parseColor("#4CAF50"));
|
||||
statusTextView.setTextSize(24);
|
||||
// Show success elements
|
||||
statusTextView.setVisibility(View.VISIBLE);
|
||||
statusTextView.setText("✅ Payment Successful!\n\nTransaction ID: " + transactionId +
|
||||
"\nReference: " + referenceId +
|
||||
"\nAmount: " + formatCurrency(grossAmount));
|
||||
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
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
public boolean onOptionsItemSelected(android.view.MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
}
|
||||
super.onBackPressed();
|
||||
Intent intent = new Intent(this, com.example.bdkipoc.MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finishAffinity();
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.List;
|
||||
import java.text.NumberFormat;
|
||||
@ -52,6 +53,14 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
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 -> {
|
||||
if (printClickListener != null) {
|
||||
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
|
||||
public int getItemCount() {
|
||||
return transactionList.size();
|
||||
}
|
||||
|
||||
static class TransactionViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView amount, referenceId;
|
||||
TextView amount, referenceId, status, paymentMethod;
|
||||
LinearLayout printSection;
|
||||
|
||||
public TransactionViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
amount = itemView.findViewById(R.id.textAmount);
|
||||
referenceId = itemView.findViewById(R.id.textReferenceId);
|
||||
status = itemView.findViewById(R.id.textStatus);
|
||||
paymentMethod = itemView.findViewById(R.id.textPaymentMethod);
|
||||
printSection = itemView.findViewById(R.id.printSection);
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,60 @@
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- Kolom 1: Reference ID -->
|
||||
<TextView
|
||||
android:id="@+id/textReferenceId"
|
||||
<!-- Kolom 1: Transaction Info -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Reference ID -->
|
||||
<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 -->
|
||||
<TextView
|
||||
android:id="@+id/textAmount"
|
||||
|
Loading…
x
Reference in New Issue
Block a user