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);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Main content layout (replaces initialPaymentLayout)
 | 
					        // Set up click listeners
 | 
				
			||||||
        initialPaymentLayout = findViewById(R.id.mainContentLayout);
 | 
					        initiatePaymentButton.setOnClickListener(v -> createTransaction());
 | 
				
			||||||
 | 
					        backNavigation.setOnClickListener(v -> finish());
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Set up numpad listeners
 | 
				
			||||||
 | 
					        setupNumpadListeners();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Initially disable the button
 | 
				
			||||||
 | 
					        initiatePaymentButton.setEnabled(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private void setupClickListeners() {
 | 
					    private void setupNumpadListeners() {
 | 
				
			||||||
        // Back navigation
 | 
					        View.OnClickListener numberClickListener = v -> {
 | 
				
			||||||
        backNavigation.setOnClickListener(v -> {
 | 
					            TextView button = (TextView) v;
 | 
				
			||||||
            addClickAnimation(v);
 | 
					            String number = button.getText().toString();
 | 
				
			||||||
            navigateBack();
 | 
					            appendNumber(number);
 | 
				
			||||||
        });
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        toolbarTitle.setOnClickListener(v -> {
 | 
					        btn1.setOnClickListener(numberClickListener);
 | 
				
			||||||
            addClickAnimation(v);
 | 
					        btn2.setOnClickListener(numberClickListener);
 | 
				
			||||||
            navigateBack();
 | 
					        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
 | 
					        btnDelete.setOnClickListener(v -> deleteLastDigit());
 | 
				
			||||||
        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);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    private void appendNumber(String number) {
 | 
				
			||||||
 | 
					        currentAmount.append(number);
 | 
				
			||||||
        updateAmountDisplay();
 | 
					        updateAmountDisplay();
 | 
				
			||||||
        updateButtonState();
 | 
					 | 
				
			||||||
        addInputFeedback();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private void deleteLastDigit() {
 | 
					    private void deleteLastDigit() {
 | 
				
			||||||
        if (currentAmount.length() > 0) {
 | 
					        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);
 | 
					            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 {
 | 
					 | 
				
			||||||
            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);
 | 
					        progressBar.setVisibility(View.VISIBLE);
 | 
				
			||||||
            statusTextView.setText("Creating transaction...");
 | 
					 | 
				
			||||||
            statusTextView.setVisibility(View.VISIBLE);
 | 
					 | 
				
			||||||
        initiatePaymentButton.setEnabled(false);
 | 
					        initiatePaymentButton.setEnabled(false);
 | 
				
			||||||
 | 
					        statusTextView.setVisibility(View.VISIBLE);
 | 
				
			||||||
 | 
					        statusTextView.setText("Creating transaction...");
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        new CreateTransactionTask().execute();
 | 
					        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() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            // MIDTRANS_AUTH = 'Basic base64string'
 | 
					            // MIDTRANS_AUTH = 'Basic base64string'
 | 
				
			||||||
            String base64 = MIDTRANS_AUTH.replace("Basic ", "");
 | 
					            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.
 | 
					            // 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) {
 | 
					    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);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    private void resetToInitialState() {
 | 
					 | 
				
			||||||
        // Reset to show the input form again
 | 
					 | 
				
			||||||
        progressBar.setVisibility(View.GONE);
 | 
					 | 
				
			||||||
            statusTextView.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));
 | 
				
			||||||
 | 
					        amountTextView.setText("Amount: " + formattedAmount);
 | 
				
			||||||
        referenceTextView.setText("Reference ID: " + referenceId);
 | 
					        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
 | 
					        // Initialize UI state
 | 
				
			||||||
        if (qrImageUrl != null) {
 | 
					        checkStatusButton.setEnabled(false);
 | 
				
			||||||
            loadQrCode(qrImageUrl);
 | 
					        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) {
 | 
					 | 
				
			||||||
            @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(() -> {
 | 
					 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
                // Simulate API call delay
 | 
					            qrImageView.setDrawingCacheEnabled(true);
 | 
				
			||||||
                Thread.sleep(2000);
 | 
					            qrImageView.buildDrawingCache();
 | 
				
			||||||
                
 | 
					            Bitmap bitmap = qrImageView.getDrawingCache();
 | 
				
			||||||
                // Generate new QR code (in real app, call Midtrans API again)
 | 
					            if (bitmap != null) {
 | 
				
			||||||
                generateNewQrCode();
 | 
					                saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
 | 
				
			||||||
                
 | 
					            } else {
 | 
				
			||||||
            } catch (InterruptedException e) {
 | 
					                Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show();
 | 
				
			||||||
                e.printStackTrace();
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }).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> {
 | 
					    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();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                int responseCode = connection.getResponseCode();
 | 
				
			||||||
 | 
					                Log.d("QrisResultFlow", "Image download response code: " + responseCode);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (responseCode == 200) {
 | 
				
			||||||
                    InputStream input = connection.getInputStream();
 | 
					                    InputStream input = connection.getInputStream();
 | 
				
			||||||
                    bitmap = BitmapFactory.decodeStream(input);
 | 
					                    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"))) {
 | 
					                                
 | 
				
			||||||
 | 
					                                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;
 | 
					                                        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;
 | 
					                            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;
 | 
					            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,17 +13,60 @@
 | 
				
			|||||||
        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:orientation="vertical">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <!-- Reference ID -->
 | 
				
			||||||
 | 
					            <TextView
 | 
				
			||||||
 | 
					                android:id="@+id/textReferenceId"
 | 
				
			||||||
 | 
					                android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					                android:layout_height="wrap_content"
 | 
				
			||||||
                android:text="ref-eowu3pin"
 | 
					                android:text="ref-eowu3pin"
 | 
				
			||||||
                android:textColor="#333333"
 | 
					                android:textColor="#333333"
 | 
				
			||||||
                android:textSize="16sp"
 | 
					                android:textSize="16sp"
 | 
				
			||||||
                android:textStyle="bold" />
 | 
					                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
 | 
				
			||||||
            android:id="@+id/textAmount"
 | 
					            android:id="@+id/textAmount"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user