Compare commits

...

26 Commits

Author SHA1 Message Date
6f78b6df3f Merge branch 'temp-feature' into development 2025-06-27 17:15:14 +07:00
da312ec3ae Implement PaymentActivity dan TransactionActivity 2025-06-27 17:03:14 +07:00
53964211c2 Result Transaction dan QRIS 2025-06-27 17:01:05 +07:00
b66ef4bb00 proeses transaction post backend and database 2025-06-26 15:48:12 +07:00
7a2ddc3f15 display result transation 2025-06-26 13:57:07 +07:00
8a73206a76 safepoint charge 2025-06-25 20:56:28 +07:00
f6650f99d0 ganti akun midtrans 2025-06-25 12:54:01 +07:00
8ac97437a2 add Success Screen 2025-06-25 10:17:56 +07:00
2b57d35553 custom appbar 2025-06-25 09:28:05 +07:00
f2c3de9f5f refactor 2025-06-25 00:35:03 +07:00
f5d9e53118 Safepoint Transaction-1 2025-06-23 20:42:33 +07:00
ece79942c1 refactor transaction 2025-06-23 11:38:31 +07:00
0af0e836b1 menambahkan mdal pada screen emv 2025-06-23 10:52:04 +07:00
f403358554 Safepoint Modal Scan Card 2025-06-23 09:20:26 +07:00
d43c4bad0c card Scanning tanpa button 2025-06-22 22:03:22 +07:00
174a1461fd refactor credit card 2025-06-22 20:10:56 +07:00
f4e5e03077 adding input amount transaction 2025-06-21 01:15:40 +07:00
f48e3e64a4 Safepoint Check EMV 2025-06-18 23:42:48 +07:00
2ea0792d28 Implement EMV 2025-06-18 14:41:18 +07:00
9834d4b841 init config EM 2025-06-18 11:49:34 +07:00
8add903edb Display UI card read 2025-06-18 11:44:11 +07:00
124da43a1e Safepoint Card Reading 2025-06-17 22:49:41 +07:00
d7617186a6 Refactor structur java 2025-06-17 15:15:40 +07:00
93fc410e37 Setting Config 2025-06-17 15:06:22 +07:00
448dfd9835 QRISFLOW DESIGN IMPROVED 2025-06-17 14:39:12 +07:00
eac3179d8a QRISFLOW Safepoint 2025-06-16 14:57:01 +07:00
85 changed files with 8625 additions and 3103 deletions

View File

@ -5,6 +5,13 @@ plugins {
android { android {
namespace 'com.example.bdkipoc' namespace 'com.example.bdkipoc'
compileSdk 35 compileSdk 35
// Tambahkan lint options
lint {
abortOnError false
disable 'GoogleAppIndexingWarning'
disable 'NonConstantResourceId'
}
defaultConfig { defaultConfig {
applicationId "com.example.bdkipoc" applicationId "com.example.bdkipoc"
@ -22,20 +29,32 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
// Keep Java 11 - lebih modern dari referensi
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11
} }
// Tambahkan sourceSets untuk native libs jika diperlukan
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation libs.appcompat implementation libs.appcompat
implementation libs.material implementation libs.material
implementation libs.activity implementation libs.activity
implementation libs.constraintlayout implementation libs.constraintlayout
implementation libs.cardview implementation libs.cardview
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'com.sunmi:printerlibrary:1.0.15'
// Test dependencies
testImplementation libs.junit testImplementation libs.junit
androidTestImplementation libs.ext.junit androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core androidTestImplementation libs.espresso.core

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,7 +8,23 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="com.sunmi.perm.LED" />
<uses-permission android:name="com.sunmi.perm.MSR" />
<uses-permission android:name="com.sunmi.perm.ICC" />
<uses-permission android:name="com.sunmi.perm.PINPAD" />
<uses-permission android:name="com.sunmi.perm.SECURITY" />
<uses-permission android:name="com.sunmi.perm.CONTACTLESS_CARD" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application <application
android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@ -29,32 +45,45 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".TransactionActivity" android:name=".cetakulang.ReprintActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".PaymentActivity" android:name=".cetakulang.ReprintAdapterActivity"
android:exported="false" />
<activity
android:name=".PinActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".ReceiptActivity" android:name=".ReceiptActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".QrisActivity" android:name=".QrisActivity"
android:exported="false" /> android:exported="false" />
<activity android:name=".QrisResultActivity" />
<activity
android:name=".QrisResultActivity"
android:exported="false" />
<activity <activity
android:name=".SettlementActivity" android:name=".SettlementActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".HistoryActivity" android:name=".HistoryActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name=".HistoryDetailActivity" android:name=".HistoryDetailActivity"
android:exported="false" /> android:exported="false" />
<activity
android:name=".transaction.CreateTransactionActivity"
android:exported="false" />
<activity
android:name=".transaction.ResultTransactionActivity"
android:exported="false" />
<activity android:name="com.sunmi.emv.l2.view.AppSelectActivity"/>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,27 @@
package com.example.bdkipoc;
import android.content.Context;
import android.content.SharedPreferences;
public class CacheHelper {
private static final String PREFERENCE_FILE_NAME = "sm_pay_demo_obj";
private static final String KEY_LANGUAGE = "key_language";
public static void saveCurrentLanguage(int language) {
SharedPreferences sharedPreferences = MyApplication.app.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
int value = sharedPreferences.getInt(KEY_LANGUAGE, Constant.LANGUAGE_AUTO);
if (value == language) return;
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt(KEY_LANGUAGE, language);
editor.apply();
}
public static int getCurrentLanguage() {
SharedPreferences sharedPreferences = MyApplication.app.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getInt(KEY_LANGUAGE, Constant.LANGUAGE_AUTO);
}
}

View File

@ -0,0 +1,17 @@
package com.example.bdkipoc;
public class Constant {
public static final String TAG = "SDKTestDemo";
public static final int LANGUAGE_AUTO = 0;
public static final int LANGUAGE_ZH_CN = 1;
public static final int LANGUAGE_EN_US = 2;
public static final int LANGUAGE_JA_JP = 3;
public static final int SCAN_MODEL_NONE = 100;
public static final int SCAN_MODEL_P2Lite = 101;
public static final String SCAN_MODEL_NONE_VALUE = "NONE";
public static final String SCAN_MODEL_P2Lite_VALUE = "P2Lite";
}

View File

@ -19,6 +19,13 @@ import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.example.bdkipoc.cetakulang.ReprintActivity;
import com.example.bdkipoc.cetakulang.ReprintAdapterActivity;
import com.example.bdkipoc.R;
import com.example.bdkipoc.transaction.CreateTransactionActivity;
import com.example.bdkipoc.transaction.ResultTransactionActivity;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private boolean isExpanded = false; // False = showing only 9 main menus, True = showing all 15 menus private boolean isExpanded = false; // False = showing only 9 main menus, True = showing all 15 menus
@ -83,12 +90,9 @@ public class MainActivity extends AppCompatActivity {
// 6 dummy menus should be hidden initially // 6 dummy menus should be hidden initially
CardView[] dummyCards = { CardView[] dummyCards = {
findViewById(R.id.card_dummy_menu_1), findViewById(R.id.card_bantuan),
findViewById(R.id.card_dummy_menu_2), findViewById(R.id.card_info_toko),
findViewById(R.id.card_dummy_menu_3), findViewById(R.id.card_pengaturan),
findViewById(R.id.card_dummy_menu_4),
findViewById(R.id.card_dummy_menu_5),
findViewById(R.id.card_dummy_menu_6)
}; };
for (CardView card : dummyCards) { for (CardView card : dummyCards) {
@ -135,21 +139,17 @@ public class MainActivity extends AppCompatActivity {
R.id.card_kartu_debit, R.id.card_kartu_debit,
R.id.card_qris, R.id.card_qris,
// Row 2 (Always visible - 3 items) // Row 2 (Always visible - 3 items)
R.id.card_transfer,
R.id.card_uang_elektronik, R.id.card_uang_elektronik,
R.id.card_cetak_ulang, R.id.card_cetak_ulang,
R.id.card_settlement,
// Row 3 (Always visible - 3 items) // Row 3 (Always visible - 3 items)
R.id.card_refund,
R.id.card_settlement,
R.id.card_histori, R.id.card_histori,
R.id.card_bantuan,
R.id.card_info_toko,
// Row 4 (Hidden initially - 3 items) // Row 4 (Hidden initially - 3 items)
R.id.card_dummy_menu_1, R.id.card_bantuan,
R.id.card_dummy_menu_2, R.id.card_info_toko,
R.id.card_dummy_menu_3, R.id.card_pengaturan,
// Row 5 (Hidden initially - 3 items)
R.id.card_dummy_menu_4,
R.id.card_dummy_menu_5,
R.id.card_dummy_menu_6
}; };
// Set up click listeners for all cards // Set up click listeners for all cards
@ -157,39 +157,37 @@ public class MainActivity extends AppCompatActivity {
CardView cardView = findViewById(cardId); CardView cardView = findViewById(cardId);
if (cardView != null) { if (cardView != null) {
cardView.setOnClickListener(v -> { cardView.setOnClickListener(v -> {
// ENHANCED: Navigate with payment type information
if (cardId == R.id.card_kartu_kredit) { if (cardId == R.id.card_kartu_kredit) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class)); navigateToCreateTransaction("credit_card", cardId, "Kartu Kredit");
} else if (cardId == R.id.card_kartu_debit) { } else if (cardId == R.id.card_kartu_debit) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class)); navigateToCreateTransaction("debit_card", cardId, "Kartu Debit");
} else if (cardId == R.id.card_qris) { } else if (cardId == R.id.card_qris) {
startActivity(new Intent(MainActivity.this, QrisActivity.class)); startActivity(new Intent(MainActivity.this, QrisActivity.class));
// Col-2
} else if (cardId == R.id.card_transfer) {
navigateToCreateTransaction("transfer", cardId, "Transfer");
} else if (cardId == R.id.card_uang_elektronik) { } else if (cardId == R.id.card_uang_elektronik) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class)); navigateToCreateTransaction("e_money", cardId, "Uang Elektronik");
} else if (cardId == R.id.card_cetak_ulang) { } else if (cardId == R.id.card_cetak_ulang) {
startActivity(new Intent(MainActivity.this, TransactionActivity.class)); startActivity(new Intent(MainActivity.this, ReprintActivity.class));
// Col-3
} else if (cardId == R.id.card_refund) {
navigateToCreateTransaction("refund", cardId, "Refund");
} else if (cardId == R.id.card_settlement) { } else if (cardId == R.id.card_settlement) {
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_histori) { } else if (cardId == R.id.card_histori) {
Toast.makeText(this, "Histori - Coming Soon", Toast.LENGTH_SHORT).show(); startActivity(new Intent(MainActivity.this, HistoryActivity.class));
// Col-4
} else if (cardId == R.id.card_bantuan) { } else if (cardId == R.id.card_bantuan) {
Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_info_toko) { } else if (cardId == R.id.card_info_toko) {
Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_1) { } else if (cardId == R.id.card_pengaturan) {
Toast.makeText(this, "Dummy Menu 1 - Coming Soon", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Pengaturan - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_2) {
Toast.makeText(this, "Dummy Menu 2 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_3) {
Toast.makeText(this, "Dummy Menu 3 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_4) {
Toast.makeText(this, "Dummy Menu 4 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_5) {
Toast.makeText(this, "Dummy Menu 5 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_6) {
Toast.makeText(this, "Dummy Menu 6 - Coming Soon", Toast.LENGTH_SHORT).show();
} else { } else {
// Fallback for any other cards // Fallback for any other cards
Toast.makeText(this, "Menu Diklik: " + getResources().getResourceEntryName(cardId), Toast.LENGTH_SHORT).show(); navigateToCreateTransaction("credit_card", cardId, "Unknown");
} }
}); });
} }
@ -197,12 +195,9 @@ public class MainActivity extends AppCompatActivity {
// Get references to ONLY the dummy cards that need to be toggled // Get references to ONLY the dummy cards that need to be toggled
CardView[] toggleableCards = { CardView[] toggleableCards = {
findViewById(R.id.card_dummy_menu_1), findViewById(R.id.card_bantuan),
findViewById(R.id.card_dummy_menu_2), findViewById(R.id.card_info_toko),
findViewById(R.id.card_dummy_menu_3), findViewById(R.id.card_pengaturan),
findViewById(R.id.card_dummy_menu_4),
findViewById(R.id.card_dummy_menu_5),
findViewById(R.id.card_dummy_menu_6)
}; };
// Set up "Lainnya" button click listener // Set up "Lainnya" button click listener
@ -249,6 +244,125 @@ public class MainActivity extends AppCompatActivity {
} }
} }
// NEW: Enhanced navigation method with payment type information
private void navigateToCreateTransaction(String paymentType, int cardMenuId, String cardName) {
try {
Intent intent = new Intent(MainActivity.this, CreateTransactionActivity.class);
// ENHANCED: Pass comprehensive payment information
intent.putExtra("PAYMENT_TYPE", paymentType);
intent.putExtra("CARD_MENU_ID", cardMenuId);
intent.putExtra("CARD_NAME", cardName);
intent.putExtra("CALLING_ACTIVITY", "MainActivity");
// DEBUG: Log navigation details
android.util.Log.d("MainActivity", "=== NAVIGATING TO CREATE TRANSACTION ===");
android.util.Log.d("MainActivity", "Payment Type: " + paymentType);
android.util.Log.d("MainActivity", "Card Menu ID: " + cardMenuId);
android.util.Log.d("MainActivity", "Card Name: " + cardName);
android.util.Log.d("MainActivity", "========================================");
startActivity(intent);
} catch (Exception e) {
android.util.Log.e("MainActivity", "Error navigating to CreateTransaction: " + e.getMessage(), e);
Toast.makeText(this, "Error opening transaction: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
// NEW: Helper method to get payment type from card ID (for backward compatibility)
private String getPaymentTypeFromCardId(int cardId) {
if (cardId == R.id.card_kartu_kredit) {
return "credit_card";
} else if (cardId == R.id.card_kartu_debit) {
return "debit_card";
} else if (cardId == R.id.card_qris) {
return "qris";
} else if (cardId == R.id.card_transfer) {
return "transfer";
} else if (cardId == R.id.card_uang_elektronik) {
return "e_money";
} else if (cardId == R.id.card_refund) {
return "refund";
} else {
android.util.Log.w("MainActivity", "Unknown card ID: " + cardId + ", defaulting to credit_card");
return "credit_card";
}
}
// NEW: Helper method to get card name from card ID
private String getCardNameFromCardId(int cardId) {
if (cardId == R.id.card_kartu_kredit) {
return "Kartu Kredit";
} else if (cardId == R.id.card_kartu_debit) {
return "Kartu Debit";
} else if (cardId == R.id.card_qris) {
return "QRIS";
} else if (cardId == R.id.card_transfer) {
return "Transfer";
} else if (cardId == R.id.card_uang_elektronik) {
return "Uang Elektronik";
} else if (cardId == R.id.card_refund) {
return "Refund";
} else if (cardId == R.id.card_settlement) {
return "Settlement";
} else if (cardId == R.id.card_histori) {
return "Histori";
} else if (cardId == R.id.card_cetak_ulang) {
return "Cetak Ulang";
} else if (cardId == R.id.card_bantuan) {
return "Bantuan";
} else if (cardId == R.id.card_info_toko) {
return "Info Toko";
} else if (cardId == R.id.card_pengaturan) {
return "Pengaturan";
} else {
return "Unknown";
}
}
// NEW: Method to validate payment type compatibility
private boolean isPaymentTypeSupported(String paymentType) {
String[] supportedTypes = {
"credit_card", "debit_card", "e_money", "qris",
"transfer", "refund"
};
for (String supportedType : supportedTypes) {
if (supportedType.equals(paymentType)) {
return true;
}
}
return false;
}
// NEW: Method to show payment type selection dialog (for future use)
private void showPaymentTypeDialog() {
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
builder.setTitle("Pilih Jenis Pembayaran");
String[] paymentTypes = {
"Kartu Kredit", "Kartu Debit", "Uang Elektronik",
"QRIS", "Transfer", "Refund"
};
String[] paymentTypeCodes = {
"credit_card", "debit_card", "e_money",
"qris", "transfer", "refund"
};
builder.setItems(paymentTypes, (dialog, which) -> {
String selectedType = paymentTypeCodes[which];
String selectedName = paymentTypes[which];
// Use a generic card ID for dialog selection
navigateToCreateTransaction(selectedType, -1, selectedName);
});
builder.setNegativeButton("Batal", null);
builder.show();
}
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
@ -263,5 +377,74 @@ public class MainActivity extends AppCompatActivity {
// Clear any transaction completion flags to avoid repeated messages // Clear any transaction completion flags to avoid repeated messages
getIntent().removeExtra("transaction_completed"); getIntent().removeExtra("transaction_completed");
getIntent().removeExtra("transaction_amount"); getIntent().removeExtra("transaction_amount");
// NEW: Log resume for debugging
android.util.Log.d("MainActivity", "MainActivity resumed");
}
@Override
protected void onPause() {
super.onPause();
android.util.Log.d("MainActivity", "MainActivity paused");
}
@Override
protected void onDestroy() {
super.onDestroy();
android.util.Log.d("MainActivity", "MainActivity destroyed");
}
// NEW: Method to handle direct payment type launch (for external calls)
public static Intent createTransactionIntent(android.content.Context context, String paymentType, String cardName) {
Intent intent = new Intent(context, CreateTransactionActivity.class);
intent.putExtra("PAYMENT_TYPE", paymentType);
intent.putExtra("CARD_NAME", cardName);
intent.putExtra("CALLING_ACTIVITY", "External");
return intent;
}
// NEW: Public method to simulate card click (for testing)
public void simulateCardClick(int cardId) {
CardView cardView = findViewById(cardId);
if (cardView != null) {
cardView.performClick();
} else {
android.util.Log.w("MainActivity", "Card not found for ID: " + cardId);
}
}
// NEW: Method to get all available payment types
public String[] getAvailablePaymentTypes() {
return new String[]{
"credit_card", "debit_card", "e_money",
"qris", "transfer", "refund"
};
}
// NEW: Method to get payment type display names
public String[] getPaymentTypeDisplayNames() {
return new String[]{
"Kartu Kredit", "Kartu Debit", "Uang Elektronik",
"QRIS", "Transfer", "Refund"
};
}
// NEW: Debug method to log all card IDs and their payment types
private void debugCardMappings() {
android.util.Log.d("MainActivity", "=== CARD PAYMENT TYPE MAPPINGS ===");
int[] cardIds = {
R.id.card_kartu_kredit, R.id.card_kartu_debit, R.id.card_qris,
R.id.card_transfer, R.id.card_uang_elektronik, R.id.card_refund
};
for (int cardId : cardIds) {
String paymentType = getPaymentTypeFromCardId(cardId);
String cardName = getCardNameFromCardId(cardId);
android.util.Log.d("MainActivity",
"Card ID: " + cardId + " -> Payment Type: " + paymentType + " -> Name: " + cardName);
}
android.util.Log.d("MainActivity", "==================================");
} }
} }

View File

@ -0,0 +1,197 @@
package com.example.bdkipoc;
import android.app.Application;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.IBinder;
import android.util.DisplayMetrics;
import com.example.bdkipoc.emv.EmvTTS;
import com.example.bdkipoc.utils.LogUtil;
import com.example.bdkipoc.utils.Utility;
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2;
import com.sunmi.pay.hardware.aidlv2.etc.ETCOptV2;
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2;
import com.sunmi.pay.hardware.aidlv2.print.PrinterOptV2;
import com.sunmi.pay.hardware.aidlv2.readcard.ReadCardOptV2;
import com.sunmi.pay.hardware.aidlv2.rfid.RFIDOptV2;
import com.sunmi.pay.hardware.aidlv2.security.BiometricManagerV2;
import com.sunmi.pay.hardware.aidlv2.security.DevCertManagerV2;
import com.sunmi.pay.hardware.aidlv2.security.NoLostKeyManagerV2;
import com.sunmi.pay.hardware.aidlv2.security.SecurityOptV2;
import com.sunmi.pay.hardware.aidlv2.system.BasicOptV2;
import com.sunmi.pay.hardware.aidlv2.tax.TaxOptV2;
import com.sunmi.pay.hardware.aidlv2.test.TestOptV2;
import com.sunmi.pay.hardware.wrapper.HCEManagerV2Wrapper;
import com.sunmi.peripheral.printer.InnerPrinterCallback;
import com.sunmi.peripheral.printer.InnerPrinterException;
import com.sunmi.peripheral.printer.InnerPrinterManager;
import com.sunmi.peripheral.printer.SunmiPrinterService;
import java.util.Locale;
import sunmi.paylib.SunmiPayKernel;
public class MyApplication extends Application {
public static MyApplication app;
public BasicOptV2 basicOptV2; // 获取基础操作模块
public ReadCardOptV2 readCardOptV2; // 获取读卡模块
public PinPadOptV2 pinPadOptV2; // 获取PinPad操作模块
public SecurityOptV2 securityOptV2; // 获取安全操作模块
public EMVOptV2 emvOptV2; // 获取EMV操作模块
public TaxOptV2 taxOptV2; // 获取税控操作模块
public ETCOptV2 etcOptV2; // 获取ETC操作模块
public PrinterOptV2 printerOptV2; // 获取打印操作模块
public TestOptV2 testOptV2; // 获取测试操作模块
public DevCertManagerV2 devCertManagerV2; // 设备证书操作模块
public NoLostKeyManagerV2 noLostKeyManagerV2; // NoLostKey操作模块
public HCEManagerV2Wrapper hceV2Wrapper; // HCE操作模块
public RFIDOptV2 rfidOptV2; // RFID操作模块
public SunmiPrinterService sunmiPrinterService; // 打印模块
//public IScanInterface scanInterface; // 扫码模块 (commented out)
public BiometricManagerV2 mBiometricManagerV2; // 生物特征模块
private boolean connectPaySDK;//是否已连接PaySDK
@Override
public void onCreate() {
super.onCreate();
app = this;
initLocaleLanguage();
initEmvTTS();
bindPrintService();
bindPaySDKService();
//bindScannerService(); // Commented out scanner service binding
}
public static void initLocaleLanguage() {
Resources resources = app.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();
int showLanguage = CacheHelper.getCurrentLanguage();
if (showLanguage == Constant.LANGUAGE_AUTO) {
LogUtil.e(Constant.TAG, config.locale.getCountry() + "---这是系统语言");
config.locale = Resources.getSystem().getConfiguration().locale;
} else if (showLanguage == Constant.LANGUAGE_ZH_CN) {
LogUtil.e(Constant.TAG, "这是中文");
config.locale = Locale.SIMPLIFIED_CHINESE;
} else if (showLanguage == Constant.LANGUAGE_EN_US) {
LogUtil.e(Constant.TAG, "这是英文");
config.locale = Locale.ENGLISH;
} else if (showLanguage == Constant.LANGUAGE_JA_JP) {
LogUtil.e(Constant.TAG, "这是日文");
config.locale = Locale.JAPAN;
}
resources.updateConfiguration(config, dm);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LogUtil.e(Constant.TAG, "onConfigurationChanged");
}
public boolean isConnectPaySDK() {
return connectPaySDK;
}
/**
* bind PaySDK service
*/
public void bindPaySDKService() {
final SunmiPayKernel payKernel = SunmiPayKernel.getInstance();
payKernel.setEmvL2Split(true);
payKernel.initPaySDK(this, new SunmiPayKernel.ConnectCallback() {
@Override
public void onConnectPaySDK() {
LogUtil.e(Constant.TAG, "onConnectPaySDK...");
emvOptV2 = payKernel.mEMVOptV2;
basicOptV2 = payKernel.mBasicOptV2;
pinPadOptV2 = payKernel.mPinPadOptV2;
readCardOptV2 = payKernel.mReadCardOptV2;
securityOptV2 = payKernel.mSecurityOptV2;
taxOptV2 = payKernel.mTaxOptV2;
etcOptV2 = payKernel.mETCOptV2;
printerOptV2 = payKernel.mPrinterOptV2;
testOptV2 = payKernel.mTestOptV2;
devCertManagerV2 = payKernel.mDevCertManagerV2;
noLostKeyManagerV2 = payKernel.mNoLostKeyManagerV2;
mBiometricManagerV2 = payKernel.mBiometricManagerV2;
hceV2Wrapper = payKernel.mHCEManagerV2Wrapper;
rfidOptV2 = payKernel.mRFIDOptV2;
connectPaySDK = true;
}
@Override
public void onDisconnectPaySDK() {
LogUtil.e(Constant.TAG, "onDisconnectPaySDK...");
connectPaySDK = false;
emvOptV2 = null;
basicOptV2 = null;
pinPadOptV2 = null;
readCardOptV2 = null;
securityOptV2 = null;
taxOptV2 = null;
etcOptV2 = null;
printerOptV2 = null;
devCertManagerV2 = null;
noLostKeyManagerV2 = null;
mBiometricManagerV2 = null;
rfidOptV2 = null;
Utility.showToast(R.string.connect_fail);
}
});
}
/**
* bind printer service
*/
private void bindPrintService() {
try {
InnerPrinterManager.getInstance().bindService(this, new InnerPrinterCallback() {
@Override
protected void onConnected(SunmiPrinterService service) {
sunmiPrinterService = service;
}
@Override
protected void onDisconnected() {
sunmiPrinterService = null;
}
});
} catch (InnerPrinterException e) {
e.printStackTrace();
}
}
/**
* bind scanner service (commented out)
*/
/*
public void bindScannerService() {
Intent intent = new Intent();
intent.setPackage("com.sunmi.scanner");
intent.setAction("com.sunmi.scanner.IScanInterface");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
scanInterface = IScanInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
scanInterface = null;
}
}, Service.BIND_AUTO_CREATE);
}
*/
private void initEmvTTS() {
EmvTTS.getInstance().init();
}
}

View File

