first working version
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
41
app/build.gradle
Normal 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
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
39
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
41
app/src/main/java/com/example/bdkipoc/MainActivity.java
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
557
app/src/main/java/com/example/bdkipoc/PaymentActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
287
app/src/main/java/com/example/bdkipoc/QrisResultActivity.java
Normal 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();
|
||||
}
|
||||
}
|
||||
204
app/src/main/java/com/example/bdkipoc/TransactionActivity.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal 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>
|
||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
92
app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||
227
app/src/main/res/layout/activity_payment.xml
Normal 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>
|
||||
82
app/src/main/res/layout/activity_qris_result.xml
Normal 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>
|
||||
54
app/src/main/res/layout/activity_transaction.xml
Normal 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>
|
||||
44
app/src/main/res/layout/item_transaction.xml
Normal 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>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
7
app/src/main/res/values-night/themes.xml
Normal 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>
|
||||
16
app/src/main/res/values/colors.xml
Normal 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>
|
||||
16
app/src/main/res/values/strings.xml
Normal 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>
|
||||
9
app/src/main/res/values/themes.xml
Normal 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>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal 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>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal 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>
|
||||
17
app/src/test/java/com/example/bdkipoc/ExampleUnitTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||