commit 4eabdd81d2ab0fe14afec22d744c65903e99df09 Author: Achmad Zaenuri Date: Wed Apr 16 07:33:55 2025 +0700 first working version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..72e02b0 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +BDKI POC \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..4b28b2d --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..09de6c5 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..976cf21 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..d843f34 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..6f930f6 --- /dev/null +++ b/app/build.gradle @@ -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 +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/bdkipoc/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/bdkipoc/ExampleInstrumentedTest.java new file mode 100644 index 0000000..faeca1b --- /dev/null +++ b/app/src/androidTest/java/com/example/bdkipoc/ExampleInstrumentedTest.java @@ -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 Testing documentation + */ +@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()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7923c09 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/MainActivity.java b/app/src/main/java/com/example/bdkipoc/MainActivity.java new file mode 100644 index 0000000..1f62a57 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/MainActivity.java @@ -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)); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java new file mode 100644 index 0000000..b2bd490 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java @@ -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 { + 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 { + @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 { + 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); + } + } +} diff --git a/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java b/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java new file mode 100644 index 0000000..1d7135f --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/QrisResultActivity.java @@ -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 { + 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(); + } +} diff --git a/app/src/main/java/com/example/bdkipoc/TransactionActivity.java b/app/src/main/java/com/example/bdkipoc/TransactionActivity.java new file mode 100644 index 0000000..51daef6 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/TransactionActivity.java @@ -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 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> { + private int pageToLoad; + private boolean error = false; + private int total = 0; + + FetchTransactionsTask(int page) { + this.pageToLoad = page; + } + + @Override + protected List doInBackground(Void... voids) { + List 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 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; + } + } +} diff --git a/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java b/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java new file mode 100644 index 0000000..e7e0703 --- /dev/null +++ b/app/src/main/java/com/example/bdkipoc/TransactionAdapter.java @@ -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 { + private List transactionList; + + public TransactionAdapter(List 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); + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..6597de9 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_payment.xml b/app/src/main/res/layout/activity_payment.xml new file mode 100644 index 0000000..4ce7109 --- /dev/null +++ b/app/src/main/res/layout/activity_payment.xml @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +