first working version

This commit is contained in:
Achmad Zaenuri
2025-04-16 07:33:55 +07:00
commit 4eabdd81d2
57 changed files with 2831 additions and 0 deletions

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

41
app/build.gradle Normal file
View File

@@ -0,0 +1,41 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.example.bdkipoc'
compileSdk 35
defaultConfig {
applicationId "com.example.bdkipoc"
minSdk 21
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
implementation libs.cardview
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}

21
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,26 @@
package com.example.bdkipoc;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.bdkipoc", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BDKIPOC"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".TransactionActivity"
android:exported="false" />
<activity
android:name=".PaymentActivity"
android:exported="false" />
<activity android:name=".QrisResultActivity" />
</application>
</manifest>

View File

@@ -0,0 +1,41 @@
package com.example.bdkipoc;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// Set up click listeners for the cards
CardView paymentCard = findViewById(R.id.card_payment);
CardView transactionsCard = findViewById(R.id.card_transactions);
paymentCard.setOnClickListener(v -> {
// Launch payment activity
startActivity(new android.content.Intent(MainActivity.this, PaymentActivity.class));
});
transactionsCard.setOnClickListener(v -> {
// Launch transactions activity
startActivity(new android.content.Intent(MainActivity.this, TransactionActivity.class));
});
}
}

View File

@@ -0,0 +1,557 @@
package com.example.bdkipoc;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import org.json.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;
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";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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);
}
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);
}
return hexString.toString();
} catch (java.security.NoSuchAlgorithmException e) {
return "";
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
private class CreateTransactionTask extends AsyncTask<Void, Void, Boolean> {
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);
}
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
@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();
}
}
}
private class SimulateWebhookTask extends AsyncTask<Void, Void, Boolean> {
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);
}
}
}

View File