@ -1,530 +0,0 @@
package com.example.bdkipoc;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class PaymentActivity extends AppCompatActivity {
// Views
private EditText editTextAmount;
private Button confirmButton;
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
// Numpad buttons
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
private ImageView btnDelete;
// Modal components
private Dialog paymentModal;
// Data
private StringBuilder currentAmount = new StringBuilder();
private static final int MAX_AMOUNT_LENGTH = 12;
// Animation
private Handler animationHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set status bar color programmatically
setStatusBarColor();
setContentView(R.layout.activity_payment);
initializeViews();
setupClickListeners();
setupInitialStates();
setupModal();
}
private void setStatusBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
// Make status bar icons white (for dark red background)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
decorView.setSystemUiVisibility(0); // Clear light status bar flag
}
}
}
private void initializeViews() {
// Main views
editTextAmount = findViewById(R.id.editTextAmount);
confirmButton = findViewById(R.id.confirmButton);
backNavigation = findViewById(R.id.back_navigation);
backArrow = findViewById(R.id.backArrow);
toolbarTitle = findViewById(R.id.toolbarTitle);
// Numpad buttons
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
btn3 = findViewById(R.id.btn3);
btn4 = findViewById(R.id.btn4);
btn5 = findViewById(R.id.btn5);
btn6 = findViewById(R.id.btn6);
btn7 = findViewById(R.id.btn7);
btn8 = findViewById(R.id.btn8);
btn9 = findViewById(R.id.btn9);
btn0 = findViewById(R.id.btn0);
btn000 = findViewById(R.id.btn000);
btnDelete = findViewById(R.id.btnDelete);
}
private void setupModal() {
// Create modal dialog
paymentModal = new Dialog(this);
paymentModal.setContentView(R.layout.modal_layout);
// Remove background dimming - make it fully transparent
if (paymentModal.getWindow() != null) {
paymentModal.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
}
// Setup modal listeners
setupModalListeners();
}
private void setupModalListeners() {
// Make modal non-cancelable by touching outside
paymentModal.setCanceledOnTouchOutside(false);
// Auto dismiss after 3 seconds (simulate card processing)
Handler modalHandler = new Handler(Looper.getMainLooper());
paymentModal.setOnShowListener(dialog -> {
modalHandler.postDelayed(() -> {
if (paymentModal != null && paymentModal.isShowing()) {
// First dismiss modal, then navigate
paymentModal.dismiss();
// Add small delay to ensure modal is fully dismissed
animationHandler.postDelayed(() -> {
navigateToPinActivity();
}, 100);
}
}, 3000);
});
}
private void setupClickListeners() {
// Back navigation - entire LinearLayout is clickable
backNavigation.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
// Individual back arrow (for additional touch area)
backArrow.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
// Toolbar title (also clickable for back navigation)
toolbarTitle.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
// Numpad listeners
btn1.setOnClickListener(v -> handleNumpadClick(v, "1"));
btn2.setOnClickListener(v -> handleNumpadClick(v, "2"));
btn3.setOnClickListener(v -> handleNumpadClick(v, "3"));
btn4.setOnClickListener(v -> handleNumpadClick(v, "4"));
btn5.setOnClickListener(v -> handleNumpadClick(v, "5"));
btn6.setOnClickListener(v -> handleNumpadClick(v, "6"));
btn7.setOnClickListener(v -> handleNumpadClick(v, "7"));
btn8.setOnClickListener(v -> handleNumpadClick(v, "8"));
btn9.setOnClickListener(v -> handleNumpadClick(v, "9"));
btn0.setOnClickListener(v -> handleNumpadClick(v, "0"));
btn000.setOnClickListener(v -> handleNumpadClick(v, "000"));
// Delete button
btnDelete.setOnClickListener(v -> {
addClickAnimation(v);
deleteLastDigit();
});
// Confirm button - NOW SHOWS MODAL INSTEAD OF DIRECT PAYMENT
confirmButton.setOnClickListener(v -> {
if (confirmButton.isEnabled()) {
addButtonClickAnimation(v);
showPaymentModal();
}
});
}
private void navigateBack() {
// Simple back navigation without card animation
finish();
}
private void handleNumpadClick(View view, String digit) {
addClickAnimation(view);
addDigit(digit);
}
private void setupInitialStates() {
// Set initial amount display
editTextAmount.setText("");
// Set initial button state
updateButtonState();
// Disable EditText input (only numpad input allowed)
editTextAmount.setFocusable(false);
editTextAmount.setClickable(false);
editTextAmount.setCursorVisible(false);
}
private void addDigit(String digit) {
// Validate input length
if (currentAmount.length() >= MAX_AMOUNT_LENGTH) {
showToast("Maksimal " + MAX_AMOUNT_LENGTH + " digit");
return;
}
// Handle leading zeros
if (currentAmount.length() == 0) {
if (digit.equals("000")) {
// Don't allow 000 as first input
return;
}
currentAmount.append(digit);
} else if (currentAmount.length() == 1 && currentAmount.toString().equals("0")) {
if (!digit.equals("000")) {
// Replace single 0 with new digit
currentAmount = new StringBuilder(digit);
} else {
return;
}
} else {
currentAmount.append(digit);
}
updateAmountDisplay();
updateButtonState();
addInputFeedback();
}
private void deleteLastDigit() {
if (currentAmount.length() > 0) {
String current = currentAmount.toString();
// If current ends with 000, remove all three digits
if (current.endsWith("000") && current.length() >= 3) {
currentAmount.delete(currentAmount.length() - 3, currentAmount.length());
} else {
currentAmount.deleteCharAt(currentAmount.length() - 1);
}
updateAmountDisplay();
updateButtonState();
addDeleteFeedback();
}
}
private void updateAmountDisplay() {
String amount = currentAmount.toString();
if (amount.isEmpty() || amount.equals("0")) {
editTextAmount.setText("");
} else {
String formattedAmount = formatCurrency(amount);
editTextAmount.setText(formattedAmount);
}
}
private String formatCurrency(String amount) {
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
return "";
}
try {
long number = Long.parseLong(amount);
return String.format("%,d", number).replace(',', '.');
} catch (NumberFormatException e) {
return amount;
}
}
private void updateButtonState() {
boolean hasValidAmount = currentAmount.length() > 0 &&
!currentAmount.toString().equals("0") &&
!currentAmount.toString().isEmpty();
confirmButton.setEnabled(hasValidAmount);
if (hasValidAmount) {
// Active state
confirmButton.setBackgroundResource(R.drawable.button_active_background);
confirmButton.setTextColor(Color.WHITE);
confirmButton.setAlpha(1.0f);
} else {
// Inactive state
confirmButton.setBackgroundResource(R.drawable.button_inactive_background);
confirmButton.setTextColor(Color.parseColor("#999999"));
confirmButton.setAlpha(0.6f);
}
}
// NEW METHOD: Show payment modal instead of direct payment processing
private void showPaymentModal() {
String amount = currentAmount.toString();
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
showToast("Masukkan jumlah pembayaran");
return;
}
try {
long amountValue = Long.parseLong(amount);
// Validate minimum amount
if (amountValue < 1000) {
showToast("Minimal pembayaran Rp 1.000");
return;
}
// Validate maximum amount
if (amountValue > 999999999L) {
showToast("Maksimal pembayaran Rp 999.999.999");
return;
}
// Show modal with animation
showModalWithAnimation();
} catch (NumberFormatException e) {
showToast("Format jumlah tidak valid");
}
}
private void showModalWithAnimation() {
// Add debug log
showToast("Showing card modal...");
paymentModal.show();
// Add slide-up animation
View modalView = paymentModal.findViewById(android.R.id.content);
if (modalView != null) {
ObjectAnimator slideUp = ObjectAnimator.ofFloat(modalView, "translationY", 300f, 0f);
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(modalView, "alpha", 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(slideUp, fadeIn);
animatorSet.setDuration(300);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.start();
}
}
private void dismissModal() {
if (paymentModal != null && paymentModal.isShowing()) {
// Add slide-down animation before dismissing
View modalView = paymentModal.findViewById(android.R.id.content);
if (modalView != null) {
ObjectAnimator slideDown = ObjectAnimator.ofFloat(modalView, "translationY", 0f, 300f);
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(modalView, "alpha", 1f, 0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(slideDown, fadeOut);
animatorSet.setDuration(200);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.addListener(new android.animation.AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(android.animation.Animator animation) {
paymentModal.dismiss();
}
});
animatorSet.start();
} else {
paymentModal.dismiss();
}
}
}
private void processModalConfirmation() {
// This method is no longer needed since modal auto-dismisses
}
private void navigateToPinActivity() {
String amount = currentAmount.toString();
// Add debug log
showToast("Navigating to PIN Activity...");
try {
long amountValue = Long.parseLong(amount);
// Launch PIN Activity with amount data
Intent intent = new Intent(this, PinActivity.class);
intent.putExtra(PinActivity.EXTRA_SOURCE_ACTIVITY, "PaymentActivity");
intent.putExtra(PinActivity.EXTRA_AMOUNT, String.valueOf(amountValue));
startActivityForResult(intent, 100);
} catch (NumberFormatException e) {
showToast("Format jumlah tidak valid: " + e.getMessage());
} catch (Exception e) {
showToast("Error navigating to PIN: " + e.getMessage());
}
}
private void processCardPayment() {
// This method is called after PIN verification is successful
// Now process the actual payment
String amount = currentAmount.toString();
try {
long amountValue = Long.parseLong(amount);
// Show processing message
showToast("PIN berhasil diverifikasi! Memproses pembayaran...");
// Process the final payment
processPayment(amountValue);
} catch (NumberFormatException e) {
showToast("Format jumlah tidak valid");
}
}
private void processPayment(long amount) {
// Show loading state
confirmButton.setText("Memproses...");
confirmButton.setEnabled(false);
// Simulate payment processing
animationHandler.postDelayed(() -> {
// Show success message
showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount)));
// Reset state and go back (this is final step after PIN verification)
resetPaymentState();
navigateBack();
}, 2000);
}
private void resetPaymentState() {
currentAmount = new StringBuilder();
updateAmountDisplay();
updateButtonState();
confirmButton.setText("Konfirmasi");
}
// Animation methods (only for numpad interactions)
private void addClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(150);
animatorSet.start();
}
private void addButtonClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(200);
animatorSet.start();
}
private void addInputFeedback() {
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextAmount, "alpha", 0.7f, 1f);
fadeIn.setDuration(200);
fadeIn.start();
}
private void addDeleteFeedback() {
ObjectAnimator shake = ObjectAnimator.ofFloat(editTextAmount, "translationX", 0f, -10f, 10f, 0f);
shake.setDuration(300);
shake.start();
}
// Utility methods
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onBackPressed() {
// Check if modal is showing, dismiss it first
if (paymentModal != null && paymentModal.isShowing()) {
dismissModal();
} else {
navigateBack();
}
super.onBackPressed();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Handle result from PIN Activity
if (resultCode == RESULT_OK && data != null) {
boolean pinVerified = data.getBooleanExtra("pin_verified", false);
if (pinVerified) {
// PIN verification successful, process payment
processCardPayment();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (animationHandler != null) {
animationHandler.removeCallbacksAndMessages(null);
}
// Clean up modal
if (paymentModal != null && paymentModal.isShowing()) {
paymentModal.dismiss();
}
}
// Public methods for testing
public String getCurrentAmount() {
return currentAmount.toString();
}
public boolean isConfirmButtonEnabled() {
return confirmButton.isEnabled();
}
}

View File

@ -1,558 +0,0 @@
package com.example.bdkipoc;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class PinActivity extends AppCompatActivity {
// Intent Extra Keys
public static final String EXTRA_TITLE = "extra_title";
public static final String EXTRA_SUBTITLE = "extra_subtitle";
public static final String EXTRA_AMOUNT = "extra_amount";
public static final String EXTRA_SOURCE_ACTIVITY = "extra_source_activity";
// Views
private EditText editTextPin;
private Button confirmButton;
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
// Success screen views
private View successScreen;
private ImageView successIcon;
private TextView successMessage;
// Numpad buttons
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
private ImageView btnDelete;
// Data
private StringBuilder currentPin = new StringBuilder();
private static final int MAX_PIN_LENGTH = 6;
private static final int MIN_PIN_LENGTH = 4;
// Extra data from intent
private String sourceActivity;
private String amount;
// Animation
private Handler animationHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set status bar color programmatically
setStatusBarColor();
setContentView(R.layout.activity_pin);
// Get intent extras
getIntentExtras();
initializeViews();
setupClickListeners();
setupInitialStates();
setupSuccessScreen();
}
private void setStatusBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
// Make status bar icons white (for dark red background)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
decorView.setSystemUiVisibility(0); // Clear light status bar flag
}
}
}
private void getIntentExtras() {
Intent intent = getIntent();
if (intent != null) {
sourceActivity = intent.getStringExtra(EXTRA_SOURCE_ACTIVITY);
amount = intent.getStringExtra(EXTRA_AMOUNT);
}
}
private void initializeViews() {
// Main views
editTextPin = findViewById(R.id.editTextPin);
confirmButton = findViewById(R.id.confirmButton);
backNavigation = findViewById(R.id.back_navigation);
backArrow = findViewById(R.id.backArrow);
toolbarTitle = findViewById(R.id.toolbarTitle);
// Success screen views
successScreen = findViewById(R.id.success_screen);
successIcon = findViewById(R.id.success_icon);
successMessage = findViewById(R.id.success_message);
// Numpad buttons
btn1 = findViewById(R.id.btn1);
btn2 = findViewById(R.id.btn2);
btn3 = findViewById(R.id.btn3);
btn4 = findViewById(R.id.btn4);
btn5 = findViewById(R.id.btn5);
btn6 = findViewById(R.id.btn6);
btn7 = findViewById(R.id.btn7);
btn8 = findViewById(R.id.btn8);
btn9 = findViewById(R.id.btn9);
btn0 = findViewById(R.id.btn0);
btn000 = findViewById(R.id.btn000);
btnDelete = findViewById(R.id.btnDelete);
}
private void setupSuccessScreen() {
// Initially hide success screen
if (successScreen != null) {
successScreen.setVisibility(View.GONE);
}
}
private void setupClickListeners() {
// Back navigation - entire LinearLayout is clickable
backNavigation.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
// Individual back arrow (for additional touch area)
backArrow.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
// Toolbar title (also clickable for back navigation)
toolbarTitle.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
// Numpad listeners
btn1.setOnClickListener(v -> handleNumpadClick(v, "1"));
btn2.setOnClickListener(v -> handleNumpadClick(v, "2"));
btn3.setOnClickListener(v -> handleNumpadClick(v, "3"));
btn4.setOnClickListener(v -> handleNumpadClick(v, "4"));
btn5.setOnClickListener(v -> handleNumpadClick(v, "5"));
btn6.setOnClickListener(v -> handleNumpadClick(v, "6"));
btn7.setOnClickListener(v -> handleNumpadClick(v, "7"));
btn8.setOnClickListener(v -> handleNumpadClick(v, "8"));
btn9.setOnClickListener(v -> handleNumpadClick(v, "9"));
btn0.setOnClickListener(v -> handleNumpadClick(v, "0"));
btn000.setOnClickListener(v -> handleNumpadClick(v, "000"));
// Delete button
btnDelete.setOnClickListener(v -> {
addClickAnimation(v);
deleteLastDigit();
});
// Confirm button
confirmButton.setOnClickListener(v -> {
if (confirmButton.isEnabled()) {
addButtonClickAnimation(v);
handleConfirmPin();
}
});
}
private void navigateBack() {
finish();
}
private void handleNumpadClick(View view, String digit) {
addClickAnimation(view);
addDigit(digit);
}
private void setupInitialStates() {
// Set initial PIN display
editTextPin.setText("");
// Set initial button state
updateButtonState();
// Disable EditText input (only numpad input allowed)
editTextPin.setFocusable(false);
editTextPin.setClickable(false);
editTextPin.setCursorVisible(false);
}
private void addDigit(String digit) {
// Validate input length
if (currentPin.length() >= MAX_PIN_LENGTH) {
showToast("Maksimal " + MAX_PIN_LENGTH + " digit");
return;
}
// Handle special case for 000
if (digit.equals("000")) {
if (currentPin.length() + 3 <= MAX_PIN_LENGTH) {
currentPin.append("000");
} else {
int remainingLength = MAX_PIN_LENGTH - currentPin.length();
if (remainingLength > 0) {
currentPin.append("0".repeat(remainingLength));
}
}
} else {
currentPin.append(digit);
}
updatePinDisplay();
updateButtonState();
addInputFeedback();
}
private void deleteLastDigit() {
if (currentPin.length() > 0) {
String current = currentPin.toString();
// If current ends with 000, remove all three digits
if (current.endsWith("000") && current.length() >= 3) {
currentPin.delete(currentPin.length() - 3, currentPin.length());
} else {
currentPin.deleteCharAt(currentPin.length() - 1);
}
updatePinDisplay();
updateButtonState();
addDeleteFeedback();
}
}
private void updatePinDisplay() {
String pin = currentPin.toString();
if (pin.isEmpty()) {
editTextPin.setText("");
} else {
// Convert digits to asterisks for security
String maskedPin = "*".repeat(pin.length());
editTextPin.setText(maskedPin);
}
}
private void updateButtonState() {
boolean hasValidPin = currentPin.length() >= MIN_PIN_LENGTH;
confirmButton.setEnabled(hasValidPin);
if (hasValidPin) {
// Active state
confirmButton.setBackgroundResource(R.drawable.button_active_background);
confirmButton.setTextColor(Color.WHITE);
confirmButton.setAlpha(1.0f);
} else {
// Inactive state
confirmButton.setBackgroundResource(R.drawable.button_inactive_background);
confirmButton.setTextColor(Color.parseColor("#999999"));
confirmButton.setAlpha(0.6f);
}
}
private void handleConfirmPin() {
String pin = currentPin.toString();
if (TextUtils.isEmpty(pin)) {
showToast("Masukkan PIN");
return;
}
if (pin.length() < MIN_PIN_LENGTH) {
showToast("PIN minimal " + MIN_PIN_LENGTH + " digit");
return;
}
// Process PIN verification
verifyPin(pin);
}
private void verifyPin(String pin) {
// Show loading state
confirmButton.setText("Memverifikasi...");
confirmButton.setEnabled(false);
// Simulate PIN verification
animationHandler.postDelayed(() -> {
// For demo purposes, accept any PIN with length >= 4
// In real implementation, this would call backend API
if (isValidPin(pin)) {
// Show success screen instead of toast
handleSuccessfulVerification();
} else {
showToast("PIN tidak valid. Silakan coba lagi.");
resetPinState();
}
}, 2000);
}
private boolean isValidPin(String pin) {
// Demo validation - in real app, this would validate against backend
// For now, reject simple patterns like "1111", "1234", etc.
return !pin.equals("1111") &&
!pin.equals("1234") &&
!pin.equals("0000") &&
pin.length() >= MIN_PIN_LENGTH;
}
private void handleSuccessfulVerification() {
// Show full screen success message
showSuccessScreen();
// Navigate to receipt page after 2.5 seconds
animationHandler.postDelayed(() -> {
navigateToReceiptPage();
}, 2500);
}
private void showSuccessScreen() {
if (successScreen != null) {
// Hide all other UI components first
hideMainUIComponents();
// Set success message
if (successMessage != null) {
successMessage.setText("Pembayaran Berhasil");
}
// Show success screen with fade in animation
successScreen.setVisibility(View.VISIBLE);
successScreen.setAlpha(0f);
// Fade in the background
ObjectAnimator backgroundFadeIn = ObjectAnimator.ofFloat(successScreen, "alpha", 0f, 1f);
backgroundFadeIn.setDuration(500);
backgroundFadeIn.start();
// Add scale and bounce animation to success icon
if (successIcon != null) {
// Start with invisible icon
successIcon.setScaleX(0f);
successIcon.setScaleY(0f);
successIcon.setAlpha(0f);
// Scale animation with bounce effect
ObjectAnimator scaleX = ObjectAnimator.ofFloat(successIcon, "scaleX", 0f, 1.2f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(successIcon, "scaleY", 0f, 1.2f, 1f);
ObjectAnimator iconFadeIn = ObjectAnimator.ofFloat(successIcon, "alpha", 0f, 1f);
AnimatorSet iconAnimation = new AnimatorSet();
iconAnimation.playTogether(scaleX, scaleY, iconFadeIn);
iconAnimation.setDuration(800);
iconAnimation.setStartDelay(300);
iconAnimation.setInterpolator(new android.view.animation.OvershootInterpolator(1.2f));
iconAnimation.start();
}
// Add slide up animation to success message
if (successMessage != null) {
successMessage.setAlpha(0f);
successMessage.setTranslationY(50f);
ObjectAnimator messageSlideUp = ObjectAnimator.ofFloat(successMessage, "translationY", 50f, 0f);
ObjectAnimator messageFadeIn = ObjectAnimator.ofFloat(successMessage, "alpha", 0f, 1f);
AnimatorSet messageAnimation = new AnimatorSet();
messageAnimation.playTogether(messageSlideUp, messageFadeIn);
messageAnimation.setDuration(600);
messageAnimation.setStartDelay(600);
messageAnimation.setInterpolator(new android.view.animation.DecelerateInterpolator());
messageAnimation.start();
}
}
}
private void hideMainUIComponents() {
// Hide all main UI components to create clean full screen success
if (backNavigation != null) {
backNavigation.setVisibility(View.GONE);
}
// Hide the red header backgrounds
View redStatusBar = findViewById(R.id.red_status_bar);
View redHeaderBackground = findViewById(R.id.red_header_background);
if (redStatusBar != null) {
redStatusBar.setVisibility(View.GONE);
}
if (redHeaderBackground != null) {
redHeaderBackground.setVisibility(View.GONE);
}
// Hide PIN card
View pinCard = findViewById(R.id.pin_card);
if (pinCard != null) {
pinCard.setVisibility(View.GONE);
}
// Hide numpad
View numpadGrid = findViewById(R.id.numpad_grid);
if (numpadGrid != null) {
numpadGrid.setVisibility(View.GONE);
}
// Hide confirm button
if (confirmButton != null) {
confirmButton.setVisibility(View.GONE);
}
}
private void navigateToReceiptPage() {
// Create intent to navigate to receipt/struk page
Intent intent = new Intent(this, ReceiptActivity.class);
// Pass transaction data
intent.putExtra("transaction_amount", amount);
intent.putExtra("pin_verified", true);
intent.putExtra("source_activity", sourceActivity);
// Add transaction details (you can customize these)
intent.putExtra("merchant_name", "TOKO KLONTONG PAK EKO");
intent.putExtra("merchant_location", "Ciputat Baru, Tangsel");
intent.putExtra("transaction_id", generateTransactionId());
intent.putExtra("transaction_date", getCurrentDateTime());
intent.putExtra("payment_method", "Kartu Kredit");
intent.putExtra("card_type", "BCA");
intent.putExtra("tax_percentage", "11%");
intent.putExtra("service_fee", "500");
startActivity(intent);
// Set result for calling activity
Intent resultIntent = new Intent();
resultIntent.putExtra("pin_verified", true);
resultIntent.putExtra("pin_length", currentPin.length());
if (!TextUtils.isEmpty(amount)) {
resultIntent.putExtra(EXTRA_AMOUNT, amount);
}
setResult(RESULT_OK, resultIntent);
// Finish this activity
finish();
}
private String generateTransactionId() {
// Generate a simple transaction ID (in real app, this would come from backend)
return String.valueOf(System.currentTimeMillis() % 1000000000L);
}
private String getCurrentDateTime() {
// Get current date and time (in real app, use proper date formatting)
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", java.util.Locale.getDefault());
return sdf.format(new java.util.Date());
}
private void resetPinState() {
currentPin = new StringBuilder();
updatePinDisplay();
updateButtonState();
confirmButton.setText("Konfirmasi");
confirmButton.setEnabled(false);
}
// Animation methods
private void addClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(150);
animatorSet.start();
}
private void addButtonClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(200);
animatorSet.start();
}
private void addInputFeedback() {
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextPin, "alpha", 0.7f, 1f);
fadeIn.setDuration(200);
fadeIn.start();
}
private void addDeleteFeedback() {
ObjectAnimator shake = ObjectAnimator.ofFloat(editTextPin, "translationX", 0f, -10f, 10f, 0f);
shake.setDuration(300);
shake.start();
}
// Utility methods
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onBackPressed() {
// Prevent back press when success screen is showing
if (successScreen != null && successScreen.getVisibility() == View.VISIBLE) {
return;
}
navigateBack();
super.onBackPressed();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (animationHandler != null) {
animationHandler.removeCallbacksAndMessages(null);
}
}
// Public methods for testing
public String getCurrentPin() {
return currentPin.toString();
}
public boolean isConfirmButtonEnabled() {
return confirmButton.isEnabled();
}
// Static helper method to launch PinActivity
public static void launch(android.content.Context context, String sourceActivity, String amount) {
Intent intent = new Intent(context, PinActivity.class);
intent.putExtra(EXTRA_SOURCE_ACTIVITY, sourceActivity);
if (!TextUtils.isEmpty(amount)) {
intent.putExtra(EXTRA_AMOUNT, amount);
}
// Launch for result if context is an Activity
if (context instanceof AppCompatActivity) {
((AppCompatActivity) context).startActivityForResult(intent, 100);
} else {
context.startActivity(intent);
}
}
}

View File

