Compare commits
No commits in common. "6f78b6df3f61ad845dbdbfacd86e6cff9051a335" and "729bdddad43442a8ea463b1cdc57dafe9be72428" have entirely different histories.
6f78b6df3f
...
729bdddad4
@ -5,13 +5,6 @@ plugins {
|
||||
android {
|
||||
namespace 'com.example.bdkipoc'
|
||||
compileSdk 35
|
||||
|
||||
// Tambahkan lint options
|
||||
lint {
|
||||
abortOnError false
|
||||
disable 'GoogleAppIndexingWarning'
|
||||
disable 'NonConstantResourceId'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.bdkipoc"
|
||||
@ -29,32 +22,20 @@ 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
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -8,23 +8,7 @@
|
||||
<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"
|
||||
@ -45,45 +29,32 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".cetakulang.ReprintActivity"
|
||||
android:name=".TransactionActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".cetakulang.ReprintAdapterActivity"
|
||||
android:name=".PaymentActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".PinActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ReceiptActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".QrisActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".QrisResultActivity"
|
||||
android:exported="false" />
|
||||
|
||||
<activity android:name=".QrisResultActivity" />
|
||||
<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>
|
@ -1,27 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
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,13 +19,6 @@ 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
|
||||
@ -90,9 +83,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
// 6 dummy menus should be hidden initially
|
||||
CardView[] dummyCards = {
|
||||
findViewById(R.id.card_bantuan),
|
||||
findViewById(R.id.card_info_toko),
|
||||
findViewById(R.id.card_pengaturan),
|
||||
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)
|
||||
};
|
||||
|
||||
for (CardView card : dummyCards) {
|
||||
@ -139,17 +135,21 @@ 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,
|
||||
// Row 3 (Always visible - 3 items)
|
||||
R.id.card_refund,
|
||||
R.id.card_settlement,
|
||||
// Row 3 (Always visible - 3 items)
|
||||
R.id.card_histori,
|
||||
// Row 4 (Hidden initially - 3 items)
|
||||
R.id.card_bantuan,
|
||||
R.id.card_info_toko,
|
||||
R.id.card_pengaturan,
|
||||
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
|
||||
};
|
||||
|
||||
// Set up click listeners for all cards
|
||||
@ -157,37 +157,39 @@ 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) {
|
||||
navigateToCreateTransaction("credit_card", cardId, "Kartu Kredit");
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
} else if (cardId == R.id.card_kartu_debit) {
|
||||
navigateToCreateTransaction("debit_card", cardId, "Kartu Debit");
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
} 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) {
|
||||
navigateToCreateTransaction("e_money", cardId, "Uang Elektronik");
|
||||
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
|
||||
} else if (cardId == R.id.card_cetak_ulang) {
|
||||
startActivity(new Intent(MainActivity.this, ReprintActivity.class));
|
||||
// Col-3
|
||||
} else if (cardId == R.id.card_refund) {
|
||||
navigateToCreateTransaction("refund", cardId, "Refund");
|
||||
startActivity(new Intent(MainActivity.this, TransactionActivity.class));
|
||||
} else if (cardId == R.id.card_settlement) {
|
||||
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} else if (cardId == R.id.card_histori) {
|
||||
startActivity(new Intent(MainActivity.this, HistoryActivity.class));
|
||||
// Col-4
|
||||
Toast.makeText(this, "Histori - Coming Soon", Toast.LENGTH_SHORT).show();
|
||||
} 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_pengaturan) {
|
||||
Toast.makeText(this, "Pengaturan - 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 {
|
||||
// Fallback for any other cards
|
||||
navigateToCreateTransaction("credit_card", cardId, "Unknown");
|
||||
Toast.makeText(this, "Menu Diklik: " + getResources().getResourceEntryName(cardId), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -195,9 +197,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
// Get references to ONLY the dummy cards that need to be toggled
|
||||
CardView[] toggleableCards = {
|
||||
findViewById(R.id.card_bantuan),
|
||||
findViewById(R.id.card_info_toko),
|
||||
findViewById(R.id.card_pengaturan),
|
||||
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)
|
||||
};
|
||||
|
||||
// Set up "Lainnya" button click listener
|
||||
@ -244,125 +249,6 @@ 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);
|
||||
@ -377,74 +263,5 @@ 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", "==================================");
|
||||
}
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
530
app/src/main/java/com/example/bdkipoc/PaymentActivity.java
Normal file
530
app/src/main/java/com/example/bdkipoc/PaymentActivity.java
Normal file
@ -0,0 +1,530 @@
|
||||
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();
|
||||
}
|
||||
}
|
558
app/src/main/java/com/example/bdkipoc/PinActivity.java
Normal file
558
app/src/main/java/com/example/bdkipoc/PinActivity.java
Normal file
@ -0,0 +1,558 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 U0ItTWlkLXNlcnZlci1PM2t1bXkwVDl4M1VvYnVvVTc3NW5QbXc=";
|
||||
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key
|
||||
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
|
||||
|
||||
@Override
|
689
app/src/main/java/com/example/bdkipoc/QrisResultActivity.java
Normal file
689
app/src/main/java/com/example/bdkipoc/QrisResultActivity.java
Normal file
@ -0,0 +1,689 @@
|
||||
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,10 +23,6 @@ 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 {
|
||||
|
||||
@ -54,28 +50,6 @@ 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);
|
||||
@ -142,27 +116,10 @@ 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 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);
|
||||
Log.d("ReceiptActivity", "=== LOADING TRANSACTION DATA ===");
|
||||
|
||||
// Get all available data from intent
|
||||
String amount = intent.getStringExtra("transaction_amount");
|
||||
@ -176,154 +133,99 @@ 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 : "TOKO KLONTONG PAK EKO");
|
||||
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Ciputat Baru, Tangsel");
|
||||
merchantName.setText(merchantNameStr != null ? merchantNameStr : "Marcel Panjaitan");
|
||||
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Jakarta, Indonesia");
|
||||
|
||||
// 2. Set MID and TID
|
||||
midText.setText("MID: " + (mid != null ? mid : "123456789901"));
|
||||
tidText.setText("TID: " + (tid != null ? tid : "123456789901"));
|
||||
midText.setText(mid != null ? mid : "71000026521");
|
||||
tidText.setText(tid != null ? tid : "73001500");
|
||||
|
||||
// 3. Set transaction number
|
||||
String displayTransactionNumber = getDisplayTransactionNumber(referenceId, transactionId, orderId);
|
||||
transactionNumber.setText(displayTransactionNumber);
|
||||
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");
|
||||
|
||||
// 4. Set transaction date
|
||||
String displayDate = getDisplayTransactionDate(createdAt, transactionDateStr, isEmvTransaction);
|
||||
String displayDate = null;
|
||||
if (createdAt != null && !createdAt.isEmpty()) {
|
||||
displayDate = formatDateFromCreatedAt(createdAt);
|
||||
} else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
|
||||
displayDate = transactionDateStr;
|
||||
} else {
|
||||
displayDate = getCurrentDateTime();
|
||||
}
|
||||
transactionDate.setText(displayDate);
|
||||
|
||||
// 5. ✅ ENHANCED: Set payment method based on transaction type
|
||||
String displayPaymentMethod = getDisplayPaymentMethod(channelCode, paymentMethodStr, isEmvTransaction, emvMode);
|
||||
// 5. Set payment method
|
||||
String displayPaymentMethod = getPaymentMethodFromChannelCode(channelCode, paymentMethodStr);
|
||||
paymentMethod.setText(displayPaymentMethod);
|
||||
|
||||
// 6. ✅ ENHANCED: Set card type with EMV and QRIS priority
|
||||
String displayCardType = getDisplayCardType(cardTypeStr, acquirer, channelCode, isEmvTransaction,
|
||||
isQrisTransaction, midtransResponse, referenceId);
|
||||
cardType.setText(displayCardType);
|
||||
// 6. ✅ IMPROVED: Enhanced card type detection for QRIS
|
||||
String displayCardType = null;
|
||||
|
||||
// 7. ✅ Format and set amounts with proper calculation
|
||||
setAmountDataEnhanced(amount, grossAmount, isEmvTransaction, isQrisTransaction);
|
||||
|
||||
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 (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);
|
||||
}
|
||||
|
||||
// If all formats fail, return as-is
|
||||
Log.w("ReceiptActivity", "Could not format EMV date: " + dateString);
|
||||
return dateString;
|
||||
cardType.setText(displayCardType);
|
||||
Log.d("ReceiptActivity", "💳 FINAL CARD TYPE: " + displayCardType);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("ReceiptActivity", "Error formatting EMV date: " + dateString, e);
|
||||
return dateString;
|
||||
// 7. Format and set amounts
|
||||
setAmountData(amount, grossAmount);
|
||||
|
||||
Log.d("ReceiptActivity", "=== TRANSACTION DATA LOADED ===");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String formatDateFromCreatedAt(String createdAt) {
|
||||
try {
|
||||
// Input format from database: "yyyy-MM-dd HH:mm:ss"
|
||||
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 MMMM yyyy HH:mm", new Locale("id", "ID"));
|
||||
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID"));
|
||||
|
||||
Date date = inputFormat.parse(createdAt);
|
||||
String formatted = outputFormat.format(date);
|
||||
@ -338,39 +240,6 @@ 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
|
||||
*/
|
||||
@ -420,78 +289,6 @@ 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")) {
|
||||
@ -500,14 +297,88 @@ 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":
|
||||
@ -867,78 +738,6 @@ 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;
|
||||
@ -957,79 +756,51 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ ENHANCED: Set amount data with EMV and QRIS support
|
||||
*/
|
||||
private void setAmountDataEnhanced(String amount, String grossAmount, boolean isEmvTransaction, boolean isQrisTransaction) {
|
||||
private void setAmountData(String amount, String grossAmount) {
|
||||
// Prioritize 'amount' over 'grossAmount' for transaction data
|
||||
String amountToUse = amount != null ? amount : grossAmount;
|
||||
|
||||
Log.d("ReceiptActivity", "Setting enhanced amount data - amount: " + amount +
|
||||
", grossAmount: " + grossAmount + ", using: " + amountToUse +
|
||||
", isEMV: " + isEmvTransaction + ", isQRIS: " + isQrisTransaction);
|
||||
Log.d("ReceiptActivity", "Setting amount data - amount: " + amount +
|
||||
", grossAmount: " + grossAmount + ", using: " + amountToUse);
|
||||
|
||||
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(formatCurrency(amountLong));
|
||||
transactionTotal.setText("Rp " + formatCurrency(amountLong));
|
||||
|
||||
// ✅ 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);
|
||||
}
|
||||
// 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;
|
||||
|
||||
// Set calculated values
|
||||
taxPercentage.setText(tax > 0 ? "11%" : "0%");
|
||||
serviceFee.setText(formatCurrency(serviceFeeValue));
|
||||
finalTotal.setText(formatCurrency(total));
|
||||
taxPercentage.setText("Rp 0");
|
||||
serviceFee.setText("Rp 0");
|
||||
finalTotal.setText("Rp " + formatCurrency(total));
|
||||
|
||||
Log.d("ReceiptActivity", "Enhanced amount formatting successful: " + amountLong + " -> " + formatCurrency(total));
|
||||
Log.d("ReceiptActivity", "Amount formatting successful: " + amountLong + " -> Rp " + formatCurrency(total));
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("ReceiptActivity", "Error parsing enhanced amount: " + amountToUse, e);
|
||||
Log.e("ReceiptActivity", "Error parsing amount: " + amountToUse, e);
|
||||
// Fallback if parsing fails
|
||||
transactionTotal.setText(amountToUse);
|
||||
taxPercentage.setText("0%");
|
||||
serviceFee.setText("0");
|
||||
finalTotal.setText(amountToUse);
|
||||
transactionTotal.setText("Rp " + amountToUse);
|
||||
taxPercentage.setText("Rp 0");
|
||||
serviceFee.setText("Rp 0");
|
||||
finalTotal.setText("Rp " + amountToUse);
|
||||
}
|
||||
} else {
|
||||
// Default values if no amount provided
|
||||
transactionTotal.setText("0");
|
||||
taxPercentage.setText("0%");
|
||||
serviceFee.setText("0");
|
||||
finalTotal.setText("0");
|
||||
transactionTotal.setText("Rp 0");
|
||||
taxPercentage.setText("Rp 0");
|
||||
serviceFee.setText("Rp 0");
|
||||
finalTotal.setText("Rp 0");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1103,7 +874,7 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private String getCurrentDateTime() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy HH:mm", new Locale("id", "ID"));
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID"));
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
|
||||
@ -1131,9 +902,9 @@ public class ReceiptActivity extends AppCompatActivity {
|
||||
|
||||
if (callingActivity != null) {
|
||||
switch (callingActivity) {
|
||||
case "ReprintActivity":
|
||||
case "TransactionActivity":
|
||||
// Go back to transaction list
|
||||
Intent transactionIntent = new Intent(this, ReprintActivity.class);
|
||||
Intent transactionIntent = new Intent(this, TransactionActivity.class);
|
||||
transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
startActivity(transactionIntent);
|
||||
break;
|
||||
@ -1143,11 +914,6 @@ 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,6 +1,5 @@
|
||||
package com.example.bdkipoc.cetakulang;
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import com.example.bdkipoc.R;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
@ -51,12 +50,9 @@ import java.util.TimeZone;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.widget.DatePicker;
|
||||
|
||||
import com.example.bdkipoc.ReceiptActivity;
|
||||
import com.example.bdkipoc.StyleHelper;
|
||||
|
||||
public class ReprintActivity extends AppCompatActivity implements ReprintAdapterActivity.OnPrintClickListener {
|
||||
public class TransactionActivity extends AppCompatActivity implements TransactionAdapter.OnPrintClickListener {
|
||||
private RecyclerView recyclerView;
|
||||
private ReprintAdapterActivity adapter;
|
||||
private TransactionAdapter adapter;
|
||||
private List<Transaction> transactionList;
|
||||
private List<Transaction> filteredList;
|
||||
private ProgressBar progressBar;
|
||||
@ -93,7 +89,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_reprint);
|
||||
setContentView(R.layout.activity_transaction);
|
||||
|
||||
// ✅ Initialize SharedPreferences for local tracking
|
||||
prefs = getSharedPreferences("transaction_prefs", MODE_PRIVATE);
|
||||
@ -163,7 +159,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
transactionList = new ArrayList<>();
|
||||
filteredList = new ArrayList<>();
|
||||
|
||||
adapter = new ReprintAdapterActivity(filteredList);
|
||||
adapter = new TransactionAdapter(filteredList);
|
||||
adapter.setPrintClickListener(this);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||
@ -346,13 +342,13 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
filterButtonText.setTextColor(getResources().getColor(android.R.color.holo_blue_dark));
|
||||
filterButtonText.setTextSize(12); // Smaller text when filter is active
|
||||
|
||||
Log.d("ReprintActivity", "🎨 Filter button updated: " + displayText);
|
||||
Log.d("TransactionActivity", "🎨 Filter button updated: " + displayText);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Apply date filter
|
||||
private void applyDateFilter() {
|
||||
Log.d("ReprintActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate);
|
||||
Log.d("TransactionActivity", "🗓️ Applying date filter: " + fromDate + " to " + toDate);
|
||||
|
||||
// Reset to first page and reload data
|
||||
currentPage = 1;
|
||||
@ -370,7 +366,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
filterButtonText.setTextSize(14); // Reset to normal size
|
||||
}
|
||||
|
||||
Log.d("ReprintActivity", "🗓️ Date filter cleared");
|
||||
Log.d("TransactionActivity", "🗓️ Date filter cleared");
|
||||
|
||||
// Reload data without date filter
|
||||
currentPage = 1;
|
||||
@ -381,7 +377,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("ReprintActivity", "🔄 Navigating to page " + page);
|
||||
Log.d("TransactionActivity", "🔄 Navigating to page " + page);
|
||||
|
||||
if (currentSearchQuery.isEmpty()) {
|
||||
// Load from API
|
||||
@ -414,7 +410,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
// Scroll to top
|
||||
recyclerView.scrollToPosition(0);
|
||||
|
||||
Log.d("ReprintActivity", "📄 Displaying search results page " + currentPage +
|
||||
Log.d("TransactionActivity", "📄 Displaying search results page " + currentPage +
|
||||
" (items " + (startIndex + 1) + "-" + endIndex + " of " + filteredList.size() + ")");
|
||||
}
|
||||
|
||||
@ -456,10 +452,10 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
|
||||
|
||||
// ✅ PASTIKAN TIDAK PERLU SORT LAGI karena sudah sorted dari API response
|
||||
Log.d("ReprintActivity", "📋 FILTERED LIST ORDER (no search - maintaining API order):");
|
||||
Log.d("TransactionActivity", "📋 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("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
} else {
|
||||
// ✅ SEARCH MODE: Filter all available data
|
||||
@ -522,7 +518,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
paginationControls.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Log.d("ReprintActivity", "📊 Pagination updated: " +
|
||||
Log.d("TransactionActivity", "📊 Pagination updated: " +
|
||||
"Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords);
|
||||
}
|
||||
|
||||
@ -588,7 +584,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
pageNumbersContainer.addView(pageButton);
|
||||
}
|
||||
|
||||
Log.d("ReprintActivity", "🔢 Page buttons created: " + startPage + " to " + endPage +
|
||||
Log.d("TransactionActivity", "🔢 Page buttons created: " + startPage + " to " + endPage +
|
||||
" with size: " + buttonSize + "px");
|
||||
}
|
||||
|
||||
@ -617,7 +613,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
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("ReprintActivity", "🔍 Fetching transactions page " + pageToLoad +
|
||||
Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad +
|
||||
" (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at" +
|
||||
" - Date Filter: " + fromDate + " to " + toDate);
|
||||
|
||||
@ -646,7 +642,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
apiTotal = results.getInt("total");
|
||||
JSONArray data = results.getJSONArray("data");
|
||||
|
||||
Log.d("ReprintActivity", "📊 API response: " + data.length() +
|
||||
Log.d("TransactionActivity", "📊 API response: " + data.length() +
|
||||
" records, total: " + apiTotal);
|
||||
|
||||
// ✅ STEP 1: Parse all transactions from API
|
||||
@ -671,14 +667,14 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
// ✅ STEP 2: Apply intelligent deduplication
|
||||
result = applyAdvancedDeduplication(rawTransactions);
|
||||
|
||||
Log.d("ReprintActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
|
||||
Log.d("TransactionActivity", "✅ After advanced deduplication: " + result.size() + " unique transactions");
|
||||
|
||||
} else {
|
||||
Log.e("ReprintActivity", "❌ HTTP Error: " + responseCode);
|
||||
Log.e("TransactionActivity", "❌ HTTP Error: " + responseCode);
|
||||
error = true;
|
||||
}
|
||||
} catch (IOException | JSONException | URISyntaxException e) {
|
||||
Log.e("ReprintActivity", "❌ Exception: " + e.getMessage(), e);
|
||||
Log.e("TransactionActivity", "❌ Exception: " + e.getMessage(), e);
|
||||
error = true;
|
||||
}
|
||||
return result;
|
||||
@ -691,7 +687,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if (error) {
|
||||
Toast.makeText(ReprintActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
|
||||
updatePaginationDisplay();
|
||||
return;
|
||||
}
|
||||
@ -709,11 +705,11 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
|
||||
if (date1 != null && date2 != null) {
|
||||
int comparison = date2.compareTo(date1); // Newest first
|
||||
Log.d("ReprintActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison);
|
||||
Log.d("TransactionActivity", "🔄 Sorting: " + t2.createdAt + " vs " + t1.createdAt + " = " + comparison);
|
||||
return comparison;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("ReprintActivity", "Date comparison error: " + e.getMessage());
|
||||
Log.w("TransactionActivity", "Date comparison error: " + e.getMessage());
|
||||
}
|
||||
return Integer.compare(t2.id, t1.id); // Fallback by ID (higher ID = newer)
|
||||
});
|
||||
@ -722,14 +718,14 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
transactionList.clear();
|
||||
transactionList.addAll(transactions);
|
||||
|
||||
Log.d("ReprintActivity", "📋 Page " + currentPage + " loaded and sorted: " +
|
||||
Log.d("TransactionActivity", "📋 Page " + currentPage + " loaded and sorted: " +
|
||||
transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages");
|
||||
|
||||
// ✅ VERIFIKASI SORTING ORDER
|
||||
Log.d("ReprintActivity", "📋 SORTED ORDER VERIFICATION:");
|
||||
Log.d("TransactionActivity", "📋 SORTED ORDER VERIFICATION:");
|
||||
for (int i = 0; i < Math.min(5, transactionList.size()); i++) {
|
||||
Transaction tx = transactionList.get(i);
|
||||
Log.d("ReprintActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
|
||||
// Update filtered list based on current search
|
||||
@ -765,7 +761,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
|
||||
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // Handle timezone properly
|
||||
Date parsed = sdf.parse(rawDate);
|
||||
Log.d("ReprintActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format);
|
||||
Log.d("TransactionActivity", "✅ Date parsed successfully: " + rawDate + " -> " + parsed + " using format: " + format);
|
||||
return parsed;
|
||||
} catch (Exception e) {
|
||||
// Continue to next format
|
||||
@ -783,10 +779,10 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
Date parsed = sdf.parse(cleanedDate);
|
||||
Log.d("ReprintActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed);
|
||||
Log.d("TransactionActivity", "✅ Date parsed with fallback: " + rawDate + " -> " + parsed);
|
||||
return parsed;
|
||||
} catch (Exception e) {
|
||||
Log.w("ReprintActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage());
|
||||
Log.w("TransactionActivity", "❌ Could not parse date: " + rawDate + " - Error: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -795,11 +791,11 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
* ✅ ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
|
||||
*/
|
||||
private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
|
||||
Log.d("ReprintActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("ReprintActivity", "📥 Input transactions order (first 5):");
|
||||
Log.d("TransactionActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("TransactionActivity", "📥 Input transactions order (first 5):");
|
||||
for (int i = 0; i < Math.min(5, rawTransactions.size()); i++) {
|
||||
Transaction tx = rawTransactions.get(i);
|
||||
Log.d("ReprintActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId);
|
||||
}
|
||||
|
||||
// Strategy 1: Group by reference_id
|
||||
@ -827,7 +823,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
if (group.size() == 1) {
|
||||
// No duplicates for this reference
|
||||
deduplicatedList.add(group.get(0));
|
||||
Log.d("ReprintActivity", "✅ Unique transaction: " + referenceId);
|
||||
Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId);
|
||||
} else {
|
||||
// Multiple transactions with same reference_id - sort group by date first
|
||||
Collections.sort(group, (t1, t2) -> {
|
||||
@ -847,15 +843,15 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
deduplicatedList.add(bestTransaction);
|
||||
duplicatesRemoved += (group.size() - 1);
|
||||
|
||||
Log.d("ReprintActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId +
|
||||
Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId +
|
||||
" (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ", date: " + bestTransaction.createdAt + ")");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
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");
|
||||
|
||||
return deduplicatedList;
|
||||
}
|
||||
@ -864,7 +860,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
* ✅ ENHANCED SELECTION: Advanced algorithm to pick the best transaction
|
||||
*/
|
||||
private Transaction selectBestTransactionAdvanced(List<Transaction> duplicates, String referenceId) {
|
||||
Log.d("ReprintActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId);
|
||||
Log.d("TransactionActivity", "🎯 Selecting best from " + duplicates.size() + " duplicates for: " + referenceId);
|
||||
|
||||
Transaction bestTransaction = duplicates.get(0);
|
||||
int bestPriority = getStatusPriority(bestTransaction.status);
|
||||
@ -875,7 +871,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
int currentPriority = getStatusPriority(tx.status);
|
||||
Date currentDate = parseCreatedAtDate(tx.createdAt);
|
||||
|
||||
Log.d("ReprintActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
", Status=" + tx.status + " (priority=" + currentPriority + ")" +
|
||||
", Created=" + tx.createdAt);
|
||||
|
||||
@ -907,11 +903,11 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
bestTransaction = tx;
|
||||
bestPriority = currentPriority;
|
||||
bestDate = currentDate;
|
||||
Log.d("ReprintActivity", " ⭐ NEW BEST selected: " + reason);
|
||||
Log.d("TransactionActivity", " ⭐ NEW BEST selected: " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("ReprintActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id +
|
||||
Log.d("TransactionActivity", "🏆 FINAL SELECTION: ID=" + bestTransaction.id +
|
||||
", Status=" + bestTransaction.status + ", Created=" + bestTransaction.createdAt);
|
||||
|
||||
return bestTransaction;
|
||||
@ -929,7 +925,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
return date1.after(date2);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("ReprintActivity", "Date comparison error, falling back to ID comparison");
|
||||
Log.w("TransactionActivity", "Date comparison error, falling back to ID comparison");
|
||||
}
|
||||
|
||||
// Fallback: higher ID usually means newer
|
||||
@ -994,7 +990,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
|
||||
// Tier 5: Unknown status
|
||||
default:
|
||||
Log.w("ReprintActivity", "Unknown status encountered: " + status);
|
||||
Log.w("TransactionActivity", "Unknown status encountered: " + status);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -1006,12 +1002,12 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
Intent intent = new Intent(this, ReceiptActivity.class);
|
||||
|
||||
// Add calling activity information for proper back navigation
|
||||
intent.putExtra("calling_activity", "ReprintActivity");
|
||||
intent.putExtra("calling_activity", "TransactionActivity");
|
||||
|
||||
// Extract and send raw amount properly
|
||||
String rawAmount = extractRawAmount(transaction.amount);
|
||||
|
||||
Log.d("ReprintActivity", "Opening receipt for transaction: " + transaction.referenceId +
|
||||
Log.d("TransactionActivity", "Opening receipt for transaction: " + transaction.referenceId +
|
||||
", channel: " + transaction.channelCode + ", original amount: '" + transaction.amount + "'");
|
||||
|
||||
// Send transaction data to ReceiptActivity
|
||||
@ -1036,7 +1032,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
String acquirer = getRealAcquirerForQris(transaction.referenceId, transaction.channelCode);
|
||||
intent.putExtra("acquirer", acquirer); // Jenis Kartu
|
||||
|
||||
Log.d("ReprintActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
|
||||
Log.d("TransactionActivity", "🎯 Determined acquirer: " + acquirer + " for channel: " + transaction.channelCode);
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
@ -1088,7 +1084,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
|
||||
// For QRIS, we could implement real-time acquirer lookup here
|
||||
// For now, return "qris" and let ReceiptActivity handle the detection
|
||||
Log.d("ReprintActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity");
|
||||
Log.d("TransactionActivity", "🔍 QRIS transaction detected, deferring acquirer detection to ReceiptActivity");
|
||||
return "qris";
|
||||
}
|
||||
|
||||
@ -1132,7 +1128,7 @@ public class ReprintActivity extends AppCompatActivity implements ReprintAdapter
|
||||
Long.parseLong(cleaned);
|
||||
return cleaned;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("ReprintActivity", "Invalid amount: " + formattedAmount);
|
||||
Log.e("TransactionActivity", "Invalid amount: " + formattedAmount);
|
||||
return "0";
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package com.example.bdkipoc.cetakulang;
|
||||
package com.example.bdkipoc;
|
||||
|
||||
import com.example.bdkipoc.R;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -27,17 +26,15 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.example.bdkipoc.StyleHelper;
|
||||
|
||||
public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterActivity.TransactionViewHolder> {
|
||||
private List<ReprintActivity.Transaction> transactionList;
|
||||
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
|
||||
private List<TransactionActivity.Transaction> transactionList;
|
||||
private OnPrintClickListener printClickListener;
|
||||
|
||||
public interface OnPrintClickListener {
|
||||
void onPrintClick(ReprintActivity.Transaction transaction);
|
||||
void onPrintClick(TransactionActivity.Transaction transaction);
|
||||
}
|
||||
|
||||
public ReprintAdapterActivity(List<ReprintActivity.Transaction> transactionList) {
|
||||
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
|
||||
this.transactionList = transactionList;
|
||||
}
|
||||
|
||||
@ -48,23 +45,23 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
/**
|
||||
* Update data without numbering (removed as per request)
|
||||
*/
|
||||
public void updateData(List<ReprintActivity.Transaction> newData, int startIndex) {
|
||||
public void updateData(List<TransactionActivity.Transaction> newData, int startIndex) {
|
||||
this.transactionList = newData;
|
||||
notifyDataSetChanged();
|
||||
|
||||
Log.d("ReprintAdapterActivity", "📋 Data updated: " + newData.size() + " items");
|
||||
Log.d("TransactionAdapter", "📋 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_reprint, parent, false);
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false);
|
||||
return new TransactionViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
|
||||
ReprintActivity.Transaction t = transactionList.get(position);
|
||||
TransactionActivity.Transaction t = transactionList.get(position);
|
||||
|
||||
// ✅ STRIPE TABLE: Set alternating row colors
|
||||
LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer);
|
||||
@ -76,10 +73,10 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.background_light));
|
||||
}
|
||||
|
||||
Log.d("ReprintAdapterActivity", "📋 Binding transaction " + position + ":");
|
||||
Log.d("ReprintAdapterActivity", " Reference: " + t.referenceId);
|
||||
Log.d("ReprintAdapterActivity", " Status: " + t.status);
|
||||
Log.d("ReprintAdapterActivity", " Amount: " + t.amount);
|
||||
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":");
|
||||
Log.d("TransactionAdapter", " Reference: " + t.referenceId);
|
||||
Log.d("TransactionAdapter", " Status: " + t.status);
|
||||
Log.d("TransactionAdapter", " Amount: " + t.amount);
|
||||
|
||||
// Set reference ID
|
||||
holder.referenceId.setText(t.referenceId);
|
||||
@ -91,10 +88,10 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
String formattedAmount = formatRupiah(amountValue);
|
||||
holder.amount.setText(formattedAmount);
|
||||
|
||||
Log.d("ReprintAdapterActivity", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
|
||||
Log.d("TransactionAdapter", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("ReprintAdapterActivity", "❌ Amount format error: " + t.amount, e);
|
||||
Log.e("TransactionAdapter", "❌ Amount format error: " + t.amount, e);
|
||||
String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount;
|
||||
holder.amount.setText(fallback);
|
||||
}
|
||||
@ -102,7 +99,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
// ✅ ENHANCED STATUS HANDLING dengan comprehensive checking
|
||||
String displayStatus = t.status;
|
||||
|
||||
Log.d("ReprintAdapterActivity", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")");
|
||||
Log.d("TransactionAdapter", "🔍 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)) {
|
||||
@ -111,7 +108,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
holder.status.setText("CHECKING...");
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING");
|
||||
|
||||
Log.d("ReprintAdapterActivity", "🔄 Starting comprehensive check for: " + t.referenceId);
|
||||
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId);
|
||||
|
||||
// Check real status dari semua kemungkinan sources
|
||||
checkMidtransStatus(t.referenceId, holder.status);
|
||||
@ -119,13 +116,13 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
// No reference ID to check
|
||||
holder.status.setText(displayStatus.toUpperCase());
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
|
||||
Log.w("ReprintAdapterActivity", "⚠️ No reference ID for status check");
|
||||
Log.w("TransactionAdapter", "⚠️ 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("ReprintAdapterActivity", "✅ Using confirmed status: " + displayStatus);
|
||||
Log.d("TransactionAdapter", "✅ Using confirmed status: " + displayStatus);
|
||||
}
|
||||
|
||||
// Set payment method
|
||||
@ -136,7 +133,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
String formattedDate = formatCreatedAtDate(t.createdAt);
|
||||
holder.createdAt.setText(formattedDate);
|
||||
|
||||
Log.d("ReprintAdapterActivity", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
|
||||
Log.d("TransactionAdapter", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
|
||||
|
||||
// Set click listeners
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
@ -151,7 +148,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
}
|
||||
});
|
||||
|
||||
Log.d("ReprintAdapterActivity", "✅ Transaction binding complete for: " + t.referenceId);
|
||||
Log.d("TransactionAdapter", "✅ Transaction binding complete for: " + t.referenceId);
|
||||
}
|
||||
|
||||
private String cleanAmountString(String amount) {
|
||||
@ -159,7 +156,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
return "0";
|
||||
}
|
||||
|
||||
Log.d("ReprintAdapterActivity", "Cleaning amount: '" + amount + "'");
|
||||
Log.d("TransactionAdapter", "Cleaning amount: '" + amount + "'");
|
||||
|
||||
// Remove currency symbols and spaces
|
||||
String cleaned = amount
|
||||
@ -202,7 +199,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
// Remove any commas
|
||||
cleaned = cleaned.replace(",", "");
|
||||
|
||||
Log.d("ReprintAdapterActivity", "Cleaned result: '" + cleaned + "'");
|
||||
Log.d("TransactionAdapter", "Cleaned result: '" + cleaned + "'");
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
@ -219,7 +216,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
private void checkMidtransStatus(String referenceId, TextView statusTextView) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d("ReprintAdapterActivity", "🔍 Comprehensive status check for reference: " + referenceId);
|
||||
Log.d("TransactionAdapter", "🔍 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";
|
||||
@ -247,7 +244,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
String foundAcquirer = null;
|
||||
|
||||
if (results != null && results.length() > 0) {
|
||||
Log.d("ReprintAdapterActivity", "📊 Processing " + results.length() + " log entries");
|
||||
Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries");
|
||||
|
||||
// STEP 2: Comprehensive search dengan multiple matching strategies
|
||||
for (int i = 0; i < results.length(); i++) {
|
||||
@ -273,7 +270,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
String appReferenceId = customData.optString("app_reference_id", "");
|
||||
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
|
||||
isRefreshMatch = true;
|
||||
Log.d("ReprintAdapterActivity", "🔄 Found refresh match: " + logOrderId);
|
||||
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Ignore custom field parsing errors
|
||||
@ -291,7 +288,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
if (itemName.contains("(Ref: " + referenceId + ")") ||
|
||||
itemName.contains("- " + referenceId)) {
|
||||
isItemMatch = true;
|
||||
Log.d("ReprintAdapterActivity", "📦 Found item match: " + logOrderId);
|
||||
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -302,11 +299,11 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch;
|
||||
|
||||
if (isRelatedTransaction) {
|
||||
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: " +
|
||||
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: " +
|
||||
(isDirectMatch ? "DIRECT " : "") +
|
||||
(isRefreshMatch ? "REFRESH " : "") +
|
||||
(isItemMatch ? "ITEM" : ""));
|
||||
@ -318,29 +315,29 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
finalStatus = "PAID";
|
||||
foundOrderId = logOrderId;
|
||||
foundAcquirer = logAcquirer;
|
||||
Log.d("ReprintAdapterActivity", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
|
||||
Log.d("TransactionAdapter", "✅ 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("ReprintAdapterActivity", "⏳ PENDING found: " + logOrderId);
|
||||
Log.d("TransactionAdapter", "⏳ 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("ReprintAdapterActivity", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus);
|
||||
Log.d("TransactionAdapter", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
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"));
|
||||
}
|
||||
|
||||
// STEP 3: Update UI di main thread
|
||||
@ -351,10 +348,10 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
statusTextView.setText(displayStatus);
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus);
|
||||
|
||||
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"));
|
||||
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"));
|
||||
});
|
||||
|
||||
// ✅ BONUS: Update backend jika status berubah ke PAID
|
||||
@ -363,7 +360,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.w("ReprintAdapterActivity", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("ERROR");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR");
|
||||
@ -371,7 +368,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
}
|
||||
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.e("ReprintAdapterActivity", "❌ Comprehensive status check error: " + e.getMessage(), e);
|
||||
Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e);
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("INIT");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT");
|
||||
@ -386,7 +383,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
private void updateBackendTransactionStatus(String referenceId, String status, String orderId, String acquirer) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Log.d("ReprintAdapterActivity", "🔄 Updating backend status for reference: " + referenceId);
|
||||
Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId);
|
||||
|
||||
JSONObject updatePayload = new JSONObject();
|
||||
updatePayload.put("status", status);
|
||||
@ -417,16 +414,16 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
Log.d("ReprintAdapterActivity", "📥 Backend update response: " + responseCode);
|
||||
Log.d("TransactionAdapter", "📥 Backend update response: " + responseCode);
|
||||
|
||||
if (responseCode == 200 || responseCode == 201) {
|
||||
Log.d("ReprintAdapterActivity", "✅ Backend status updated successfully");
|
||||
Log.d("TransactionAdapter", "✅ Backend status updated successfully");
|
||||
} else {
|
||||
Log.e("ReprintAdapterActivity", "❌ Backend update failed: " + responseCode);
|
||||
Log.e("TransactionAdapter", "❌ Backend update failed: " + responseCode);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("ReprintAdapterActivity", "❌ Backend update error: " + e.getMessage(), e);
|
||||
Log.e("TransactionAdapter", "❌ Backend update error: " + e.getMessage(), e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
@ -439,7 +436,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
Log.d("ReprintAdapterActivity", "📅 Input date: '" + rawDate + "'");
|
||||
Log.d("TransactionAdapter", "📅 Input date: '" + rawDate + "'");
|
||||
|
||||
try {
|
||||
// Handle different possible input formats from API
|
||||
@ -463,7 +460,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
}
|
||||
|
||||
Log.d("ReprintAdapterActivity", "📅 Cleaned date: '" + cleanedDate + "'");
|
||||
Log.d("TransactionAdapter", "📅 Cleaned date: '" + cleanedDate + "'");
|
||||
|
||||
// Output format: d/M/yyyy H:mm:ss
|
||||
SimpleDateFormat outputFormat = new SimpleDateFormat("d/M/yyyy H:mm:ss", Locale.getDefault());
|
||||
@ -471,11 +468,11 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
Date date = inputFormat.parse(cleanedDate);
|
||||
if (date != null) {
|
||||
String formatted = outputFormat.format(date);
|
||||
Log.d("ReprintAdapterActivity", "📅 Date formatted: " + rawDate + " -> " + formatted);
|
||||
Log.d("TransactionAdapter", "📅 Date formatted: " + rawDate + " -> " + formatted);
|
||||
return formatted;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ReprintAdapterActivity", "❌ Date formatting error for: " + rawDate, e);
|
||||
Log.e("TransactionAdapter", "❌ Date formatting error for: " + rawDate, e);
|
||||
}
|
||||
|
||||
// Fallback: Manual parsing
|
||||
@ -488,7 +485,7 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
workingDate = workingDate.substring(0, workingDate.indexOf("."));
|
||||
}
|
||||
|
||||
Log.d("ReprintAdapterActivity", "📅 Manual parsing attempt: '" + workingDate + "'");
|
||||
Log.d("TransactionAdapter", "📅 Manual parsing attempt: '" + workingDate + "'");
|
||||
|
||||
// Split into date and time parts
|
||||
String[] parts = workingDate.split(" ");
|
||||
@ -514,16 +511,16 @@ public class ReprintAdapterActivity extends RecyclerView.Adapter<ReprintAdapterA
|
||||
String second = timeComponents[2];
|
||||
|
||||
String result = dayInt + "/" + monthInt + "/" + year + " " + hour + ":" + minute + ":" + second;
|
||||
Log.d("ReprintAdapterActivity", "📅 Manual format result: " + result);
|
||||
Log.d("TransactionAdapter", "📅 Manual format result: " + result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("ReprintAdapterActivity", "❌ Manual date formatting failed: " + e.getMessage());
|
||||
Log.w("TransactionAdapter", "❌ Manual date formatting failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
Log.w("ReprintAdapterActivity", "📅 Using fallback - returning original date: " + rawDate);
|
||||
Log.w("TransactionAdapter", "📅 Using fallback - returning original date: " + rawDate);
|
||||
return rawDate;
|
||||
}
|
||||
|
@ -1,145 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
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);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,711 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,258 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,417 +0,0 @@
|
||||
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
@ -1,121 +0,0 @@
|
||||
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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
@ -1,358 +0,0 @@
|
||||
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, "==============================");
|
||||
}
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
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"
|
||||
android:duration="300"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
@ -1,10 +0,0 @@
|
||||
<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>
|
@ -1,10 +0,0 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:duration="300"
|
||||
android:fromYDelta="50%p"
|
||||
android:toYDelta="0%p" />
|
||||
<alpha
|
||||
android:duration="300"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
</set>
|
Binary file not shown.
Before Width: | Height: | Size: 112 KiB |
@ -1,6 +0,0 @@
|
||||
<?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>
|
@ -1,5 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:width="2dp" android:color="#E31937"/>
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="@android:color/transparent"/>
|
||||
</shape>
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
10
app/src/main/res/drawable/ic_e_money.xml
Normal file
10
app/src/main/res/drawable/ic_e_money.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4V8h-2V7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1H9v2h2v1z"/>
|
||||
</vector>
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
10
app/src/main/res/drawable/ic_settlement.xml
Normal file
10
app/src/main/res/drawable/ic_settlement.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,7c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,19L6,19v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1L18,19z"/>
|
||||
</vector>
|
Binary file not shown.
Before Width: | Height: | Size: 2.2 KiB |
10
app/src/main/res/drawable/ic_transfer.xml
Normal file
10
app/src/main/res/drawable/ic_transfer.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?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>
|
@ -1,5 +0,0 @@
|
||||
<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,8 +8,4 @@
|
||||
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>
|
Binary file not shown.
@ -1,328 +0,0 @@
|
||||
<?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,40 +216,7 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Row 2: Uang Elektronik, Cetak Ulang, Settlement -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_uang_elektronik"
|
||||
android:layout_width="0dp"
|
||||
@ -304,7 +271,7 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_print"
|
||||
android:src="@drawable/ic_reprint"
|
||||
app:tint="#E31937"/>
|
||||
|
||||
<TextView
|
||||
@ -316,40 +283,6 @@
|
||||
</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"
|
||||
@ -357,7 +290,6 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -384,6 +316,7 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 3: Histori, Bantuan, Info Toko -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_histori"
|
||||
android:layout_width="0dp"
|
||||
@ -391,7 +324,6 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -418,7 +350,6 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 4: Bantuan, Info Toko, Pengaturan -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_bantuan"
|
||||
android:layout_width="0dp"
|
||||
@ -426,7 +357,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -460,7 +391,7 @@
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_margin="8dp"
|
||||
android:visibility="gone"
|
||||
android:visibility="visible"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="#F3F4F3">
|
||||
@ -487,8 +418,9 @@
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Row 4: Dummy Menu 1, 2, 3 (Hidden initially) -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_pengaturan"
|
||||
android:id="@+id/card_dummy_menu_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_columnWeight="1"
|
||||
@ -509,17 +441,189 @@
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_settings"
|
||||
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="Pengaturan"
|
||||
android:text="Dummy Menu 1"
|
||||
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 -->
|
||||
|
268
app/src/main/res/layout/activity_payment.xml
Normal file
268
app/src/main/res/layout/activity_payment.xml
Normal file
@ -0,0 +1,268 @@
|
||||
<?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>
|
298
app/src/main/res/layout/activity_pin.xml
Normal file
298
app/src/main/res/layout/activity_pin.xml
Normal file
@ -0,0 +1,298 @@
|
||||
<?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,241 +1,223 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout 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="#F5F5F5"
|
||||
tools:context=".QrisResultActivity">
|
||||
android:orientation="vertical"
|
||||
android:background="#FFFFFF">
|
||||
|
||||
<!-- Header Background -->
|
||||
<!-- Red Status Bar -->
|
||||
<View
|
||||
android:id="@+id/header_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:background="#E31937"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
android:layout_height="44dp"
|
||||
android:background="#E31937" />
|
||||
|
||||
<!-- Back Navigation -->
|
||||
<!-- Header with Back Navigation -->
|
||||
<LinearLayout
|
||||
android:id="@+id/back_navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
android:background="#E31937"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<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"
|
||||
<LinearLayout
|
||||
android:id="@+id/back_navigation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Generate QR"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="‹"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Kembali"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Content Card -->
|
||||
<!-- White Card Container -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/main_card"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginTop="0dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="8dp"
|
||||
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">
|
||||
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">
|
||||
android:padding="24dp"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<!-- QRIS Logo -->
|
||||
<!-- 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 -->
|
||||
<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:textColor="#000000"
|
||||
android:letterSpacing="0.1"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
android:fontFamily="monospace"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<!-- QR Code -->
|
||||
<ImageView
|
||||
android:id="@+id/qrImageView"
|
||||
android:layout_width="240dp"
|
||||
android:layout_height="240dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:scaleType="centerInside"
|
||||
android:background="#FFFFFF"
|
||||
android:padding="8dp" />
|
||||
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" />
|
||||
|
||||
<!-- Amount -->
|
||||
<!-- Amount Display -->
|
||||
<TextView
|
||||
android:id="@+id/amountTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="RP.200.000"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#333333"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:fontFamily="@font/inter" />
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Timer -->
|
||||
<!-- Timer/Counter -->
|
||||
<TextView
|
||||
android:id="@+id/timerTextView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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:gravity="center"
|
||||
android:background="@drawable/timer_circle_background"
|
||||
android:fontFamily="@font/inter" />
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="visible" />
|
||||
|
||||
<!-- Action Buttons Section -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="16dp">
|
||||
|
||||
<!-- Download QRIS Button -->
|
||||
<Button
|
||||
android:id="@+id/downloadQrisButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="Download QRIS"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:background="#4CAF50"
|
||||
android:visibility="visible" />
|
||||
|
||||
<!-- Check Payment Status Button -->
|
||||
<Button
|
||||
android:id="@+id/checkStatusButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="Check Payment Status"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:background="#2196F3"
|
||||
android:visibility="visible" />
|
||||
|
||||
<!-- Return to Main Button -->
|
||||
<Button
|
||||
android:id="@+id/returnMainButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:text="Return to Main"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:background="#FF9800"
|
||||
android:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Hidden views for compatibility -->
|
||||
<TextView
|
||||
android:id="@+id/referenceTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Reference ID: ref-12345"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Status Success"
|
||||
android:textColor="@android:color/black"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<!-- Spacer to push button to bottom -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- Bottom 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: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="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" />
|
||||
android:textStyle="normal"
|
||||
android:background="@android:color/transparent"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
|
||||
<!-- Hidden Elements for Functionality -->
|
||||
<TextView
|
||||
android:id="@+id/referenceTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrStatusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/downloadQrisButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/checkStatusButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/returnMainButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<!-- Success Screen (Full Screen Overlay) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/success_screen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="#E31937"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<!-- Success Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/success_icon"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:src="@drawable/ic_success_payment"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:scaleType="centerInside"/>
|
||||
|
||||
<!-- Success Message -->
|
||||
<TextView
|
||||
android:id="@+id/success_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pembayaran Berhasil"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="@font/inter"
|
||||
android:gravity="center"
|
||||
android:letterSpacing="0.02"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
@ -104,7 +104,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payvora "
|
||||
android:text="EDC "
|
||||
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="PRO"
|
||||
android:text="Merchant"
|
||||
android:textColor="#3F51B5"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
@ -121,6 +121,15 @@
|
||||
|
||||
</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 -->
|
||||
@ -159,36 +168,46 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mid_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="MID:12345678901"
|
||||
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_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="1234567890"
|
||||
android:textColor="#333333"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"/>
|
||||
|
||||
<!-- Vertical Separator -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=" | "
|
||||
android:textColor="#999999"
|
||||
android:layout_weight="1"
|
||||
android:text="TID: "
|
||||
android:textColor="#666666"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"
|
||||
android:paddingHorizontal="8dp"/>
|
||||
android:fontFamily="@font/inter"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tid_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TID:12345678901"
|
||||
android:layout_weight="1"
|
||||
android:text="1234567890"
|
||||
android:textColor="#333333"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="@font/inter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Separator Line -->
|
||||
|
@ -79,14 +79,15 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cetak Ulang Struk"
|
||||
android:textColor="#333333"
|
||||
android:textSize="16sp"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="inter-bold" />
|
||||
android:fontFamily="sans-serif" />
|
||||
|
||||
</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"
|
||||
@ -220,7 +221,8 @@
|
||||
android:visibility="gone"
|
||||
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"
|
||||
@ -229,7 +231,8 @@
|
||||
android:background="#ffffff"
|
||||
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"
|
@ -1,47 +0,0 @@
|
||||
<?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,10 +101,16 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/ic_print" />
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,28 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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>
|
||||
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</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>
|
||||
@ -32,88 +13,4 @@
|
||||
<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>
|
@ -1,8 +0,0 @@
|
||||
<?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,11 +13,4 @@
|
||||
<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,11 +29,6 @@
|
||||
<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>
|
||||
@ -50,172 +45,4 @@
|
||||
<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,9 +1,8 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Base.Theme.BDKIPOC" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/red</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Customize your light theme here. -->
|
||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
||||
</style>
|
||||
|
||||
<style name="Theme.BDKIPOC" parent="Base.Theme.BDKIPOC" />
|
||||
|
@ -16,16 +16,8 @@ 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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "BDKI POC"
|
||||
include ':app'
|
||||
include ':app'
|
||||
|
Loading…
x
Reference in New Issue
Block a user