From a7fa40d60add922608a75596c6d5d8a58092ad5d Mon Sep 17 00:00:00 2001 From: riz081 Date: Tue, 27 May 2025 15:46:20 +0700 Subject: [PATCH] UI Kartu Kredit --- .../com/example/bdkipoc/PaymentActivity.java | 859 +++++++----------- app/src/main/res/anim/fade_in.xml | 4 + app/src/main/res/anim/fade_out.xml | 4 + app/src/main/res/anim/scale_in.xml | 14 + app/src/main/res/anim/scale_out.xml | 14 + app/src/main/res/anim/slide_in_left.xml | 10 + app/src/main/res/anim/slide_in_right.xml | 10 + app/src/main/res/anim/slide_out_left.xml | 10 + app/src/main/res/anim/slide_out_right.xml | 10 + .../res/drawable/button_active_background.xml | 6 + .../drawable/button_background_selector.xml | 5 + .../drawable/button_inactive_background.xml | 6 + app/src/main/res/drawable/card_background.xml | 6 + app/src/main/res/drawable/ic_arrow_back.png | Bin 0 -> 220 bytes app/src/main/res/drawable/ic_backspace.xml | 10 + app/src/main/res/layout/activity_payment.xml | 443 +++++---- app/src/main/res/values/styles.xml | 38 +- 17 files changed, 725 insertions(+), 724 deletions(-) create mode 100644 app/src/main/res/anim/fade_in.xml create mode 100644 app/src/main/res/anim/fade_out.xml create mode 100644 app/src/main/res/anim/scale_in.xml create mode 100644 app/src/main/res/anim/scale_out.xml create mode 100644 app/src/main/res/anim/slide_in_left.xml create mode 100644 app/src/main/res/anim/slide_in_right.xml create mode 100644 app/src/main/res/anim/slide_out_left.xml create mode 100644 app/src/main/res/anim/slide_out_right.xml create mode 100644 app/src/main/res/drawable/button_active_background.xml create mode 100644 app/src/main/res/drawable/button_background_selector.xml create mode 100644 app/src/main/res/drawable/button_inactive_background.xml create mode 100644 app/src/main/res/drawable/card_background.xml create mode 100644 app/src/main/res/drawable/ic_arrow_back.png create mode 100644 app/src/main/res/drawable/ic_backspace.xml diff --git a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java index b2bd490..5ea2b5a 100644 --- a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java +++ b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java @@ -1,557 +1,372 @@ package com.example.bdkipoc; -import android.content.Context; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.Intent; -import android.graphics.Bitmap; -import android.os.AsyncTask; +import android.graphics.Color; +import android.os.Build; import android.os.Bundle; -import android.util.Log; -import android.view.MenuItem; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; import android.view.View; -import android.view.inputmethod.InputMethodManager; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; -import android.widget.ProgressBar; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; - -import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Random; -import java.util.UUID; public class PaymentActivity extends AppCompatActivity { - private ProgressBar progressBar; - private Button initiatePaymentButton; - private Button simulatePaymentButton; - private ImageView qrCodeImageView; - private TextView statusTextView; + // Views private EditText editTextAmount; - private TextView referenceIdTextView; - private View paymentDetailsLayout; - private View paymentSuccessLayout; - private Button returnToMainButton; - - private String transactionId; - private String transactionUuid; - private String referenceId; - private int amount; - private JSONObject midtransResponse; - - 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_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key - private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans"; + private Button confirmButton; + private LinearLayout backNavigation; + private ImageView backArrow; + private TextView toolbarTitle; + + // Numpad buttons + private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000; + private ImageView btnDelete; + + // Data + private StringBuilder currentAmount = new StringBuilder(); + private static final int MAX_AMOUNT_LENGTH = 12; + + // Animation + private Handler animationHandler = new Handler(Looper.getMainLooper()); @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + // Set status bar color programmatically + setStatusBarColor(); + setContentView(R.layout.activity_payment); - - // Set up the toolbar - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - getSupportActionBar().setTitle("QRIS Payment"); - } - - // Initialize views - progressBar = findViewById(R.id.progressBar); - initiatePaymentButton = findViewById(R.id.initiatePaymentButton); - simulatePaymentButton = findViewById(R.id.simulatePaymentButton); - qrCodeImageView = findViewById(R.id.qrCodeImageView); - statusTextView = findViewById(R.id.statusTextView); - editTextAmount = findViewById(R.id.editTextAmount); - referenceIdTextView = findViewById(R.id.referenceIdTextView); - paymentDetailsLayout = findViewById(R.id.paymentDetailsLayout); - paymentSuccessLayout = findViewById(R.id.paymentSuccessLayout); - returnToMainButton = findViewById(R.id.returnToMainButton); - - // Generate a random amount between 100,000 and 999,999 - amount = new Random().nextInt(900000) + 100000; - // Format and display the amount - editTextAmount.setText(""); - editTextAmount.requestFocus(); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) { - imm.showSoftInput(editTextAmount, InputMethodManager.SHOW_IMPLICIT); - } - - // Generate reference ID - referenceId = "ref-" + generateRandomString(8); - referenceIdTextView.setText(referenceId); - - // Set up click listeners - initiatePaymentButton.setOnClickListener(v -> createTransaction()); - simulatePaymentButton.setOnClickListener(v -> simulateWebhook()); - returnToMainButton.setOnClickListener(v -> finish()); - - // Initially hide the QR code and payment success views - paymentDetailsLayout.setVisibility(View.GONE); - paymentSuccessLayout.setVisibility(View.GONE); - simulatePaymentButton.setVisibility(View.GONE); + initializeViews(); + setupClickListeners(); + setupInitialStates(); + // REMOVED: addAnimations() - No more card sliding animation } - private void createTransaction() { - progressBar.setVisibility(View.VISIBLE); - initiatePaymentButton.setEnabled(false); - statusTextView.setText("Creating transaction..."); - - new CreateTransactionTask().execute(); - } - - private void displayQrCode(String qrImageUrl) { - new DownloadImageTask().execute(qrImageUrl); - } - - private void simulateWebhook() { - progressBar.setVisibility(View.VISIBLE); - simulatePaymentButton.setEnabled(false); - statusTextView.setText("Processing payment..."); - - new SimulateWebhookTask().execute(); - } - - private void showSuccessScreen() { - paymentDetailsLayout.setVisibility(View.GONE); - paymentSuccessLayout.setVisibility(View.VISIBLE); - statusTextView.setText("Payment successful!"); - progressBar.setVisibility(View.GONE); - } - - private String generateRandomString(int length) { - String chars = "abcdefghijklmnopqrstuvwxyz0123456789"; - StringBuilder sb = new StringBuilder(); - Random random = new Random(); - for (int i = 0; i < length; i++) { - int index = random.nextInt(chars.length()); - sb.append(chars.charAt(index)); - } - return sb.toString(); - } - - private String getServerKey() { - // MIDTRANS_AUTH = 'Basic base64string' - String base64 = MIDTRANS_AUTH.replace("Basic ", ""); - String decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT).toString(); - // Format is usually 'SB-Mid-server-xxxx:'. Remove trailing colon if present. - return decoded.replace(":\n", ""); - } - - 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); + 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 } - return hexString.toString(); - } catch (java.security.NoSuchAlgorithmException e) { + } + } + + private void initializeViews() { + // Main views + editTextAmount = findViewById(R.id.editTextAmount); + confirmButton = findViewById(R.id.confirmButton); + backNavigation = findViewById(R.id.back_navigation); + backArrow = findViewById(R.id.backArrow); + toolbarTitle = findViewById(R.id.toolbarTitle); + + // Numpad buttons + btn1 = findViewById(R.id.btn1); + btn2 = findViewById(R.id.btn2); + btn3 = findViewById(R.id.btn3); + btn4 = findViewById(R.id.btn4); + btn5 = findViewById(R.id.btn5); + btn6 = findViewById(R.id.btn6); + btn7 = findViewById(R.id.btn7); + btn8 = findViewById(R.id.btn8); + btn9 = findViewById(R.id.btn9); + btn0 = findViewById(R.id.btn0); + btn000 = findViewById(R.id.btn000); + btnDelete = findViewById(R.id.btnDelete); + } + + private void setupClickListeners() { + // Back navigation - entire LinearLayout is clickable + backNavigation.setOnClickListener(v -> { + addClickAnimation(v); + navigateBack(); + }); + + // Individual back arrow (for additional touch area) + backArrow.setOnClickListener(v -> { + addClickAnimation(v); + navigateBack(); + }); + + // Toolbar title (also clickable for back navigation) + toolbarTitle.setOnClickListener(v -> { + addClickAnimation(v); + navigateBack(); + }); + + // Numpad listeners + btn1.setOnClickListener(v -> handleNumpadClick(v, "1")); + btn2.setOnClickListener(v -> handleNumpadClick(v, "2")); + btn3.setOnClickListener(v -> handleNumpadClick(v, "3")); + btn4.setOnClickListener(v -> handleNumpadClick(v, "4")); + btn5.setOnClickListener(v -> handleNumpadClick(v, "5")); + btn6.setOnClickListener(v -> handleNumpadClick(v, "6")); + btn7.setOnClickListener(v -> handleNumpadClick(v, "7")); + btn8.setOnClickListener(v -> handleNumpadClick(v, "8")); + btn9.setOnClickListener(v -> handleNumpadClick(v, "9")); + btn0.setOnClickListener(v -> handleNumpadClick(v, "0")); + btn000.setOnClickListener(v -> handleNumpadClick(v, "000")); + + // Delete button + btnDelete.setOnClickListener(v -> { + addClickAnimation(v); + deleteLastDigit(); + }); + + // Confirm button + confirmButton.setOnClickListener(v -> { + if (confirmButton.isEnabled()) { + addButtonClickAnimation(v); + handleConfirmPayment(); + } + }); + } + + private void navigateBack() { + // Simple back navigation without card animation + finish(); + } + + private void handleNumpadClick(View view, String digit) { + addClickAnimation(view); + addDigit(digit); + } + + private void setupInitialStates() { + // Set initial amount display + editTextAmount.setText(""); + + // Set initial button state + updateButtonState(); + + // Disable EditText input (only numpad input allowed) + editTextAmount.setFocusable(false); + editTextAmount.setClickable(false); + editTextAmount.setCursorVisible(false); + } + + // REMOVED: addAnimations() method - No card sliding animation on startup + + private void addDigit(String digit) { + // Validate input length + if (currentAmount.length() >= MAX_AMOUNT_LENGTH) { + showToast("Maksimal " + MAX_AMOUNT_LENGTH + " digit"); + return; + } + + // Handle leading zeros + if (currentAmount.length() == 0) { + if (digit.equals("000")) { + // Don't allow 000 as first input + return; + } + currentAmount.append(digit); + } else if (currentAmount.length() == 1 && currentAmount.toString().equals("0")) { + if (!digit.equals("000")) { + // Replace single 0 with new digit + currentAmount = new StringBuilder(digit); + } else { + return; + } + } else { + currentAmount.append(digit); + } + + updateAmountDisplay(); + updateButtonState(); + addInputFeedback(); + } + + private void deleteLastDigit() { + if (currentAmount.length() > 0) { + String current = currentAmount.toString(); + + // If current ends with 000, remove all three digits + if (current.endsWith("000") && current.length() >= 3) { + currentAmount.delete(currentAmount.length() - 3, currentAmount.length()); + } else { + currentAmount.deleteCharAt(currentAmount.length() - 1); + } + + updateAmountDisplay(); + updateButtonState(); + addDeleteFeedback(); + } + } + + private void updateAmountDisplay() { + String amount = currentAmount.toString(); + + if (amount.isEmpty() || amount.equals("0")) { + editTextAmount.setText(""); + } else { + String formattedAmount = formatCurrency(amount); + editTextAmount.setText(formattedAmount); + } + } + + private String formatCurrency(String amount) { + if (TextUtils.isEmpty(amount) || amount.equals("0")) { return ""; } + + try { + long number = Long.parseLong(amount); + return String.format("%,d", number).replace(',', '.'); + } catch (NumberFormatException e) { + return amount; + } + } + + private void updateButtonState() { + boolean hasValidAmount = currentAmount.length() > 0 && + !currentAmount.toString().equals("0") && + !currentAmount.toString().isEmpty(); + + confirmButton.setEnabled(hasValidAmount); + + if (hasValidAmount) { + // Active state + confirmButton.setBackgroundResource(R.drawable.button_active_background); + confirmButton.setTextColor(Color.WHITE); + confirmButton.setAlpha(1.0f); + } else { + // Inactive state + confirmButton.setBackgroundResource(R.drawable.button_inactive_background); + confirmButton.setTextColor(Color.parseColor("#999999")); + confirmButton.setAlpha(0.6f); + } + } + + private void handleConfirmPayment() { + String amount = currentAmount.toString(); + + if (TextUtils.isEmpty(amount) || amount.equals("0")) { + showToast("Masukkan jumlah pembayaran"); + return; + } + + try { + long amountValue = Long.parseLong(amount); + + // Validate minimum amount + if (amountValue < 1000) { + showToast("Minimal pembayaran Rp 1.000"); + return; + } + + // Validate maximum amount + if (amountValue > 999999999L) { + showToast("Maksimal pembayaran Rp 999.999.999"); + return; + } + + // Process payment + processPayment(amountValue); + + } catch (NumberFormatException e) { + showToast("Format jumlah tidak valid"); + } + } + + private void processPayment(long amount) { + // Show loading state + confirmButton.setText("Memproses..."); + confirmButton.setEnabled(false); + + // Simulate payment processing + animationHandler.postDelayed(() -> { + // Show success message + showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount))); + + // Reset state and go back + resetPaymentState(); + navigateBack(); + }, 2000); + } + + private void resetPaymentState() { + currentAmount = new StringBuilder(); + updateAmountDisplay(); + updateButtonState(); + confirmButton.setText("Konfirmasi"); + } + + // Animation methods (only for numpad interactions) + private void addClickAnimation(View view) { + ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f); + ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(scaleX, scaleY); + animatorSet.setDuration(150); + animatorSet.start(); + } + + private void addButtonClickAnimation(View view) { + ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f); + ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(scaleX, scaleY); + animatorSet.setDuration(200); + animatorSet.start(); + } + + private void addInputFeedback() { + ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextAmount, "alpha", 0.7f, 1f); + fadeIn.setDuration(200); + fadeIn.start(); + } + + private void addDeleteFeedback() { + ObjectAnimator shake = ObjectAnimator.ofFloat(editTextAmount, "translationX", 0f, -10f, 10f, 0f); + shake.setDuration(300); + shake.start(); + } + + // Utility methods + private void showToast(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); } @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); + public void onBackPressed() { + navigateBack(); } - - private class CreateTransactionTask extends AsyncTask { - private String errorMessage; - - @Override - protected Boolean doInBackground(Void... voids) { - try { - // Generate a UUID for the transaction - transactionUuid = UUID.randomUUID().toString(); - - // Create transaction JSON payload - JSONObject payload = new JSONObject(); - payload.put("type", "PAYMENT"); - payload.put("channel_category", "RETAIL_OUTLET"); - payload.put("channel_code", "QRIS"); - payload.put("reference_id", referenceId); - - // Read amount from EditText and log it - String amountText = editTextAmount.getText().toString().trim(); - Log.d("MidtransCharge", "Raw amount text: " + amountText); - - try { - // Parse amount - expecting integer in lowest denomination (Indonesian Rupiah) - amount = Integer.parseInt(amountText); - Log.d("MidtransCharge", "Parsed amount: " + amount); - } catch (NumberFormatException e) { - Log.e("MidtransCharge", "Amount parsing error: " + e.getMessage()); - errorMessage = "Invalid amount format"; - return false; - } - - payload.put("amount", amount); - payload.put("cashflow", "MONEY_IN"); - payload.put("status", "INIT"); - payload.put("device_id", 1); - payload.put("transaction_uuid", transactionUuid); - payload.put("transaction_time_seconds", 0.0); - payload.put("device_code", "PB4K252T00021"); - payload.put("merchant_name", "Marcel Panjaitan"); - payload.put("mid", "71000026521"); - payload.put("tid", "73001500"); - - // Make the API call - URL url = new URI(BACKEND_BASE + "/transactions").toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("Accept", "application/json"); - conn.setDoOutput(true); - - try (OutputStream os = conn.getOutputStream()) { - byte[] input = payload.toString().getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - if (responseCode == 200 || responseCode == 201) { - // Read the response - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); - StringBuilder response = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - response.append(responseLine.trim()); - } - - // Parse the response to get transaction ID - JSONObject jsonResponse = new JSONObject(response.toString()); - JSONObject data = jsonResponse.getJSONObject("data"); - transactionId = String.valueOf(data.getInt("id")); - - // Now generate QRIS via Midtrans - return generateQris(amount); - } else { - // Read error response - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); - StringBuilder response = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - response.append(responseLine.trim()); - } - errorMessage = "Error creating transaction: " + response.toString(); - return false; - } - } catch (Exception e) { - Log.e("MidtransCharge", "Exception: " + e.getMessage(), e); - errorMessage = "Unexpected error: " + e.getMessage(); - return false; - } - } - - private boolean generateQris(int amount) { - try { - // Create QRIS charge JSON payload - JSONObject payload = new JSONObject(); - payload.put("payment_type", "qris"); - - JSONObject transactionDetails = new JSONObject(); - transactionDetails.put("order_id", transactionUuid); - transactionDetails.put("gross_amount", amount); - payload.put("transaction_details", transactionDetails); - - // Log the request details - Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL); - Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH); - Log.d("MidtransCharge", "Accept: application/json"); - Log.d("MidtransCharge", "Content-Type: application/json"); - Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL); - Log.d("MidtransCharge", "Payload: " + payload.toString()); - // Make the API call to Midtrans - URL url = new URI(MIDTRANS_CHARGE_URL).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Accept", "application/json"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("Authorization", MIDTRANS_AUTH); - conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL); - conn.setDoOutput(true); - - try (OutputStream os = conn.getOutputStream()) { - byte[] input = payload.toString().getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - if (responseCode == 200 || responseCode == 201) { - InputStream inputStream = conn.getInputStream(); - if (inputStream != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8")); - StringBuilder response = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - response.append(responseLine.trim()); - } - - // Parse the response - midtransResponse = new JSONObject(response.toString()); - return true; - } else { - Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available"); - errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No input stream available"; - return false; - } - } else { - InputStream errorStream = conn.getErrorStream(); - if (errorStream != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(errorStream, "utf-8")); - StringBuilder errorResponse = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - errorResponse.append(responseLine.trim()); - } - Log.e("MidtransCharge", "HTTP " + responseCode + ": " + errorResponse.toString()); - errorMessage = "Error generating QRIS: HTTP " + responseCode + ": " + errorResponse.toString(); - } else { - Log.e("MidtransCharge", "HTTP " + responseCode + ": No error stream available"); - errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No error stream available"; - } - return false; - } - } catch (Exception e) { - Log.e("MidtransCharge", "Exception: " + e.getMessage(), e); - errorMessage = "Unexpected error: " + e.getMessage(); - return false; - } - } - - @Override - protected void onPostExecute(Boolean success) { - if (success && midtransResponse != null) { - try { - // Extract needed values from midtransResponse - JSONObject actions = midtransResponse.getJSONArray("actions").getJSONObject(0); - String qrImageUrl = actions.getString("url"); - - // Extract transaction_id - String transactionId = midtransResponse.getString("transaction_id"); - String transactionTime = midtransResponse.getString("transaction_time"); - String acquirer = midtransResponse.getString("acquirer"); - String merchantId = midtransResponse.getString("merchant_id"); - String exactGrossAmount = midtransResponse.getString("gross_amount"); - - // Log everything before launching activity - Log.d("MidtransCharge", "Creating QrisResultActivity intent with:"); - Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl); - Log.d("MidtransCharge", "amount: " + amount); - Log.d("MidtransCharge", "referenceId: " + referenceId); - Log.d("MidtransCharge", "transactionUuid (orderId): " + transactionUuid); - Log.d("MidtransCharge", "transaction_id: " + transactionId); - Log.d("MidtransCharge", "exactGrossAmount: " + exactGrossAmount); - - // Instead of showing QR inline, launch QrisResultActivity - Intent intent = new Intent(PaymentActivity.this, QrisResultActivity.class); - intent.putExtra("qrImageUrl", qrImageUrl); - intent.putExtra("amount", amount); - intent.putExtra("referenceId", referenceId); - intent.putExtra("orderId", transactionUuid); // Order ID - intent.putExtra("transactionId", transactionId); // Actual Midtrans transaction_id - intent.putExtra("grossAmount", exactGrossAmount); // Exact gross amount from response - intent.putExtra("transactionTime", transactionTime); // For timestamp - intent.putExtra("acquirer", acquirer); - intent.putExtra("merchantId", merchantId); - - try { - startActivity(intent); - } catch (Exception e) { - Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e); - Toast.makeText(PaymentActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - return; - } catch (JSONException e) { - Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e); - Toast.makeText(PaymentActivity.this, "Error processing QRIS response", Toast.LENGTH_LONG).show(); - } - } else { - String message = (errorMessage != null && !errorMessage.isEmpty()) ? errorMessage : "Unknown error occurred. Please check Logcat for details."; - Toast.makeText(PaymentActivity.this, message, Toast.LENGTH_LONG).show(); - initiatePaymentButton.setEnabled(true); - } - progressBar.setVisibility(View.GONE); + @Override + protected void onDestroy() { + super.onDestroy(); + if (animationHandler != null) { + animationHandler.removeCallbacksAndMessages(null); } } - - private class DownloadImageTask extends AsyncTask { - @Override - protected Bitmap doInBackground(String... urls) { - String urlDisplay = urls[0]; - Bitmap bitmap = null; - try { - URL url = new URI(urlDisplay).toURL(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setDoInput(true); - connection.connect(); - java.io.InputStream input = connection.getInputStream(); - bitmap = android.graphics.BitmapFactory.decodeStream(input); - } catch (Exception e) { - e.printStackTrace(); - } - return bitmap; - } - @Override - protected void onPostExecute(Bitmap result) { - if (result != null) { - qrCodeImageView.setImageBitmap(result); - } else { - Toast.makeText(PaymentActivity.this, "Error loading QR code image", Toast.LENGTH_LONG).show(); - } - } + // Public methods for testing + public String getCurrentAmount() { + return currentAmount.toString(); } - - private class SimulateWebhookTask extends AsyncTask { - private String errorMessage; - - @Override - protected Boolean doInBackground(Void... voids) { - try { - // Wait a moment to simulate real-world timing - Thread.sleep(1500); - - // Get server key and prepare signature - String serverKey = getServerKey(); - String grossAmount = String.valueOf(amount) + ".00"; - String signatureKey = generateSignature( - transactionUuid, - "200", - grossAmount, - serverKey - ); - - // Create webhook payload - JSONObject payload = new JSONObject(); - payload.put("transaction_type", "on-us"); - payload.put("transaction_time", midtransResponse.getString("transaction_time")); - payload.put("transaction_status", "settlement"); - payload.put("transaction_id", midtransResponse.getString("transaction_id")); - payload.put("status_message", "midtrans payment notification"); - payload.put("status_code", "200"); - payload.put("signature_key", signatureKey); - payload.put("settlement_time", midtransResponse.getString("transaction_time")); - payload.put("payment_type", "qris"); - payload.put("order_id", transactionUuid); - payload.put("merchant_id", midtransResponse.getString("merchant_id")); - payload.put("issuer", midtransResponse.getString("acquirer")); - payload.put("gross_amount", grossAmount); - payload.put("fraud_status", "accept"); - payload.put("currency", "IDR"); - payload.put("acquirer", midtransResponse.getString("acquirer")); - payload.put("shopeepay_reference_number", ""); - payload.put("reference_id", referenceId); - - // Call the webhook URL - URL url = new URI(WEBHOOK_URL).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("Accept", "application/json"); - conn.setDoOutput(true); - - try (OutputStream os = conn.getOutputStream()) { - byte[] input = payload.toString().getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - if (responseCode == 200 || responseCode == 201) { - // Wait briefly to allow the backend to process - Thread.sleep(2000); - return checkTransactionStatus(); - } else { - // Read error response - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); - StringBuilder response = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - response.append(responseLine.trim()); - } - errorMessage = "Error simulating payment: " + response.toString(); - return false; - } - } catch (Exception e) { - errorMessage = "Error: " + e.getMessage(); - return false; - } - } - - private boolean checkTransactionStatus() { - try { - // Check transaction status - URL url = new URI(BACKEND_BASE + "/transactions/" + transactionId).toURL(); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Accept", "application/json"); - - int responseCode = conn.getResponseCode(); - if (responseCode == 200) { - // Read the response - BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); - StringBuilder response = new StringBuilder(); - String responseLine; - while ((responseLine = br.readLine()) != null) { - response.append(responseLine.trim()); - } - - // Parse the response - JSONObject jsonResponse = new JSONObject(response.toString()); - JSONObject data = jsonResponse.getJSONObject("data"); - String status = data.getString("status"); - - return status.equalsIgnoreCase("SUCCESS"); - } else { - errorMessage = "Error checking transaction status. HTTP response code: " + responseCode; - return false; - } - } catch (Exception e) { - errorMessage = "Error checking transaction status: " + e.getMessage(); - return false; - } - } - - @Override - protected void onPostExecute(Boolean success) { - if (success) { - showSuccessScreen(); - } else { - String message = (errorMessage != null && !errorMessage.isEmpty()) ? errorMessage : "Unknown error occurred. Please check Logcat for details."; - Toast.makeText(PaymentActivity.this, message, Toast.LENGTH_LONG).show(); - simulatePaymentButton.setEnabled(true); - } - progressBar.setVisibility(View.GONE); - } + + public boolean isConfirmButtonEnabled() { + return confirmButton.isEnabled(); } -} +} \ No newline at end of file diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000..b553a7a --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000..b60a3df --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/src/main/res/anim/scale_in.xml b/app/src/main/res/anim/scale_in.xml new file mode 100644 index 0000000..413b3c3 --- /dev/null +++ b/app/src/main/res/anim/scale_in.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/scale_out.xml b/app/src/main/res/anim/scale_out.xml new file mode 100644 index 0000000..29d9aa4 --- /dev/null +++ b/app/src/main/res/anim/scale_out.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_left.xml b/app/src/main/res/anim/slide_in_left.xml new file mode 100644 index 0000000..ef8aca4 --- /dev/null +++ b/app/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000..941161b --- /dev/null +++ b/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000..bb7db94 --- /dev/null +++ b/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_right.xml b/app/src/main/res/anim/slide_out_right.xml new file mode 100644 index 0000000..6711dcb --- /dev/null +++ b/app/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_active_background.xml b/app/src/main/res/drawable/button_active_background.xml new file mode 100644 index 0000000..34640c6 --- /dev/null +++ b/app/src/main/res/drawable/button_active_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_background_selector.xml b/app/src/main/res/drawable/button_background_selector.xml new file mode 100644 index 0000000..3aa96d2 --- /dev/null +++ b/app/src/main/res/drawable/button_background_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_inactive_background.xml b/app/src/main/res/drawable/button_inactive_background.xml new file mode 100644 index 0000000..a15fc01 --- /dev/null +++ b/app/src/main/res/drawable/button_inactive_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_background.xml b/app/src/main/res/drawable/card_background.xml new file mode 100644 index 0000000..2ae373d --- /dev/null +++ b/app/src/main/res/drawable/card_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_back.png b/app/src/main/res/drawable/ic_arrow_back.png new file mode 100644 index 0000000000000000000000000000000000000000..c4bf352f5c3552d9f1c2bfb7edcce125f3e6d54a GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9E$svykh8Km+7D9BhG zIYKtEk0ROi%|9~6 zDgE2hAmy*N??Lmcr*-Df+dgeHv6>maX5*4Gs@Q~!J + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_payment.xml b/app/src/main/res/layout/activity_payment.xml index 4ce7109..01e8af9 100644 --- a/app/src/main/res/layout/activity_payment.xml +++ b/app/src/main/res/layout/activity_payment.xml @@ -1,227 +1,268 @@ - + android:layout_height="match_parent" + android:fillViewport="true" + android:overScrollMode="never" + android:scrollbars="none" + android:background="#FFFFFF"> - - - - - - - + android:background="#FFFFFF" + tools:context=".PaymentActivity"> - + + android:layout_height="24dp" + android:background="#E31937" + app:layout_constraintTop_toTopOf="parent"/> - + + + + + + + + + + + android:layout_marginStart="8dp" + android:text="Kembali" + android:textColor="@android:color/white" + android:textSize="12sp" + android:fontFamily="@font/inter" + android:textStyle="normal" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:id="@+id/btn2" + style="@style/NumpadButton" + android:text="2" /> - - + - + + - + - + - + + - + - - - + -