Merge branch 'temp-feature' into development
This commit is contained in:
commit
6f78b6df3f
@ -6,6 +6,13 @@ android {
|
||||
namespace 'com.example.bdkipoc'
|
||||
compileSdk 35
|
||||
|
||||
// Tambahkan lint options
|
||||
lint {
|
||||
abortOnError false
|
||||
disable 'GoogleAppIndexingWarning'
|
||||
disable 'NonConstantResourceId'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.bdkipoc"
|
||||
minSdk 21
|
||||
@ -22,20 +29,32 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// Keep Java 11 - lebih modern dari referensi
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
// Tambahkan sourceSets untuk native libs jika diperlukan
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
|
||||
implementation libs.appcompat
|
||||
implementation libs.material
|
||||
implementation libs.activity
|
||||
implementation libs.constraintlayout
|
||||
implementation libs.cardview
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation 'com.sunmi:printerlibrary:1.0.15'
|
||||
|
||||
// Test dependencies
|
||||
testImplementation libs.junit
|
||||
androidTestImplementation libs.ext.junit
|
||||
androidTestImplementation libs.espresso.core
|
||||
|
BIN
app/libs/PayLib-release-2.0.17-sources.jar
Normal file
BIN
app/libs/PayLib-release-2.0.17-sources.jar
Normal file
Binary file not shown.
BIN
app/libs/PayLib-release-2.0.17.aar
Normal file
BIN
app/libs/PayLib-release-2.0.17.aar
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libAE_100.so
Normal file
BIN
app/libs/armeabi-v7a/libAE_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libCPACE_100.so
Normal file
BIN
app/libs/armeabi-v7a/libCPACE_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libDPAS_100.so
Normal file
BIN
app/libs/armeabi-v7a/libDPAS_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libEFTPOS_001.so
Normal file
BIN
app/libs/armeabi-v7a/libEFTPOS_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libEMVL2Base.so
Normal file
BIN
app/libs/armeabi-v7a/libEMVL2Base.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libEMVL2Dirct.so
Normal file
BIN
app/libs/armeabi-v7a/libEMVL2Dirct.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libEMV_100.so
Normal file
BIN
app/libs/armeabi-v7a/libEMV_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libEntry.so
Normal file
BIN
app/libs/armeabi-v7a/libEntry.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libFLASH_001.so
Normal file
BIN
app/libs/armeabi-v7a/libFLASH_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libJCB_100.so
Normal file
BIN
app/libs/armeabi-v7a/libJCB_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libMIR_001.so
Normal file
BIN
app/libs/armeabi-v7a/libMIR_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libPAGO_001.so
Normal file
BIN
app/libs/armeabi-v7a/libPAGO_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libPURE_001.so
Normal file
BIN
app/libs/armeabi-v7a/libPURE_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libPaypass_100.so
Normal file
BIN
app/libs/armeabi-v7a/libPaypass_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libPaywave_100.so
Normal file
BIN
app/libs/armeabi-v7a/libPaywave_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libQPBOC_100.so
Normal file
BIN
app/libs/armeabi-v7a/libQPBOC_100.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libRupay_001.so
Normal file
BIN
app/libs/armeabi-v7a/libRupay_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libSamsungPay_001.so
Normal file
BIN
app/libs/armeabi-v7a/libSamsungPay_001.so
Normal file
Binary file not shown.
BIN
app/libs/armeabi-v7a/libsunmiemvl2.so
Normal file
BIN
app/libs/armeabi-v7a/libsunmiemvl2.so
Normal file
Binary file not shown.
BIN
app/libs/sunmiemvl2split-1.0.1.jar
Normal file
BIN
app/libs/sunmiemvl2split-1.0.1.jar
Normal file
Binary file not shown.
@ -8,7 +8,23 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_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
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
@ -29,32 +45,45 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".TransactionActivity"
|
||||
android:name=".cetakulang.ReprintActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".PaymentActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".PinActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:name=".cetakulang.ReprintAdapterActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ReceiptActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".QrisActivity"
|
||||
android:exported="false" />
|
||||
<activity android:name=".QrisResultActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".QrisResultActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".SettlementActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".HistoryActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".HistoryDetailActivity"
|
||||
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>
|
||||
|
||||
</manifest>
|
27
app/src/main/java/com/example/bdkipoc/CacheHelper.java
Normal file
27
app/src/main/java/com/example/bdkipoc/CacheHelper.java
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
17
app/src/main/java/com/example/bdkipoc/Constant.java
Normal file
17
app/src/main/java/com/example/bdkipoc/Constant.java
Normal 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";
|
||||
}
|
@ -19,6 +19,13 @@ import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
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 {
|
||||
|
||||
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
|
||||
CardView[] dummyCards = {
|
||||
findViewById(R.id.card_dummy_menu_1),
|
||||
findViewById(R.id.card_dummy_menu_2),
|
||||
findViewById(R.id.card_dummy_menu_3),
|
||||
findViewById(R.id.card_dummy_menu_4),
|
||||
findViewById(R.id.card_dummy_menu_5),
|
||||
findViewById(R.id.card_dummy_menu_6)
|
||||
findViewById(R.id.card_bantuan),
|
||||
findViewById(R.id.card_info_toko),
|
||||
findViewById(R.id.card_pengaturan),
|
||||
};
|
||||
|
||||
for (CardView card : dummyCards) {
|
||||
@ -135,21 +139,17 @@ public class MainActivity extends AppCompatActivity {
|
||||
R.id.card_kartu_debit,
|
||||
R.id.card_qris,
|
||||
// Row 2 (Always visible - 3 items)
|
||||
R.id.card_transfer,
|
||||
R.id.card_uang_elektronik,
|
||||
R.id.card_cetak_ulang,
|
||||
R.id.card_settlement,
|
||||
// Row 3 (Always visible - 3 items)
|
||||
R.id.card_refund,
|
||||
R.id.card_settlement,
|
||||
R.id.card_histori,
|
||||
// Row 4 (Hidden initially - 3 items)
|
||||
R.id.card_bantuan,
|
||||
R.id.card_info_toko,
|
||||
// Row 4 (Hidden initially - 3 items)
|
||||
R.id.card_dummy_menu_1,
|
||||
R.id.card_dummy_menu_2,
|
||||
R.id.card_dummy_menu_3,
|
||||
// Row 5 (Hidden initially - 3 items)
|
||||
R.id.card_dummy_menu_4,
|
||||
R.id.card_dummy_menu_5,
|
||||
R.id.card_dummy_menu_6
|
||||
R.id.card_pengaturan,
|
||||
};
|
||||
|
||||
// Set up click listeners for all cards
|
||||
@ -157,39 +157,37 @@ public class MainActivity extends AppCompatActivity {
|
||||
CardView cardView = findViewById(cardId);
|
||||
if (cardView != null) {
|
||||
cardView.setOnClickListener(v -> {
|
||||
// ✅ ENHANCED: Navigate with payment type information
|
||||
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) {
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
navigateToCreateTransaction("debit_card", cardId, "Kartu Debit");
|
||||
} else if (cardId == R.id.card_qris) {
|
||||
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) {
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
navigateToCreateTransaction("e_money", cardId, "Uang Elektronik");
|
||||
} 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) {
|
||||
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} 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) {
|
||||
Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_info_toko) {
|
||||
Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_dummy_menu_1) {
|
||||
Toast.makeText(this, "Dummy Menu 1 - 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 if (cardId == R.id.card_pengaturan) {
|
||||
Toast.makeText(this, "Pengaturan - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// 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
|
||||
CardView[] toggleableCards = {
|
||||
findViewById(R.id.card_dummy_menu_1),
|
||||
findViewById(R.id.card_dummy_menu_2),
|
||||
findViewById(R.id.card_dummy_menu_3),
|
||||
findViewById(R.id.card_dummy_menu_4),
|
||||
findViewById(R.id.card_dummy_menu_5),
|
||||
findViewById(R.id.card_dummy_menu_6)
|
||||
findViewById(R.id.card_bantuan),
|
||||
findViewById(R.id.card_info_toko),
|
||||
findViewById(R.id.card_pengaturan),
|
||||
};
|
||||
|
||||
// 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
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
@ -263,5 +377,74 @@ public class MainActivity extends AppCompatActivity {
|
||||
// Clear any transaction completion flags to avoid repeated messages
|
||||
getIntent().removeExtra("transaction_completed");
|
||||
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", "==================================");
|
||||
}
|
||||
}
|
197
app/src/main/java/com/example/bdkipoc/MyApplication.java
Normal file
197
app/src/main/java/com/example/bdkipoc/MyApplication.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,10 @@ import java.net.HttpURLConnection;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.example.bdkipoc.cetakulang.ReprintActivity;
|
||||
|
||||
public class ReceiptActivity extends AppCompatActivity {
|
||||
|
||||
@ -50,6 +54,28 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
private LinearLayout emailButton;
|
||||
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
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -116,10 +142,27 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
finishButton.setOnClickListener(v -> handleFinish());
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ ENHANCED: Load transaction data with support for both EMV/Card and QRIS
|
||||
*/
|
||||
private void loadTransactionData() {
|
||||
Intent intent = getIntent();
|
||||
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
|
||||
String amount = intent.getStringExtra("transaction_amount");
|
||||
@ -133,89 +176,144 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
String createdAt = intent.getStringExtra("created_at");
|
||||
String paymentMethodStr = intent.getStringExtra("payment_method");
|
||||
String cardTypeStr = intent.getStringExtra("card_type");
|
||||
String channelCode = intent.getStringExtra("channel_code");
|
||||
String channelCategory = intent.getStringExtra("channel_category");
|
||||
String acquirer = intent.getStringExtra("acquirer");
|
||||
String mid = intent.getStringExtra("mid");
|
||||
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.d("ReceiptActivity", "🔍 RECEIVED DATA:");
|
||||
Log.d("ReceiptActivity", " amount: " + amount);
|
||||
Log.d("ReceiptActivity", " referenceId: " + referenceId);
|
||||
Log.d("ReceiptActivity", " orderId: " + orderId);
|
||||
Log.d("ReceiptActivity", " channelCode: " + channelCode);
|
||||
Log.d("ReceiptActivity", " acquirer (from intent): " + acquirer);
|
||||
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
|
||||
merchantName.setText(merchantNameStr != null ? merchantNameStr : "Marcel Panjaitan");
|
||||
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Jakarta, Indonesia");
|
||||
merchantName.setText(merchantNameStr != null ? merchantNameStr : "TOKO KLONTONG PAK EKO");
|
||||
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Ciputat Baru, Tangsel");
|
||||
|
||||
// 2. Set MID and TID
|
||||
midText.setText(mid != null ? mid : "71000026521");
|
||||
tidText.setText(tid != null ? tid : "73001500");
|
||||
midText.setText("MID: " + (mid != null ? mid : "123456789901"));
|
||||
tidText.setText("TID: " + (tid != null ? tid : "123456789901"));
|
||||
|
||||
// 3. Set transaction number
|
||||
String displayTransactionNumber = null;
|
||||
if (referenceId != null && !referenceId.isEmpty()) {
|
||||
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");
|
||||
String displayTransactionNumber = getDisplayTransactionNumber(referenceId, transactionId, orderId);
|
||||
transactionNumber.setText(displayTransactionNumber);
|
||||
|
||||
// 4. Set transaction date
|
||||
String displayDate = null;
|
||||
if (createdAt != null && !createdAt.isEmpty()) {
|
||||
displayDate = formatDateFromCreatedAt(createdAt);
|
||||
} else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
|
||||
displayDate = transactionDateStr;
|
||||
} else {
|
||||
displayDate = getCurrentDateTime();
|
||||
}
|
||||
String displayDate = getDisplayTransactionDate(createdAt, transactionDateStr, isEmvTransaction);
|
||||
transactionDate.setText(displayDate);
|
||||
|
||||
// 5. Set payment method
|
||||
String displayPaymentMethod = getPaymentMethodFromChannelCode(channelCode, paymentMethodStr);
|
||||
// 5. ✅ ENHANCED: Set payment method based on transaction type
|
||||
String displayPaymentMethod = getDisplayPaymentMethod(channelCode, paymentMethodStr, isEmvTransaction, emvMode);
|
||||
paymentMethod.setText(displayPaymentMethod);
|
||||
|
||||
// 6. ✅ IMPROVED: Enhanced card type detection for QRIS
|
||||
String displayCardType = null;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 6. ✅ ENHANCED: Set card type with EMV and QRIS priority
|
||||
String displayCardType = getDisplayCardType(cardTypeStr, acquirer, channelCode, isEmvTransaction,
|
||||
isQrisTransaction, midtransResponse, referenceId);
|
||||
cardType.setText(displayCardType);
|
||||
Log.d("ReceiptActivity", "💳 FINAL CARD TYPE: " + displayCardType);
|
||||
|
||||
// 7. Format and set amounts
|
||||
setAmountData(amount, grossAmount);
|
||||
// 7. ✅ Format and set amounts with proper calculation
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +323,7 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
|
||||
// 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);
|
||||
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
|
||||
*/
|
||||
@ -289,6 +420,78 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
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) {
|
||||
// STEP 1: If we have a valid acquirer that's not generic "qris", use it
|
||||
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
|
||||
@ -297,88 +500,14 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
Log.d("ReceiptActivity", "🔍 Mapping acquirer: '" + acquirer + "' -> '" + acq + "'");
|
||||
|
||||
// ✅ 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) {
|
||||
// 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
|
||||
case "visa": return "Visa";
|
||||
case "mastercard":
|
||||
@ -738,6 +867,78 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
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) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
@ -756,51 +957,79 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
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;
|
||||
|
||||
Log.d("ReceiptActivity", "Setting amount data - amount: " + amount +
|
||||
", grossAmount: " + grossAmount + ", using: " + amountToUse);
|
||||
Log.d("ReceiptActivity", "Setting enhanced amount data - amount: " + amount +
|
||||
", grossAmount: " + grossAmount + ", using: " + amountToUse +
|
||||
", isEMV: " + isEmvTransaction + ", isQRIS: " + isQrisTransaction);
|
||||
|
||||
if (amountToUse != null) {
|
||||
try {
|
||||
// Clean and parse the amount
|
||||
String cleanAmount = cleanAmountString(amountToUse);
|
||||
Log.d("ReceiptActivity", "Cleaned amount: " + cleanAmount);
|
||||
|
||||
// Parse as long integer (Indonesian Rupiah doesn't use decimal cents)
|
||||
long amountLong = Long.parseLong(cleanAmount);
|
||||
|
||||
// Set transaction total
|
||||
transactionTotal.setText("Rp " + formatCurrency(amountLong));
|
||||
transactionTotal.setText(formatCurrency(amountLong));
|
||||
|
||||
// Calculate tax and service fee (for QRIS, typically no additional fees)
|
||||
long tax = 0; // QRIS usually doesn't have tax
|
||||
long serviceFeeValue = 0; // QRIS usually doesn't have service fee
|
||||
long total = amountLong + tax + serviceFeeValue;
|
||||
// ✅ CALCULATE FEES BASED ON TRANSACTION TYPE
|
||||
long tax = 0;
|
||||
long serviceFeeValue = 0;
|
||||
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
|
||||
taxPercentage.setText("Rp 0");
|
||||
serviceFee.setText("Rp 0");
|
||||
finalTotal.setText("Rp " + formatCurrency(total));
|
||||
taxPercentage.setText(tax > 0 ? "11%" : "0%");
|
||||
serviceFee.setText(formatCurrency(serviceFeeValue));
|
||||
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) {
|
||||
Log.e("ReceiptActivity", "Error parsing amount: " + amountToUse, e);
|
||||
Log.e("ReceiptActivity", "Error parsing enhanced amount: " + amountToUse, e);
|
||||
// Fallback if parsing fails
|
||||
transactionTotal.setText("Rp " + amountToUse);
|
||||
taxPercentage.setText("Rp 0");
|
||||
serviceFee.setText("Rp 0");
|
||||
finalTotal.setText("Rp " + amountToUse);
|
||||
transactionTotal.setText(amountToUse);
|
||||
taxPercentage.setText("0%");
|
||||
serviceFee.setText("0");
|
||||
finalTotal.setText(amountToUse);
|
||||
}
|
||||
} else {
|
||||
// Default values if no amount provided
|
||||
transactionTotal.setText("Rp 0");
|
||||
taxPercentage.setText("Rp 0");
|
||||
serviceFee.setText("Rp 0");
|
||||
finalTotal.setText("Rp 0");
|
||||
transactionTotal.setText("0");
|
||||
taxPercentage.setText("0%");
|
||||
serviceFee.setText("0");
|
||||
finalTotal.setText("0");
|
||||
}
|
||||
}
|
||||
|
||||
@ -874,7 +1103,7 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@ -902,9 +1131,9 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
|
||||
if (callingActivity != null) {
|
||||
switch (callingActivity) {
|
||||
case "TransactionActivity":
|
||||
case "ReprintActivity":
|
||||
// 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);
|
||||
startActivity(transactionIntent);
|
||||
break;
|
||||
@ -914,6 +1143,11 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
navigateToHomePage();
|
||||
break;
|
||||
|
||||
case "ResultTransactionActivity":
|
||||
// ✅ NEW: Handle back from ResultTransactionActivity
|
||||
navigateToHomePage();
|
||||
break;
|
||||
|
||||
case "PaymentActivity":
|
||||
case "QrisActivity":
|
||||
// Go back to payment/qris activity
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.example.bdkipoc;
|
||||
package com.example.bdkipoc.cetakulang;
|
||||
|
||||
import com.example.bdkipoc.R;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
@ -50,9 +51,12 @@ import java.util.TimeZone;
|
||||
import android.app.DatePickerDialog;
|
||||
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 TransactionAdapter adapter;
|
||||
private ReprintAdapterActivity adapter;
|
||||
private List<Transaction> transactionList;
|
||||
private List<Transaction> filteredList;
|
||||
private ProgressBar progressBar;
|
||||
@ -89,7 +93,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_transaction);
|
||||
setContentView(R.layout.activity_reprint);
|
||||
|
||||
// ✅ Initialize SharedPreferences for local tracking
|
||||
prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE);
|
||||
@ -159,7 +163,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
transactionList = new ArrayList<>();
|
||||
filteredList = new ArrayList<>();
|
||||
|
||||
adapter = new TransactionAdapter(filteredList);
|
||||
adapter = new ReprintAdapterActivity(filteredList);
|
||||
adapter.setPrintClickListener(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.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
|
||||
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
|
||||
currentPage = 1;
|
||||
@ -366,7 +370,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
filterButtonText.setTextSize(14); // Reset to normal size
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🗓️ Date filter cleared");
|
||||
Log.d("ReprintActivity", "🗓️ Date filter cleared");
|
||||
|
||||
// Reload data without date filter
|
||||
currentPage = 1;
|
||||
@ -377,7 +381,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🔄 Navigating to page " + page);
|
||||
Log.d("ReprintActivity", "🔄 Navigating to page " + page);
|
||||
|
||||
if (currentSearchQuery.isEmpty()) {
|
||||
// Load from API
|
||||
@ -410,7 +414,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
// Scroll to top
|
||||
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() + ")");
|
||||
}
|
||||
|
||||
@ -452,10 +456,10 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
|
||||
|
||||
// ✅ 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++) {
|
||||
Transaction tx = filteredList.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
} else {
|
||||
// ✅ SEARCH MODE: Filter all available data
|
||||
@ -518,7 +522,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
paginationControls.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "📊 Pagination updated: " +
|
||||
Log.d("ReprintActivity", "📊 Pagination updated: " +
|
||||
"Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords);
|
||||
}
|
||||
|
||||
@ -584,7 +588,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
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");
|
||||
}
|
||||
|
||||
@ -613,7 +617,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
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";
|
||||
|
||||
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" +
|
||||
" - Date Filter: " + fromDate + " to " + toDate);
|
||||
|
||||
@ -642,7 +646,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
apiTotal = results.getInt("total");
|
||||
JSONArray data = results.getJSONArray("data");
|
||||
|
||||
Log.d("TransactionActivity", "📊 API response: " + data.length() +
|
||||
Log.d("ReprintActivity", "📊 API response: " + data.length() +
|
||||
" records, total: " + apiTotal);
|
||||
|
||||
// ✅ STEP 1: Parse all transactions from API
|
||||
@ -667,14 +671,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
// ✅ STEP 2: Apply intelligent deduplication
|
||||
result = applyAdvancedDeduplication(rawTransactions);
|
||||
|
||||
Log.d("TransactionActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
|
||||
Log.d("ReprintActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
|
||||
|
||||
} else {
|
||||
Log.e("TransactionActivity", "❌ HTTP Error: " + responseCode);
|
||||
Log.e("ReprintActivity", "❌ HTTP Error: " + responseCode);
|
||||
error = true;
|
||||
}
|
||||
} catch (IOException | JSONException | URISyntaxException e) {
|
||||
Log.e("TransactionActivity", "❌ Exception: " + e.getMessage(), e);
|
||||
Log.e("ReprintActivity", "❌ Exception: " + e.getMessage(), e);
|
||||
error = true;
|
||||
}
|
||||
return result;
|
||||
@ -687,7 +691,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
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();
|
||||
return;
|
||||
}
|
||||
@ -705,11 +709,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
if (date1 != null && date2 != null) {
|
||||
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;
|
||||
}
|
||||
} 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)
|
||||
});
|
||||
@ -718,14 +722,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
transactionList.clear();
|
||||
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");
|
||||
|
||||
// ✅ 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++) {
|
||||
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
|
||||
@ -761,7 +765,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // Handle timezone properly
|
||||
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;
|
||||
} catch (Exception e) {
|
||||
// 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());
|
||||
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;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -791,11 +795,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
* ✅ ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
|
||||
*/
|
||||
private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
|
||||
Log.d("TransactionActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("TransactionActivity", "📥 Input transactions order (first 5):");
|
||||
Log.d("ReprintActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("ReprintActivity", "📥 Input transactions order (first 5):");
|
||||
for (int i = 0; i < Math.min(5, rawTransactions.size()); 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
|
||||
@ -823,7 +827,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
if (group.size() == 1) {
|
||||
// No duplicates for this reference
|
||||
deduplicatedList.add(group.get(0));
|
||||
Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId);
|
||||
Log.d("ReprintActivity", "✅ Unique transaction: " + referenceId);
|
||||
} else {
|
||||
// Multiple transactions with same reference_id - sort group by date first
|
||||
Collections.sort(group, (t1, t2) -> {
|
||||
@ -843,15 +847,15 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
deduplicatedList.add(bestTransaction);
|
||||
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 + ")");
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "✅ Advanced deduplication complete:");
|
||||
Log.d("TransactionActivity", " 📥 Input: " + rawTransactions.size() + " transactions");
|
||||
Log.d("TransactionActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions");
|
||||
Log.d("TransactionActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates");
|
||||
Log.d("ReprintActivity", "✅ Advanced deduplication complete:");
|
||||
Log.d("ReprintActivity", " 📥 Input: " + rawTransactions.size() + " transactions");
|
||||
Log.d("ReprintActivity", " 📤 Output: " + deduplicatedList.size() + " unique transactions");
|
||||
Log.d("ReprintActivity", " 🗑️ Removed: " + duplicatesRemoved + " duplicates");
|
||||
|
||||
return deduplicatedList;
|
||||
}
|
||||
@ -860,7 +864,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
* ✅ ENHANCED SELECTION: Advanced algorithm to pick the best transaction
|
||||
*/
|
||||
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);
|
||||
int bestPriority = getStatusPriority(bestTransaction.status);
|
||||
@ -871,7 +875,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
int currentPriority = getStatusPriority(tx.status);
|
||||
Date currentDate = parseCreatedAtDate(tx.createdAt);
|
||||
|
||||
Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
Log.d("ReprintActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
", Status=" + tx.status + " (priority=" + currentPriority + ")" +
|
||||
", Created=" + tx.createdAt);
|
||||
|
||||
@ -903,11 +907,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
bestTransaction = tx;
|
||||
bestPriority = currentPriority;
|
||||
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);
|
||||
|
||||
return bestTransaction;
|
||||
@ -925,7 +929,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
return date1.after(date2);
|
||||
}
|
||||
} 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
|
||||
@ -990,7 +994,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
// Tier 5: Unknown status
|
||||
default:
|
||||
Log.w("TransactionActivity", "Unknown status encountered: " + status);
|
||||
Log.w("ReprintActivity", "Unknown status encountered: " + status);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -1002,12 +1006,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
Intent intent = new Intent(this, ReceiptActivity.class);
|
||||
|
||||
// Add calling activity information for proper back navigation
|
||||
intent.putExtra("calling_activity", "TransactionActivity");
|
||||
intent.putExtra("calling_activity", "ReprintActivity");
|
||||
|
||||
// Extract and send raw amount properly
|
||||
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 + "'");
|
||||
|
||||
// Send transaction data to ReceiptActivity
|
||||
@ -1032,7 +1036,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
|
||||
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);
|
||||
}
|
||||
@ -1084,7 +1088,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
// For QRIS, we could implement real-time acquirer lookup here
|
||||
// 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";
|
||||
}
|
||||
|
||||
@ -1128,7 +1132,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
Long.parseLong(cleaned);
|
||||
return cleaned;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("TransactionActivity", "Invalid amount: " + formattedAmount);
|
||||
Log.e("ReprintActivity", "Invalid amount: " + formattedAmount);
|
||||
return "0";
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package com.example.bdkipoc;
|
||||
package com.example.bdkipoc.cetakulang;
|
||||
|
||||
import com.example.bdkipoc.R;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -26,15 +27,17 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
|
||||
private List<TransactionActivity.Transaction> transactionList;
|
||||
import com.example.bdkipoc.StyleHelper;
|
||||
|
||||
public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterActivity.TransactionViewHolder> {
|
||||
private List<ReprintActivity.Transaction> transactionList;
|
||||
private OnPrintClickListener printClickListener;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -45,23 +48,23 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
/**
|
||||
* 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;
|
||||
notifyDataSetChanged();
|
||||
|
||||
Log.d("TransactionAdapter", "📋 Data updated: " + newData.size() + " items");
|
||||
Log.d("ReprintAdapterActivity", "📋 Data updated: " + newData.size() + " items");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
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));
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":");
|
||||
Log.d("TransactionAdapter", " Reference: " + t.referenceId);
|
||||
Log.d("TransactionAdapter", " Status: " + t.status);
|
||||
Log.d("TransactionAdapter", " Amount: " + t.amount);
|
||||
Log.d("ReprintAdapterActivity", "📋 Binding transaction " + position + ":");
|
||||
Log.d("ReprintAdapterActivity", " Reference: " + t.referenceId);
|
||||
Log.d("ReprintAdapterActivity", " Status: " + t.status);
|
||||
Log.d("ReprintAdapterActivity", " Amount: " + t.amount);
|
||||
|
||||
// Set reference ID
|
||||
holder.referenceId.setText(t.referenceId);
|
||||
@ -88,10 +91,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String formattedAmount = formatRupiah(amountValue);
|
||||
holder.amount.setText(formattedAmount);
|
||||
|
||||
Log.d("TransactionAdapter", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
|
||||
Log.d("ReprintAdapterActivity", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
|
||||
|
||||
} 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;
|
||||
holder.amount.setText(fallback);
|
||||
}
|
||||
@ -99,7 +102,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
// ✅ ENHANCED STATUS HANDLING dengan comprehensive checking
|
||||
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
|
||||
if ("INIT".equalsIgnoreCase(t.status) || "PENDING".equalsIgnoreCase(t.status)) {
|
||||
@ -108,7 +111,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
holder.status.setText("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
|
||||
checkMidtransStatus(t.referenceId, holder.status);
|
||||
@ -116,13 +119,13 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
// No reference ID to check
|
||||
holder.status.setText(displayStatus.toUpperCase());
|
||||
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 {
|
||||
// Use existing status yang sudah confirmed
|
||||
holder.status.setText(displayStatus.toUpperCase());
|
||||
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
|
||||
@ -133,7 +136,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String formattedDate = formatCreatedAtDate(t.createdAt);
|
||||
holder.createdAt.setText(formattedDate);
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
|
||||
Log.d("ReprintAdapterActivity", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
|
||||
|
||||
// Set click listeners
|
||||
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) {
|
||||
@ -156,7 +159,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
return "0";
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "Cleaning amount: '" + amount + "'");
|
||||
Log.d("ReprintAdapterActivity", "Cleaning amount: '" + amount + "'");
|
||||
|
||||
// Remove currency symbols and spaces
|
||||
String cleaned = amount
|
||||
@ -199,7 +202,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
// Remove any commas
|
||||
cleaned = cleaned.replace(",", "");
|
||||
|
||||
Log.d("TransactionAdapter", "Cleaned result: '" + cleaned + "'");
|
||||
Log.d("ReprintAdapterActivity", "Cleaned result: '" + cleaned + "'");
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
@ -216,7 +219,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
private void checkMidtransStatus(String referenceId, TextView statusTextView) {
|
||||
new Thread(() -> {
|
||||
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
|
||||
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;
|
||||
|
||||
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
|
||||
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", "");
|
||||
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
|
||||
isRefreshMatch = true;
|
||||
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", "🔄 Found refresh match: " + logOrderId);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Ignore custom field parsing errors
|
||||
@ -288,7 +291,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
if (itemName.contains("(Ref: " + referenceId + ")") ||
|
||||
itemName.contains("- " + referenceId)) {
|
||||
isItemMatch = true;
|
||||
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", "📦 Found item match: " + logOrderId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -299,11 +302,11 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch;
|
||||
|
||||
if (isRelatedTransaction) {
|
||||
Log.d("TransactionAdapter", "🎯 MATCH FOUND!");
|
||||
Log.d("TransactionAdapter", " Order ID: " + logOrderId);
|
||||
Log.d("TransactionAdapter", " Status: " + logTransactionStatus);
|
||||
Log.d("TransactionAdapter", " Acquirer: " + logAcquirer);
|
||||
Log.d("TransactionAdapter", " Match Type: " +
|
||||
Log.d("ReprintAdapterActivity", "🎯 MATCH FOUND!");
|
||||
Log.d("ReprintAdapterActivity", " Order ID: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", " Status: " + logTransactionStatus);
|
||||
Log.d("ReprintAdapterActivity", " Acquirer: " + logAcquirer);
|
||||
Log.d("ReprintAdapterActivity", " Match Type: " +
|
||||
(isDirectMatch ? "DIRECT " : "") +
|
||||
(isRefreshMatch ? "REFRESH " : "") +
|
||||
(isItemMatch ? "ITEM" : ""));
|
||||
@ -315,29 +318,29 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
finalStatus = "PAID";
|
||||
foundOrderId = logOrderId;
|
||||
foundAcquirer = logAcquirer;
|
||||
Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
|
||||
Log.d("ReprintAdapterActivity", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
|
||||
break; // Found paid status, stop searching
|
||||
} else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) {
|
||||
finalStatus = "PENDING";
|
||||
foundOrderId = logOrderId;
|
||||
foundAcquirer = logAcquirer;
|
||||
Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId);
|
||||
Log.d("ReprintAdapterActivity", "⏳ PENDING found: " + logOrderId);
|
||||
} else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) {
|
||||
if (finalStatus.equals("INIT")) { // Only update if no better status found
|
||||
finalStatus = "FAILED";
|
||||
foundOrderId = logOrderId;
|
||||
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("TransactionAdapter", " Status: " + finalStatus);
|
||||
Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
|
||||
Log.d("TransactionAdapter", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A"));
|
||||
Log.d("ReprintAdapterActivity", "🔍 FINAL RESULT for " + referenceId + ":");
|
||||
Log.d("ReprintAdapterActivity", " Status: " + finalStatus);
|
||||
Log.d("ReprintAdapterActivity", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
|
||||
Log.d("ReprintAdapterActivity", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A"));
|
||||
}
|
||||
|
||||
// STEP 3: Update UI di main thread
|
||||
@ -348,10 +351,10 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
statusTextView.setText(displayStatus);
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus);
|
||||
|
||||
Log.d("TransactionAdapter", "🎨 UI UPDATED:");
|
||||
Log.d("TransactionAdapter", " Reference: " + referenceId);
|
||||
Log.d("TransactionAdapter", " Display Status: " + displayStatus);
|
||||
Log.d("TransactionAdapter", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown"));
|
||||
Log.d("ReprintAdapterActivity", "🎨 UI UPDATED:");
|
||||
Log.d("ReprintAdapterActivity", " Reference: " + referenceId);
|
||||
Log.d("ReprintAdapterActivity", " Display Status: " + displayStatus);
|
||||
Log.d("ReprintAdapterActivity", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown"));
|
||||
});
|
||||
|
||||
// ✅ BONUS: Update backend jika status berubah ke PAID
|
||||
@ -360,7 +363,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
Log.w("ReprintAdapterActivity", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("ERROR");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR");
|
||||
@ -368,7 +371,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
} 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.setText("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) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId);
|
||||
Log.d("ReprintAdapterActivity", "🔄 Updating backend status for reference: " + referenceId);
|
||||
|
||||
JSONObject updatePayload = new JSONObject();
|
||||
updatePayload.put("status", status);
|
||||
@ -414,16 +417,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("TransactionAdapter", "📥 Backend update response: " + responseCode);
|
||||
Log.d("ReprintAdapterActivity", "📥 Backend update response: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
Log.d("TransactionAdapter", "✅ Backend status updated successfully");
|
||||
Log.d("ReprintAdapterActivity", "✅ Backend status updated successfully");
|
||||
} else {
|
||||
Log.e("TransactionAdapter", "❌ Backend update failed: " + responseCode);
|
||||
Log.e("ReprintAdapterActivity", "❌ Backend update failed: " + responseCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("TransactionAdapter", "❌ Backend update error: " + e.getMessage(), e);
|
||||
Log.e("ReprintAdapterActivity", "❌ Backend update error: " + e.getMessage(), e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@ -436,7 +439,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Input date: '" + rawDate + "'");
|
||||
Log.d("ReprintAdapterActivity", "📅 Input date: '" + rawDate + "'");
|
||||
|
||||
try {
|
||||
// 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());
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📅 Cleaned date: '" + cleanedDate + "'");
|
||||
Log.d("ReprintAdapterActivity", "📅 Cleaned date: '" + cleanedDate + "'");
|
||||
|
||||
// Output format: d/M/yyyy H:mm:ss
|
||||
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);
|
||||
if (date != null) {
|
||||
String formatted = outputFormat.format(date);
|
||||
Log.d("TransactionAdapter", "📅 Date formatted: " + rawDate + " -> " + formatted);
|
||||
Log.d("ReprintAdapterActivity", "📅 Date formatted: " + rawDate + " -> " + formatted);
|
||||
return formatted;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("TransactionAdapter", "❌ Date formatting error for: " + rawDate, e);
|
||||
Log.e("ReprintAdapterActivity", "❌ Date formatting error for: " + rawDate, e);
|
||||
}
|
||||
|
||||
// Fallback: Manual parsing
|
||||
@ -485,7 +488,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
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
|
||||
String[] parts = workingDate.split(" ");
|
||||
@ -511,16 +514,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
String second = timeComponents[2];
|
||||
|
||||
String result = dayInt + "/" + monthInt + "/" + year + " " + hour + ":" + minute + ":" + second;
|
||||
Log.d("TransactionAdapter", "📅 Manual format result: " + result);
|
||||
Log.d("ReprintAdapterActivity", "📅 Manual format result: " + result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
145
app/src/main/java/com/example/bdkipoc/emv/EmvTTS.java
Normal file
145
app/src/main/java/com/example/bdkipoc/emv/EmvTTS.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -70,7 +70,7 @@ public class QrisActivity extends AppCompatActivity {
|
||||
|
||||
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
|
||||
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
|
||||
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key
|
||||
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc=";
|
||||
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
|
||||
|
||||
@Override
|
1448
app/src/main/java/com/example/bdkipoc/qris/QrisResultActivity.java
Normal file
1448
app/src/main/java/com/example/bdkipoc/qris/QrisResultActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
@ -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, "==============================");
|
||||
}
|
||||
}
|
265
app/src/main/java/com/example/bdkipoc/utils/ByteUtil.java
Normal file
265
app/src/main/java/com/example/bdkipoc/utils/ByteUtil.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
89
app/src/main/java/com/example/bdkipoc/utils/LogUtil.java
Normal file
89
app/src/main/java/com/example/bdkipoc/utils/LogUtil.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
107
app/src/main/java/com/example/bdkipoc/utils/Utility.java
Normal file
107
app/src/main/java/com/example/bdkipoc/utils/Utility.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
@ -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:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
10
app/src/main/res/anim/slide_down.xml
Normal file
10
app/src/main/res/anim/slide_down.xml
Normal 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>
|
10
app/src/main/res/anim/slide_up.xml
Normal file
10
app/src/main/res/anim/slide_up.xml
Normal 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>
|
BIN
app/src/main/res/drawable/banner.png
Normal file
BIN
app/src/main/res/drawable/banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
6
app/src/main/res/drawable/bg_status.xml
Normal file
6
app/src/main/res/drawable/bg_status.xml
Normal 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>
|
5
app/src/main/res/drawable/button_cancel_background.xml
Normal file
5
app/src/main/res/drawable/button_cancel_background.xml
Normal 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>
|
BIN
app/src/main/res/drawable/ic_e_money.png
Normal file
BIN
app/src/main/res/drawable/ic_e_money.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -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>
|
BIN
app/src/main/res/drawable/ic_settings.png
Normal file
BIN
app/src/main/res/drawable/ic_settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable/ic_settlement.png
Normal file
BIN
app/src/main/res/drawable/ic_settlement.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -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>
|
BIN
app/src/main/res/drawable/ic_transfer.png
Normal file
BIN
app/src/main/res/drawable/ic_transfer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
@ -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>
|
5
app/src/main/res/drawable/timer_circle_background.xml
Normal file
5
app/src/main/res/drawable/timer_circle_background.xml
Normal 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>
|
@ -8,4 +8,8 @@
|
||||
app:font="@font/inter_medium"
|
||||
app:fontWeight="500"
|
||||
app:fontStyle="normal"/>
|
||||
<font
|
||||
app:font="@font/inter_bold"
|
||||
app:fontWeight="700"
|
||||
app:fontStyle="normal"/>
|
||||
</font-family>
|
BIN
app/src/main/res/font/inter_bold.ttf
Normal file
BIN
app/src/main/res/font/inter_bold.ttf
Normal file
Binary file not shown.
328
app/src/main/res/layout/activity_create_transaction.xml
Normal file
328
app/src/main/res/layout/activity_create_transaction.xml
Normal 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>
|
@ -216,7 +216,40 @@
|
||||
</LinearLayout>
|
||||
</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
|
||||
android:id="@+id/card_uang_elektronik"
|
||||
android:layout_width="0dp"
|
||||
@ -271,7 +304,7 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_reprint"
|
||||
android:src="@drawable/ic_print"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
@ -283,6 +316,40 @@
|
||||
</LinearLayout>
|
||||
</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
|
||||
android:id="@+id/card_settlement"
|
||||
android:layout_width="0dp"
|
||||
@ -290,6 +357,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -316,7 +384,6 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 3: Histori, Bantuan, Info Toko -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_histori"
|
||||
android:layout_width="0dp"
|
||||
@ -324,6 +391,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -350,6 +418,7 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 4: Bantuan, Info Toko, Pengaturan -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_bantuan"
|
||||
android:layout_width="0dp"
|
||||
@ -357,7 +426,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -391,7 +460,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -418,9 +487,8 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 4: Dummy Menu 1, 2, 3 (Hidden initially) -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_dummy_menu_1"
|
||||
android:id="@+id/card_pengaturan"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
@ -441,189 +509,17 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_qr_code"
|
||||
android:src="@drawable/ic_settings"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Dummy Menu 1"
|
||||
android:text="Pengaturan"
|
||||
style="@style/MenuCardTitle"/>
|
||||
</LinearLayout>
|
||||
</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>
|
||||
|
||||
<!-- Lainnya Button -->
|
||||
|
@ -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>
|
@ -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>
|
@ -1,223 +1,241 @@
|
||||
<?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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#FFFFFF">
|
||||
android:background="#F5F5F5"
|
||||
tools:context=".QrisResultActivity">
|
||||
|
||||
<!-- Red Status Bar -->
|
||||
<!-- Header Background -->
|
||||
<View
|
||||
android:id="@+id/header_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:background="#E31937" />
|
||||
|
||||
<!-- Header with Back Navigation -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="100dp"
|
||||
android:background="#E31937"
|
||||
android:paddingBottom="16dp">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- 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:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
android:padding="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="‹"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
<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_height="wrap_content"
|
||||
android:text="Kembali"
|
||||
android:text="Generate QR"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- White Card Container -->
|
||||
<!-- Main Content Card -->
|
||||
<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_margin="16dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:gravity="center_horizontal">
|
||||
android:padding="32dp"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- Generate QR Title -->
|
||||
<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 -->
|
||||
<!-- QRIS Logo -->
|
||||
<TextView
|
||||
android:id="@+id/qris_logo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="QRIS"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="monospace"
|
||||
android:layout_marginBottom="24dp" />
|
||||
android:textColor="#000000"
|
||||
android:letterSpacing="0.1"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<!-- QR Code -->
|
||||
<ImageView
|
||||
android:id="@+id/qrImageView"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="QRIS QR Code"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginBottom="24dp" />
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="240dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#FFFFFF"
|
||||
android:padding="8dp" />
|
||||
|
||||
<!-- Amount Display -->
|
||||
<!-- Amount -->
|
||||
<TextView
|
||||
android:id="@+id/amountTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RP.200.000"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="20sp"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp" />
|
||||
android:textColor="#333333"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
|
||||
<!-- Timer/Counter -->
|
||||
<!-- Timer -->
|
||||
<TextView
|
||||
android:id="@+id/timerTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:text="60"
|
||||
android:textColor="#E31937"
|
||||
android:textSize="18sp"
|
||||
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:textSize="12sp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="visible" />
|
||||
android:gravity="center"
|
||||
android:background="@drawable/timer_circle_background"
|
||||
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 -->
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="Batalkan"
|
||||
android:textColor="#E31937"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:background="@drawable/button_cancel_background"
|
||||
android:fontFamily="@font/inter"
|
||||
android:textAllCaps="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- Hidden Elements for Functionality -->
|
||||
<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" />
|
||||
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:text="Payment Status Success"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<!-- Spacer to push button to bottom -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
<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" />
|
||||
|
||||
<!-- Bottom Cancel Button -->
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_margin="16dp"
|
||||
android:text="Batalkan"
|
||||
android:textColor="#E31937"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="normal"
|
||||
android:background="@android:color/transparent"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
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" />
|
||||
|
||||
</LinearLayout>
|
||||
<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>
|
@ -104,7 +104,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="EDC "
|
||||
android:text="Payvora "
|
||||
android:textColor="#E31937"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
@ -113,7 +113,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Merchant"
|
||||
android:text="PRO"
|
||||
android:textColor="#3F51B5"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
@ -121,15 +121,6 @@
|
||||
|
||||
</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>
|
||||
|
||||
<!-- Merchant Info -->
|
||||
@ -168,46 +159,36 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
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
|
||||
android:id="@+id/mid_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="1234567890"
|
||||
android:text="MID:12345678901"
|
||||
android:textColor="#333333"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"/>
|
||||
|
||||
<!-- Vertical Separator -->
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="TID: "
|
||||
android:textColor="#666666"
|
||||
android:text=" | "
|
||||
android:textColor="#999999"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"/>
|
||||
android:fontFamily="@font/inter"
|
||||
android:paddingHorizontal="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tid_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="1234567890"
|
||||
android:text="TID:12345678901"
|
||||
android:textColor="#333333"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Separator Line -->
|
||||
|
@ -79,15 +79,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cetak Ulang Struk"
|
||||
android:textColor="#333333"
|
||||
android:textSize="20sp"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="sans-serif" />
|
||||
android:fontFamily="inter-bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- ✅ PERBAIKAN: Gunakan LinearLayout dengan weight distribution yang lebih baik -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -222,7 +221,6 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<!-- ✅ PERBAIKAN: RecyclerView dengan height yang tepat untuk mencegah pagination terpotong -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
@ -232,7 +230,6 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<!-- ✅ PERBAIKAN: Pagination Controls dengan padding dan margin yang lebih baik -->
|
||||
<LinearLayout
|
||||
android:id="@+id/paginationControls"
|
||||
android:layout_width="match_parent"
|
47
app/src/main/res/layout/component_appbar.xml
Normal file
47
app/src/main/res/layout/component_appbar.xml
Normal 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>
|
@ -101,16 +101,10 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cetak Ulang"
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp"
|
||||
android:drawableLeft="@android:drawable/ic_menu_edit"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_print" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,9 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="colorPrimary">#3F51B5</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 -->
|
||||
<color name="red">#DE0701</color>
|
||||
<color name="primary_blue">#1976D2</color>
|
||||
<color name="light_blue">#BBDEFB</color>
|
||||
<color name="accent_teal">#009688</color>
|
||||
@ -13,4 +32,88 @@
|
||||
<color name="light_gray">#F5F5F5</color>
|
||||
<color name="medium_gray">#E0E0E0</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>
|
8
app/src/main/res/values/dimens.xml
Normal file
8
app/src/main/res/values/dimens.xml
Normal 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>
|
@ -13,4 +13,11 @@
|
||||
<string name="payment_status_success">Payment Successful!</string>
|
||||
<string name="return_main">Return to Main Screen</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>
|
@ -29,6 +29,11 @@
|
||||
<item name="titleTextAppearance">@style/ToolbarTitleStyle</item>
|
||||
</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 -->
|
||||
<style name="NumpadButton">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
@ -45,4 +50,172 @@
|
||||
<item name="android:focusable">true</item>
|
||||
</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>
|
@ -1,8 +1,9 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.BDKIPOC" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your light theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/red</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.BDKIPOC" parent="Base.Theme.BDKIPOC" />
|
||||
|
@ -16,6 +16,14 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user