@ -1,689 +0,0 @@
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.*;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class QrisResultActivity extends AppCompatActivity {
// UI Components
private ImageView qrImageView;
private TextView amountTextView, referenceTextView, statusTextView;
private TextView timerTextView, qrStatusTextView;
private Button downloadQrisButton, checkStatusButton, returnMainButton;
private ProgressBar progressBar;
// QR Refresh Components
private Handler qrRefreshHandler;
private Runnable qrRefreshRunnable;
private int countdownSeconds = 60;
private boolean isQrRefreshActive = true;
// Transaction Data
private String orderId, grossAmount, referenceId, transactionId;
private String transactionTime, acquirer, merchantId, currentQrImageUrl;
private int originalAmount;
private List<String> allOrderIds = new ArrayList<>();
// Configuration
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM=";
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qris_result);
initializeViews();
extractIntentData();
validateAndSetupUI();
startMonitoring();
setupClickListeners();
}
private void initializeViews() {
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);
timerTextView = findViewById(R.id.timerTextView);
qrStatusTextView = findViewById(R.id.qrStatusTextView);
qrRefreshHandler = new Handler(Looper.getMainLooper());
}
private void extractIntentData() {
Intent intent = getIntent();
currentQrImageUrl = intent.getStringExtra("qrImageUrl");
originalAmount = 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");
allOrderIds.add(orderId);
Log.d("QrisResult", "Initialized with Order ID: " + orderId);
}
private void validateAndSetupUI() {
if (orderId == null || transactionId == null) {
Toast.makeText(this, "Missing transaction details!", Toast.LENGTH_LONG).show();
finish();
return;
}
String formattedAmount = formatCurrency(grossAmount != null ? grossAmount : String.valueOf(originalAmount));
amountTextView.setText(formattedAmount);
referenceTextView.setText("Reference ID: " + referenceId);
loadQrImage(currentQrImageUrl);
checkStatusButton.setEnabled(false);
statusTextView.setText("Waiting for payment...");
qrStatusTextView.setText("QR Code akan refresh dalam");
}
private void startMonitoring() {
startQrRefreshTimer();
startPaymentMonitoring();
pollPendingPaymentLog(orderId);
}
private void startQrRefreshTimer() {
countdownSeconds = 60;
isQrRefreshActive = true;
qrRefreshRunnable = new Runnable() {
@Override
public void run() {
if (!isQrRefreshActive) return;
if (countdownSeconds > 0) {
timerTextView.setText(String.valueOf(countdownSeconds));
countdownSeconds--;
qrRefreshHandler.postDelayed(this, 1000);
} else {
refreshQrCode();
}
}
};
qrRefreshHandler.post(qrRefreshRunnable);
}
private void refreshQrCode() {
if (!isQrRefreshActive) return;
timerTextView.setText("...");
qrStatusTextView.setText("Generating new QR Code...");
new Thread(() -> {
try {
String newQrUrl = generateNewQrCode();
runOnUiThread(() -> {
if (newQrUrl != null) {
currentQrImageUrl = newQrUrl;
loadQrImage(newQrUrl);
allOrderIds.add(orderId);
updateUIAfterRefresh();
countdownSeconds = 60;
qrStatusTextView.setText("QR Code akan refresh dalam");
Toast.makeText(this, "QR Code refreshed", Toast.LENGTH_SHORT).show();
} else {
qrStatusTextView.setText("Failed to refresh QR - trying again in 30s");
countdownSeconds = 30;
}
qrRefreshHandler.postDelayed(qrRefreshRunnable, 1000);
});
} catch (Exception e) {
Log.e("QrisResult", "QR refresh error: " + e.getMessage(), e);
runOnUiThread(() -> {
qrStatusTextView.setText("QR refresh error - retrying in 30s");
countdownSeconds = 30;
qrRefreshHandler.postDelayed(qrRefreshRunnable, 1000);
});
}
}).start();
}
private String generateNewQrCode() {
try {
String newOrderId = java.util.UUID.randomUUID().toString();
JSONObject customField = new JSONObject();
customField.put("refresh_of", orderId);
customField.put("refresh_time", getCurrentISOTime());
customField.put("original_reference", referenceId);
JSONObject payload = createQrisPayload(newOrderId, customField);
String response = sendMidtransRequest(payload);
if (response != null) {
JSONObject jsonResponse = new JSONObject(response);
if (jsonResponse.has("actions")) {
JSONArray actions = jsonResponse.getJSONArray("actions");
if (actions.length() > 0) {
String newQrUrl = actions.getJSONObject(0).getString("url");
// Update transaction info
this.transactionId = jsonResponse.optString("transaction_id", transactionId);
this.transactionTime = jsonResponse.optString("transaction_time", transactionTime);
this.orderId = newOrderId;
return newQrUrl;
}
}
}
} catch (Exception e) {
Log.e("QrisResult", "Generate QR error: " + e.getMessage(), e);
}
return null;
}
private JSONObject createQrisPayload(String orderIdParam, JSONObject customField) throws Exception {
JSONObject payload = new JSONObject();
payload.put("payment_type", "qris");
JSONObject transactionDetails = new JSONObject();
transactionDetails.put("order_id", orderIdParam);
transactionDetails.put("gross_amount", originalAmount);
payload.put("transaction_details", transactionDetails);
JSONObject customerDetails = new JSONObject();
customerDetails.put("first_name", "Test");
customerDetails.put("last_name", "Customer");
customerDetails.put("email", "test@example.com");
customerDetails.put("phone", "081234567890");
payload.put("customer_details", customerDetails);
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
item.put("id", "item1_refresh_" + System.currentTimeMillis());
item.put("price", originalAmount);
item.put("quantity", 1);
item.put("name", "QRIS Payment - Refreshed (Ref: " + referenceId + ")");
itemDetails.put(item);
payload.put("item_details", itemDetails);
payload.put("custom_field1", customField.toString());
return payload;
}
private String sendMidtransRequest(JSONObject payload) {
try {
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);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
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) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line.trim());
}
return response.toString();
}
} catch (Exception e) {
Log.e("QrisResult", "Midtrans request error: " + e.getMessage(), e);
}
return null;
}
private void startPaymentMonitoring() {
Handler paymentHandler = new Handler(Looper.getMainLooper());
Runnable paymentRunnable = new Runnable() {
@Override
public void run() {
checkAllOrderIdsStatus();
if (!isFinishing() && isQrRefreshActive) {
paymentHandler.postDelayed(this, 3000);
}
}
};
paymentHandler.post(paymentRunnable);
}
private void checkAllOrderIdsStatus() {
new Thread(() -> {
try {
for (String checkOrderId : allOrderIds) {
if (checkOrderId == null || checkOrderId.isEmpty()) continue;
if (checkPaymentStatus(checkOrderId)) {
runOnUiThread(() -> {
stopQrRefresh();
syncTransactionStatusToBackend("PAID");
showPaymentSuccess();
Toast.makeText(this, "Payment Successful! 🎉", Toast.LENGTH_LONG).show();
});
return;
}
}
} catch (Exception e) {
Log.e("QrisResult", "Payment status check error: " + e.getMessage(), e);
}
}).start();
}
private boolean checkPaymentStatus(String checkOrderId) {
try {
String urlStr = BACKEND_BASE + "/api-logs?request_body_search_strict=" +
java.net.URLEncoder.encode("{\"order_id\":\"" + checkOrderId + "\"}", "UTF-8");
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if (conn.getResponseCode() == 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) {
String transactionStatus = reqBody.optString("transaction_status");
String logOrderId = reqBody.optString("order_id");
if (checkOrderId.equals(logOrderId) &&
(transactionStatus.equals("settlement") ||
transactionStatus.equals("capture") ||
transactionStatus.equals("success"))) {
return true;
}
}
}
}
}
} catch (Exception e) {
Log.e("QrisResult", "Payment check error: " + e.getMessage(), e);
}
return false;
}
private void loadQrImage(String qrImageUrl) {
if (qrImageUrl != null && !qrImageUrl.isEmpty()) {
new DownloadImageTask(qrImageView).execute(qrImageUrl);
} else {
qrImageView.setVisibility(View.GONE);
downloadQrisButton.setEnabled(false);
}
}
private void setupClickListeners() {
downloadQrisButton.setOnClickListener(v -> downloadQrCode());
checkStatusButton.setOnClickListener(v -> {
stopQrRefresh();
simulateWebhook();
});
returnMainButton.setOnClickListener(v -> returnToMain());
}
private void stopQrRefresh() {
isQrRefreshActive = false;
if (qrRefreshHandler != null && qrRefreshRunnable != null) {
qrRefreshHandler.removeCallbacks(qrRefreshRunnable);
}
timerTextView.setVisibility(View.GONE);
qrStatusTextView.setVisibility(View.GONE);
}
private void showPaymentSuccess() {
stopQrRefresh();
qrImageView.setVisibility(View.GONE);
amountTextView.setVisibility(View.GONE);
referenceTextView.setVisibility(View.GONE);
downloadQrisButton.setVisibility(View.GONE);
checkStatusButton.setVisibility(View.GONE);
statusTextView.setText("✅ Payment Successful!\n\nTransaction ID: " + transactionId +
"\nReference: " + referenceId +
"\nAmount: " + formatCurrency(grossAmount));
returnMainButton.setVisibility(View.VISIBLE);
new Handler(Looper.getMainLooper()).postDelayed(this::launchReceiptActivity, 2000);
}
private void syncTransactionStatusToBackend(String status) {
new Thread(() -> {
try {
JSONObject updatePayload = new JSONObject();
updatePayload.put("status", status);
updatePayload.put("transaction_status", status);
updatePayload.put("updated_at", getCurrentISOTime());
updatePayload.put("settlement_time", getCurrentISOTime());
JSONObject body = new JSONObject();
body.put("reference_id", referenceId);
body.put("update_data", updatePayload);
String updateUrl = BACKEND_BASE + "/transactions/update-by-reference";
URL url = new URI(updateUrl).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = body.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d("QrisResult", "Backend sync response: " + responseCode);
} catch (Exception e) {
Log.e("QrisResult", "Backend sync error: " + e.getMessage(), e);
}
}).start();
}
private void simulateWebhook() {
progressBar.setVisibility(View.VISIBLE);
statusTextView.setText("Simulating payment...");
checkStatusButton.setEnabled(false);
stopQrRefresh();
new Thread(() -> {
try {
JSONObject payload = createWebhookPayload();
sendWebhookRequest(payload);
Thread.sleep(2000);
} catch (Exception e) {
Log.e("QrisResult", "Webhook simulation error: " + e.getMessage(), e);
}
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
showPaymentSuccess();
});
}).start();
}
private JSONObject createWebhookPayload() throws Exception {
String serverKey = getServerKey();
String signatureKey = generateSignature(orderId, "200", grossAmount, serverKey);
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", transactionTime != null ? transactionTime : getCurrentISOTime());
payload.put("transaction_status", "settlement");
payload.put("transaction_id", transactionId);
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", signatureKey);
payload.put("payment_type", "qris");
payload.put("order_id", orderId);
payload.put("gross_amount", grossAmount);
payload.put("reference_id", referenceId);
return payload;
}
private void sendWebhookRequest(JSONObject payload) {
try {
URL url = new URL(WEBHOOK_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(payload.toString().getBytes());
}
Log.d("QrisResult", "Webhook response: " + conn.getResponseCode());
} catch (Exception e) {
Log.e("QrisResult", "Webhook request error: " + e.getMessage(), e);
}
}
// Utility Methods
private String formatCurrency(String amount) {
try {
double amountDouble = Double.parseDouble(amount);
NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
return formatter.format(amountDouble);
} catch (NumberFormatException e) {
return "IDR " + amount;
}
}
private String getCurrentISOTime() {
return new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
.format(new java.util.Date());
}
private String getServerKey() {
try {
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
return new String(decoded).replace(":", "");
} catch (Exception e) {
return "";
}
}
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
String input = orderId + statusCode + grossAmount + serverKey;
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-512");
byte[] messageDigest = md.digest(input.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
return "dummy_signature";
}
}
// Navigation and Lifecycle
private void returnToMain() {
stopQrRefresh();
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finishAffinity();
}
private void launchReceiptActivity() {
Intent intent = new Intent(this, ReceiptActivity.class);
intent.putExtra("calling_activity", "QrisResultActivity");
intent.putExtra("transaction_id", transactionId);
intent.putExtra("reference_id", referenceId);
intent.putExtra("order_id", orderId);
intent.putExtra("transaction_amount", String.valueOf(originalAmount));
intent.putExtra("gross_amount", grossAmount != null ? grossAmount : String.valueOf(originalAmount));
intent.putExtra("created_at", getCurrentISOTime());
intent.putExtra("payment_method", "QRIS");
intent.putExtra("acquirer", acquirer != null ? acquirer : "qris");
intent.putExtra("mid", "71000026521");
intent.putExtra("tid", "73001500");
startActivity(intent);
}
private void downloadQrCode() {
try {
qrImageView.setDrawingCacheEnabled(true);
qrImageView.buildDrawingCache();
Bitmap bitmap = qrImageView.getDrawingCache();
if (bitmap != null) {
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
} else {
Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(this, "Error downloading QR code", Toast.LENGTH_LONG).show();
} finally {
qrImageView.setDrawingCacheEnabled(false);
}
}
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) {
Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(this, "Error saving QRIS", Toast.LENGTH_LONG).show();
}
}
private void updateUIAfterRefresh() {
String refreshTime = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date());
referenceTextView.setText("Reference ID: " + referenceId + " (Refreshed at " + refreshTime + ")");
}
private void pollPendingPaymentLog(String orderId) {
progressBar.setVisibility(View.VISIBLE);
statusTextView.setText("Checking payment status...");
new Thread(() -> {
int maxAttempts = 12;
boolean found = false;
for (int attempt = 0; attempt < maxAttempts && !found; attempt++) {
try {
found = checkPaymentStatus(orderId);
if (!found && attempt < maxAttempts - 1) {
Thread.sleep(2000);
}
} catch (Exception e) {
Log.e("QrisResult", "Polling error: " + e.getMessage());
}
}
final boolean logFound = found;
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
if (logFound) {
checkStatusButton.setEnabled(true);
statusTextView.setText("Ready to simulate payment");
Toast.makeText(this, "Payment log found!", Toast.LENGTH_SHORT).show();
} else {
statusTextView.setText("Payment log not found");
checkStatusButton.setEnabled(true);
}
});
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
stopQrRefresh();
}
@Override
public void onBackPressed() {
stopQrRefresh();
returnToMain();
super.onBackPressed();
}
// AsyncTask for downloading QR image
private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
private String errorMessage;
DownloadImageTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... urls) {
try {
URL url = new URI(urls[0]).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.connect();
if (connection.getResponseCode() == 200) {
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} else {
errorMessage = "Failed to download QR code";
}
} catch (Exception e) {
errorMessage = "Error downloading QR code: " + e.getMessage();
}
return null;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
imageView.setImageBitmap(result);
} else {
imageView.setImageResource(android.R.drawable.ic_menu_report_image);
if (errorMessage != null && imageView.getContext() != null) {
Toast.makeText(imageView.getContext(), errorMessage, Toast.LENGTH_LONG).show();
}
}
}
}
}

View File