@@ -0,0 +1,287 @@
package com.example.bdkipoc;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
public class QrisResultActivity extends AppCompatActivity {
private ImageView qrImageView;
private TextView amountTextView;
private TextView referenceTextView;
private Button downloadQrisButton;
private Button checkStatusButton;
private TextView statusTextView;
private Button returnMainButton;
private ProgressBar progressBar;
private String orderId;
private String grossAmount;
private String referenceId;
private String transactionId;
private String transactionTime;
private String acquirer;
private String merchantId;
private String backendBase = "https://be-edc.msvc.app";
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qris_result);
qrImageView = findViewById(R.id.qrImageView);
amountTextView = findViewById(R.id.amountTextView);
referenceTextView = findViewById(R.id.referenceTextView);
downloadQrisButton = findViewById(R.id.downloadQrisButton);
checkStatusButton = findViewById(R.id.checkStatusButton);
statusTextView = findViewById(R.id.statusTextView);
returnMainButton = findViewById(R.id.returnMainButton);
progressBar = findViewById(R.id.progressBar);
Intent intent = getIntent();
String qrImageUrl = intent.getStringExtra("qrImageUrl");
int amount = intent.getIntExtra("amount", 0);
referenceId = intent.getStringExtra("referenceId");
orderId = intent.getStringExtra("orderId");
grossAmount = intent.getStringExtra("grossAmount");
transactionId = intent.getStringExtra("transactionId");
transactionTime = intent.getStringExtra("transactionTime");
acquirer = intent.getStringExtra("acquirer");
merchantId = intent.getStringExtra("merchantId");
if (orderId == null || transactionId == null) {
Log.e("QrisResultFlow", "orderId or transactionId is null! Intent extras: " + intent.getExtras());
android.widget.Toast.makeText(this, "Missing transaction details!", android.widget.Toast.LENGTH_LONG).show();
}
// Get the exact amount from the grossAmount string value instead of the integer
String amountStr = "Amount: " + grossAmount;
amountTextView.setText(amountStr);
referenceTextView.setText("Reference ID: " + referenceId);
// Load QR image
new DownloadImageTask(qrImageView).execute(qrImageUrl);
// Disable check status button initially
checkStatusButton.setEnabled(false);
// Start polling for pending payment log
pollPendingPaymentLog(orderId);
// Download QRIS button
downloadQrisButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
qrImageView.setDrawingCacheEnabled(true);
Bitmap bitmap = qrImageView.getDrawingCache();
if (bitmap != null) {
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
}
qrImageView.setDrawingCacheEnabled(false);
}
});
// Check Payment Status button
checkStatusButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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 static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
DownloadImageTask(ImageView bmImage) {
this.bmImage = bmImage;
}
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();
InputStream input = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(input);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
protected void onPostExecute(Bitmap result) {
if (result != null) {
bmImage.setImageBitmap(result);
}
}
}
// Save bitmap to gallery
private void saveImageToGallery(Bitmap bitmap, String fileName) {
try {
String savedImageURL = android.provider.MediaStore.Images.Media.insertImage(
getContentResolver(), bitmap, fileName, "QRIS Payment QR Code");
if (savedImageURL != null) {
android.widget.Toast.makeText(this, "QRIS saved to gallery", android.widget.Toast.LENGTH_SHORT).show();
} else {
android.widget.Toast.makeText(this, "Failed to save QRIS", android.widget.Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
android.widget.Toast.makeText(this, "Error saving QRIS: " + e.getMessage(), android.widget.Toast.LENGTH_LONG).show();
}
}
private void pollPendingPaymentLog(final String orderId) {
Log.d("QrisResultFlow", "Polling for orderId (transaction_uuid): " + orderId);
progressBar.setVisibility(View.VISIBLE);
new Thread(() -> {
int maxAttempts = 10;
int intervalMs = 1500;
int attempt = 0;
boolean found = false;
while (attempt < maxAttempts && !found) {
try {
String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + orderId + "\"}";
Log.d("QrisResultFlow", "Polling URL: " + urlStr);
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
JSONObject json = new JSONObject(response.toString());
JSONArray results = json.optJSONArray("results");
if (results != null && results.length() > 0) {
for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null && "pending".equals(reqBody.optString("transaction_status"))) {
found = true;
break;
}
}
}
}
} catch (Exception e) {
Log.e("QrisResultFlow", "Polling error: " + e.getMessage(), e);
}
if (!found) {
attempt++;
try { Thread.sleep(intervalMs); } catch (InterruptedException ignored) {}
}
}
final boolean logFound = found;
new Handler(Looper.getMainLooper()).post(() -> {
progressBar.setVisibility(View.GONE);
if (logFound) {
checkStatusButton.setEnabled(true);
android.widget.Toast.makeText(QrisResultActivity.this, "Pending payment log found!", android.widget.Toast.LENGTH_SHORT).show();
} else {
android.widget.Toast.makeText(QrisResultActivity.this, "Pending payment log NOT found.", android.widget.Toast.LENGTH_LONG).show();
}
});
}).start();
}
// Simulate webhook callback
private void simulateWebhook() {
progressBar.setVisibility(View.VISIBLE);
new Thread(() -> {
try {
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
payload.put("transaction_status", "settlement");
payload.put("transaction_id", transactionId); // Use the actual transaction_id
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", "dummy_signature");
payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
payload.put("payment_type", "qris");
payload.put("order_id", orderId); // Use order_id
payload.put("merchant_id", merchantId != null ? merchantId : "DUMMY_MERCHANT_ID");
payload.put("issuer", acquirer != null ? acquirer : "gopay");
payload.put("gross_amount", grossAmount); // Use exact gross amount
payload.put("fraud_status", "accept");
payload.put("currency", "IDR");
payload.put("acquirer", acquirer != null ? acquirer : "gopay");
payload.put("shopeepay_reference_number", "");
payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID");
Log.d("QrisResultFlow", "Webhook payload: " + payload.toString());
URL url = new URL(webhookUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(payload.toString().getBytes());
os.flush();
os.close();
int responseCode = conn.getResponseCode();
BufferedReader br = new BufferedReader(new InputStreamReader(
responseCode < 400 ? conn.getInputStream() : conn.getErrorStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
Log.d("QrisResultFlow", "Webhook response: " + response.toString());
} catch (Exception e) {
Log.e("QrisResultFlow", "Webhook error: " + e.getMessage(), e);
}
new Handler(Looper.getMainLooper()).post(() -> {
progressBar.setVisibility(View.GONE);
// Proceed to show status/result
qrImageView.setVisibility(View.GONE);
amountTextView.setVisibility(View.GONE);
referenceTextView.setVisibility(View.GONE);
downloadQrisButton.setVisibility(View.GONE);
checkStatusButton.setVisibility(View.GONE);
statusTextView.setVisibility(View.VISIBLE);
returnMainButton.setVisibility(View.VISIBLE);
});
}).start();
}
}

View File

@@ -0,0 +1,204 @@
package com.example.bdkipoc;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class TransactionActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private TransactionAdapter adapter;
private ProgressBar progressBar;
private FloatingActionButton refreshButton;
private int page = 0;
private final int limit = 10;
private boolean isLoading = false;
private boolean isLastPage = false;
private List<Transaction> transactionList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transaction);
// Set up the toolbar as the action bar
androidx.appcompat.widget.Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Enable the back button in the action bar
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
recyclerView = findViewById(R.id.recyclerView);
progressBar = findViewById(R.id.progressBar);
refreshButton = findViewById(R.id.refreshButton);
adapter = new TransactionAdapter(transactionList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
refreshButton.setOnClickListener(v -> refreshTransactions());
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1) && !isLoading && !isLastPage) {
loadTransactions(page + 1);
}
}
});
loadTransactions(0);
}
private void refreshTransactions() {
page = 0;
isLastPage = false;
transactionList.clear();
adapter.notifyDataSetChanged();
loadTransactions(0);
}
private void loadTransactions(int pageToLoad) {
isLoading = true;
progressBar.setVisibility(View.VISIBLE);
new FetchTransactionsTask(pageToLoad).execute();
}
private class FetchTransactionsTask extends AsyncTask<Void, Void, List<Transaction>> {
private int pageToLoad;
private boolean error = false;
private int total = 0;
FetchTransactionsTask(int page) {
this.pageToLoad = page;
}
@Override
protected List<Transaction> doInBackground(Void... voids) {
List<Transaction> result = new ArrayList<>();
try {
String urlString = "https://be-edc.msvc.app/transactions?page=" + pageToLoad + "&limit=" + limit + "&sortOrder=DESC&from_date=&to_date=&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=id";
URI uri = new URI(urlString);
URL url = uri.toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("accept", "*/*");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
response.append(line);
}
in.close();
JSONObject jsonObject = new JSONObject(response.toString());
JSONObject results = jsonObject.getJSONObject("results");
total = results.getInt("total");
JSONArray data = results.getJSONArray("data");
for (int i = 0; i < data.length(); i++) {
JSONObject t = data.getJSONObject(i);
Transaction tx = new Transaction(
t.getInt("id"),
t.getString("type"),
t.getString("channel_category"),
t.getString("channel_code"),
t.getString("reference_id"),
t.getString("amount"),
t.getString("cashflow"),
t.getString("status"),
t.getString("created_at"),
t.getString("merchant_name")
);
result.add(tx);
}
} else {
error = true;
}
} catch (IOException | JSONException | URISyntaxException e) {
error = true;
}
return result;
}
@Override
protected void onPostExecute(List<Transaction> transactions) {
isLoading = false;
progressBar.setVisibility(View.GONE);
if (error) {
Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
return;
}
if (pageToLoad == 0) {
transactionList.clear();
}
transactionList.addAll(transactions);
adapter.notifyDataSetChanged();
page = pageToLoad;
if (transactionList.size() >= total) {
isLastPage = true;
}
}
}
@Override
public boolean onOptionsItemSelected(android.view.MenuItem item) {
if (item.getItemId() == android.R.id.home) {
// Handle the back button click
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
static class Transaction {
int id;
String type;
String channelCategory;
String channelCode;
String referenceId;
String amount;
String cashflow;
String status;
String createdAt;
String merchantName;
Transaction(int id, String type, String channelCategory, String channelCode, String referenceId, String amount, String cashflow, String status, String createdAt, String merchantName) {
this.id = id;
this.type = type;
this.channelCategory = channelCategory;
this.channelCode = channelCode;
this.referenceId = referenceId;
this.amount = amount;
this.cashflow = cashflow;
this.status = status;
this.createdAt = createdAt;
this.merchantName = merchantName;
}
}
}

View File

@@ -0,0 +1,64 @@
package com.example.bdkipoc;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import java.text.NumberFormat;
import java.util.Locale;
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
private List<TransactionActivity.Transaction> transactionList;
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
this.transactionList = transactionList;
}
@NonNull
@Override
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false);
return new TransactionViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
TransactionActivity.Transaction t = transactionList.get(position);
// Format the amount as Indonesian Rupiah
try {
double amountValue = Double.parseDouble(t.amount);
NumberFormat rupiahFormat = NumberFormat.getCurrencyInstance(new Locale.Builder().setLanguage("id").setRegion("ID").build());
holder.amount.setText(rupiahFormat.format(amountValue));
} catch (NumberFormatException e) {
holder.amount.setText("Rp " + t.amount);
}
holder.status.setText(t.status);
holder.referenceId.setText(t.referenceId);
holder.merchantName.setText(t.merchantName);
holder.createdAt.setText(t.createdAt.replace("T", " ").substring(0, 19));
}
@Override
public int getItemCount() {
return transactionList.size();
}
static class TransactionViewHolder extends RecyclerView.ViewHolder {
TextView amount, status, referenceId, merchantName, createdAt;
public TransactionViewHolder(@NonNull View itemView) {
super(itemView);
amount = itemView.findViewById(R.id.textAmount);
status = itemView.findViewById(R.id.textStatus);
referenceId = itemView.findViewById(R.id.textReferenceId);
merchantName = itemView.findViewById(R.id.textMerchantName);
createdAt = itemView.findViewById(R.id.textCreatedAt);
}
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_title"
android:textSize="24sp"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.cardview.widget.CardView
android:id="@+id/card_payment"
android:layout_width="140dp"
android:layout_height="140dp"
android:layout_marginTop="64dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/light_blue"
app:layout_constraintEnd_toStartOf="@+id/card_transactions"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_text">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@android:drawable/ic_menu_send"
android:contentDescription="@string/payment"
app:tint="@color/primary_blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/payment2"
android:textColor="@color/primary_blue"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_transactions"
android:layout_width="140dp"
android:layout_height="140dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/light_gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_payment"
app:layout_constraintTop_toTopOf="@+id/card_payment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@android:drawable/ic_menu_recent_history"
android:contentDescription="@string/transactions"
app:tint="@color/accent_teal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/transactions2"
android:textColor="@color/accent_teal"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="QRIS Payment" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone" />
<TextView
android:id="@+id/statusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="Ready to make a payment"
android:textSize="18sp" />
<!-- Initial Payment Form -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/light_blue"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
<EditText
android:id="@+id/editTextAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Enter amount"
android:inputType="number"
android:maxLength="12"
android:importantForAutofill="no"
android:singleLine="true"
android:textColor="@color/primary_blue"
android:textSize="24sp"
android:textStyle="bold"
android:gravity="end" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Reference ID"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
<TextView
android:id="@+id/referenceIdTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="ref-abcd1234"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/initiatePaymentButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/primary_blue"
android:text="Start Payment" />
</LinearLayout>
<!-- QR Code and Payment Details -->
<LinearLayout
android:id="@+id/paymentDetailsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/qrCodeImageView"
android:layout_width="250dp"
android:layout_height="250dp"
android:contentDescription="QRIS Code"
android:scaleType="fitCenter" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="Scan with your banking app or e-wallet to pay"
android:textSize="14sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/simulatePaymentButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/accent_green"
android:text="Confirm Payment" />
</LinearLayout>
<!-- Payment Success -->
<LinearLayout
android:id="@+id/paymentSuccessLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/light_gray"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:contentDescription="Success Icon"
android:src="@android:drawable/ic_dialog_info"
android:tint="@color/accent_green" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="Payment Successful!"
android:textColor="@color/accent_green"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Your transaction has been completed successfully."
android:textSize="14sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/returnToMainButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/primary_blue"
android:text="Return to Main" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:background="#181824"
>
<TextView
android:id="@+id/amountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/amount"
android:textColor="#2D5DA1"
android:textSize="20sp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/referenceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reference_id"
android:textColor="#2D5DA1"
android:textSize="16sp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
android:layout_marginTop="8dp"/>
<ImageView
android:id="@+id/qrImageView"
android:layout_width="280dp"
android:layout_height="280dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:contentDescription="@string/qr_code"/>
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download_qris"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"/>
<Button
android:id="@+id/checkStatusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/check_payment_status"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone"/>
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/payment_status_success"
android:textColor="#2D5DA1"
android:textSize="20sp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:visibility="gone"/>
<Button
android:id="@+id/returnMainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/return_main"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:visibility="gone"/>
</LinearLayout>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="Transactions History" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/refreshButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="Refresh"
android:src="@android:drawable/ic_popup_sync"
app:tint="@android:color/white" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="@android:color/white"
android:layout_marginBottom="8dp"
android:elevation="2dp">
<TextView
android:id="@+id/textAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount"
android:textStyle="bold"
android:textSize="18sp" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status"
android:textColor="#4CAF50"
android:textStyle="bold" />
<TextView
android:id="@+id/textReferenceId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reference ID" />
<TextView
android:id="@+id/textMerchantName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Merchant Name" />
<TextView
android:id="@+id/textCreatedAt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Created At" />
</LinearLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.BDKIPOC" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- Banking App Theme Colors -->
<color name="primary_blue">#1976D2</color>
<color name="light_blue">#BBDEFB</color>
<color name="accent_teal">#009688</color>
<color name="accent_orange">#FF5722</color>
<color name="accent_purple">#673AB7</color>
<color name="accent_green">#4CAF50</color>
<color name="light_gray">#F5F5F5</color>
<color name="medium_gray">#E0E0E0</color>
<color name="dark_gray">#757575</color>
</resources>

View File

@@ -0,0 +1,16 @@
<resources>
<string name="app_name">BDKI POC</string>
<string name="payment">Payment (QRIS)</string>
<string name="payment2">Payment (QRIS)</string>
<string name="transactions">Transactions</string>
<string name="transactions2">Transactions</string>
<string name="amount">Amount:</string>
<string name="reference_id">Reference ID:</string>
<string name="qr_code">QR Code</string>
<string name="back">Back</string>
<string name="download_qris">Download QRIS</string>
<string name="check_payment_status">Check Payment Status</string>
<string name="payment_status_success">Payment Successful!</string>
<string name="return_main">Return to Main Screen</string>
<string name="main_title">POC</string>
</resources>

View File

@@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.BDKIPOC" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.BDKIPOC" parent="Base.Theme.BDKIPOC" />
</resources>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@@ -0,0 +1,17 @@
package com.example.bdkipoc;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}