@ -23,6 +23,10 @@ import java.net.HttpURLConnection;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.net.URI; import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import com.example.bdkipoc.cetakulang.ReprintActivity;
public class ReceiptActivity extends AppCompatActivity { public class ReceiptActivity extends AppCompatActivity {
@ -50,6 +54,28 @@ public class ReceiptActivity extends AppCompatActivity {
private LinearLayout emailButton; private LinearLayout emailButton;
private Button finishButton; private Button finishButton;
// ENHANCED: Mapping dari technical issuer ke display name
private static final Map<String, String> ISSUER_DISPLAY_MAP = new HashMap<String, String>() {{
put("airpay shopee", "ShopeePay");
put("shopeepay", "ShopeePay");
put("shopee", "ShopeePay");
put("linkaja", "LinkAja");
put("link aja", "LinkAja");
put("dana", "DANA");
put("ovo", "OVO");
put("gopay", "GoPay");
put("jenius", "Jenius");
put("sakuku", "Sakuku");
put("bni", "BNI");
put("bca", "BCA");
put("mandiri", "Mandiri");
put("bri", "BRI");
put("cimb", "CIMB Niaga");
put("permata", "Permata");
put("maybank", "Maybank");
put("qris", "QRIS");
}};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -116,10 +142,27 @@ public class ReceiptActivity extends AppCompatActivity {
finishButton.setOnClickListener(v -> handleFinish()); finishButton.setOnClickListener(v -> handleFinish());
} }
/**
* ENHANCED: Load transaction data with support for both EMV/Card and QRIS
*/
private void loadTransactionData() { private void loadTransactionData() {
Intent intent = getIntent(); Intent intent = getIntent();
if (intent != null) { if (intent != null) {
Log.d("ReceiptActivity", "=== LOADING TRANSACTION DATA ==="); Log.d("ReceiptActivity", "=== LOADING ENHANCED TRANSACTION DATA ===");
// DETECT TRANSACTION TYPE
String callingActivity = intent.getStringExtra("calling_activity");
String channelCategory = intent.getStringExtra("channel_category");
String channelCode = intent.getStringExtra("channel_code");
boolean isEmvTransaction = "EMV_CARD".equals(channelCategory) || "ResultTransactionActivity".equals(callingActivity);
boolean isQrisTransaction = "QRIS".equalsIgnoreCase(channelCode) || "QrisResultActivity".equals(callingActivity);
Log.d("ReceiptActivity", "🔍 TRANSACTION TYPE DETECTION:");
Log.d("ReceiptActivity", " Calling Activity: " + callingActivity);
Log.d("ReceiptActivity", " Channel Category: " + channelCategory);
Log.d("ReceiptActivity", " Channel Code: " + channelCode);
Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction);
Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction);
// Get all available data from intent // Get all available data from intent
String amount = intent.getStringExtra("transaction_amount"); String amount = intent.getStringExtra("transaction_amount");
@ -133,99 +176,154 @@ public class ReceiptActivity extends AppCompatActivity {
String createdAt = intent.getStringExtra("created_at"); String createdAt = intent.getStringExtra("created_at");
String paymentMethodStr = intent.getStringExtra("payment_method"); String paymentMethodStr = intent.getStringExtra("payment_method");
String cardTypeStr = intent.getStringExtra("card_type"); String cardTypeStr = intent.getStringExtra("card_type");
String channelCode = intent.getStringExtra("channel_code");
String channelCategory = intent.getStringExtra("channel_category");
String acquirer = intent.getStringExtra("acquirer"); String acquirer = intent.getStringExtra("acquirer");
String mid = intent.getStringExtra("mid"); String mid = intent.getStringExtra("mid");
String tid = intent.getStringExtra("tid"); String tid = intent.getStringExtra("tid");
// EMV SPECIFIC DATA
String emvCardholderName = intent.getStringExtra("emv_cardholder_name");
String emvAid = intent.getStringExtra("emv_aid");
String emvExpiry = intent.getStringExtra("emv_expiry");
String cardNumber = intent.getStringExtra("card_number");
String midtransResponse = intent.getStringExtra("midtrans_response");
boolean emvMode = intent.getBooleanExtra("emv_mode", false);
// QRIS SPECIFIC DATA
String qrString = intent.getStringExtra("qr_string");
// Log received data for debugging // Log received data for debugging
Log.d("ReceiptActivity", "🔍 RECEIVED DATA:"); Log.d("ReceiptActivity", "🔍 RECEIVED DATA:");
Log.d("ReceiptActivity", " amount: " + amount); Log.d("ReceiptActivity", " amount: " + amount);
Log.d("ReceiptActivity", " referenceId: " + referenceId); Log.d("ReceiptActivity", " referenceId: " + referenceId);
Log.d("ReceiptActivity", " orderId: " + orderId); Log.d("ReceiptActivity", " orderId: " + orderId);
Log.d("ReceiptActivity", " channelCode: " + channelCode);
Log.d("ReceiptActivity", " acquirer (from intent): " + acquirer); Log.d("ReceiptActivity", " acquirer (from intent): " + acquirer);
Log.d("ReceiptActivity", " createdAt: " + createdAt); Log.d("ReceiptActivity", " createdAt: " + createdAt);
Log.d("ReceiptActivity", " EMV Mode: " + emvMode);
Log.d("ReceiptActivity", " EMV Cardholder: " + emvCardholderName);
Log.d("ReceiptActivity", " Card Number: " + (cardNumber != null ? cardNumber : "N/A"));
Log.d("ReceiptActivity", " QR String Available: " + (qrString != null && !qrString.isEmpty()));
// 1. Set merchant data with defaults // 1. Set merchant data with defaults
merchantName.setText(merchantNameStr != null ? merchantNameStr : "Marcel Panjaitan"); merchantName.setText(merchantNameStr != null ? merchantNameStr : "TOKO KLONTONG PAK EKO");
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Jakarta, Indonesia"); merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Ciputat Baru, Tangsel");
// 2. Set MID and TID // 2. Set MID and TID
midText.setText(mid != null ? mid : "71000026521"); midText.setText("MID: " + (mid != null ? mid : "123456789901"));
tidText.setText(tid != null ? tid : "73001500"); tidText.setText("TID: " + (tid != null ? tid : "123456789901"));
// 3. Set transaction number // 3. Set transaction number
String displayTransactionNumber = null; String displayTransactionNumber = getDisplayTransactionNumber(referenceId, transactionId, orderId);
if (referenceId != null && !referenceId.isEmpty()) { transactionNumber.setText(displayTransactionNumber);
displayTransactionNumber = referenceId;
} else if (transactionId != null && !transactionId.isEmpty()) {
displayTransactionNumber = transactionId;
} else if (orderId != null && !orderId.isEmpty()) {
displayTransactionNumber = orderId;
}
transactionNumber.setText(displayTransactionNumber != null ? displayTransactionNumber : "N/A");
// 4. Set transaction date // 4. Set transaction date
String displayDate = null; String displayDate = getDisplayTransactionDate(createdAt, transactionDateStr, isEmvTransaction);
if (createdAt != null && !createdAt.isEmpty()) {
displayDate = formatDateFromCreatedAt(createdAt);
} else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
displayDate = transactionDateStr;
} else {
displayDate = getCurrentDateTime();
}
transactionDate.setText(displayDate); transactionDate.setText(displayDate);
// 5. Set payment method // 5. ENHANCED: Set payment method based on transaction type
String displayPaymentMethod = getPaymentMethodFromChannelCode(channelCode, paymentMethodStr); String displayPaymentMethod = getDisplayPaymentMethod(channelCode, paymentMethodStr, isEmvTransaction, emvMode);
paymentMethod.setText(displayPaymentMethod); paymentMethod.setText(displayPaymentMethod);
// 6. IMPROVED: Enhanced card type detection for QRIS // 6. ENHANCED: Set card type with EMV and QRIS priority
String displayCardType = null; String displayCardType = getDisplayCardType(cardTypeStr, acquirer, channelCode, isEmvTransaction,
isQrisTransaction, midtransResponse, referenceId);
if (channelCode != null && channelCode.equalsIgnoreCase("QRIS")) {
Log.d("ReceiptActivity", "🔍 QRIS transaction detected - searching for real acquirer");
// For QRIS, try to get real acquirer from webhook data
if (referenceId != null && !referenceId.isEmpty()) {
String realAcquirer = fetchRealAcquirerSync(referenceId);
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
displayCardType = getCardTypeFromAcquirer(realAcquirer, null, null);
Log.d("ReceiptActivity", "✅ QRIS real acquirer found: " + realAcquirer + " -> " + displayCardType);
} else {
Log.w("ReceiptActivity", "⚠️ QRIS real acquirer not found, using generic QRIS");
displayCardType = "QRIS";
// Start async search for better results
fetchRealAcquirerFromWebhook(referenceId);
}
} else {
displayCardType = "QRIS";
}
} else {
// Non-QRIS transaction
displayCardType = getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr);
}
cardType.setText(displayCardType); cardType.setText(displayCardType);
Log.d("ReceiptActivity", "💳 FINAL CARD TYPE: " + displayCardType);
// 7. Format and set amounts // 7. Format and set amounts with proper calculation
setAmountData(amount, grossAmount); setAmountDataEnhanced(amount, grossAmount, isEmvTransaction, isQrisTransaction);
Log.d("ReceiptActivity", "=== TRANSACTION DATA LOADED ==="); Log.d("ReceiptActivity", "💳 FINAL DISPLAY VALUES:");
Log.d("ReceiptActivity", " Payment Method: " + displayPaymentMethod);
Log.d("ReceiptActivity", " Card Type: " + displayCardType);
Log.d("ReceiptActivity", " Transaction Number: " + displayTransactionNumber);
Log.d("ReceiptActivity", "=== ENHANCED TRANSACTION DATA LOADED ===");
} }
} }
/**
* Get display transaction number with priority
*/
private String getDisplayTransactionNumber(String referenceId, String transactionId, String orderId) {
if (referenceId != null && !referenceId.isEmpty()) {
return referenceId;
} else if (transactionId != null && !transactionId.isEmpty()) {
// For long transaction IDs, show last 10 characters
return transactionId.length() > 10 ?
transactionId.substring(transactionId.length() - 10) : transactionId;
} else if (orderId != null && !orderId.isEmpty()) {
return orderId.length() > 10 ?
orderId.substring(orderId.length() - 10) : orderId;
}
return String.valueOf(System.currentTimeMillis() % 10000000000L);
}
/**
* Get display transaction date with proper formatting
*/
private String getDisplayTransactionDate(String createdAt, String transactionDateStr, boolean isEmvTransaction) {
String dateToFormat = null;
if (createdAt != null && !createdAt.isEmpty()) {
dateToFormat = createdAt;
} else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
dateToFormat = transactionDateStr;
}
if (dateToFormat != null) {
if (isEmvTransaction) {
return formatDateForEmvTransaction(dateToFormat);
} else {
return formatDateFromCreatedAt(dateToFormat);
}
}
return getCurrentDateTime();
}
/**
* Format date specifically for EMV transactions
*/
private String formatDateForEmvTransaction(String dateString) {
try {
// EMV transactions might use different date formats
String[] inputFormats = {
"dd MMMM yyyy HH:mm",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"dd/MM/yyyy HH:mm"
};
SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
for (String format : inputFormats) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat(format,
format.contains("MMMM") ? new Locale("id", "ID") : Locale.getDefault());
Date date = inputFormat.parse(dateString);
String formatted = outputFormat.format(date);
Log.d("ReceiptActivity", "EMV Date formatting: '" + dateString + "' -> '" + formatted + "'");
return formatted;
} catch (Exception ignored) {
// Try next format
}
}
// If all formats fail, return as-is
Log.w("ReceiptActivity", "Could not format EMV date: " + dateString);
return dateString;
} catch (Exception e) {
Log.e("ReceiptActivity", "Error formatting EMV date: " + dateString, e);
return dateString;
}
}
private String formatDateFromCreatedAt(String createdAt) { private String formatDateFromCreatedAt(String createdAt) {
try { try {
// Input format from database: "yyyy-MM-dd HH:mm:ss" // Input format from database: "yyyy-MM-dd HH:mm:ss"
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// Output format for receipt: "dd/MM/yyyy HH:mm" // Output format for receipt: "dd/MM/yyyy HH:mm"
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID")); SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
Date date = inputFormat.parse(createdAt); Date date = inputFormat.parse(createdAt);
String formatted = outputFormat.format(date); String formatted = outputFormat.format(date);
@ -240,6 +338,39 @@ public class ReceiptActivity extends AppCompatActivity {
} }
} }
/**
* Get display payment method with EMV support
*/
private String getDisplayPaymentMethod(String channelCode, String fallbackPaymentMethod,
boolean isEmvTransaction, boolean emvMode) {
if (isEmvTransaction) {
// For EMV transactions, be more specific
if (emvMode) {
if (channelCode != null) {
switch (channelCode.toUpperCase()) {
case "CREDIT": return "Kartu Kredit (EMV)";
case "DEBIT": return "Kartu Debit (EMV)";
default: return "Kartu Kredit (EMV)";
}
}
return "Kartu Kredit (EMV)";
} else {
// Magnetic stripe
if (channelCode != null) {
switch (channelCode.toUpperCase()) {
case "CREDIT": return "Kartu Kredit";
case "DEBIT": return "Kartu Debit";
default: return "Kartu Kredit";
}
}
return "Kartu Kredit";
}
} else {
// For QRIS and other transactions, use existing logic
return getPaymentMethodFromChannelCode(channelCode, fallbackPaymentMethod);
}
}
/** /**
* Get payment method name from channel_code with comprehensive mapping * Get payment method name from channel_code with comprehensive mapping
*/ */
@ -289,6 +420,78 @@ public class ReceiptActivity extends AppCompatActivity {
return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS"; return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS";
} }
/**
* ENHANCED: Card type detection with EMV and QRIS priority
*/
private String getDisplayCardType(String cardTypeStr, String acquirer, String channelCode,
boolean isEmvTransaction, boolean isQrisTransaction,
String midtransResponse, String referenceId) {
Log.d("ReceiptActivity", "🔍 ENHANCED CARD TYPE DETECTION:");
Log.d("ReceiptActivity", " Input Card Type: " + cardTypeStr);
Log.d("ReceiptActivity", " Input Acquirer: " + acquirer);
Log.d("ReceiptActivity", " Is EMV Transaction: " + isEmvTransaction);
Log.d("ReceiptActivity", " Is QRIS Transaction: " + isQrisTransaction);
if (isEmvTransaction) {
// FOR EMV TRANSACTIONS: Priority to cardTypeStr from ResultTransactionActivity
if (cardTypeStr != null && !cardTypeStr.isEmpty() && !cardTypeStr.equalsIgnoreCase("unknown")) {
Log.d("ReceiptActivity", "✅ EMV: Using provided card type: " + cardTypeStr);
return cardTypeStr;
}
// FALLBACK: Try to extract from Midtrans response
if (midtransResponse != null && !midtransResponse.isEmpty()) {
String bankFromMidtrans = extractBankFromMidtransResponse(midtransResponse);
if (bankFromMidtrans != null && !bankFromMidtrans.isEmpty()) {
Log.d("ReceiptActivity", "✅ EMV: Using bank from Midtrans response: " + bankFromMidtrans);
return bankFromMidtrans;
}
}
// FALLBACK: Use acquirer
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
String mappedAcquirer = getCardTypeFromAcquirer(acquirer, channelCode, null);
Log.d("ReceiptActivity", "✅ EMV: Using mapped acquirer: " + mappedAcquirer);
return mappedAcquirer;
}
Log.d("ReceiptActivity", "⚠️ EMV: Using default fallback: BCA");
return "BCA"; // Default for EMV
} else if (isQrisTransaction) {
// FOR QRIS TRANSACTIONS: Enhanced detection
Log.d("ReceiptActivity", "🔍 QRIS transaction detected - searching for real acquirer");
// Priority 1: Use provided cardTypeStr if it's not generic
if (cardTypeStr != null && !cardTypeStr.isEmpty() &&
!cardTypeStr.equalsIgnoreCase("qris") && !cardTypeStr.equalsIgnoreCase("unknown")) {
Log.d("ReceiptActivity", "✅ QRIS: Using provided specific card type: " + cardTypeStr);
return cardTypeStr;
}
// Priority 2: Search webhook logs for real acquirer
if (referenceId != null && !referenceId.isEmpty()) {
String realAcquirer = fetchRealAcquirerSync(referenceId);
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
String mappedQrisAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
Log.d("ReceiptActivity", "✅ QRIS real acquirer found: " + realAcquirer + " -> " + mappedQrisAcquirer);
return mappedQrisAcquirer;
} else {
Log.w("ReceiptActivity", "⚠️ QRIS real acquirer not found, using generic QRIS");
// Start async search for better results
fetchRealAcquirerFromWebhook(referenceId);
return "QRIS";
}
} else {
return "QRIS";
}
} else {
// FOR OTHER TRANSACTIONS: Use standard logic
return getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr);
}
}
private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) { private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) {
// STEP 1: If we have a valid acquirer that's not generic "qris", use it // STEP 1: If we have a valid acquirer that's not generic "qris", use it
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) { if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
@ -297,88 +500,14 @@ public class ReceiptActivity extends AppCompatActivity {
Log.d("ReceiptActivity", "🔍 Mapping acquirer: '" + acquirer + "' -> '" + acq + "'"); Log.d("ReceiptActivity", "🔍 Mapping acquirer: '" + acquirer + "' -> '" + acq + "'");
// COMPREHENSIVE acquirer mapping (case-insensitive) // COMPREHENSIVE acquirer mapping (case-insensitive)
String mappedName = ISSUER_DISPLAY_MAP.get(acq);
if (mappedName != null) {
Log.d("ReceiptActivity", "✅ Mapped acquirer: " + acquirer + " -> " + mappedName);
return mappedName;
}
// Additional mapping for variations not in the map
switch (acq) { switch (acq) {
// E-Wallet acquirers (most common for QRIS)
case "gopay":
case "go-pay":
case "gojek": return "GoPay";
case "shopeepay":
case "shopee_pay":
case "shopee": return "ShopeePay";
case "ovo": return "OVO";
case "dana": return "DANA";
case "linkaja":
case "link_aja":
case "tcash": return "LinkAja";
case "jenius":
case "btpn": return "Jenius";
case "kaspro":
case "kas_pro": return "KasPro";
case "sakuku":
case "saku_ku": return "SakuKu";
case "doku":
case "doku_wallet": return "DOKU";
case "paymi":
case "pay_mi": return "PayMi";
case "isaku":
case "i_saku": return "i.Saku";
// Bank acquirers
case "bca":
case "bank_bca": return "BCA";
case "mandiri":
case "bank_mandiri":
case "mandiri_bill": return "Mandiri";
case "bni":
case "bank_bni":
case "bni_va": return "BNI";
case "bri":
case "bank_bri":
case "bri_va": return "BRI";
case "permata":
case "bank_permata":
case "permata_va": return "Permata";
case "cimb":
case "cimb_niaga":
case "bank_cimb":
case "cimb_va": return "CIMB Niaga";
case "danamon":
case "bank_danamon":
case "danamon_va": return "Danamon";
case "bsi":
case "bank_bsi":
case "bsi_va":
case "syariah_indonesia": return "BSI";
case "maybank":
case "bank_maybank": return "Maybank";
case "bca_digital":
case "blu": return "BCA Digital";
case "jago":
case "bank_jago": return "Bank Jago";
case "seabank":
case "sea_bank": return "SeaBank";
// Credit card acquirers // Credit card acquirers
case "visa": return "Visa"; case "visa": return "Visa";
case "mastercard": case "mastercard":
@ -738,6 +867,78 @@ public class ReceiptActivity extends AppCompatActivity {
return null; // No acquirer found for specified criteria return null; // No acquirer found for specified criteria
} }
/**
* Extract bank from Midtrans response JSON string
*/
private String extractBankFromMidtransResponse(String midtransResponse) {
try {
org.json.JSONObject response = new org.json.JSONObject(midtransResponse);
// Try different possible bank fields
String[] bankFields = {"bank", "issuer", "acquiring_bank", "issuer_bank"};
for (String field : bankFields) {
if (response.has(field)) {
String bankValue = response.getString(field);
if (bankValue != null && !bankValue.trim().isEmpty() && !bankValue.equalsIgnoreCase("qris")) {
Log.d("ReceiptActivity", "Found bank in Midtrans response (" + field + "): " + bankValue);
return formatBankNameForReceipt(bankValue);
}
}
}
Log.w("ReceiptActivity", "No valid bank found in Midtrans response");
return null;
} catch (Exception e) {
Log.e("ReceiptActivity", "Error extracting bank from Midtrans response: " + e.getMessage());
return null;
}
}
/**
* Format bank name specifically for receipt display
*/
private String formatBankNameForReceipt(String bankName) {
if (bankName == null || bankName.trim().isEmpty()) {
return "BCA"; // Default
}
String formatted = bankName.trim();
// Common bank name mappings for receipt display
switch (formatted.toUpperCase()) {
case "BCA":
case "BANK BCA":
case "BANK CENTRAL ASIA": return "BCA";
case "MANDIRI":
case "BANK MANDIRI": return "Mandiri";
case "BNI":
case "BANK BNI":
case "BANK NEGARA INDONESIA": return "BNI";
case "BRI":
case "BANK BRI":
case "BANK RAKYAT INDONESIA": return "BRI";
case "CIMB":
case "CIMB NIAGA":
case "BANK CIMB NIAGA": return "CIMB Niaga";
case "DANAMON":
case "BANK DANAMON": return "Danamon";
case "PERMATA":
case "BANK PERMATA": return "Permata";
default:
// Return capitalized version
return capitalizeFirstLetter(formatted);
}
}
private String capitalizeFirstLetter(String input) { private String capitalizeFirstLetter(String input) {
if (input == null || input.isEmpty()) { if (input == null || input.isEmpty()) {
return input; return input;
@ -756,51 +957,79 @@ public class ReceiptActivity extends AppCompatActivity {
return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase(); return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase();
} }
private void setAmountData(String amount, String grossAmount) { /**
// Prioritize 'amount' over 'grossAmount' for transaction data * ENHANCED: Set amount data with EMV and QRIS support
*/
private void setAmountDataEnhanced(String amount, String grossAmount, boolean isEmvTransaction, boolean isQrisTransaction) {
String amountToUse = amount != null ? amount : grossAmount; String amountToUse = amount != null ? amount : grossAmount;
Log.d("ReceiptActivity", "Setting amount data - amount: " + amount + Log.d("ReceiptActivity", "Setting enhanced amount data - amount: " + amount +
", grossAmount: " + grossAmount + ", using: " + amountToUse); ", grossAmount: " + grossAmount + ", using: " + amountToUse +
", isEMV: " + isEmvTransaction + ", isQRIS: " + isQrisTransaction);
if (amountToUse != null) { if (amountToUse != null) {
try { try {
// Clean and parse the amount
String cleanAmount = cleanAmountString(amountToUse); String cleanAmount = cleanAmountString(amountToUse);
Log.d("ReceiptActivity", "Cleaned amount: " + cleanAmount); Log.d("ReceiptActivity", "Cleaned amount: " + cleanAmount);
// Parse as long integer (Indonesian Rupiah doesn't use decimal cents)
long amountLong = Long.parseLong(cleanAmount); long amountLong = Long.parseLong(cleanAmount);
// Set transaction total // Set transaction total
transactionTotal.setText("Rp " + formatCurrency(amountLong)); transactionTotal.setText(formatCurrency(amountLong));
// Calculate tax and service fee (for QRIS, typically no additional fees) // CALCULATE FEES BASED ON TRANSACTION TYPE
long tax = 0; // QRIS usually doesn't have tax long tax = 0;
long serviceFeeValue = 0; // QRIS usually doesn't have service fee long serviceFeeValue = 0;
long total = amountLong + tax + serviceFeeValue; long total = amountLong;
if (isEmvTransaction) {
// For EMV transactions, check if gross amount includes additional fees
if (grossAmount != null && !grossAmount.equals(amount)) {
try {
long grossAmountLong = Long.parseLong(cleanAmountString(grossAmount));
long difference = grossAmountLong - amountLong;
if (difference > 0) {
// Assume 11% tax and 500 service fee (adjust based on your business logic)
tax = Math.round(amountLong * 0.11);
serviceFeeValue = 500;
total = grossAmountLong;
Log.d("ReceiptActivity", "EMV: Calculated tax=" + tax + ", service=" + serviceFeeValue + ", total=" + total);
}
} catch (Exception e) {
Log.w("ReceiptActivity", "Could not parse gross amount for EMV: " + grossAmount);
}
}
} else if (isQrisTransaction) {
// For QRIS, typically no additional fees (tax=0, service=0)
tax = 0;
serviceFeeValue = 0;
total = amountLong;
Log.d("ReceiptActivity", "QRIS: No additional fees - total=" + total);
}
// Set calculated values // Set calculated values
taxPercentage.setText("Rp 0"); taxPercentage.setText(tax > 0 ? "11%" : "0%");
serviceFee.setText("Rp 0"); serviceFee.setText(formatCurrency(serviceFeeValue));
finalTotal.setText("Rp " + formatCurrency(total)); finalTotal.setText(formatCurrency(total));
Log.d("ReceiptActivity", "Amount formatting successful: " + amountLong + " -> Rp " + formatCurrency(total)); Log.d("ReceiptActivity", "Enhanced amount formatting successful: " + amountLong + " -> " + formatCurrency(total));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e("ReceiptActivity", "Error parsing amount: " + amountToUse, e); Log.e("ReceiptActivity", "Error parsing enhanced amount: " + amountToUse, e);
// Fallback if parsing fails // Fallback if parsing fails
transactionTotal.setText("Rp " + amountToUse); transactionTotal.setText(amountToUse);
taxPercentage.setText("Rp 0"); taxPercentage.setText("0%");
serviceFee.setText("Rp 0"); serviceFee.setText("0");
finalTotal.setText("Rp " + amountToUse); finalTotal.setText(amountToUse);
} }
} else { } else {
// Default values if no amount provided // Default values if no amount provided
transactionTotal.setText("Rp 0"); transactionTotal.setText("0");
taxPercentage.setText("Rp 0"); taxPercentage.setText("0%");
serviceFee.setText("Rp 0"); serviceFee.setText("0");
finalTotal.setText("Rp 0"); finalTotal.setText("0");
} }
} }
@ -874,7 +1103,7 @@ public class ReceiptActivity extends AppCompatActivity {
} }
private String getCurrentDateTime() { private String getCurrentDateTime() {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID")); SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
return sdf.format(new Date()); return sdf.format(new Date());
} }
@ -902,9 +1131,9 @@ public class ReceiptActivity extends AppCompatActivity {
if (callingActivity != null) { if (callingActivity != null) {
switch (callingActivity) { switch (callingActivity) {
case "TransactionActivity": case "ReprintActivity":
// Go back to transaction list // Go back to transaction list
Intent transactionIntent = new Intent(this, TransactionActivity.class); Intent transactionIntent = new Intent(this, ReprintActivity.class);
transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(transactionIntent); startActivity(transactionIntent);
break; break;
@ -914,6 +1143,11 @@ public class ReceiptActivity extends AppCompatActivity {
navigateToHomePage(); navigateToHomePage();
break; break;
case "ResultTransactionActivity":
// NEW: Handle back from ResultTransactionActivity
navigateToHomePage();
break;
case "PaymentActivity": case "PaymentActivity":
case "QrisActivity": case "QrisActivity":
// Go back to payment/qris activity // Go back to payment/qris activity

View File

@ -1,5 +1,6 @@
package com.example.bdkipoc; package com.example.bdkipoc.cetakulang;
import com.example.bdkipoc.R;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -50,9 +51,12 @@ import java.util.TimeZone;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
import android.widget.DatePicker; import android.widget.DatePicker;
public class TransactionActivity extends AppCompatActivity implements TransactionAdapter.OnPrintClickListener { import com.example.bdkipoc.ReceiptActivity;
import com.example.bdkipoc.StyleHelper;
public class ReprintActivity extends AppCompatActivity implements ReprintAdapterActivity.OnPrintClickListener {
private RecyclerView recyclerView; private RecyclerView recyclerView;
private TransactionAdapter adapter; private ReprintAdapterActivity adapter;
private List<Transaction> transactionList; private List<Transaction> transactionList;
private List<Transaction> filteredList; private List<Transaction> filteredList;
private ProgressBar progressBar; private ProgressBar progressBar;
@ -89,7 +93,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transaction); setContentView(R.layout.activity_reprint);
// Initialize SharedPreferences for local tracking // Initialize SharedPreferences for local tracking
prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE); prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE);
@ -159,7 +163,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
transactionList = new ArrayList<>(); transactionList = new ArrayList<>();
filteredList = new ArrayList<>(); filteredList = new ArrayList<>();
adapter = new TransactionAdapter(filteredList); adapter = new ReprintAdapterActivity(filteredList);
adapter.setPrintClickListener(this); adapter.setPrintClickListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(this); LinearLayoutManager layoutManager = new LinearLayoutManager(this);
@ -342,13 +346,13 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
filterButtonText.setTextColor(getResources().getColor(android.R.color.holo_blue_dark)); filterButtonText.setTextColor(getResources().getColor(android.R.color.holo_blue_dark));
filterButtonText.setTextSize(12); // Smaller text when filter is active filterButtonText.setTextSize(12); // Smaller text when filter is active
Log.d("TransactionActivity", "🎨 Filter button updated: " + displayText); Log.d("ReprintActivity", "🎨 Filter button updated: " + displayText);
} }
} }
// NEW METHOD: Apply date filter // NEW METHOD: Apply date filter
private void applyDateFilter() { private void applyDateFilter() {
Log.d("TransactionActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate); Log.d("ReprintActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate);
// Reset to first page and reload data // Reset to first page and reload data
currentPage = 1; currentPage = 1;
@ -366,7 +370,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
filterButtonText.setTextSize(14); // Reset to normal size filterButtonText.setTextSize(14); // Reset to normal size
} }
Log.d("TransactionActivity", "🗓️ Date filter cleared"); Log.d("ReprintActivity", "🗓️ Date filter cleared");
// Reload data without date filter // Reload data without date filter
currentPage = 1; currentPage = 1;
@ -377,7 +381,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
return; return;
} }
Log.d("TransactionActivity", "🔄 Navigating to page " + page); Log.d("ReprintActivity", "🔄 Navigating to page " + page);
if (currentSearchQuery.isEmpty()) { if (currentSearchQuery.isEmpty()) {
// Load from API // Load from API
@ -410,7 +414,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
// Scroll to top // Scroll to top
recyclerView.scrollToPosition(0); recyclerView.scrollToPosition(0);
Log.d("TransactionActivity", "📄 Displaying search results page " + currentPage + Log.d("ReprintActivity", "📄 Displaying search results page " + currentPage +
" (items " + (startIndex + 1) + "-" + endIndex + " of " + filteredList.size() + ")"); " (items " + (startIndex + 1) + "-" + endIndex + " of " + filteredList.size() + ")");
} }
@ -452,10 +456,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage); totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
// PASTIKAN TIDAK PERLU SORT LAGI karena sudah sorted dari API response // PASTIKAN TIDAK PERLU SORT LAGI karena sudah sorted dari API response
Log.d("TransactionActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):"); Log.d("ReprintActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):");
for (int i = 0; i < Math.min(5, filteredList.size()); i++) { for (int i = 0; i < Math.min(5, filteredList.size()); i++) {
Transaction tx = filteredList.get(i); Transaction tx = filteredList.get(i);
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId); Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
} }
} else { } else {
// SEARCH MODE: Filter all available data // SEARCH MODE: Filter all available data
@ -518,7 +522,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
paginationControls.setVisibility(View.GONE); paginationControls.setVisibility(View.GONE);
} }
Log.d("TransactionActivity", "📊 Pagination updated: " + Log.d("ReprintActivity", "📊 Pagination updated: " +
"Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords); "Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords);
} }
@ -584,7 +588,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
pageNumbersContainer.addView(pageButton); pageNumbersContainer.addView(pageButton);
} }
Log.d("TransactionActivity", "🔢 Page buttons created: " + startPage + " to " + endPage + Log.d("ReprintActivity", "🔢 Page buttons created: " + startPage + " to " + endPage +
" with size: " + buttonSize + "px"); " with size: " + buttonSize + "px");
} }
@ -613,7 +617,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage + String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage +
"&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=" + fromDate + "&to_date=" + toDate + "&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at"; "&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=" + fromDate + "&to_date=" + toDate + "&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at";
Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad + Log.d("ReprintActivity", "🔍 Fetching transactions page " + pageToLoad +
" (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at" + " (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at" +
" - Date Filter: " + fromDate + " to " + toDate); " - Date Filter: " + fromDate + " to " + toDate);
@ -642,7 +646,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
apiTotal = results.getInt("total"); apiTotal = results.getInt("total");
JSONArray data = results.getJSONArray("data"); JSONArray data = results.getJSONArray("data");
Log.d("TransactionActivity", "📊 API response: " + data.length() + Log.d("ReprintActivity", "📊 API response: " + data.length() +
" records, total: " + apiTotal); " records, total: " + apiTotal);
// STEP 1: Parse all transactions from API // STEP 1: Parse all transactions from API
@ -667,14 +671,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
// STEP 2: Apply intelligent deduplication // STEP 2: Apply intelligent deduplication
result = applyAdvancedDeduplication(rawTransactions); result = applyAdvancedDeduplication(rawTransactions);
Log.d("TransactionActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions"); Log.d("ReprintActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
} else { } else {
Log.e("TransactionActivity", "❌ HTTP Error: " + responseCode); Log.e("ReprintActivity", "❌ HTTP Error: " + responseCode);
error = true; error = true;
} }
} catch (IOException | JSONException | URISyntaxException e) { } catch (IOException | JSONException | URISyntaxException e) {
Log.e("TransactionActivity", "❌ Exception: " + e.getMessage(), e); Log.e("ReprintActivity", "❌ Exception: " + e.getMessage(), e);
error = true; error = true;
} }
return result; return result;
@ -687,7 +691,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
if (error) { if (error) {
Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show(); Toast.makeText(ReprintActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
updatePaginationDisplay(); updatePaginationDisplay();
return; return;
} }
@ -705,11 +709,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
if (date1 != null && date2 != null) { if (date1 != null && date2 != null) {
int comparison = date2.compareTo(date1); // Newest first int comparison = date2.compareTo(date1); // Newest first
Log.d("TransactionActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison); Log.d("ReprintActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison);
return comparison; return comparison;
} }
} catch (Exception e) { } catch (Exception e) {
Log.w("TransactionActivity", "Date comparison error: " + e.getMessage()); Log.w("ReprintActivity", "Date comparison error: " + e.getMessage());
} }
return Integer.compare(t2.id, t1.id); // Fallback by ID (higher ID = newer) return Integer.compare(t2.id, t1.id); // Fallback by ID (higher ID = newer)
}); });
@ -718,14 +722,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
transactionList.clear(); transactionList.clear();
transactionList.addAll(transactions); transactionList.addAll(transactions);
Log.d("TransactionActivity", "📋 Page " + currentPage + " loaded and sorted: " + Log.d("ReprintActivity", "📋 Page " + currentPage + " loaded and sorted: " +
transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages"); transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages");
// VERIFIKASI SORTING ORDER // VERIFIKASI SORTING ORDER
Log.d("TransactionActivity", "📋 SORTED ORDER VERIFICATION:"); Log.d("ReprintActivity", "📋 SORTED ORDER VERIFICATION:");
for (int i = 0; i < Math.min(5, transactionList.size()); i++) { for (int i = 0; i < Math.min(5, transactionList.size()); i++) {
Transaction tx = transactionList.get(i); Transaction tx = transactionList.get(i);
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId); Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
} }
// Update filtered list based on current search // Update filtered list based on current search
@ -761,7 +765,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault()); SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // Handle timezone properly sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // Handle timezone properly
Date parsed = sdf.parse(rawDate); Date parsed = sdf.parse(rawDate);
Log.d("TransactionActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format); Log.d("ReprintActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format);
return parsed; return parsed;
} catch (Exception e) { } catch (Exception e) {
// Continue to next format // Continue to next format
@ -779,10 +783,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
Date parsed = sdf.parse(cleanedDate); Date parsed = sdf.parse(cleanedDate);
Log.d("TransactionActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed); Log.d("ReprintActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed);
return parsed; return parsed;
} catch (Exception e) { } catch (Exception e) {
Log.w("TransactionActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage()); Log.w("ReprintActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage());
return null; return null;
} }
} }
@ -791,11 +795,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
* ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies * ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
*/ */
private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) { private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
Log.d("TransactionActivity", "🧠 Starting advanced deduplication..."); Log.d("ReprintActivity", "🧠 Starting advanced deduplication...");
Log.d("TransactionActivity", "📥 Input transactions order (first 5):"); Log.d("ReprintActivity", "📥 Input transactions order (first 5):");
for (int i = 0; i < Math.min(5, rawTransactions.size()); i++) { for (int i = 0; i < Math.min(5, rawTransactions.size()); i++) {
Transaction tx = rawTransactions.get(i); Transaction tx = rawTransactions.get(i);
Log.d("TransactionActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId); Log.d("ReprintActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId);
} }
// Strategy 1: Group by reference_id // Strategy 1: Group by reference_id
@ -823,7 +827,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
if (group.size() == 1) { if (group.size() == 1) {
// No duplicates for this reference // No duplicates for this reference
deduplicatedList.add(group.get(0)); deduplicatedList.add(group.get(0));
Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId); Log.d("ReprintActivity", "✅ Unique transaction: " + referenceId);
} else { } else {
// Multiple transactions with same reference_id - sort group by date first // Multiple transactions with same reference_id - sort group by date first
Collections.sort(group, (t1, t2) -> { Collections.sort(group, (t1, t2) -> {
@ -843,15 +847,15 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
deduplicatedList.add(bestTransaction); deduplicatedList.add(bestTransaction);
duplicatesRemoved += (group.size() - 1); duplicatesRemoved += (group.size() - 1);
Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId + Log.d("ReprintActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId +
" (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ", date: " + bestTransaction.createdAt + ")"); " (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ", date: " + bestTransaction.createdAt + ")");
} }
} }
Log.d("TransactionActivity", "✅ Advanced deduplication complete:"); Log.d("ReprintActivity", "✅ Advanced deduplication complete:");
Log.d("TransactionActivity", " 📥 Input: " + rawTransactions.size() + " transactions"); Log.d("ReprintActivity", " 📥 Input: " + rawTransactions.size() + " transactions");
Log.d("TransactionActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions"); Log.d("ReprintActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions");
Log.d("TransactionActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates"); Log.d("ReprintActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates");
return deduplicatedList; return deduplicatedList;
} }
@ -860,7 +864,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
* ENHANCED SELECTION: Advanced algorithm to pick the best transaction * ENHANCED SELECTION: Advanced algorithm to pick the best transaction
*/ */
private Transaction selectBestTransactionAdvanced(List<Transaction> duplicates, String referenceId) { private Transaction selectBestTransactionAdvanced(List<Transaction> duplicates, String referenceId) {
Log.d("TransactionActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId); Log.d("ReprintActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId);
Transaction bestTransaction = duplicates.get(0); Transaction bestTransaction = duplicates.get(0);
int bestPriority = getStatusPriority(bestTransaction.status); int bestPriority = getStatusPriority(bestTransaction.status);
@ -871,7 +875,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
int currentPriority = getStatusPriority(tx.status); int currentPriority = getStatusPriority(tx.status);
Date currentDate = parseCreatedAtDate(tx.createdAt); Date currentDate = parseCreatedAtDate(tx.createdAt);
Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id + Log.d("ReprintActivity", " 📊 Candidate: ID=" + tx.id +
", Status=" + tx.status + " (priority=" + currentPriority + ")" + ", Status=" + tx.status + " (priority=" + currentPriority + ")" +
", Created=" + tx.createdAt); ", Created=" + tx.createdAt);
@ -903,11 +907,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
bestTransaction = tx; bestTransaction = tx;
bestPriority = currentPriority; bestPriority = currentPriority;
bestDate = currentDate; bestDate = currentDate;
Log.d("TransactionActivity", " ⭐ NEW BEST selected: " + reason); Log.d("ReprintActivity", " ⭐ NEW BEST selected: " + reason);
} }
} }
Log.d("TransactionActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id + Log.d("ReprintActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id +
", Status=" + bestTransaction.status + ", Created=" + bestTransaction.createdAt); ", Status=" + bestTransaction.status + ", Created=" + bestTransaction.createdAt);
return bestTransaction; return bestTransaction;
@ -925,7 +929,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
return date1.after(date2); return date1.after(date2);
} }
} catch (Exception e) { } catch (Exception e) {
Log.w("TransactionActivity", "Date comparison error, falling back to ID comparison"); Log.w("ReprintActivity", "Date comparison error, falling back to ID comparison");
} }
// Fallback: higher ID usually means newer // Fallback: higher ID usually means newer
@ -990,7 +994,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
// Tier 5: Unknown status // Tier 5: Unknown status
default: default:
Log.w("TransactionActivity", "Unknown status encountered: " + status); Log.w("ReprintActivity", "Unknown status encountered: " + status);
return 1; return 1;
} }
} }
@ -1002,12 +1006,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
Intent intent = new Intent(this, ReceiptActivity.class); Intent intent = new Intent(this, ReceiptActivity.class);
// Add calling activity information for proper back navigation // Add calling activity information for proper back navigation
intent.putExtra("calling_activity", "TransactionActivity"); intent.putExtra("calling_activity", "ReprintActivity");
// Extract and send raw amount properly // Extract and send raw amount properly
String rawAmount = extractRawAmount(transaction.amount); String rawAmount = extractRawAmount(transaction.amount);
Log.d("TransactionActivity", "Opening receipt for transaction: " + transaction.referenceId + Log.d("ReprintActivity", "Opening receipt for transaction: " + transaction.referenceId +
", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'"); ", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'");
// Send transaction data to ReceiptActivity // Send transaction data to ReceiptActivity
@ -1032,7 +1036,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode); String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
intent.putExtra("acquirer", acquirer); // Jenis Kartu intent.putExtra("acquirer", acquirer); // Jenis Kartu
Log.d("TransactionActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode); Log.d("ReprintActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
startActivity(intent); startActivity(intent);
} }
@ -1084,7 +1088,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
// For QRIS, we could implement real-time acquirer lookup here // For QRIS, we could implement real-time acquirer lookup here
// For now, return "qris" and let ReceiptActivity handle the detection // For now, return "qris" and let ReceiptActivity handle the detection
Log.d("TransactionActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity"); Log.d("ReprintActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity");
return "qris"; return "qris";
} }
@ -1128,7 +1132,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
Long.parseLong(cleaned); Long.parseLong(cleaned);
return cleaned; return cleaned;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e("TransactionActivity", "Invalid amount: " + formattedAmount); Log.e("ReprintActivity", "Invalid amount: " + formattedAmount);
return "0"; return "0";
} }
} }

View File

@ -1,5 +1,6 @@
package com.example.bdkipoc; package com.example.bdkipoc.cetakulang;
import com.example.bdkipoc.R;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -26,15 +27,17 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> { import com.example.bdkipoc.StyleHelper;
private List<TransactionActivity.Transaction> transactionList;
public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterActivity.TransactionViewHolder> {
private List<ReprintActivity.Transaction> transactionList;
private OnPrintClickListener printClickListener; private OnPrintClickListener printClickListener;
public interface OnPrintClickListener { public interface OnPrintClickListener {
void onPrintClick(TransactionActivity.Transaction transaction); void onPrintClick(ReprintActivity.Transaction transaction);
} }
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) { public ReprintAdapterActivity(List<ReprintActivity.Transaction> transactionList) {
this.transactionList = transactionList; this.transactionList = transactionList;
} }
@ -45,23 +48,23 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
/** /**
* Update data without numbering (removed as per request) * Update data without numbering (removed as per request)
*/ */
public void updateData(List<TransactionActivity.Transaction> newData, int startIndex) { public void updateData(List<ReprintActivity.Transaction> newData, int startIndex) {
this.transactionList = newData; this.transactionList = newData;
notifyDataSetChanged(); notifyDataSetChanged();
Log.d("TransactionAdapter", "📋 Data updated: " + newData.size() + " items"); Log.d("ReprintAdapterActivity", "📋 Data updated: " + newData.size() + " items");
} }
@NonNull @NonNull
@Override @Override
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reprint, parent, false);
return new TransactionViewHolder(view); return new TransactionViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) { public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
TransactionActivity.Transaction t = transactionList.get(position); ReprintActivity.Transaction t = transactionList.get(position);
// STRIPE TABLE: Set alternating row colors // STRIPE TABLE: Set alternating row colors
LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer); LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer);
@ -73,10 +76,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.background_light)); itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.background_light));
} }
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":"); Log.d("ReprintAdapterActivity", "📋 Binding transaction " + position + ":");
Log.d("TransactionAdapter", " Reference: " + t.referenceId); Log.d("ReprintAdapterActivity", " Reference: " + t.referenceId);
Log.d("TransactionAdapter", " Status: " + t.status); Log.d("ReprintAdapterActivity", " Status: " + t.status);
Log.d("TransactionAdapter", " Amount: " + t.amount); Log.d("ReprintAdapterActivity", " Amount: " + t.amount);
// Set reference ID // Set reference ID
holder.referenceId.setText(t.referenceId); holder.referenceId.setText(t.referenceId);
@ -88,10 +91,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
String formattedAmount = formatRupiah(amountValue); String formattedAmount = formatRupiah(amountValue);
holder.amount.setText(formattedAmount); holder.amount.setText(formattedAmount);
Log.d("TransactionAdapter", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'"); Log.d("ReprintAdapterActivity", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e("TransactionAdapter", "❌ Amount format error: " + t.amount, e); Log.e("ReprintAdapterActivity", "❌ Amount format error: " + t.amount, e);
String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount; String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount;
holder.amount.setText(fallback); holder.amount.setText(fallback);
} }
@ -99,7 +102,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
// ENHANCED STATUS HANDLING dengan comprehensive checking // ENHANCED STATUS HANDLING dengan comprehensive checking
String displayStatus = t.status; String displayStatus = t.status;
Log.d("TransactionAdapter", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")"); Log.d("ReprintAdapterActivity", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")");
// Jika status adalah INIT atau PENDING, lakukan comprehensive check // Jika status adalah INIT atau PENDING, lakukan comprehensive check
if ("INIT".equalsIgnoreCase(t.status) || "PENDING".equalsIgnoreCase(t.status)) { if ("INIT".equalsIgnoreCase(t.status) || "PENDING".equalsIgnoreCase(t.status)) {
@ -108,7 +111,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
holder.status.setText("CHECKING..."); holder.status.setText("CHECKING...");
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING"); StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING");
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId); Log.d("ReprintAdapterActivity", "🔄 Starting comprehensive check for: " + t.referenceId);
// Check real status dari semua kemungkinan sources // Check real status dari semua kemungkinan sources
checkMidtransStatus(t.referenceId, holder.status); checkMidtransStatus(t.referenceId, holder.status);
@ -116,13 +119,13 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
// No reference ID to check // No reference ID to check
holder.status.setText(displayStatus.toUpperCase()); holder.status.setText(displayStatus.toUpperCase());
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus); StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
Log.w("TransactionAdapter", "⚠️ No reference ID for status check"); Log.w("ReprintAdapterActivity", "⚠️ No reference ID for status check");
} }
} else { } else {
// Use existing status yang sudah confirmed // Use existing status yang sudah confirmed
holder.status.setText(displayStatus.toUpperCase()); holder.status.setText(displayStatus.toUpperCase());
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus); StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
Log.d("TransactionAdapter", "✅ Using confirmed status: " + displayStatus); Log.d("ReprintAdapterActivity", "✅ Using confirmed status: " + displayStatus);
} }
// Set payment method // Set payment method
@ -133,7 +136,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
String formattedDate = formatCreatedAtDate(t.createdAt); String formattedDate = formatCreatedAtDate(t.createdAt);
holder.createdAt.setText(formattedDate); holder.createdAt.setText(formattedDate);
Log.d("TransactionAdapter", "📅 Created at: " + t.createdAt + " -> " + formattedDate); Log.d("ReprintAdapterActivity", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
// Set click listeners // Set click listeners
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
@ -148,7 +151,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} }
}); });
Log.d("TransactionAdapter", "✅ Transaction binding complete for: " + t.referenceId); Log.d("ReprintAdapterActivity", "✅ Transaction binding complete for: " + t.referenceId);
} }
private String cleanAmountString(String amount) { private String cleanAmountString(String amount) {
@ -156,7 +159,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
return "0"; return "0";
} }
Log.d("TransactionAdapter", "Cleaning amount: '" + amount + "'"); Log.d("ReprintAdapterActivity", "Cleaning amount: '" + amount + "'");
// Remove currency symbols and spaces // Remove currency symbols and spaces
String cleaned = amount String cleaned = amount
@ -199,7 +202,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
// Remove any commas // Remove any commas
cleaned = cleaned.replace(",", ""); cleaned = cleaned.replace(",", "");
Log.d("TransactionAdapter", "Cleaned result: '" + cleaned + "'"); Log.d("ReprintAdapterActivity", "Cleaned result: '" + cleaned + "'");
return cleaned; return cleaned;
} }
@ -216,7 +219,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
private void checkMidtransStatus(String referenceId, TextView statusTextView) { private void checkMidtransStatus(String referenceId, TextView statusTextView) {
new Thread(() -> { new Thread(() -> {
try { try {
Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId); Log.d("ReprintAdapterActivity", "🔍 Comprehensive status check for reference: " + referenceId);
// STEP 1: Query webhook logs untuk semua order_id yang terkait // STEP 1: Query webhook logs untuk semua order_id yang terkait
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at"; String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at";
@ -244,7 +247,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
String foundAcquirer = null; String foundAcquirer = null;
if (results != null && results.length() > 0) { if (results != null && results.length() > 0) {
Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries"); Log.d("ReprintAdapterActivity", "📊 Processing " + results.length() + " log entries");
// STEP 2: Comprehensive search dengan multiple matching strategies // STEP 2: Comprehensive search dengan multiple matching strategies
for (int i = 0; i < results.length(); i++) { for (int i = 0; i < results.length(); i++) {
@ -270,7 +273,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
String appReferenceId = customData.optString("app_reference_id", ""); String appReferenceId = customData.optString("app_reference_id", "");
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) { if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
isRefreshMatch = true; isRefreshMatch = true;
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId); Log.d("ReprintAdapterActivity", "🔄 Found refresh match: " + logOrderId);
} }
} catch (JSONException e) { } catch (JSONException e) {
// Ignore custom field parsing errors // Ignore custom field parsing errors
@ -288,7 +291,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
if (itemName.contains("(Ref: " + referenceId + ")") || if (itemName.contains("(Ref: " + referenceId + ")") ||
itemName.contains("- " + referenceId)) { itemName.contains("- " + referenceId)) {
isItemMatch = true; isItemMatch = true;
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId); Log.d("ReprintAdapterActivity", "📦 Found item match: " + logOrderId);
break; break;
} }
} }
@ -299,11 +302,11 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch; boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch;
if (isRelatedTransaction) { if (isRelatedTransaction) {
Log.d("TransactionAdapter", "🎯 MATCH FOUND!"); Log.d("ReprintAdapterActivity", "🎯 MATCH FOUND!");
Log.d("TransactionAdapter", " Order ID: " + logOrderId); Log.d("ReprintAdapterActivity", " Order ID: " + logOrderId);
Log.d("TransactionAdapter", " Status: " + logTransactionStatus); Log.d("ReprintAdapterActivity", " Status: " + logTransactionStatus);
Log.d("TransactionAdapter", " Acquirer: " + logAcquirer); Log.d("ReprintAdapterActivity", " Acquirer: " + logAcquirer);
Log.d("TransactionAdapter", " Match Type: " + Log.d("ReprintAdapterActivity", " Match Type: " +
(isDirectMatch ? "DIRECT " : "") + (isDirectMatch ? "DIRECT " : "") +
(isRefreshMatch ? "REFRESH " : "") + (isRefreshMatch ? "REFRESH " : "") +
(isItemMatch ? "ITEM" : "")); (isItemMatch ? "ITEM" : ""));
@ -315,29 +318,29 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
finalStatus = "PAID"; finalStatus = "PAID";
foundOrderId = logOrderId; foundOrderId = logOrderId;
foundAcquirer = logAcquirer; foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus); Log.d("ReprintAdapterActivity", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
break; // Found paid status, stop searching break; // Found paid status, stop searching
} else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) { } else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) {
finalStatus = "PENDING"; finalStatus = "PENDING";
foundOrderId = logOrderId; foundOrderId = logOrderId;
foundAcquirer = logAcquirer; foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId); Log.d("ReprintAdapterActivity", "⏳ PENDING found: " + logOrderId);
} else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) { } else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) {
if (finalStatus.equals("INIT")) { // Only update if no better status found if (finalStatus.equals("INIT")) { // Only update if no better status found
finalStatus = "FAILED"; finalStatus = "FAILED";
foundOrderId = logOrderId; foundOrderId = logOrderId;
foundAcquirer = logAcquirer; foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus); Log.d("ReprintAdapterActivity", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus);
} }
} }
} }
} }
} }
Log.d("TransactionAdapter", "🔍 FINAL RESULT for " + referenceId + ":"); Log.d("ReprintAdapterActivity", "🔍 FINAL RESULT for " + referenceId + ":");
Log.d("TransactionAdapter", " Status: " + finalStatus); Log.d("ReprintAdapterActivity", " Status: " + finalStatus);
Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A")); Log.d("ReprintAdapterActivity", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
Log.d("TransactionAdapter", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A")); Log.d("ReprintAdapterActivity", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A"));
} }
// STEP 3: Update UI di main thread // STEP 3: Update UI di main thread
@ -348,10 +351,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
statusTextView.setText(displayStatus); statusTextView.setText(displayStatus);
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus);
Log.d("TransactionAdapter", "🎨 UI UPDATED:"); Log.d("ReprintAdapterActivity", "🎨 UI UPDATED:");
Log.d("TransactionAdapter", " Reference: " + referenceId); Log.d("ReprintAdapterActivity", " Reference: " + referenceId);
Log.d("TransactionAdapter", " Display Status: " + displayStatus); Log.d("ReprintAdapterActivity", " Display Status: " + displayStatus);
Log.d("TransactionAdapter", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown")); Log.d("ReprintAdapterActivity", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown"));
}); });
// BONUS: Update backend jika status berubah ke PAID // BONUS: Update backend jika status berubah ke PAID
@ -360,7 +363,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} }
} else { } else {
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode()); Log.w("ReprintAdapterActivity", "⚠️ API call failed with code: " + conn.getResponseCode());
statusTextView.post(() -> { statusTextView.post(() -> {
statusTextView.setText("ERROR"); statusTextView.setText("ERROR");
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR"); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR");
@ -368,7 +371,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} }
} catch (IOException | JSONException e) { } catch (IOException | JSONException e) {
Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e); Log.e("ReprintAdapterActivity", "❌ Comprehensive status check error: " + e.getMessage(), e);
statusTextView.post(() -> { statusTextView.post(() -> {
statusTextView.setText("INIT"); statusTextView.setText("INIT");
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT"); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT");
@ -383,7 +386,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
private void updateBackendTransactionStatus(String referenceId, String status, String orderId, String acquirer) { private void updateBackendTransactionStatus(String referenceId, String status, String orderId, String acquirer) {
new Thread(() -> { new Thread(() -> {
try { try {
Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId); Log.d("ReprintAdapterActivity", "🔄 Updating backend status for reference: " + referenceId);
JSONObject updatePayload = new JSONObject(); JSONObject updatePayload = new JSONObject();
updatePayload.put("status", status); updatePayload.put("status", status);
@ -414,16 +417,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} }
int responseCode = conn.getResponseCode(); int responseCode = conn.getResponseCode();
Log.d("TransactionAdapter", "📥 Backend update response: " + responseCode); Log.d("ReprintAdapterActivity", "📥 Backend update response: " + responseCode);
if (responseCode == 200 || responseCode == 201) { if (responseCode == 200 || responseCode == 201) {
Log.d("TransactionAdapter", "✅ Backend status updated successfully"); Log.d("ReprintAdapterActivity", "✅ Backend status updated successfully");
} else { } else {
Log.e("TransactionAdapter", "❌ Backend update failed: " + responseCode); Log.e("ReprintAdapterActivity", "❌ Backend update failed: " + responseCode);
} }
} catch (Exception e) { } catch (Exception e) {
Log.e("TransactionAdapter", "❌ Backend update error: " + e.getMessage(), e); Log.e("ReprintAdapterActivity", "❌ Backend update error: " + e.getMessage(), e);
} }
}).start(); }).start();
} }
@ -436,7 +439,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
return "N/A"; return "N/A";
} }
Log.d("TransactionAdapter", "📅 Input date: '" + rawDate + "'"); Log.d("ReprintAdapterActivity", "📅 Input date: '" + rawDate + "'");
try { try {
// Handle different possible input formats from API // Handle different possible input formats from API
@ -460,7 +463,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
} }
Log.d("TransactionAdapter", "📅 Cleaned date: '" + cleanedDate + "'"); Log.d("ReprintAdapterActivity", "📅 Cleaned date: '" + cleanedDate + "'");
// Output format: d/M/yyyy H:mm:ss // Output format: d/M/yyyy H:mm:ss
SimpleDateFormat outputFormat = new SimpleDateFormat("d/M/yyyy H:mm:ss", Locale.getDefault()); SimpleDateFormat outputFormat = new SimpleDateFormat("d/M/yyyy H:mm:ss", Locale.getDefault());
@ -468,11 +471,11 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
Date date = inputFormat.parse(cleanedDate); Date date = inputFormat.parse(cleanedDate);
if (date != null) { if (date != null) {
String formatted = outputFormat.format(date); String formatted = outputFormat.format(date);
Log.d("TransactionAdapter", "📅 Date formatted: " + rawDate + " -> " + formatted); Log.d("ReprintAdapterActivity", "📅 Date formatted: " + rawDate + " -> " + formatted);
return formatted; return formatted;
} }
} catch (Exception e) { } catch (Exception e) {
Log.e("TransactionAdapter", "❌ Date formatting error for: " + rawDate, e); Log.e("ReprintAdapterActivity", "❌ Date formatting error for: " + rawDate, e);
} }
// Fallback: Manual parsing // Fallback: Manual parsing
@ -485,7 +488,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
workingDate = workingDate.substring(0, workingDate.indexOf(".")); workingDate = workingDate.substring(0, workingDate.indexOf("."));
} }
Log.d("TransactionAdapter", "📅 Manual parsing attempt: '" + workingDate + "'"); Log.d("ReprintAdapterActivity", "📅 Manual parsing attempt: '" + workingDate + "'");
// Split into date and time parts // Split into date and time parts
String[] parts = workingDate.split(" "); String[] parts = workingDate.split(" ");
@ -511,16 +514,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
String second = timeComponents[2]; String second = timeComponents[2];
String result = dayInt + "/" + monthInt + "/" + year + " " + hour + ":" + minute + ":" + second; String result = dayInt + "/" + monthInt + "/" + year + " " + hour + ":" + minute + ":" + second;
Log.d("TransactionAdapter", "📅 Manual format result: " + result); Log.d("ReprintAdapterActivity", "📅 Manual format result: " + result);
return result; return result;
} }
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.w("TransactionAdapter", "❌ Manual date formatting failed: " + e.getMessage()); Log.w("ReprintAdapterActivity", "❌ Manual date formatting failed: " + e.getMessage());
} }
Log.w("TransactionAdapter", "📅 Using fallback - returning original date: " + rawDate); Log.w("ReprintAdapterActivity", "📅 Using fallback - returning original date: " + rawDate);
return rawDate; return rawDate;
} }

View File

@ -0,0 +1,145 @@
package com.example.bdkipoc.emv;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.example.bdkipoc.utils.LogUtil;
import java.util.Locale;
public final class EmvTTS extends UtteranceProgressListener {
private static final String TAG = "EmvTTS";
private TextToSpeech textToSpeech;
private boolean supportTTS;
private ITTSProgressListener listener;
private EmvTTS() {
}
public static EmvTTS getInstance() {
return SingletonHolder.INSTANCE;
}
public void setTTSListener(ITTSProgressListener l) {
listener = l;
}
public void removeTTSListener() {
listener = null;
}
private static final class SingletonHolder {
private static final EmvTTS INSTANCE = new EmvTTS();
}
public void init() {
//初始化TTS对象
destroy();
textToSpeech = new TextToSpeech(MyApplication.app, this::onTTSInit);
textToSpeech.setOnUtteranceProgressListener(this);
}
public void play(String text) {
play(text, "0");
}
public void play(String text, String utteranceId) {
if (!supportTTS) {
Log.e(TAG, "PinPadTTS: play TTS failed, TTS not support...");
return;
}
if (textToSpeech == null) {
Log.e(TAG, "PinPadTTS: play TTS slipped, textToSpeech not init..");
return;
}
Log.e(TAG, "play() text: [" + text + "]");
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId);
}
@Override
public void onStart(String utteranceId) {
Log.e(TAG, "播放开始,utteranceId:" + utteranceId);
if (listener != null) {
listener.onStart(utteranceId);
}
}
@Override
public void onDone(String utteranceId) {
Log.e(TAG, "播放结束,utteranceId:" + utteranceId);
if (listener != null) {
listener.onDone(utteranceId);
}
}
@Override
public void onError(String utteranceId) {
Log.e(TAG, "播放出错,utteranceId:" + utteranceId);
if (listener != null) {
listener.onError(utteranceId);
}
}
@Override
public void onStop(String utteranceId, boolean interrupted) {
Log.e(TAG, "播放停止,utteranceId:" + utteranceId + ",interrupted:" + interrupted);
if (listener != null) {
listener.onStop(utteranceId, interrupted);
}
}
void stop() {
if (textToSpeech != null) {
int code = textToSpeech.stop();
Log.e(TAG, "tts stop() code:" + code);
}
}
boolean isSpeaking() {
if (textToSpeech != null) {
return textToSpeech.isSpeaking();
}
return false;
}
void destroy() {
if (textToSpeech != null) {
textToSpeech.stop();
textToSpeech.shutdown();
textToSpeech = null;
}
}
/** TTS初始化回调 */
private void onTTSInit(int status) {
if (status != TextToSpeech.SUCCESS) {
LogUtil.e(TAG, "PinPadTTS: init TTS failed, status:" + status);
supportTTS = false;
return;
}
updateTtsLanguage();
if (supportTTS) {
textToSpeech.setPitch(1.0f);
textToSpeech.setSpeechRate(1.0f);
LogUtil.e(TAG, "onTTSInit() success,locale:" + textToSpeech.getVoice().getLocale());
}
}
/** 更新TTS语言 */
private void updateTtsLanguage() {
Locale locale = Locale.ENGLISH;
int result = textToSpeech.setLanguage(locale);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
supportTTS = false; //系统不支持当前Locale对应的语音播报
LogUtil.e(TAG, "updateTtsLanguage() failed, TTS not support in locale:" + locale);
} else {
supportTTS = true;
LogUtil.e(TAG, "updateTtsLanguage() success, TTS locale:" + locale);
}
}
}

View File

@ -0,0 +1,57 @@
package com.example.bdkipoc.emv;
import android.speech.tts.TextToSpeech;
public interface ITTSProgressListener {
/**
* Called when an utterance "starts" as perceived by the caller. This will
* be soon before audio is played back in the case of a {@link TextToSpeech#speak}
* or before the first bytes of a file are written to the file system in the case
* of {@link TextToSpeech#synthesizeToFile}.
*
* @param utteranceId The utterance ID of the utterance.
*/
void onStart(String utteranceId);
/**
* Called when an utterance has successfully completed processing.
* All audio will have been played back by this point for audible output, and all
* output will have been written to disk for file synthesis requests.
* <p>
* This request is guaranteed to be called after {@link #onStart(String)}.
*
* @param utteranceId The utterance ID of the utterance.
*/
void onDone(String utteranceId);
/**
* Called when an error has occurred during processing. This can be called
* at any point in the synthesis process. Note that there might be calls
* to {@link #onStart(String)} for specified utteranceId but there will never
* be a call to both {@link #onDone(String)} and {@link #onError(String)} for
* the same utterance.
*
* @param utteranceId The utterance ID of the utterance.
* @deprecated Use {@link #onError(String, int)} instead
*/
/**
* @deprecated Use {@link #onError(String, int)} instead
*/
@Deprecated
void onError(String utteranceId);
/**
* Called when an utterance has been stopped while in progress or flushed from the
* synthesis queue. This can happen if a client calls {@link TextToSpeech#stop()}
* or uses {@link TextToSpeech#QUEUE_FLUSH} as an argument with the
* {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.
*
* @param utteranceId The utterance ID of the utterance.
* @param interrupted If true, then the utterance was interrupted while being synthesized
* and its output is incomplete. If false, then the utterance was flushed
* before the synthesis started.
*/
void onStop(String utteranceId, boolean interrupted);
}

View File

@ -70,7 +70,7 @@ public class QrisActivity extends AppCompatActivity {
private static final String BACKEND_BASE = "https://be-edc.msvc.app"; private static final String BACKEND_BASE = "https://be-edc.msvc.app";
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge"; private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc=";
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans"; private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
@Override @Override

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,711 @@
package com.example.bdkipoc.transaction;
import android.content.Intent;
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.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.example.bdkipoc.R;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* ResultTransactionActivity - Enhanced Receipt-style Display using activity_receipt.xml
* Shows EMV/Card transaction results using the same layout as QRIS receipts
*/
public class ResultTransactionActivity extends AppCompatActivity {
private static final String TAG = "ResultTransaction";
// UI Components using activity_receipt.xml IDs
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
// Receipt details
private TextView merchantName;
private TextView merchantLocation;
private TextView midText;
private TextView tidText;
private TextView transactionNumber;
private TextView transactionDate;
private TextView paymentMethod;
private TextView cardType;
private TextView transactionTotal;
private TextView taxPercentage;
private TextView serviceFee;
private TextView finalTotal;
// Action buttons
private LinearLayout printButton;
private LinearLayout emailButton;
private Button finishButton;
// Data from intent
private String transactionAmount;
private String cardTypeFromIntent;
private boolean emvMode;
private String referenceId;
private String cardNo;
private String midtransResponse;
private boolean paymentSuccess;
private String emvCardholderName;
private String emvAid;
private String emvExpiry;
// Internal data
private JSONObject responseJsonData;
private boolean isNavigating = false;
// Receipt calculation data
private long subtotalAmount = 0;
private long taxAmount = 0;
private long serviceFeeAmount = 500; // Default service fee
private double taxPercentageValue = 0.11; // 11% tax
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// CRITICAL: Use the same layout as ReceiptActivity
setContentView(R.layout.activity_receipt);
Log.d(TAG, "=== RESULT TRANSACTION ACTIVITY STARTED ===");
Log.d(TAG, "✅ Using activity_receipt.xml layout");
initViews();
extractIntentData();
debugAllDataSources();
setupListeners();
calculateAmounts();
displayReceiptData();
logTransactionDetails();
}
private void initViews() {
// Initialize views using activity_receipt.xml IDs
// Navigation
backNavigation = findViewById(R.id.back_navigation);
backArrow = findViewById(R.id.backArrow);
toolbarTitle = findViewById(R.id.toolbarTitle);
// Receipt details
merchantName = findViewById(R.id.merchant_name);
merchantLocation = findViewById(R.id.merchant_location);
midText = findViewById(R.id.mid_text);
tidText = findViewById(R.id.tid_text);
transactionNumber = findViewById(R.id.transaction_number);
transactionDate = findViewById(R.id.transaction_date);
paymentMethod = findViewById(R.id.payment_method);
cardType = findViewById(R.id.card_type);
transactionTotal = findViewById(R.id.transaction_total);
taxPercentage = findViewById(R.id.tax_percentage);
serviceFee = findViewById(R.id.service_fee);
finalTotal = findViewById(R.id.final_total);
// Action buttons
printButton = findViewById(R.id.print_button);
emailButton = findViewById(R.id.email_button);
finishButton = findViewById(R.id.finish_button);
Log.d(TAG, "✅ All views initialized using activity_receipt.xml");
}
private void extractIntentData() {
Intent intent = getIntent();
transactionAmount = intent.getStringExtra("TRANSACTION_AMOUNT");
cardTypeFromIntent = intent.getStringExtra("CARD_TYPE");
emvMode = intent.getBooleanExtra("EMV_MODE", false);
referenceId = intent.getStringExtra("REFERENCE_ID");
cardNo = intent.getStringExtra("CARD_NO");
midtransResponse = intent.getStringExtra("MIDTRANS_RESPONSE");
paymentSuccess = intent.getBooleanExtra("PAYMENT_SUCCESS", true);
emvCardholderName = intent.getStringExtra("EMV_CARDHOLDER_NAME");
emvAid = intent.getStringExtra("EMV_AID");
emvExpiry = intent.getStringExtra("EMV_EXPIRY");
Log.d(TAG, "=== EXTRACTING INTENT DATA ===");
Log.d(TAG, "Card Type: " + cardTypeFromIntent);
Log.d(TAG, "EMV Mode: " + emvMode);
Log.d(TAG, "Transaction Amount: " + transactionAmount);
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "Midtrans Response Length: " + (midtransResponse != null ? midtransResponse.length() : 0));
// Parse Midtrans response if available
if (midtransResponse != null && !midtransResponse.isEmpty()) {
try {
responseJsonData = new JSONObject(midtransResponse);
Log.d(TAG, "✅ Midtrans Response parsed successfully!");
// Check for bank field specifically
if (responseJsonData.has("bank")) {
String bankValue = responseJsonData.getString("bank");
Log.d(TAG, "✅ Bank field found: '" + bankValue + "'");
} else {
Log.w(TAG, "⚠️ No 'bank' field in Midtrans response");
}
} catch (JSONException e) {
Log.e(TAG, "❌ Error parsing Midtrans response: " + e.getMessage());
responseJsonData = null;
}
} else {
Log.w(TAG, "⚠️ No Midtrans response data available");
responseJsonData = null;
}
Log.d(TAG, "===============================");
}
private void setupListeners() {
// Back navigation
backNavigation.setOnClickListener(v -> {
if (isNavigating) return;
navigateBack();
});
backArrow.setOnClickListener(v -> {
if (isNavigating) return;
navigateBack();
});
toolbarTitle.setOnClickListener(v -> {
if (isNavigating) return;
navigateBack();
});
// Print button
printButton.setOnClickListener(v -> {
showToast("Mencetak struk...");
printReceipt();
});
// Email button
emailButton.setOnClickListener(v -> {
showToast("Mengirim email...");
emailReceipt();
});
// Finish button - Navigate to new transaction
finishButton.setOnClickListener(v -> {
if (isNavigating) return;
navigateToNewTransaction();
});
Log.d(TAG, "✅ All click listeners setup");
}
private void calculateAmounts() {
try {
if (transactionAmount != null && !transactionAmount.isEmpty()) {
subtotalAmount = Long.parseLong(transactionAmount);
} else {
subtotalAmount = 3500000; // Default amount for demo
}
// Calculate tax (11%)
taxAmount = Math.round(subtotalAmount * taxPercentageValue);
// Service fee is fixed
serviceFeeAmount = 500;
Log.d(TAG, "Amounts calculated - Subtotal: " + subtotalAmount +
", Tax: " + taxAmount + ", Service: " + serviceFeeAmount);
} catch (NumberFormatException e) {
Log.e(TAG, "Error calculating amounts: " + e.getMessage());
// Set default values
subtotalAmount = 3500000;
taxAmount = 385000;
serviceFeeAmount = 500;
}
}
private void displayReceiptData() {
Log.d(TAG, "=== DISPLAYING RECEIPT DATA ===");
debugAllDataSources();
// 1. Set merchant data
merchantName.setText("TOKO KLONTONG PAK EKO");
merchantLocation.setText("Ciputat Baru, Tangsel");
// 2. Set MID and TID
String tid = extractTidFromResponse();
midText.setText("MID: " + tid);
tidText.setText("TID: " + tid);
// 3. Set transaction number
String displayTransactionNumber = extractTransactionNumberFromResponse();
transactionNumber.setText(displayTransactionNumber);
// 4. Set transaction date
String displayDate = formatTransactionDate();
transactionDate.setText(displayDate);
// 5. Set payment method
String displayPaymentMethod = getPaymentMethodDisplay();
paymentMethod.setText(displayPaymentMethod);
// 6. ENHANCED: Set card type with comprehensive detection
String displayCardType = getCardTypeDisplay();
cardType.setText(displayCardType);
// 7. Set amount details
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
transactionTotal.setText(formatter.format(subtotalAmount));
taxPercentage.setText(Math.round(taxPercentageValue * 100) + "%");
serviceFee.setText(formatter.format(serviceFeeAmount));
// Final total
long finalTotalAmount = subtotalAmount + taxAmount + serviceFeeAmount;
finalTotal.setText(formatter.format(finalTotalAmount));
Log.d(TAG, "✅ Receipt data displayed successfully");
Log.d(TAG, " Payment Method: " + displayPaymentMethod);
Log.d(TAG, " Card Type: " + displayCardType);
Log.d(TAG, " Final Total: " + formatter.format(finalTotalAmount));
Log.d(TAG, "================================");
}
private String extractTidFromResponse() {
if (responseJsonData != null && responseJsonData.has("tid")) {
try {
return responseJsonData.getString("tid");
} catch (JSONException e) {
Log.e(TAG, "Error extracting TID: " + e.getMessage());
}
}
return "123456789901"; // Default TID
}
private String extractTransactionNumberFromResponse() {
if (responseJsonData != null) {
try {
if (responseJsonData.has("transaction_id")) {
String fullTransactionId = responseJsonData.getString("transaction_id");
// Extract last 10 digits for display
if (fullTransactionId.length() > 10) {
return fullTransactionId.substring(fullTransactionId.length() - 10);
}
return fullTransactionId;
}
} catch (JSONException e) {
Log.e(TAG, "Error extracting transaction number: " + e.getMessage());
}
}
// Generate from reference ID or use default
if (referenceId != null && referenceId.length() > 10) {
return referenceId.substring(referenceId.length() - 10);
}
return String.valueOf(System.currentTimeMillis() % 10000000000L);
}
private String formatTransactionDate() {
if (responseJsonData != null) {
try {
if (responseJsonData.has("transaction_time")) {
String transactionTime = responseJsonData.getString("transaction_time");
return formatDateForDisplay(transactionTime);
}
} catch (JSONException e) {
Log.e(TAG, "Error extracting transaction time: " + e.getMessage());
}
}
// Use current date and time
return formatDateForDisplay(new Date());
}
private String formatDateForDisplay(String dateString) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
Date date = inputFormat.parse(dateString);
return outputFormat.format(date);
} catch (Exception e) {
Log.e(TAG, "Error formatting date: " + e.getMessage());
return formatDateForDisplay(new Date());
}
}
private String formatDateForDisplay(Date date) {
SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
return outputFormat.format(date);
}
private String getPaymentMethodDisplay() {
if (cardTypeFromIntent == null) return "Kartu Kredit";
switch (cardTypeFromIntent.toUpperCase()) {
case "EMV_MIDTRANS":
case "IC":
case "NFC":
return emvMode ? "Kartu Kredit (EMV)" : "Kartu Kredit";
case "DEBIT":
return emvMode ? "Kartu Debit (EMV)" : "Kartu Debit";
case "MAGNETIC":
return "Kartu Kredit";
default:
return "Kartu Kredit";
}
}
// ENHANCED: Comprehensive bank detection for EMV transactions
private String getCardTypeDisplay() {
Log.d(TAG, "=== DETERMINING CARD TYPE DISPLAY (EMV) ===");
// Priority 1: Get bank from Midtrans response (most accurate)
if (responseJsonData != null) {
Log.d(TAG, "✅ Midtrans response available, checking for bank...");
try {
String bankFromResponse = null;
if (responseJsonData.has("bank")) {
bankFromResponse = responseJsonData.getString("bank");
Log.d(TAG, "Found 'bank' field: '" + bankFromResponse + "'");
} else if (responseJsonData.has("issuer")) {
bankFromResponse = responseJsonData.getString("issuer");
Log.d(TAG, "Found 'issuer' field: '" + bankFromResponse + "'");
} else if (responseJsonData.has("acquiring_bank")) {
bankFromResponse = responseJsonData.getString("acquiring_bank");
Log.d(TAG, "Found 'acquiring_bank' field: '" + bankFromResponse + "'");
}
if (bankFromResponse != null && !bankFromResponse.trim().isEmpty()) {
String formattedBank = formatBankName(bankFromResponse);
Log.d(TAG, "✅ Bank from Midtrans response: '" + bankFromResponse + "' -> '" + formattedBank + "'");
return formattedBank;
}
} catch (JSONException e) {
Log.e(TAG, "❌ Error extracting bank from response: " + e.getMessage());
}
}
// Priority 2: EMV AID detection
Log.d(TAG, "Trying EMV AID detection...");
if (emvAid != null && !emvAid.trim().isEmpty()) {
String bankFromAid = getBankFromAid(emvAid);
if (!bankFromAid.equals("BCA")) { // If not default
Log.d(TAG, "✅ Bank from EMV AID: " + bankFromAid);
return bankFromAid;
}
}
// Priority 3: Card BIN detection
Log.d(TAG, "Trying Card BIN detection...");
if (cardNo != null && cardNo.length() >= 6) {
String cardBin = cardNo.substring(0, 6);
String bankFromBin = getBankFromComprehensiveBin(cardBin);
Log.d(TAG, "✅ Bank from BIN (" + cardBin + "): " + bankFromBin);
return bankFromBin;
}
Log.d(TAG, "⚠️ Using default bank: BCA");
Log.d(TAG, "====================================");
return "BCA"; // Default fallback
}
// ENHANCED: Better bank name formatting
private String formatBankName(String bankName) {
if (bankName == null || bankName.trim().isEmpty()) {
return "BCA"; // Default
}
String formatted = bankName.trim().toUpperCase();
// Handle common bank name variations
switch (formatted) {
case "BCA":
case "BANK BCA":
case "BANK CENTRAL ASIA":
return "BCA";
case "MANDIRI":
case "BANK MANDIRI":
return "Mandiri";
case "BNI":
case "BANK BNI":
case "BANK NEGARA INDONESIA":
return "BNI";
case "BRI":
case "BANK BRI":
case "BANK RAKYAT INDONESIA":
return "BRI";
case "CIMB":
case "CIMB NIAGA":
case "BANK CIMB NIAGA":
return "CIMB Niaga";
case "DANAMON":
case "BANK DANAMON":
return "Danamon";
case "PERMATA":
case "BANK PERMATA":
return "Permata";
default:
return capitalizeFirstLetter(bankName);
}
}
private String getBankFromAid(String aid) {
// AID to Indonesian bank mapping
if (aid.contains("A0000000031010")) {
// VISA - check if we have card number for better detection
if (cardNo != null && cardNo.length() >= 6) {
return getBankFromComprehensiveBin(cardNo.substring(0, 6));
}
return "BCA"; // Default for VISA
}
if (aid.contains("A0000000041010")) {
// MASTERCARD
if (cardNo != null && cardNo.length() >= 6) {
return getBankFromComprehensiveBin(cardNo.substring(0, 6));
}
return "Mandiri"; // Default for Mastercard
}
return "BCA"; // Ultimate fallback
}
// ENHANCED: Comprehensive Indonesian bank BIN mapping
private String getBankFromComprehensiveBin(String bin) {
if (bin == null || bin.length() < 4) {
return "BCA"; // Default
}
String bin4 = bin.substring(0, 4);
String bin6 = bin.length() >= 6 ? bin.substring(0, 6) : bin4;
// BCA patterns
if (bin4.equals("4621") || bin4.equals("4699") || bin4.equals("5221") || bin4.equals("6277")) {
return "BCA";
}
// MANDIRI patterns
if (bin4.equals("4313") || bin4.equals("5573") || bin4.equals("6011") || bin4.equals("6234")) {
return "Mandiri";
}
// BNI patterns
if (bin4.equals("4603") || bin4.equals("1946") || bin4.equals("5264")) {
return "BNI";
}
// BRI patterns
if (bin4.equals("4578") || bin4.equals("4479") || bin4.equals("5208")) {
return "BRI";
}
// CIMB NIAGA patterns
if (bin4.equals("4599") || bin4.equals("5249")) {
return "CIMB Niaga";
}
// DANAMON patterns
if (bin4.equals("4055") || bin4.equals("5108")) {
return "Danamon";
}
// Default fallback
Log.d(TAG, "Unknown BIN pattern: " + bin6 + ", using default BCA");
return "BCA";
}
private String capitalizeFirstLetter(String input) {
if (input == null || input.isEmpty()) {
return input;
}
return input.substring(0, 1).toUpperCase() + input.substring(1).toLowerCase();
}
// Debug methods
private void debugAllDataSources() {
Log.d(TAG, "=== DEBUGGING ALL DATA SOURCES ===");
if (responseJsonData != null) {
Log.d(TAG, "Midtrans Response Available:");
try {
java.util.Iterator<String> keys = responseJsonData.keys();
while (keys.hasNext()) {
String key = keys.next();
Object value = responseJsonData.get(key);
Log.d(TAG, " " + key + ": " + value);
}
} catch (Exception e) {
Log.e(TAG, "Error iterating response: " + e.getMessage());
}
} else {
Log.d(TAG, "❌ No Midtrans Response Data");
}
Log.d(TAG, "EMV Data:");
Log.d(TAG, " Card Number: " + (cardNo != null ? maskCardNumber(cardNo) : "null"));
Log.d(TAG, " EMV AID: " + emvAid);
Log.d(TAG, " EMV Cardholder: " + emvCardholderName);
Log.d(TAG, " EMV Mode: " + emvMode);
Log.d(TAG, "==================================");
}
private void logTransactionDetails() {
Log.d(TAG, "=== RECEIPT DETAILS ===");
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "Card Number: " + (cardNo != null ? maskCardNumber(cardNo) : "N/A"));
Log.d(TAG, "Subtotal: " + subtotalAmount);
Log.d(TAG, "Tax: " + taxAmount);
Log.d(TAG, "Service Fee: " + serviceFeeAmount);
Log.d(TAG, "Final Total: " + (subtotalAmount + taxAmount + serviceFeeAmount));
Log.d(TAG, "======================");
}
// Action Methods
private void printReceipt() {
Log.d(TAG, "Print receipt requested");
showToast("Fitur cetak akan segera tersedia");
}
private void emailReceipt() {
Log.d(TAG, "Email receipt requested");
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Struk Pembayaran - " + extractTransactionNumberFromResponse());
emailIntent.putExtra(Intent.EXTRA_TEXT, generateEmailContent());
try {
startActivity(Intent.createChooser(emailIntent, "Kirim Email"));
} catch (Exception e) {
Log.e(TAG, "Error sending email: " + e.getMessage());
showToast("Tidak dapat mengirim email");
}
}
private String generateEmailContent() {
StringBuilder content = new StringBuilder();
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
content.append("STRUK PEMBAYARAN EMV/CARD\n");
content.append("==========================\n\n");
content.append("TOKO KLONTONG PAK EKO\n");
content.append("Ciputat Baru, Tangsel\n\n");
content.append("TID: ").append(extractTidFromResponse()).append("\n");
content.append("Nomor Transaksi: ").append(extractTransactionNumberFromResponse()).append("\n");
content.append("Tanggal: ").append(formatTransactionDate()).append("\n");
content.append("Metode: ").append(getPaymentMethodDisplay()).append("\n");
content.append("Jenis Kartu: ").append(getCardTypeDisplay()).append("\n\n");
if (emvMode && emvCardholderName != null) {
content.append("DETAIL EMV:\n");
content.append("Cardholder: ").append(emvCardholderName).append("\n");
content.append("AID: ").append(emvAid).append("\n\n");
}
content.append("RINCIAN PEMBAYARAN:\n");
content.append("Total Transaksi: Rp ").append(formatter.format(subtotalAmount)).append("\n");
content.append("Pajak (11%): Rp ").append(formatter.format(taxAmount)).append("\n");
content.append("Biaya Layanan: Rp ").append(formatter.format(serviceFeeAmount)).append("\n");
content.append("------------------------\n");
content.append("TOTAL: Rp ").append(formatter.format(subtotalAmount + taxAmount + serviceFeeAmount)).append("\n");
content.append("\nTerima kasih atas pembayaran Anda!");
return content.toString();
}
// Navigation Methods
private void navigateBack() {
if (isNavigating) return;
Log.d(TAG, "Navigating back");
isNavigating = true;
finish();
}
private void navigateToNewTransaction() {
if (isNavigating) return;
new AlertDialog.Builder(this)
.setTitle("Transaksi Baru")
.setMessage("Apakah Anda ingin melakukan transaksi baru?")
.setPositiveButton("Ya", (dialog, which) -> {
performNavigateToNewTransaction();
})
.setNegativeButton("Tidak", null)
.show();
}
private void performNavigateToNewTransaction() {
Log.d(TAG, "=== NAVIGATING TO NEW TRANSACTION ===");
isNavigating = true;
new Handler(Looper.getMainLooper()).postDelayed(() -> {
try {
Intent intent = new Intent(this, CreateTransactionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
} catch (Exception e) {
Log.e(TAG, "Error navigating to new transaction: " + e.getMessage());
isNavigating = false;
showToast("Gagal membuka transaksi baru");
}
}, 300);
}
// Helper Methods
private String maskCardNumber(String cardNumber) {
if (cardNumber == null || cardNumber.length() < 8) {
return cardNumber;
}
String first4 = cardNumber.substring(0, 4);
String last4 = cardNumber.substring(cardNumber.length() - 4);
return first4 + "****" + last4;
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onBackPressed() {
if (isNavigating) {
return;
}
navigateBack();
super.onBackPressed();
}
@Override
protected void onDestroy() {
Log.d(TAG, "ResultTransactionActivity destroyed");
super.onDestroy();
}
}

View File

@ -0,0 +1,258 @@
package com.example.bdkipoc.transaction.managers;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.sunmi.pay.hardware.aidl.AidlConstants.CardType;
import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2;
import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2;
/**
* CardScannerManager - Handles card detection for both EMV and Simple modes
*/
public class CardScannerManager {
private static final String TAG = "CardScannerManager";
private CardScannerCallback callback;
private boolean isProcessing = false;
public interface CardScannerCallback {
void onCardDetected(String cardType, Bundle cardData);
void onEMVCardDetected(int cardType);
void onScanError(String errorMessage);
void onScanProgress(String message);
}
public CardScannerManager(CardScannerCallback callback) {
this.callback = callback;
}
public void startScanning(boolean isEMVMode) {
if (isProcessing) {
Log.d(TAG, "Card check already in progress - ignoring call");
return;
}
Log.d(TAG, "Starting card check - setting isProcessing = true");
isProcessing = true;
try {
// Small delay to ensure everything is ready
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (isProcessing) {
if (isEMVMode) {
startEMVCardCheck();
} else {
startSimpleCardCheck();
}
}
}, 500);
} catch (Exception e) {
Log.e(TAG, "Error in startScanning: " + e.getMessage(), e);
handleScanError("Error: " + e.getMessage());
}
}
public void stopScanning() {
try {
if (MyApplication.app != null && MyApplication.app.readCardOptV2 != null) {
MyApplication.app.readCardOptV2.cancelCheckCard();
}
isProcessing = false;
Log.d(TAG, "Card scanning stopped");
} catch (Exception e) {
Log.e(TAG, "Error stopping card check: " + e.getMessage());
}
}
public boolean isScanning() {
return isProcessing;
}
private void startEMVCardCheck() {
try {
if (callback != null) {
callback.onScanProgress("EMV Mode: Starting card scan...");
}
int cardType = AidlConstantsV2.CardType.NFC.getValue() | AidlConstantsV2.CardType.IC.getValue();
Log.d(TAG, "Starting EMV checkCard with cardType: " + cardType);
MyApplication.app.readCardOptV2.checkCard(cardType, mEMVCheckCardCallback, 60);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Error in startEMVCardCheck: " + e.getMessage());
handleScanError("Error starting EMV card scan: " + e.getMessage());
}
}
private void startSimpleCardCheck() {
try {
if (!MyApplication.app.isConnectPaySDK()) {
if (callback != null) {
callback.onScanProgress("Connecting to PaySDK...");
}
MyApplication.app.bindPaySDKService();
return;
}
if (callback != null) {
callback.onScanProgress("Simple Mode: Starting card scan...");
}
int cardType = CardType.MAGNETIC.getValue() | CardType.IC.getValue() | CardType.NFC.getValue();
MyApplication.app.readCardOptV2.checkCard(cardType, mSimpleCheckCardCallback, 60);
} catch (Exception e) {
e.printStackTrace();
handleScanError("Error starting card scan: " + e.getMessage());
}
}
private void handleScanError(String errorMessage) {
Log.e(TAG, "Scan error: " + errorMessage);
isProcessing = false;
if (callback != null) {
callback.onScanError(errorMessage);
}
}
public void resetScanning() {
Log.d(TAG, "Resetting scanning state");
isProcessing = false;
}
// Simple Card Detection Callback
private final CheckCardCallbackV2 mSimpleCheckCardCallback = new CheckCardCallbackV2.Stub() {
@Override
public void findMagCard(Bundle info) throws RemoteException {
Log.d(TAG, "Simple Mode: findMagCard callback triggered");
isProcessing = false;
if (callback != null) {
callback.onCardDetected("MAGNETIC", info);
}
}
@Override
public void findICCard(String atr) throws RemoteException {
Bundle info = new Bundle();
info.putString("atr", atr);
isProcessing = false;
if (callback != null) {
callback.onCardDetected("IC", info);
}
}
@Override
public void findRFCard(String uuid) throws RemoteException {
Bundle info = new Bundle();
info.putString("uuid", uuid);
isProcessing = false;
if (callback != null) {
callback.onCardDetected("NFC", info);
}
}
@Override
public void onError(int code, String message) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onScanError("Card error: " + message);
}
}
@Override
public void findICCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onCardDetected("IC", info);
}
}
@Override
public void findRFCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onCardDetected("NFC", info);
}
}
@Override
public void onErrorEx(Bundle info) throws RemoteException {
isProcessing = false;
String msg = info.getString("message", "Unknown error");
if (callback != null) {
callback.onScanError("Card error: " + msg);
}
}
};
// EMV Card Detection Callback
private final CheckCardCallbackV2 mEMVCheckCardCallback = new CheckCardCallbackV2.Stub() {
@Override
public void findMagCard(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onCardDetected("MAGNETIC", info);
}
}
@Override
public void findICCard(String atr) throws RemoteException {
MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0);
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.IC.getValue());
}
}
@Override
public void findRFCard(String uuid) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.NFC.getValue());
}
}
@Override
public void onError(int code, String message) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onScanError("EMV Error: " + message);
}
}
@Override
public void findICCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.IC.getValue());
}
}
@Override
public void findRFCardEx(Bundle info) throws RemoteException {
isProcessing = false;
if (callback != null) {
callback.onEMVCardDetected(AidlConstantsV2.CardType.NFC.getValue());
}
}
@Override
public void onErrorEx(Bundle info) throws RemoteException {
isProcessing = false;
String msg = info.getString("message", "Unknown error");
if (callback != null) {
callback.onScanError("EMV Error: " + msg);
}
}
};
}

View File

@ -0,0 +1,417 @@
package com.example.bdkipoc.transaction.managers;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.sunmi.pay.hardware.aidlv2.AidlConstantsV2;
import com.sunmi.pay.hardware.aidlv2.bean.EMVCandidateV2;
import com.sunmi.pay.hardware.aidlv2.emv.EMVListenerV2;
import com.sunmi.pay.hardware.aidlv2.emv.EMVOptV2;
import java.util.List;
/**
* EMVManager - Handles all EMV related operations
*/
public class EMVManager {
private static final String TAG = "EMVManager";
private EMVOptV2 mEMVOptV2;
private EMVManagerCallback callback;
// EMV Process Variables
private int mCardType;
private String mCardNo;
private int mPinType;
private String mCertInfo;
private int mProcessStep;
public interface EMVManagerCallback {
void onAppSelect(String[] candidateNames);
void onFinalAppSelect();
void onConfirmCardNo(String cardNo);
void onCertVerify(String certInfo);
void onShowPinPad(int pinType);
void onOnlineProcess();
void onSignature();
void onTransactionSuccess(int code, String desc);
void onTransactionFailed(int code, String desc);
}
public EMVManager(EMVManagerCallback callback) {
this.callback = callback;
initEMVComponents();
}
private void initEMVComponents() {
if (MyApplication.app != null) {
mEMVOptV2 = MyApplication.app.emvOptV2;
Log.d(TAG, "EMV components initialized");
} else {
Log.e(TAG, "MyApplication.app is null");
}
}
public void initEMVData() {
try {
if (mEMVOptV2 != null) {
mEMVOptV2.initEmvProcess();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
try {
initEmvTlvData();
Log.d(TAG, "EMV data initialized successfully");
} catch (Exception e) {
Log.e(TAG, "Error in delayed EMV init: " + e.getMessage());
}
}, 500);
}
} catch (Exception e) {
Log.e(TAG, "Error initializing EMV data: " + e.getMessage());
}
}
private void initEmvTlvData() {
try {
// Set PayPass (MasterCard) TLV data
String[] tagsPayPass = {"DF8117", "DF8118", "DF8119", "DF811F", "DF811E", "DF812C",
"DF8123", "DF8124", "DF8125", "DF8126", "DF811B", "DF811D", "DF8122", "DF8120", "DF8121"};
String[] valuesPayPass = {"E0", "F8", "F8", "E8", "00", "00",
"000000000000", "000000100000", "999999999999", "000000100000",
"30", "02", "0000000000", "000000000000", "000000000000"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PAYPASS, tagsPayPass, valuesPayPass);
// Set AMEX TLV data
String[] tagsAE = {"9F6D", "9F6E", "9F33", "9F35", "DF8168", "DF8167", "DF8169", "DF8170"};
String[] valuesAE = {"C0", "D8E00000", "E0E888", "22", "00", "00", "00", "60"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_AE, tagsAE, valuesAE);
// Set JCB TLV data
String[] tagsJCB = {"9F53", "DF8161"};
String[] valuesJCB = {"708000", "7F00"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_JCB, tagsJCB, valuesJCB);
// Set DPAS TLV data
String[] tagsDPAS = {"9F66"};
String[] valuesDPAS = {"B600C000"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_DPAS, tagsDPAS, valuesDPAS);
// Set Flash TLV data
String[] tagsFLASH = {"9F58", "9F59", "9F5A", "9F5D", "9F5E"};
String[] valuesFLASH = {"03", "D88700", "00", "000000000000", "E000"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_FLASH, tagsFLASH, valuesFLASH);
// Set Pure TLV data
String[] tagsPURE = {"DF7F", "DF8134", "DF8133"};
String[] valuesPURE = {"A0000007271010", "DF", "36006043F9"};
mEMVOptV2.setTlvList(AidlConstantsV2.EMV.TLVOpCode.OP_PURE, tagsPURE, valuesPURE);
Bundle bundle = new Bundle();
bundle.putBoolean("optOnlineRes", true);
mEMVOptV2.setTermParamEx(bundle);
} catch (RemoteException e) {
Log.e(TAG, "Error setting EMV TLV data: " + e.getMessage());
}
}
public void startEMVTransaction(String transactionAmount, int cardType) {
if (mProcessStep != 0) {
Log.d(TAG, "EMV transaction already in progress (step: " + mProcessStep + ") - ignoring call");
return;
}
Log.d(TAG, "Starting EMV transaction process");
mProcessStep = 1;
mCardType = cardType;
try {
mEMVOptV2.initEmvProcess();
new Handler(Looper.getMainLooper()).postDelayed(() -> {
try {
if (mProcessStep <= 0) {
Log.d(TAG, "EMV process was cancelled - not starting");
return;
}
Bundle bundle = new Bundle();
bundle.putString("amount", transactionAmount);
bundle.putString("transType", "00");
bundle.putInt("flowType", AidlConstantsV2.EMV.FlowType.TYPE_EMV_STANDARD);
bundle.putInt("cardType", mCardType);
Log.d(TAG, "Starting transactProcessEx with reset EMV");
mEMVOptV2.transactProcessEx(bundle, mEMVListener);
} catch (Exception e) {
Log.e(TAG, "Error in delayed EMV start: " + e.getMessage(), e);
if (callback != null) {
callback.onTransactionFailed(-1, "Error starting EMV: " + e.getMessage());
}
}
}, 300);
} catch (Exception e) {
Log.e(TAG, "Error starting EMV transaction: " + e.getMessage());
if (callback != null) {
callback.onTransactionFailed(-1, "Error starting EMV transaction: " + e.getMessage());
}
}
}
public void resetEMVProcess() {
try {
if (mEMVOptV2 != null) {
mEMVOptV2.initEmvProcess();
}
mProcessStep = 0;
mCardNo = null;
Log.d(TAG, "EMV process reset");
} catch (Exception e) {
Log.e(TAG, "Error resetting EMV process: " + e.getMessage());
}
}
// EMV Import Methods
public void importAppSelect(int selectIndex) {
try {
mEMVOptV2.importAppSelect(selectIndex);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importFinalAppSelectStatus(int status) {
try {
mEMVOptV2.importAppFinalSelectStatus(status);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void importCardNoStatus(int status) {
try {
mEMVOptV2.importCardNoStatus(status);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importCertStatus(int status) {
try {
mEMVOptV2.importCertStatus(status);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importPinInputStatus(int inputResult) {
try {
mEMVOptV2.importPinInputStatus(mPinType, inputResult);
} catch (Exception e) {
e.printStackTrace();
}
}
public void importSignatureStatus(int status) {
try {
mEMVOptV2.importSignatureStatus(status);
} catch (Exception e) {
e.printStackTrace();
}
}
public void mockOnlineProcess() {
new Thread(() -> {
try {
Thread.sleep(2000);
try {
String[] tags = {"71", "72", "91", "8A", "89"};
String[] values = {"", "", "", "", ""};
byte[] out = new byte[1024];
int len = mEMVOptV2.importOnlineProcStatus(0, tags, values, out);
if (len < 0) {
Log.e(TAG, "importOnlineProcessStatus error,code:" + len);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
// Getters
public String getCardNo() {
return mCardNo;
}
public int getCardType() {
return mCardType;
}
public int getPinType() {
return mPinType;
}
// Helper Methods
public String maskCardNumber(String cardNo) {
if (cardNo == null || cardNo.length() < 8) {
return cardNo;
}
String first4 = cardNo.substring(0, 4);
String last4 = cardNo.substring(cardNo.length() - 4);
StringBuilder middle = new StringBuilder();
for (int i = 0; i < cardNo.length() - 8; i++) {
middle.append("*");
}
return first4 + middle.toString() + last4;
}
public String[] getCandidateNames(List<EMVCandidateV2> candiList) {
if (candiList == null || candiList.size() == 0) return new String[0];
String[] result = new String[candiList.size()];
for (int i = 0; i < candiList.size(); i++) {
EMVCandidateV2 candi = candiList.get(i);
String name = candi.appPreName;
name = TextUtils.isEmpty(name) ? candi.appLabel : name;
name = TextUtils.isEmpty(name) ? candi.appName : name;
name = TextUtils.isEmpty(name) ? "" : name;
result[i] = name;
}
return result;
}
// EMV Listener
private final EMVListenerV2 mEMVListener = new EMVListenerV2.Stub() {
@Override
public void onWaitAppSelect(List<EMVCandidateV2> appNameList, boolean isFirstSelect) throws RemoteException {
Log.d(TAG, "onWaitAppSelect isFirstSelect:" + isFirstSelect);
mProcessStep = 1; // EMV_APP_SELECT
String[] candidateNames = getCandidateNames(appNameList);
if (callback != null) {
callback.onAppSelect(candidateNames);
}
}
@Override
public void onAppFinalSelect(String tag9F06Value) throws RemoteException {
Log.d(TAG, "onAppFinalSelect tag9F06Value:" + tag9F06Value);
mProcessStep = 2; // EMV_FINAL_APP_SELECT
if (callback != null) {
callback.onFinalAppSelect();
}
}
@Override
public void onConfirmCardNo(String cardNo) throws RemoteException {
Log.d(TAG, "onConfirmCardNo cardNo:" + maskCardNumber(cardNo));
mCardNo = cardNo;
mProcessStep = 3; // EMV_CONFIRM_CARD_NO
if (callback != null) {
callback.onConfirmCardNo(cardNo);
}
}
@Override
public void onRequestShowPinPad(int pinType, int remainTime) throws RemoteException {
Log.d(TAG, "onRequestShowPinPad pinType:" + pinType + " remainTime:" + remainTime);
mPinType = pinType;
mProcessStep = 5; // EMV_SHOW_PIN_PAD
if (callback != null) {
callback.onShowPinPad(pinType);
}
}
@Override
public void onRequestSignature() throws RemoteException {
Log.d(TAG, "onRequestSignature");
mProcessStep = 7; // EMV_SIGNATURE
if (callback != null) {
callback.onSignature();
}
}
@Override
public void onCertVerify(int certType, String certInfo) throws RemoteException {
Log.d(TAG, "onCertVerify certType:" + certType + " certInfo:" + certInfo);
mCertInfo = certInfo;
mProcessStep = 4; // EMV_CERT_VERIFY
if (callback != null) {
callback.onCertVerify(certInfo);
}
}
@Override
public void onOnlineProc() throws RemoteException {
Log.d(TAG, "onOnlineProcess");
mProcessStep = 6; // EMV_ONLINE_PROCESS
if (callback != null) {
callback.onOnlineProcess();
}
}
@Override
public void onCardDataExchangeComplete() throws RemoteException {
Log.d(TAG, "onCardDataExchangeComplete");
if (mCardType == AidlConstantsV2.CardType.NFC.getValue()) {
MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0);
}
}
@Override
public void onTransResult(int code, String desc) throws RemoteException {
Log.d(TAG, "onTransResult code:" + code + " desc:" + desc);
if (code == 1 || code == 2 || code == 5 || code == 6) {
if (callback != null) {
callback.onTransactionSuccess(code, desc);
}
} else {
if (callback != null) {
callback.onTransactionFailed(code, desc);
}
}
}
@Override
public void onConfirmationCodeVerified() throws RemoteException {
Log.d(TAG, "onConfirmationCodeVerified");
}
@Override
public void onRequestDataExchange(String cardNo) throws RemoteException {
Log.d(TAG, "onRequestDataExchange,cardNo:" + cardNo);
mEMVOptV2.importDataExchangeStatus(0);
}
@Override
public void onTermRiskManagement() throws RemoteException {
Log.d(TAG, "onTermRiskManagement");
mEMVOptV2.importTermRiskManagementStatus(0);
}
@Override
public void onPreFirstGenAC() throws RemoteException {
Log.d(TAG, "onPreFirstGenAC");
mEMVOptV2.importPreFirstGenACStatus(0);
}
@Override
public void onDataStorageProc(String[] containerID, String[] containerContent) throws RemoteException {
Log.d(TAG, "onDataStorageProc");
String[] tags = new String[0];
String[] values = new String[0];
mEMVOptV2.importDataStorage(tags, values);
}
};
}

View File

@ -0,0 +1,121 @@
package com.example.bdkipoc.transaction.managers;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.util.Log;
import com.example.bdkipoc.R;
/**
* ModalManager - Handles modal UI operations
*/
public class ModalManager {
private static final String TAG = "ModalManager";
private FrameLayout modalOverlay;
private TextView modalText;
private ImageView modalIcon;
private Animation fadeIn;
private Animation fadeOut;
private boolean isModalShowing = false;
public ModalManager(FrameLayout modalOverlay, TextView modalText, ImageView modalIcon) {
this.modalOverlay = modalOverlay;
this.modalText = modalText;
this.modalIcon = modalIcon;
initAnimations();
}
private void initAnimations() {
fadeIn = AnimationUtils.loadAnimation(modalOverlay.getContext(), android.R.anim.fade_in);
fadeOut = AnimationUtils.loadAnimation(modalOverlay.getContext(), android.R.anim.fade_out);
fadeIn.setDuration(300);
fadeOut.setDuration(300);
}
public void showScanCardModal() {
if (isModalShowing) return;
modalOverlay.post(() -> {
modalText.setText("Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat");
modalIcon.setImageResource(R.drawable.ic_card_insert);
modalOverlay.setVisibility(View.VISIBLE);
modalOverlay.startAnimation(fadeIn);
isModalShowing = true;
Log.d(TAG, "Modal scan card shown");
});
}
public void showProcessingModal(String message) {
if (!isModalShowing) {
modalOverlay.post(() -> {
modalText.setText(message);
modalIcon.setImageResource(R.drawable.ic_card_insert);
modalOverlay.setVisibility(View.VISIBLE);
modalOverlay.startAnimation(fadeIn);
isModalShowing = true;
Log.d(TAG, "Modal processing shown: " + message);
});
} else {
// Just update text if modal already showing
modalOverlay.post(() -> {
modalText.setText(message);
Log.d(TAG, "Modal text updated: " + message);
});
}
}
public void hideModal() {
if (!isModalShowing) return;
modalOverlay.post(() -> {
fadeOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
modalOverlay.setVisibility(View.GONE);
isModalShowing = false;
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
modalOverlay.startAnimation(fadeOut);
Log.d(TAG, "Modal hidden");
});
}
public boolean isShowing() {
return isModalShowing;
}
public void updateText(String text) {
if (isModalShowing) {
modalOverlay.post(() -> {
modalText.setText(text);
Log.d(TAG, "Modal text updated: " + text);
});
}
}
public void updateIcon(int iconResource) {
if (isModalShowing) {
modalOverlay.post(() -> {
modalIcon.setImageResource(iconResource);
Log.d(TAG, "Modal icon updated");
});
}
}
}

View File

@ -0,0 +1,142 @@
package com.example.bdkipoc.transaction.managers;
import android.os.RemoteException;
import android.util.Log;
import com.example.bdkipoc.MyApplication;
import com.example.bdkipoc.utils.ByteUtil;
import com.sunmi.pay.hardware.aidlv2.AidlErrorCodeV2;
import com.sunmi.pay.hardware.aidlv2.bean.PinPadConfigV2;
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadListenerV2;
import com.sunmi.pay.hardware.aidlv2.pinpad.PinPadOptV2;
/**
* PinPadManager - Handles PIN pad operations
*/
public class PinPadManager {
private static final String TAG = "PinPadManager";
private PinPadOptV2 mPinPadOptV2;
private PinPadManagerCallback callback;
public interface PinPadManagerCallback {
void onPinInputLength(int length);
void onPinInputConfirmed(byte[] pinBlock);
void onPinInputCancelled();
void onPinInputError(int code, String message);
}
public PinPadManager(PinPadManagerCallback callback) {
this.callback = callback;
initPinPadComponents();
}
private void initPinPadComponents() {
if (MyApplication.app != null) {
mPinPadOptV2 = MyApplication.app.pinPadOptV2;
Log.d(TAG, "PIN Pad components initialized");
} else {
Log.e(TAG, "MyApplication.app is null");
}
}
public void initPinPad(String cardNo, int pinType) {
Log.d(TAG, "========== PIN PAD INITIALIZATION ==========");
try {
if (mPinPadOptV2 == null) {
throw new IllegalStateException("PIN Pad service not available");
}
if (cardNo == null || cardNo.length() < 13) {
throw new IllegalArgumentException("Invalid card number for PIN");
}
PinPadConfigV2 pinPadConfig = new PinPadConfigV2();
pinPadConfig.setPinPadType(0);
pinPadConfig.setPinType(pinType);
pinPadConfig.setOrderNumKey(true); // Set to true for normal order, false for random
String panForPin = cardNo.substring(cardNo.length() - 13, cardNo.length() - 1);
byte[] panBytes = panForPin.getBytes("US-ASCII");
pinPadConfig.setPan(panBytes);
pinPadConfig.setTimeout(60 * 1000);
pinPadConfig.setPinKeyIndex(12);
pinPadConfig.setMaxInput(12);
pinPadConfig.setMinInput(0);
pinPadConfig.setKeySystem(0);
pinPadConfig.setAlgorithmType(0);
Log.d(TAG, "Initializing PIN pad with config");
mPinPadOptV2.initPinPad(pinPadConfig, mPinPadListener);
} catch (Exception e) {
Log.e(TAG, "PIN pad initialization failed: " + e.getMessage());
if (callback != null) {
callback.onPinInputError(-1, "PIN Error: " + e.getMessage());
}
}
}
public void cancelPinInput() {
try {
if (mPinPadOptV2 != null) {
// Cancel PIN input if needed
Log.d(TAG, "PIN input cancelled");
}
} catch (Exception e) {
Log.e(TAG, "Error cancelling PIN input: " + e.getMessage());
}
}
// PIN Pad Listener
private final PinPadListenerV2 mPinPadListener = new PinPadListenerV2.Stub() {
@Override
public void onPinLength(int len) throws RemoteException {
Log.d(TAG, "PIN input length: " + len);
if (callback != null) {
callback.onPinInputLength(len);
}
}
@Override
public void onConfirm(int i, byte[] pinBlock) throws RemoteException {
Log.d(TAG, "PIN input confirmed");
if (pinBlock != null) {
String hexStr = ByteUtil.bytes2HexStr(pinBlock);
Log.d(TAG, "PIN block received: " + hexStr);
if (callback != null) {
callback.onPinInputConfirmed(pinBlock);
}
} else {
Log.d(TAG, "PIN bypass confirmed");
if (callback != null) {
callback.onPinInputConfirmed(null);
}
}
}
@Override
public void onCancel() throws RemoteException {
Log.d(TAG, "PIN input cancelled by user");
if (callback != null) {
callback.onPinInputCancelled();
}
}
@Override
public void onError(int code) throws RemoteException {
Log.e(TAG, "PIN pad error: " + code);
String msg = AidlErrorCodeV2.valueOf(code).getMsg();
if (callback != null) {
callback.onPinInputError(code, msg);
}
}
@Override
public void onHover(int event, byte[] data) throws RemoteException {
Log.d(TAG, "PIN pad hover event: " + event);
}
};
}

View File

@ -0,0 +1,358 @@
package com.example.bdkipoc.transaction.managers;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.UUID;
/**
* PostTransactionBackendManager - Handles backend transaction posting
*
* This manager handles the communication with the backend service for transaction posting
* and provides the transaction_uuid needed for Midtrans integration.
*/
public class PostTransactionBackendManager {
private static final String TAG = "PostTransactionBackend";
// Backend Configuration
private static final String BACKEND_BASE_URL = "https://be-edc.msvc.app";
private static final String TRANSACTIONS_ENDPOINT = BACKEND_BASE_URL + "/transactions";
// Default values
private static final String DEFAULT_DEVICE_CODE = "PB4K252T00021";
private static final int DEFAULT_DEVICE_ID = 1;
private static final String DEFAULT_CASHFLOW = "MONEY_IN";
private static final String DEFAULT_CHANNEL_CATEGORY = "RETAIL_OUTLET";
// NEW: Static merchant data
private static final String DEFAULT_MERCHANT_NAME = "BUDIAJAIB123";
private static final String DEFAULT_MID = "542531513";
private static final String DEFAULT_TID = "535151521";
private Context context;
private PostTransactionCallback callback;
public interface PostTransactionCallback {
void onPostTransactionSuccess(JSONObject response, String transactionUuid);
void onPostTransactionError(String errorMessage);
void onPostTransactionProgress(String message);
}
public PostTransactionBackendManager(Context context, PostTransactionCallback callback) {
this.context = context;
this.callback = callback;
}
/**
* Post transaction to backend service
*/
public void postTransaction(String paymentType, String referenceId, long amount, String status) {
String channelCode = mapPaymentTypeToChannelCode(paymentType);
String transactionUuid = generateUUID();
Log.d(TAG, "=== POSTING TRANSACTION TO BACKEND ===");
Log.d(TAG, "Payment Type: " + paymentType);
Log.d(TAG, "Channel Code: " + channelCode);
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Status: " + status);
Log.d(TAG, "Transaction UUID: " + transactionUuid);
Log.d(TAG, "=====================================");
if (callback != null) {
callback.onPostTransactionProgress("Posting transaction to backend...");
}
new PostTransactionTask(paymentType, channelCode, referenceId, amount, status, transactionUuid).execute();
}
/**
* Post transaction with INIT status (for pre-authorization)
*/
public void postInitTransaction(String paymentType, String referenceId, long amount) {
postTransaction(paymentType, referenceId, amount, "INIT");
}
/**
* Post transaction with SUCCESS status (for completed transactions)
*/
public void postSuccessTransaction(String paymentType, String referenceId, long amount) {
postTransaction(paymentType, referenceId, amount, "SUCCESS");
}
/**
* Update existing transaction status
*/
public void updateTransactionStatus(String transactionUuid, String newStatus) {
Log.d(TAG, "Updating transaction " + transactionUuid + " to status: " + newStatus);
// TODO: Implement update endpoint if available
// For now, we'll create a new transaction with updated status
}
/**
* Map payment type to channel code for backend
*/
private String mapPaymentTypeToChannelCode(String paymentType) {
if (paymentType == null) {
return "CREDIT_CARD"; // Default
}
switch (paymentType.toLowerCase()) {
case "credit_card":
return "CREDIT_CARD";
case "debit_card":
return "DEBIT_CARD";
case "e_money":
return "E_MONEY";
case "qris":
return "QRIS";
default:
Log.w(TAG, "Unknown payment type: " + paymentType + ", using CREDIT_CARD");
return "CREDIT_CARD";
}
}
/**
* Generate UUID v4 for transaction
*/
private String generateUUID() {
return UUID.randomUUID().toString();
}
/**
* Get device serial number (Sunmi device code)
*/
private String getDeviceCode() {
try {
// Try to get actual device serial number
// For Sunmi devices, this might be available through system properties
String serialNumber = android.os.Build.SERIAL;
if (serialNumber != null && !serialNumber.equals("unknown") && !serialNumber.equals(android.os.Build.UNKNOWN)) {
return serialNumber;
}
} catch (Exception e) {
Log.w(TAG, "Could not get device serial number: " + e.getMessage());
}
// Fallback to default device code
return DEFAULT_DEVICE_CODE;
}
/**
* AsyncTask for posting transaction to backend
*/
private class PostTransactionTask extends AsyncTask<Void, Void, Boolean> {
private String paymentType;
private String channelCode;
private String referenceId;
private long amount;
private String status;
private String transactionUuid;
private String errorMessage;
private JSONObject responseData;
public PostTransactionTask(String paymentType, String channelCode, String referenceId,
long amount, String status, String transactionUuid) {
this.paymentType = paymentType;
this.channelCode = channelCode;
this.referenceId = referenceId;
this.amount = amount;
this.status = status;
this.transactionUuid = transactionUuid;
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
// Build transaction payload
JSONObject payload = buildTransactionPayload();
Log.d(TAG, "Backend payload: " + payload.toString());
// Make HTTP request
return makeBackendRequest(payload);
} catch (Exception e) {
Log.e(TAG, "Backend transaction exception: " + e.getMessage(), e);
errorMessage = "Backend transaction error: " + e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success && responseData != null && callback != null) {
try {
// Extract transaction_uuid from response
JSONObject data = responseData.optJSONObject("data");
String returnedUuid = null;
if (data != null) {
returnedUuid = data.optString("transaction_uuid", transactionUuid);
}
Log.d(TAG, "✅ Backend transaction successful!");
Log.d(TAG, "Original UUID: " + transactionUuid);
Log.d(TAG, "Returned UUID: " + returnedUuid);
callback.onPostTransactionSuccess(responseData, returnedUuid != null ? returnedUuid : transactionUuid);
} catch (Exception e) {
Log.e(TAG, "Error processing backend response: " + e.getMessage());
if (callback != null) {
callback.onPostTransactionError("Error processing backend response: " + e.getMessage());
}
}
} else if (callback != null) {
callback.onPostTransactionError(errorMessage != null ? errorMessage : "Unknown backend error");
}
}
private JSONObject buildTransactionPayload() throws JSONException {
JSONObject payload = new JSONObject();
// Required fields
payload.put("type", "PAYMENT");
payload.put("channel_category", DEFAULT_CHANNEL_CATEGORY);
payload.put("channel_code", channelCode);
payload.put("reference_id", referenceId);
payload.put("amount", amount);
payload.put("cashflow", DEFAULT_CASHFLOW);
payload.put("status", status);
payload.put("device_id", DEFAULT_DEVICE_ID);
payload.put("transaction_uuid", transactionUuid);
payload.put("transaction_time_seconds", 2.2); // Default value as mentioned
payload.put("device_code", getDeviceCode());
// NEW: Static merchant data (no longer null)
payload.put("merchant_name", DEFAULT_MERCHANT_NAME);
payload.put("mid", DEFAULT_MID);
payload.put("tid", DEFAULT_TID);
return payload;
}
private Boolean makeBackendRequest(JSONObject payload) {
try {
URL url = new URI(TRANSACTIONS_ENDPOINT).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set request properties
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "*/*");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
// Send payload
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
// Get response
int responseCode = conn.getResponseCode();
Log.d(TAG, "Backend response code: " + responseCode);
BufferedReader br;
StringBuilder response = new StringBuilder();
String responseLine;
if (responseCode >= 200 && responseCode < 300) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
}
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
String responseString = response.toString();
Log.d(TAG, "Backend response: " + responseString);
// Parse response
try {
responseData = new JSONObject(responseString);
// Check if response indicates success
int status = responseData.optInt("status", 0);
String message = responseData.optString("message", "");
if (status == 200 && "Successfully".equals(message)) {
return true;
} else {
errorMessage = "Backend error: " + message + " (Status: " + status + ")";
return false;
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing backend response: " + e.getMessage());
errorMessage = "Invalid backend response format";
return false;
}
} catch (Exception e) {
Log.e(TAG, "Backend request exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
return false;
}
}
}
/**
* Utility method to generate reference ID
*/
public static String generateReferenceId() {
return "ref" + System.currentTimeMillis() + (int)(Math.random() * 10000);
}
/**
* Utility method to map card menu ID to payment type
*/
public static String mapCardMenuToPaymentType(int cardMenuId) {
// Based on MainActivity.java card IDs
switch (cardMenuId) {
case 2131296346: // R.id.card_kartu_kredit
return "credit_card";
case 2131296344: // R.id.card_kartu_debit
return "debit_card";
case 2131296360: // R.id.card_uang_elektronik
return "e_money";
case 2131296352: // R.id.card_qris
return "qris";
default:
Log.w(TAG, "Unknown card menu ID: " + cardMenuId + ", defaulting to credit_card");
return "credit_card";
}
}
/**
* Debug method to log transaction details
*/
public void debugTransactionData(String paymentType, String referenceId, long amount, String status) {
Log.d(TAG, "=== TRANSACTION DEBUG INFO ===");
Log.d(TAG, "Payment Type: " + paymentType);
Log.d(TAG, "Channel Code: " + mapPaymentTypeToChannelCode(paymentType));
Log.d(TAG, "Reference ID: " + referenceId);
Log.d(TAG, "Amount: " + amount);
Log.d(TAG, "Status: " + status);
Log.d(TAG, "Device Code: " + getDeviceCode());
Log.d(TAG, "Device ID: " + DEFAULT_DEVICE_ID);
Log.d(TAG, "Merchant Name: " + DEFAULT_MERCHANT_NAME);
Log.d(TAG, "MID: " + DEFAULT_MID);
Log.d(TAG, "TID: " + DEFAULT_TID);
Log.d(TAG, "==============================");
}
}

View File

@ -0,0 +1,265 @@
package com.example.bdkipoc.utils;
import android.text.TextUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public class ByteUtil {
/** 打印内容 */
public static String byte2PrintHex(byte[] raw, int offset, int count) {
if (raw == null) {
return null;
}
if (offset < 0 || offset > raw.length) {
offset = 0;
}
int end = offset + count;
if (end > raw.length) {
end = raw.length;
}
StringBuilder hex = new StringBuilder();
for (int i = offset; i < end; i++) {
int v = raw[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
hex.append(0);
}
hex.append(hv);
hex.append(" ");
}
if (hex.length() > 0) {
hex.deleteCharAt(hex.length() - 1);
}
return hex.toString().toUpperCase();
}
/**
* 将字节数组转换成16进制字符串
*
* @param bytes 源字节数组
* @return 转换后的16进制字符串
*/
public static String bytes2HexStr(byte... bytes) {
if (bytes == null || bytes.length == 0) {
return "";
}
return bytes2HexStr(bytes, 0, bytes.length);
}
/**
* 将字节数组转换成16进制字符串
*
* @param src 源字节数组
* @param offset 偏移量
* @param len 数据长度
* @return 转换后的16进制字符串
*/
public static String bytes2HexStr(byte[] src, int offset, int len) {
int end = offset + len;
if (src == null || src.length == 0 || offset < 0 || len < 0 || end > src.length) {
return "";
}
byte[] buffer = new byte[len * 2];
int h = 0, l = 0;
for (int i = offset, j = 0; i < end; i++) {
h = src[i] >> 4 & 0x0f;
l = src[i] & 0x0f;
buffer[j++] = (byte) (h > 9 ? h - 10 + 'A' : h + '0');
buffer[j++] = (byte) (l > 9 ? l - 10 + 'A' : l + '0');
}
return new String(buffer);
}
public static byte[] hexStr2Bytes(String hexStr) {
if (TextUtils.isEmpty(hexStr)) {
return new byte[0];
}
int length = hexStr.length() / 2;
char[] chars = hexStr.toCharArray();
byte[] b = new byte[length];
for (int i = 0; i < length; i++) {
b[i] = (byte) (char2Byte(chars[i * 2]) << 4 | char2Byte(chars[i * 2 + 1]));
}
return b;
}
public static byte hexStr2Byte(String hexStr) {
return (byte) Integer.parseInt(hexStr, 16);
}
public static String hexStr2Str(String hexStr) {
String vi = "0123456789ABC DEF".trim();
char[] array = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int temp;
for (int i = 0; i < bytes.length; i++) {
char c = array[2 * i];
temp = vi.indexOf(c) * 16;
c = array[2 * i + 1];
temp += vi.indexOf(c);
bytes[i] = (byte) (temp & 0xFF);
}
return new String(bytes);
}
public static String hexStr2AsciiStr(String hexStr) {
String vi = "0123456789ABC DEF".trim();
hexStr = hexStr.trim().replace(" ", "").toUpperCase(Locale.US);
char[] array = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int temp = 0x00;
for (int i = 0; i < bytes.length; i++) {
char c = array[2 * i];
temp = vi.indexOf(c) << 4;
c = array[2 * i + 1];
temp |= vi.indexOf(c);
bytes[i] = (byte) (temp & 0xFF);
}
return new String(bytes);
}
/**
* 将无符号short转换成int大端模式(高位在前)
*/
public static int unsignedShort2IntBE(byte[] src, int offset) {
return (src[offset] & 0xff) << 8 | (src[offset + 1] & 0xff);
}
/**
* 将无符号short转换成int小端模式(低位在前)
*/
public static int unsignedShort2IntLE(byte[] src, int offset) {
return (src[offset] & 0xff) | (src[offset + 1] & 0xff) << 8;
}
/**
* 将无符号byte转换成int
*/
public static int unsignedByte2Int(byte[] src, int offset) {
return src[offset] & 0xFF;
}
/**
* 将字节数组转换成int,大端模式(高位在前)
*/
public static int unsignedInt2IntBE(byte[] src, int offset) {
int result = 0;
for (int i = offset; i < offset + 4; i++) {
result |= (src[i] & 0xff) << (offset + 3 - i) * 8;
}
return result;
}
/**
* 将字节数组转换成int,小端模式(低位在前)
*/
public static int unsignedInt2IntLE(byte[] src, int offset) {
int value = 0;
for (int i = offset; i < offset + 4; i++) {
value |= (src[i] & 0xff) << (i - offset) * 8;
}
return value;
}
/**
* 将int转换成byte数组大端模式(高位在前)
*/
public static byte[] int2BytesBE(int src) {
byte[] result = new byte[4];
for (int i = 0; i < 4; i++) {
result[i] = (byte) (src >> (3 - i) * 8);
}
return result;
}
/**
* 将int转换成byte数组小端模式(低位在前)
*/
public static byte[] int2BytesLE(int src) {
byte[] result = new byte[4];
for (int i = 0; i < 4; i++) {
result[i] = (byte) (src >> i * 8);
}
return result;
}
/**
* 将short转换成byte数组大端模式(高位在前)
*/
public static byte[] short2BytesBE(short src) {
byte[] result = new byte[2];
for (int i = 0; i < 2; i++) {
result[i] = (byte) (src >> (1 - i) * 8);
}
return result;
}
/**
* 将short转换成byte数组小端模式(低位在前)
*/
public static byte[] short2BytesLE(short src) {
byte[] result = new byte[2];
for (int i = 0; i < 2; i++) {
result[i] = (byte) (src >> i * 8);
}
return result;
}
/**
* 将字节数组列表合并成单个字节数组
*/
public static byte[] concatByteArrays(byte[]... list) {
if (list == null || list.length == 0) {
return new byte[0];
}
return concatByteArrays(Arrays.asList(list));
}
/**
* 将字节数组列表合并成单个字节数组
*/
public static byte[] concatByteArrays(List<byte[]> list) {
if (list == null || list.isEmpty()) {
return new byte[0];
}
int totalLen = 0;
for (byte[] b : list) {
if (b == null || b.length == 0) {
continue;
}
totalLen += b.length;
}
byte[] result = new byte[totalLen];
int index = 0;
for (byte[] b : list) {
if (b == null || b.length == 0) {
continue;
}
System.arraycopy(b, 0, result, index, b.length);
index += b.length;
}
return result;
}
/**
* Convert char to byte
*
* @param c char
* @return byte
*/
private static int char2Byte(char c) {
if (c >= 'a') {
return (c - 'a' + 10) & 0x0f;
}
if (c >= 'A') {
return (c - 'A' + 10) & 0x0f;
}
return (c - '0') & 0x0f;
}
}

View File

@ -0,0 +1,89 @@
package com.example.bdkipoc.utils;
import android.text.TextUtils;
import android.util.Log;
public class LogUtil {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static int LEVEL = VERBOSE;
public static void setLevel(int Level) {
LEVEL = Level;
}
public static void v(String TAG, String msg) {
if (LEVEL <= VERBOSE && !TextUtils.isEmpty(msg)) {
MyLog(VERBOSE, TAG, msg);
}
}
public static void d(String TAG, String msg) {
if (LEVEL <= DEBUG && !TextUtils.isEmpty(msg)) {
MyLog(DEBUG, TAG, msg);
}
}
public static void i(String TAG, String msg) {
if (LEVEL <= INFO && !TextUtils.isEmpty(msg)) {
MyLog(INFO, TAG, msg);
}
}
public static void w(String TAG, String msg) {
if (LEVEL <= WARN && !TextUtils.isEmpty(msg)) {
MyLog(WARN, TAG, msg);
}
}
public static void e(String TAG, String msg) {
if (LEVEL <= ERROR && !TextUtils.isEmpty(msg)) {
MyLog(ERROR, TAG, msg);
}
}
private static void MyLog(int type, String TAG, String msg) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
int index = 4;
String className = stackTrace[index].getFileName();
String methodName = stackTrace[index].getMethodName();
int lineNumber = stackTrace[index].getLineNumber();
methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[ (")
.append(className)
.append(":")
.append(lineNumber)
.append(")#")
.append(methodName)
.append(" ] ");
stringBuilder.append(msg);
String logStr = stringBuilder.toString();
switch (type) {
case VERBOSE:
Log.v(TAG, logStr);
break;
case DEBUG:
Log.d(TAG, logStr);
break;
case INFO:
Log.i(TAG, logStr);
break;
case WARN:
Log.w(TAG, logStr);
break;
case ERROR:
Log.e(TAG, logStr);
break;
default:
break;
}
}
}

View File

@ -0,0 +1,107 @@
package com.example.bdkipoc.utils;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import com.example.bdkipoc.MyApplication;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
public final class Utility {
private Utility() {
throw new AssertionError("Create instance of Utility is forbidden.");
}
/** Bundle对象转换成字符串 */
public static String bundle2String(Bundle bundle) {
return bundle2String(bundle, 1);
}
/**
* 根据key排序后将Bundle内容拼接成字符串
*
* @param bundle 要处理的bundle
* @param order 排序规则0-不排序1-升序2-降序
* @return 拼接后的字符串
*/
public static String bundle2String(Bundle bundle, int order) {
if (bundle == null || bundle.keySet().isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
List<String> list = new ArrayList<>(bundle.keySet());
if (order == 1) { //升序
Collections.sort(list, String::compareTo);
} else if (order == 2) {//降序
Collections.sort(list, Collections.reverseOrder());
}
for (String key : list) {
sb.append(key);
sb.append(":");
Object value = bundle.get(key);
if (value instanceof byte[]) {
sb.append(ByteUtil.bytes2HexStr((byte[]) value));
} else {
sb.append(value);
}
sb.append("\n");
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/** 将null转换成空串 */
public static String null2String(String str) {
return str == null ? "" : str;
}
public static String formatStr(String format, Object... params) {
return String.format(Locale.ENGLISH, format, params);
}
/** check whether src is hex format */
public static boolean checkHexValue(String src) {
return Pattern.matches("[0-9a-fA-F]+", src);
}
/** 显示Toast */
public static void showToast(final String msg) {
Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> Toast.makeText(MyApplication.app, msg, Toast.LENGTH_SHORT).show());
}
/** 显示Toast */
public static void showToast(int resId) {
showToast(MyApplication.app.getString(resId));
}
/** 根据结果码获取成功失败信息 */
public static String getStateString(int code) {
return code == 0 ? "success" : "failed, code:" + code;
}
/** 根据结果状态获取成功失败信息 */
public static String getStateString(boolean state) {
return state ? "success" : "failed";
}
/** 将dp转成px */
public static int dp2px(int dp) {
float density = MyApplication.app.getResources().getDisplayMetrics().density;
return Math.round(dp * density);
}
/** 将px转成dp */
public static int px2dp(int px) {
float density = MyApplication.app.getResources().getDisplayMetrics().density;
return Math.round(px / density);
}
}

View File

@ -0,0 +1,44 @@
package com.example.bdkipoc.wrapper;
import android.os.Bundle;
import android.os.RemoteException;
import com.sunmi.pay.hardware.aidlv2.readcard.CheckCardCallbackV2;
public class CheckCardCallbackV2Wrapper extends CheckCardCallbackV2.Stub {
@Override
public void findMagCard(Bundle info) throws RemoteException {
}
@Override
public void findICCard(String atr) throws RemoteException {
}
@Override
public void findRFCard(String uuid) throws RemoteException {
}
@Override
public void onError(int code, String message) throws RemoteException {
}
@Override
public void findICCardEx(Bundle info) throws RemoteException {
}
@Override
public void findRFCardEx(Bundle info) throws RemoteException {
}
@Override
public void onErrorEx(Bundle info) throws RemoteException {
}
}

View File

@ -1,4 +1,4 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android" <alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300" android:duration="300"
android:fromAlpha="0.0" android:fromAlpha="0.0"
android:toAlpha="1.0" /> android:toAlpha="1.0" />

View File

@ -0,0 +1,10 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="0%p"
android:toYDelta="50%p" />
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>

View File

@ -0,0 +1,10 @@
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="50%p"
android:toYDelta="0%p" />
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F0F0F0"/>
<corners android:radius="8dp"/>
<stroke android:width="1dp" android:color="#E0E0E0"/>
</shape>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="2dp" android:color="#E31937"/>
<corners android:radius="8dp"/>
<solid android:color="@android:color/transparent"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4V8h-2V7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1H9v2h2v1z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,7c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,19L6,19v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1L18,19z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M16,17.01V10h-2v7.01h-3L15,20l4,-3h-3zM9,3L5,6h3v7.01h2V6h3L9,3z"/>
</vector>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F0F0F0"/>
<corners android:radius="24dp"/>
<stroke android:width="1dp" android:color="#E0E0E0"/>
</shape>

View File

@ -8,4 +8,8 @@
app:font="@font/inter_medium" app:font="@font/inter_medium"
app:fontWeight="500" app:fontWeight="500"
app:fontStyle="normal"/> app:fontStyle="normal"/>
<font
app:font="@font/inter_bold"
app:fontWeight="700"
app:fontStyle="normal"/>
</font-family> </font-family>

Binary file not shown.

View File

@ -0,0 +1,328 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".transaction.CreateTransactionActivity">
<!-- Main Content -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#FFFFFF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/component_appbar" />
<!-- Payment Card - Positioned to overlap with red header -->
<androidx.cardview.widget.CardView
android:id="@+id/paymentCard"
android:layout_width="match_parent"
android:layout_height="191dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="-85dp"
app:cardBackgroundColor="#3498DB"
app:cardCornerRadius="16dp"
app:cardElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:paddingTop="32dp">
<!-- Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TOTAL PEMBAYARAN"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="20dp" />
<!-- Amount Input Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RP"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginEnd="8dp"
android:layout_gravity="bottom" />
<TextView
android:id="@+id/tv_amount_display"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/transparent"
android:textColor="@android:color/white"
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:text=""
android:gravity="start"
android:paddingBottom="4dp" />
</LinearLayout>
<!-- White Underline -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@android:color/white"
android:layout_marginBottom="12dp" />
<!-- Description -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pastikan kembali nominal pembayaran pelanggan Anda"
android:textColor="@android:color/white"
android:textSize="10sp"
android:fontFamily="@font/inter"
android:textStyle="normal"
android:alpha="0.9" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Numpad -->
<GridLayout
android:id="@+id/numpad_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_marginTop="32dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<!-- Row 1: 1, 2, 3 -->
<TextView
android:id="@+id/btn_1"
style="@style/NumpadButton"
android:text="1" />
<TextView
android:id="@+id/btn_2"
style="@style/NumpadButton"
android:text="2" />
<TextView
android:id="@+id/btn_3"
style="@style/NumpadButton"
android:text="3" />
<!-- Row 2: 4, 5, 6 -->
<TextView
android:id="@+id/btn_4"
style="@style/NumpadButton"
android:text="4" />
<TextView
android:id="@+id/btn_5"
style="@style/NumpadButton"
android:text="5" />
<TextView
android:id="@+id/btn_6"
style="@style/NumpadButton"
android:text="6" />
<!-- Row 3: 7, 8, 9 -->
<TextView
android:id="@+id/btn_7"
style="@style/NumpadButton"
android:text="7" />
<TextView
android:id="@+id/btn_8"
style="@style/NumpadButton"
android:text="8" />
<TextView
android:id="@+id/btn_9"
style="@style/NumpadButton"
android:text="9" />
<!-- Row 4: 000, 0, Delete -->
<TextView
android:id="@+id/btn_00"
style="@style/NumpadButton"
android:text="000" />
<TextView
android:id="@+id/btn_0"
style="@style/NumpadButton"
android:text="0" />
<ImageView
android:id="@+id/btn_clear"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_columnWeight="1"
android:layout_margin="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_backspace"
android:scaleType="center"
android:contentDescription="Delete" />
</GridLayout>
<!-- Confirmation Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_confirm"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Konfirmasi"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="24dp"
app:backgroundTint="@null"
android:background="@drawable/button_confirm_background_selector"
android:enabled="false"
app:cornerRadius="8dp"
app:rippleColor="#B3000000" />
<!-- Hidden Components -->
<TextView
android:id="@+id/tv_mode_indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
<Button
android:id="@+id/btn_toggle_mode"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyle"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:visibility="gone"
android:indeterminateTint="#E31937" />
</LinearLayout>
</ScrollView>
<!-- Modal Overlay -->
<FrameLayout
android:id="@+id/modal_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:visibility="gone"
android:clickable="true"
android:focusable="true"
android:elevation="100dp">
<androidx.cardview.widget.CardView
android:id="@+id/modal_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="32dp"
app:cardCornerRadius="16dp"
app:cardElevation="20dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<ImageView
android:id="@+id/modal_icon"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_card_insert"
android:layout_marginBottom="24dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
app:tint="#E31937" />
<TextView
android:id="@+id/modal_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"
style="@style/StatusTextStyle"
android:textAlignment="center"
android:gravity="center"
android:lineSpacingExtra="4dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
<LinearLayout
android:id="@+id/success_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#E31937"
android:visibility="gone"
android:elevation="100dp">
<!-- Success Icon -->
<ImageView
android:id="@+id/success_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_success_payment"
android:layout_marginBottom="32dp"
android:scaleType="centerInside"/>
<!-- Success Message -->
<TextView
android:id="@+id/success_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pembayaran Berhasil"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:gravity="center"
android:letterSpacing="0.02"/>
</LinearLayout>
</FrameLayout>

View File

@ -216,7 +216,40 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Row 2: Uang Elektronik, Cetak Ulang, Settlement --> <!-- Row 2: Transfer, Uang Elektronik, Cetak Ulang -->
<androidx.cardview.widget.CardView
android:id="@+id/card_transfer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_transfer"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Transfer"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_uang_elektronik" android:id="@+id/card_uang_elektronik"
android:layout_width="0dp" android:layout_width="0dp"
@ -271,7 +304,7 @@
<ImageView <ImageView
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:src="@drawable/ic_reprint" android:src="@drawable/ic_print"
app:tint="#E31937"/> app:tint="#E31937"/>
<TextView <TextView
@ -283,6 +316,40 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Row 3: Refund, Settlement, Histori -->
<androidx.cardview.widget.CardView
android:id="@+id/card_refund"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_refund"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Refund"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_settlement" android:id="@+id/card_settlement"
android:layout_width="0dp" android:layout_width="0dp"
@ -290,6 +357,7 @@
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_margin="8dp" android:layout_margin="8dp"
android:visibility="visible"
app:cardCornerRadius="12dp" app:cardCornerRadius="12dp"
app:cardElevation="2dp" app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3"> app:cardBackgroundColor="#F3F4F3">
@ -316,7 +384,6 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Row 3: Histori, Bantuan, Info Toko -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_histori" android:id="@+id/card_histori"
android:layout_width="0dp" android:layout_width="0dp"
@ -324,6 +391,7 @@
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_margin="8dp" android:layout_margin="8dp"
android:visibility="visible"
app:cardCornerRadius="12dp" app:cardCornerRadius="12dp"
app:cardElevation="2dp" app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3"> app:cardBackgroundColor="#F3F4F3">
@ -350,6 +418,7 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Row 4: Bantuan, Info Toko, Pengaturan -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_bantuan" android:id="@+id/card_bantuan"
android:layout_width="0dp" android:layout_width="0dp"
@ -357,7 +426,7 @@
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_margin="8dp" android:layout_margin="8dp"
android:visibility="visible" android:visibility="gone"
app:cardCornerRadius="12dp" app:cardCornerRadius="12dp"
app:cardElevation="2dp" app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3"> app:cardBackgroundColor="#F3F4F3">
@ -391,7 +460,7 @@
android:layout_columnWeight="1" android:layout_columnWeight="1"
android:layout_rowWeight="1" android:layout_rowWeight="1"
android:layout_margin="8dp" android:layout_margin="8dp"
android:visibility="visible" android:visibility="gone"
app:cardCornerRadius="12dp" app:cardCornerRadius="12dp"
app:cardElevation="2dp" app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3"> app:cardBackgroundColor="#F3F4F3">
@ -418,9 +487,8 @@
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Row 4: Dummy Menu 1, 2, 3 (Hidden initially) -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_1" android:id="@+id/card_pengaturan"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_columnWeight="1" android:layout_columnWeight="1"
@ -441,189 +509,17 @@
<ImageView <ImageView
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:src="@drawable/ic_qr_code" android:src="@drawable/ic_settings"
app:tint="#E31937"/> app:tint="#E31937"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:text="Dummy Menu 1" android:text="Pengaturan"
style="@style/MenuCardTitle"/> style="@style/MenuCardTitle"/>
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 2"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 3"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Row 5: Dummy Menu 4, 5, 6 (Hidden initially) -->
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 4"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 5"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 6"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</GridLayout> </GridLayout>
<!-- Lainnya Button --> <!-- Lainnya Button -->

View File

@ -1,268 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#FFFFFF">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".PaymentActivity">
<!-- Red Status Bar (Override purple) -->
<View
android:id="@+id/red_status_bar"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<!-- Red Background Header (Extended height untuk back navigation) -->
<View
android:id="@+id/red_header_background"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="#E31937"
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
<!-- Back Navigation (Positioned closer to status bar/appbar) -->
<LinearLayout
android:id="@+id/back_navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginBottom="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
<!-- Back Arrow -->
<ImageView
android:id="@+id/backArrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_arrow_back"
android:contentDescription="Back" />
<!-- Title Text -->
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="12sp"
android:fontFamily="@font/inter"
android:textStyle="normal" />
</LinearLayout>
<!-- Payment Card -->
<androidx.cardview.widget.CardView
android:id="@+id/paymentCard"
android:layout_width="match_parent"
android:layout_height="191dp"
android:layout_margin="16dp"
android:layout_marginTop="5dp"
app:cardBackgroundColor="#3498DB"
app:cardCornerRadius="12dp"
app:cardElevation="8dp"
app:layout_constraintTop_toBottomOf="@id/back_navigation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<!-- Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TOTAL PEMBAYARAN"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="24dp" />
<!-- Amount Input Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RP"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginEnd="8dp"
android:layout_gravity="bottom" />
<EditText
android:id="@+id/editTextAmount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/transparent"
android:textColor="@android:color/white"
android:textColorHint="#80FFFFFF"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:hint=""
android:inputType="none"
android:focusable="false"
android:clickable="false"
android:cursorVisible="false"
android:text=""
android:gravity="start"
android:paddingBottom="4dp" />
</LinearLayout>
<!-- White Underline -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@android:color/white"
android:layout_marginBottom="16dp" />
<!-- Description -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pastikan kembali nominal pembayaran pelanggan Anda"
android:textColor="@android:color/white"
android:textSize="12sp"
android:fontFamily="@font/inter"
android:alpha="0.9" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Numpad -->
<GridLayout
android:id="@+id/numpad_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toBottomOf="@id/paymentCard">
<!-- Row 1: 1, 2, 3 -->
<TextView
android:id="@+id/btn1"
style="@style/NumpadButton"
android:text="1" />
<TextView
android:id="@+id/btn2"
style="@style/NumpadButton"
android:text="2" />
<TextView
android:id="@+id/btn3"
style="@style/NumpadButton"
android:text="3" />
<!-- Row 2: 4, 5, 6 -->
<TextView
android:id="@+id/btn4"
style="@style/NumpadButton"
android:text="4" />
<TextView
android:id="@+id/btn5"
style="@style/NumpadButton"
android:text="5" />
<TextView
android:id="@+id/btn6"
style="@style/NumpadButton"
android:text="6" />
<!-- Row 3: 7, 8, 9 -->
<TextView
android:id="@+id/btn7"
style="@style/NumpadButton"
android:text="7" />
<TextView
android:id="@+id/btn8"
style="@style/NumpadButton"
android:text="8" />
<TextView
android:id="@+id/btn9"
style="@style/NumpadButton"
android:text="9" />
<!-- Row 4: 000, 0, Delete -->
<TextView
android:id="@+id/btn000"
style="@style/NumpadButton"
android:text="000" />
<TextView
android:id="@+id/btn0"
style="@style/NumpadButton"
android:text="0" />
<ImageView
android:id="@+id/btnDelete"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_columnWeight="1"
android:layout_margin="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_backspace"
android:scaleType="center"
android:contentDescription="Delete" />
</GridLayout>
<!-- Confirmation Button (UPDATED: Menggunakan MaterialButton) -->
<com.google.android.material.button.MaterialButton
android:id="@+id/confirmButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Konfirmasi"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:enabled="false"
app:backgroundTint="#DE0701"
app:cornerRadius="8dp"
app:rippleColor="#B3000000"
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -1,298 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#F5F5F5">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".PinActivity">
<!-- Red Status Bar (Override purple) -->
<View
android:id="@+id/red_status_bar"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<!-- Red Background Header (Extended height untuk back navigation) -->
<View
android:id="@+id/red_header_background"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="#E31937"
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
<!-- Header with Back Navigation -->
<LinearLayout
android:id="@+id/back_navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginBottom="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
<!-- Back Arrow -->
<ImageView
android:id="@+id/backArrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_arrow_back"
android:contentDescription="Back" />
<!-- Title Text -->
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- PIN Card -->
<androidx.cardview.widget.CardView
android:id="@+id/pin_card"
android:layout_width="match_parent"
android:layout_height="191dp"
android:layout_margin="16dp"
android:layout_marginTop="5dp"
app:cardBackgroundColor="#3498DB"
app:cardCornerRadius="12dp"
app:cardElevation="8dp"
app:layout_constraintTop_toBottomOf="@id/back_navigation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<!-- Title Text -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SILAKAN MASUKAN PIN"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="24dp"
android:gravity="center"
android:textAlignment="center"/>
<!-- PIN Input Display -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginEnd="8dp"
android:layout_gravity="center_vertical" />
<EditText
android:id="@+id/editTextPin"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@android:color/transparent"
android:textColor="@android:color/white"
android:textColorHint="#80FFFFFF"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:hint=""
android:inputType="none"
android:focusable="false"
android:clickable="false"
android:cursorVisible="false"
android:text=""
android:gravity="center"
android:textAlignment="center"
android:paddingBottom="4dp" />
</LinearLayout>
<!-- White Underline -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@android:color/white"
android:layout_marginBottom="16dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Numpad Grid -->
<GridLayout
android:id="@+id/numpad_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_margin="16dp"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/pin_card">
<!-- Row 1: 1, 2, 3 -->
<TextView
android:id="@+id/btn1"
style="@style/NumpadButton"
android:text="1"/>
<TextView
android:id="@+id/btn2"
style="@style/NumpadButton"
android:text="2"/>
<TextView
android:id="@+id/btn3"
style="@style/NumpadButton"
android:text="3"/>
<!-- Row 2: 4, 5, 6 -->
<TextView
android:id="@+id/btn4"
style="@style/NumpadButton"
android:text="4"/>
<TextView
android:id="@+id/btn5"
style="@style/NumpadButton"
android:text="5"/>
<TextView
android:id="@+id/btn6"
style="@style/NumpadButton"
android:text="6"/>
<!-- Row 3: 7, 8, 9 -->
<TextView
android:id="@+id/btn7"
style="@style/NumpadButton"
android:text="7"/>
<TextView
android:id="@+id/btn8"
style="@style/NumpadButton"
android:text="8"/>
<TextView
android:id="@+id/btn9"
style="@style/NumpadButton"
android:text="9"/>
<!-- Row 4: 000, 0, Delete -->
<TextView
android:id="@+id/btn000"
style="@style/NumpadButton"
android:text="000"/>
<TextView
android:id="@+id/btn0"
style="@style/NumpadButton"
android:text="0"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="8dp"
android:gravity="center">
<ImageView
android:id="@+id/btnDelete"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_backspace"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="12dp"
app:tint="#666666"/>
</LinearLayout>
</GridLayout>
<!-- Confirmation Button -->
<Button
android:id="@+id/confirmButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:layout_marginTop="32dp"
android:text="Konfirmasi"
android:textColor="#999999"
android:textSize="16sp"
android:textStyle="bold"
android:background="@drawable/button_inactive_background"
android:enabled="false"
android:alpha="0.6"
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"/>
<!-- Success Screen (Full Screen Overlay) - IMPROVED VERSION -->
<LinearLayout
android:id="@+id/success_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#E31937"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Success Icon -->
<ImageView
android:id="@+id/success_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_success_payment"
android:layout_marginBottom="32dp"
android:scaleType="centerInside"/>
<!-- Success Message -->
<TextView
android:id="@+id/success_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pembayaran Berhasil"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:gravity="center"
android:letterSpacing="0.02"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -1,223 +1,241 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:background="#F5F5F5"
android:background="#FFFFFF"> tools:context=".QrisResultActivity">
<!-- Red Status Bar --> <!-- Header Background -->
<View <View
android:id="@+id/header_background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="44dp" android:layout_height="100dp"
android:background="#E31937" />
<!-- Header with Back Navigation -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#E31937" android:background="#E31937"
android:paddingBottom="16dp"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout <!-- Back Navigation -->
android:id="@+id/back_navigation" <LinearLayout
android:id="@+id/back_navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp"
android:layout_marginTop="24dp"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/backArrow"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_arrow_back"
android:layout_marginEnd="8dp" />
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:text="Generate QR"
android:gravity="center_vertical" android:textColor="@android:color/white"
android:layout_marginStart="16dp" android:textSize="18sp"
android:background="?attr/selectableItemBackgroundBorderless" android:textStyle="bold"
android:padding="8dp" android:fontFamily="@font/inter" />
android:clickable="true"
android:focusable="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginEnd="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<!-- White Card Container --> <!-- Main Content Card -->
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:layout_width="match_parent" android:id="@+id/main_card"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="0dp" android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardElevation="8dp" app:cardElevation="8dp"
app:cardBackgroundColor="@android:color/white"> app:layout_constraintTop_toBottomOf="@id/header_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/cancel_button"
app:layout_constraintVertical_bias="0.3">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="24dp" android:padding="32dp"
android:gravity="center_horizontal"> android:gravity="center">
<!-- Generate QR Title --> <!-- QRIS Logo -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Generate QR"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<!-- QRIS Logo Text -->
<TextView <TextView
android:id="@+id/qris_logo"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="QRIS" android:text="QRIS"
android:textColor="@android:color/black"
android:textSize="32sp" android:textSize="32sp"
android:textStyle="bold" android:textStyle="bold"
android:fontFamily="monospace" android:textColor="#000000"
android:layout_marginBottom="24dp" /> android:letterSpacing="0.1"
android:layout_marginBottom="24dp"
android:fontFamily="@font/inter" />
<!-- QR Code --> <!-- QR Code -->
<ImageView <ImageView
android:id="@+id/qrImageView" android:id="@+id/qrImageView"
android:layout_width="200dp" android:layout_width="240dp"
android:layout_height="200dp" android:layout_height="240dp"
android:layout_gravity="center_horizontal" android:layout_marginBottom="24dp"
android:contentDescription="QRIS QR Code" android:scaleType="centerInside"
android:scaleType="fitCenter" android:background="#FFFFFF"
android:background="#F0F0F0" android:padding="8dp" />
android:layout_marginBottom="24dp" />
<!-- Amount Display --> <!-- Amount -->
<TextView <TextView
android:id="@+id/amountTextView" android:id="@+id/amountTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="RP.200.000" android:text="RP.200.000"
android:textColor="@android:color/black" android:textSize="24sp"
android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="16dp" /> android:textColor="#333333"
android:layout_marginBottom="16dp"
android:fontFamily="@font/inter" />
<!-- Timer/Counter --> <!-- Timer -->
<TextView <TextView
android:id="@+id/timerTextView" android:id="@+id/timerTextView"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:text="60" android:text="60"
android:textColor="#E31937"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="24dp" />
<!-- QR Refresh Status -->
<TextView
android:id="@+id/qrStatusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QR Code akan refresh dalam"
android:textColor="#666666" android:textColor="#666666"
android:textSize="12sp" android:gravity="center"
android:layout_marginBottom="16dp" android:background="@drawable/timer_circle_background"
android:visibility="visible" /> android:fontFamily="@font/inter" />
<!-- Action Buttons Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp">
<!-- Download QRIS Button -->
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:text="Download QRIS"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#4CAF50"
android:visibility="visible" />
<!-- Check Payment Status Button -->
<Button
android:id="@+id/checkStatusButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:text="Check Payment Status"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#2196F3"
android:visibility="visible" />
<!-- Return to Main Button -->
<Button
android:id="@+id/returnMainButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Return to Main"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#FF9800"
android:visibility="visible" />
</LinearLayout>
<!-- Hidden views for compatibility -->
<TextView
android:id="@+id/referenceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reference ID: ref-12345"
android:textColor="@android:color/black"
android:textSize="14sp"
android:visibility="gone" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Payment Status Success"
android:textColor="@android:color/black"
android:textSize="18sp"
android:visibility="gone" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<!-- Spacer to push button to bottom --> <!-- Cancel Button -->
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- Bottom Cancel Button -->
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancel_button"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="48dp" android:layout_height="52dp"
android:layout_margin="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp"
android:text="Batalkan" android:text="Batalkan"
android:textColor="#E31937" android:textColor="#E31937"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="normal" android:textStyle="bold"
android:background="@android:color/transparent" android:background="@drawable/button_cancel_background"
style="?android:attr/borderlessButtonStyle" /> android:fontFamily="@font/inter"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</LinearLayout> <!-- Hidden Elements for Functionality -->
<TextView
android:id="@+id/referenceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/qrStatusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/checkStatusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/returnMainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- Success Screen (Full Screen Overlay) -->
<LinearLayout
android:id="@+id/success_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#E31937"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Success Icon -->
<ImageView
android:id="@+id/success_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_success_payment"
android:layout_marginBottom="32dp"
android:scaleType="centerInside"/>
<!-- Success Message -->
<TextView
android:id="@+id/success_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pembayaran Berhasil"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:gravity="center"
android:letterSpacing="0.02"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -104,7 +104,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="EDC " android:text="Payvora "
android:textColor="#E31937" android:textColor="#E31937"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
@ -113,7 +113,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Merchant" android:text="PRO"
android:textColor="#3F51B5" android:textColor="#3F51B5"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" android:textStyle="bold"
@ -121,15 +121,6 @@
</LinearLayout> </LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BANK BRI"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter"
android:layout_marginTop="2dp"/>
</LinearLayout> </LinearLayout>
<!-- Merchant Info --> <!-- Merchant Info -->
@ -168,46 +159,36 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="MID: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
<TextView <TextView
android:id="@+id/mid_text" android:id="@+id/mid_text"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:text="MID:12345678901"
android:text="1234567890"
android:textColor="#333333" android:textColor="#333333"
android:textSize="12sp" android:textSize="12sp"
android:fontFamily="@font/inter"/> android:fontFamily="@font/inter"/>
<!-- Vertical Separator -->
<TextView <TextView
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:text=" | "
android:text="TID: " android:textColor="#999999"
android:textColor="#666666"
android:textSize="12sp" android:textSize="12sp"
android:fontFamily="@font/inter"/> android:fontFamily="@font/inter"
android:paddingHorizontal="8dp"/>
<TextView <TextView
android:id="@+id/tid_text" android:id="@+id/tid_text"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:text="TID:12345678901"
android:text="1234567890"
android:textColor="#333333" android:textColor="#333333"
android:textSize="12sp" android:textSize="12sp"
android:fontFamily="@font/inter"/> android:fontFamily="@font/inter"/>
</LinearLayout> </LinearLayout>
<!-- Separator Line --> <!-- Separator Line -->

View File

@ -79,15 +79,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Cetak Ulang Struk" android:text="Cetak Ulang Struk"
android:textColor="#333333" android:textColor="#333333"
android:textSize="20sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
android:fontFamily="sans-serif" /> android:fontFamily="inter-bold" />
</LinearLayout> </LinearLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<!-- ✅ PERBAIKAN: Gunakan LinearLayout dengan weight distribution yang lebih baik -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -221,8 +220,7 @@
android:visibility="gone" android:visibility="gone"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" /> android:layout_marginTop="8dp" />
<!-- ✅ PERBAIKAN: RecyclerView dengan height yang tepat untuk mencegah pagination terpotong -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -231,8 +229,7 @@
android:background="#ffffff" android:background="#ffffff"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="8dp" /> android:paddingBottom="8dp" />
<!-- ✅ PERBAIKAN: Pagination Controls dengan padding dan margin yang lebih baik -->
<LinearLayout <LinearLayout
android:id="@+id/paginationControls" android:id="@+id/paginationControls"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,47 @@
<?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:layout_width="match_parent"
android:layout_height="130dp"
android:background="#DE0701"
android:fitsSystemWindows="true">
<!-- Back Navigation -->
<LinearLayout
android:id="@+id/back_navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- Back Arrow -->
<ImageView
android:id="@+id/backArrow"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/ic_arrow_back"
android:contentDescription="Back"
app:tint="@android:color/white" />
<!-- Title Text -->
<TextView
android:id="@+id/appbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="10sp"
android:fontFamily="@font/inter"
android:textStyle="normal" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -101,16 +101,10 @@
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
<TextView <ImageView
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:text="Cetak Ulang" android:src="@drawable/ic_print" />
android:textColor="#666666"
android:textSize="12sp"
android:drawableLeft="@android:drawable/ic_menu_edit"
android:drawablePadding="4dp"
android:gravity="center_vertical" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,9 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="black">#FF000000</color> <color name="colorPrimary">#3F51B5</color>
<color name="white">#FFFFFFFF</color> <color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="white">#FFFFFF</color>
<color name="colorOrange">#FF6600</color>
<color name="transparent">#00000000</color>
<color name="colorBackground">#F0F2F5</color>
<color name="colorTextTitle">#222222</color>
<color name="colorTextContent">#666666</color>
<color name="colorTextHelp">#999999</color>
<color name="colorLineColor">#d7d7d7</color>
<color name="FD5A52">#FD5A52</color>
<color name="CE6E6E6">#E6E6E6</color>
<color name="FF3C00">#FF3C00</color>
<color name="C999999">#999999</color>
<color name="black">#000000</color>
<!-- Banking App Theme Colors --> <!-- Banking App Theme Colors -->
<color name="red">#DE0701</color>
<color name="primary_blue">#1976D2</color> <color name="primary_blue">#1976D2</color>
<color name="light_blue">#BBDEFB</color> <color name="light_blue">#BBDEFB</color>
<color name="accent_teal">#009688</color> <color name="accent_teal">#009688</color>
@ -13,4 +32,88 @@
<color name="light_gray">#F5F5F5</color> <color name="light_gray">#F5F5F5</color>
<color name="medium_gray">#E0E0E0</color> <color name="medium_gray">#E0E0E0</color>
<color name="dark_gray">#757575</color> <color name="dark_gray">#757575</color>
<!-- Alias untuk kemudahan penggunaan dalam Clean Architecture -->
<!-- Menggunakan warna yang sudah ada -->
<!-- Button Colors -->
<color name="button_primary">@color/primary_blue</color>
<color name="button_primary_pressed">@color/colorPrimaryDark</color>
<color name="button_secondary">@color/white</color>
<color name="button_outline_text">@color/primary_blue</color>
<color name="button_disabled">@color/medium_gray</color>
<!-- Background Colors -->
<color name="background_main">@color/colorBackground</color>
<color name="background_card">@color/white</color>
<color name="background_surface">@color/white</color>
<!-- Text Colors -->
<color name="text_primary">@color/colorTextTitle</color>
<color name="text_secondary">@color/colorTextContent</color>
<color name="text_hint">@color/colorTextHelp</color>
<color name="text_disabled">@color/C999999</color>
<color name="text_on_primary">@color/white</color>
<!-- Status Colors -->
<color name="status_success">@color/accent_green</color>
<color name="status_error">@color/FD5A52</color>
<color name="status_warning">@color/colorOrange</color>
<color name="status_info">@color/light_blue</color>
<!-- EMV Processing Colors -->
<color name="emv_processing">@color/colorOrange</color>
<color name="emv_success">@color/accent_green</color>
<color name="emv_error">@color/FD5A52</color>
<color name="emv_idle">@color/dark_gray</color>
<!-- Card Colors -->
<color name="card_background">@color/white</color>
<color name="card_shadow">@color/medium_gray</color>
<color name="card_border">@color/colorLineColor</color>
<!-- Amount Display Colors -->
<color name="amount_primary">@color/primary_blue</color>
<color name="amount_background">@color/light_blue</color>
<color name="amount_success">@color/accent_green</color>
<!-- Border and Divider Colors -->
<color name="border_light">@color/colorLineColor</color>
<color name="border_medium">@color/medium_gray</color>
<color name="border_dark">@color/dark_gray</color>
<!-- Overlay and Shadow Colors -->
<color name="overlay_light">@color/transparent</color>
<color name="overlay_dark">@color/transparent</color>
<color name="shadow_color">@color/medium_gray</color>
<!-- Transaction Flow Colors -->
<color name="transaction_amount">@color/primary_blue</color>
<color name="transaction_success">@color/accent_green</color>
<color name="transaction_processing">@color/colorOrange</color>
<color name="transaction_failed">@color/FD5A52</color>
<!-- Keypad Colors -->
<color name="keypad_button_text">@color/colorTextTitle</color>
<color name="keypad_button_background">@color/white</color>
<color name="keypad_button_pressed">@color/light_gray</color>
<!-- PIN Pad Colors -->
<color name="pinpad_background">@color/primary_blue</color>
<color name="pinpad_text">@color/white</color>
<color name="pinpad_button">@color/white</color>
<color name="pinpad_button_pressed">@color/light_gray</color>
<!-- Navigation Colors -->
<color name="toolbar_background">@color/primary_blue</color>
<color name="toolbar_text">@color/white</color>
<color name="navigation_background">@color/white</color>
<!-- Icon Colors -->
<color name="icon_primary">@color/primary_blue</color>
<color name="icon_secondary">@color/dark_gray</color>
<color name="icon_success">@color/accent_green</color>
<color name="icon_error">@color/FD5A52</color>
<color name="icon_on_primary">@color/white</color>
</resources> </resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="smallSize">24dp</dimen>
<dimen name="itemSize">48dp</dimen>
<dimen name="titleSize">56dp</dimen>
</resources>

View File

@ -13,4 +13,11 @@
<string name="payment_status_success">Payment Successful!</string> <string name="payment_status_success">Payment Successful!</string>
<string name="return_main">Return to Main Screen</string> <string name="return_main">Return to Main Screen</string>
<string name="main_title">POC</string> <string name="main_title">POC</string>
<!-- In res/values/strings.xml -->
<string name="card_test_credit_card">Credit Card Test</string>
<string name="card_mag_card_detected">Magnetic Card Detected</string>
<string name="card_ic_card_detected">IC Card Detected</string>
<string name="card_start_check_card">Start Check Card</string>
<string name="card_stop_check_card">Stop Check Card</string>
<string name="connect_fail">Connection failed</string>
</resources> </resources>

View File

@ -29,6 +29,11 @@
<item name="titleTextAppearance">@style/ToolbarTitleStyle</item> <item name="titleTextAppearance">@style/ToolbarTitleStyle</item>
</style> </style>
<style name="Toolbar.TitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
<item name="android:textSize">18sp</item>
<item name="android:textColor">@android:color/white</item>
</style>
<!-- Numpad Button Style --> <!-- Numpad Button Style -->
<style name="NumpadButton"> <style name="NumpadButton">
<item name="android:layout_width">0dp</item> <item name="android:layout_width">0dp</item>
@ -45,4 +50,172 @@
<item name="android:focusable">true</item> <item name="android:focusable">true</item>
</style> </style>
<!-- Additional Styles for Clean Architecture -->
<!-- Keypad Button Styles untuk CreateTransaction -->
<style name="KeypadButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">56dp</item>
<item name="android:layout_columnWeight">1</item>
<item name="android:layout_margin">4dp</item>
<item name="android:background">?attr/selectableItemBackground</item>
<item name="android:textColor">@color/colorTextTitle</item>
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:gravity">center</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:elevation">2dp</item>
</style>
<style name="KeypadButtonSecondary" parent="KeypadButton">
<item name="android:textColor">@color/dark_gray</item>
<item name="android:textSize">18sp</item>
</style>
<!-- Button Styles -->
<style name="PrimaryButton">
<item name="android:background">@color/primary_blue</item>
<item name="android:textColor">@color/white</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:elevation">4dp</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:padding">12dp</item>
</style>
<style name="SecondaryButton">
<item name="android:background">@color/white</item>
<item name="android:textColor">@color/primary_blue</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:padding">12dp</item>
</style>
<style name="OutlineButton">
<item name="android:background">?attr/selectableItemBackground</item>
<item name="android:textColor">@color/primary_blue</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:padding">12dp</item>
</style>
<!-- Text Styles -->
<style name="HeaderTextStyle">
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/colorTextTitle</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:gravity">center</item>
</style>
<style name="SubHeaderTextStyle">
<item name="android:textSize">16sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/colorTextContent</item>
<item name="android:fontFamily">@font/inter</item>
</style>
<style name="BodyTextStyle">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/colorTextTitle</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:lineSpacingExtra">2dp</item>
</style>
<style name="MonospaceTextStyle" parent="BodyTextStyle">
<item name="android:fontFamily">monospace</item>
<item name="android:textSize">12sp</item>
<item name="android:textColor">@color/colorTextContent</item>
</style>
<style name="HintTextStyle">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/colorTextHelp</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:lineSpacingExtra">2dp</item>
</style>
<!-- Amount Display Style -->
<style name="AmountDisplayStyle">
<item name="android:textSize">32sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/primary_blue</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:gravity">center</item>
</style>
<!-- Status Text Style -->
<style name="StatusTextStyle">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/colorTextTitle</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:gravity">center</item>
<item name="android:lineSpacingExtra">4dp</item>
</style>
<!-- Success Text Style -->
<style name="SuccessTextStyle">
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/white</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:gravity">center</item>
</style>
<!-- Card Title Style -->
<style name="CardTitleStyle">
<item name="android:textSize">18sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/colorTextTitle</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:layout_marginBottom">16dp</item>
</style>
<!-- Mode Indicator Style -->
<style name="ModeIndicatorStyle">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/colorTextContent</item>
<item name="android:fontFamily">@font/inter</item>
<item name="android:gravity">center</item>
</style>
<!-- Transaction Summary Style -->
<style name="TransactionSummaryStyle">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/colorTextContent</item>
<item name="android:fontFamily">monospace</item>
<item name="android:lineSpacingExtra">2dp</item>
</style>
<!-- Amount Card Style -->
<style name="AmountCardStyle">
<item name="android:background">@color/primary_blue</item>
<item name="android:padding">20dp</item>
<item name="android:elevation">4dp</item>
</style>
<!-- Success Card Style -->
<style name="SuccessCardStyle">
<item name="android:background">@color/accent_green</item>
<item name="android:padding">20dp</item>
<item name="android:elevation">4dp</item>
</style>
<!-- Data Card Style -->
<style name="DataCardStyle">
<item name="android:background">@color/white</item>
<item name="android:padding">20dp</item>
<item name="android:elevation">4dp</item>
</style>
</resources> </resources>

View File

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

View File

@ -16,8 +16,16 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
// Tambahkan repositories Sunmi
maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" }
maven { url "https://s01.oss.sonatype.org/content/groups/public/" }
maven { url "https://jcenter.bintray.com" }
maven { url "https://repo.spring.io/libs-milestone" }
flatDir {
dirs 'app/libs'
}
} }
} }
rootProject.name = "BDKI POC" rootProject.name = "BDKI POC"
include ':app' include ':app'