summaryrefslogtreecommitdiff
path: root/cashier/src/main/java/net/taler
diff options
context:
space:
mode:
Diffstat (limited to 'cashier/src/main/java/net/taler')
-rw-r--r--cashier/src/main/java/net/taler/cashier/BalanceFragment.kt36
-rw-r--r--cashier/src/main/java/net/taler/cashier/HttpHelper.kt12
-rw-r--r--cashier/src/main/java/net/taler/cashier/MainActivity.kt1
-rw-r--r--cashier/src/main/java/net/taler/cashier/MainViewModel.kt2
-rw-r--r--cashier/src/main/java/net/taler/cashier/Response.kt2
-rw-r--r--cashier/src/main/java/net/taler/cashier/SignedAmount.kt8
-rw-r--r--cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt16
-rw-r--r--cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt10
-rw-r--r--cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt11
-rw-r--r--cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt14
-rw-r--r--cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt51
11 files changed, 93 insertions, 70 deletions
diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
index fa9600b..4fd3143 100644
--- a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
@@ -54,19 +54,19 @@ class BalanceFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
- ): View? {
+ ): View {
setHasOptionsMenu(true)
ui = FragmentBalanceBinding.inflate(layoutInflater, container, false)
return ui.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- withdrawManager.lastTransaction.observe(viewLifecycleOwner, { lastTransaction ->
+ withdrawManager.lastTransaction.observe(viewLifecycleOwner) { lastTransaction ->
onLastTransaction(lastTransaction)
- })
- viewModel.balance.observe(viewLifecycleOwner, { result ->
+ }
+ viewModel.balance.observe(viewLifecycleOwner) { result ->
onBalanceUpdated(result)
- })
+ }
ui.button5.setOnClickListener { onAmountButtonPressed(5) }
ui.button10.setOnClickListener { onAmountButtonPressed(10) }
ui.button20.setOnClickListener { onAmountButtonPressed(20) }
@@ -81,9 +81,9 @@ class BalanceFragment : Fragment() {
true
} else false
}
- configManager.currency.observe(viewLifecycleOwner, { currency ->
+ configManager.currency.observe(viewLifecycleOwner) { currency ->
ui.currencyView.text = currency
- })
+ }
ui.confirmWithdrawalButton.setOnClickListener { onAmountConfirmed(getAmountFromView()) }
}
@@ -104,11 +104,13 @@ class BalanceFragment : Fragment() {
}
}
+ @Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.balance, menu)
super.onCreateOptionsMenu(menu, inflater)
}
+ @Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_reconfigure -> {
findNavController().navigate(configManager.configDestination)
@@ -164,13 +166,19 @@ class BalanceFragment : Fragment() {
private fun onAmountConfirmed(amount: Amount) {
if (amount.isZero()) {
ui.amountView.error = getString(R.string.withdraw_error_zero)
- } else if (!withdrawManager.hasSufficientBalance(amount)) {
- ui.amountView.error = getString(R.string.withdraw_error_insufficient_balance)
- } else {
- ui.amountView.error = null
- withdrawManager.withdraw(amount)
- actionBalanceFragmentToTransactionFragment().let {
- findNavController().navigate(it)
+ } else when (withdrawManager.hasSufficientBalance(amount)) {
+ true -> {
+ ui.amountView.error = null
+ withdrawManager.withdraw(amount)
+ actionBalanceFragmentToTransactionFragment().let {
+ findNavController().navigate(it)
+ }
+ }
+ false -> {
+ ui.amountView.error = getString(R.string.withdraw_error_insufficient_balance)
+ }
+ null -> {
+ ui.amountView.error = getString(R.string.withdraw_error_currency_mismatch)
}
}
}
diff --git a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt
index fd48b2d..69cc46f 100644
--- a/cashier/src/main/java/net/taler/cashier/HttpHelper.kt
+++ b/cashier/src/main/java/net/taler/cashier/HttpHelper.kt
@@ -50,11 +50,13 @@ object HttpHelper {
Log.e(TAG, "Error retrieving $url", e)
return HttpJsonResult.Error(0)
}
- return if (response.code == 200 && response.body != null) {
+ return if (response.code == 204) {
+ HttpJsonResult.Success(JSONObject())
+ } else if (response.code in 200..299 && response.body != null) {
val jsonObject = JSONObject(response.body!!.string())
HttpJsonResult.Success(jsonObject)
} else {
- Log.e(TAG, "Received status ${response.code} from $url expected 200")
+ Log.e(TAG, "Received status ${response.code} from $url expected 2xx")
HttpJsonResult.Error(response.code, getErrorBody(response))
}
}
@@ -76,11 +78,13 @@ object HttpHelper {
Log.e(TAG, "Error retrieving $url", e)
return HttpJsonResult.Error(0)
}
- return if (response.code == 200 && response.body != null) {
+ return if (response.code == 204) {
+ HttpJsonResult.Success(JSONObject())
+ } else if (response.code in 200..299 && response.body != null) {
val jsonObject = JSONObject(response.body!!.string())
HttpJsonResult.Success(jsonObject)
} else {
- Log.e(TAG, "Received status ${response.code} from $url expected 200")
+ Log.e(TAG, "Received status ${response.code} from $url expected 2xx")
HttpJsonResult.Error(response.code, getErrorBody(response))
}
}
diff --git a/cashier/src/main/java/net/taler/cashier/MainActivity.kt b/cashier/src/main/java/net/taler/cashier/MainActivity.kt
index 2f4c4ec..aacc225 100644
--- a/cashier/src/main/java/net/taler/cashier/MainActivity.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainActivity.kt
@@ -52,6 +52,7 @@ class MainActivity : AppCompatActivity() {
}
}
+ @Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (!configManager.hasConfig() && nav.currentDestination?.id == R.id.configFragment) {
// we are in the configuration screen and need a config to continue
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
index 21d0209..2196e78 100644
--- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -64,7 +64,7 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) {
fun getBalance() = viewModelScope.launch(Dispatchers.IO) {
check(configManager.hasConfig()) { "No config to get balance" }
val config = configManager.config
- val url = "${config.bankUrl}/access-api/accounts/${config.username}"
+ val url = "${config.bankUrl}/accounts/${config.username}"
Log.d(TAG, "Checking balance at $url")
val result = when (val response = makeJsonGetRequest(url, config)) {
is HttpJsonResult.Success -> {
diff --git a/cashier/src/main/java/net/taler/cashier/Response.kt b/cashier/src/main/java/net/taler/cashier/Response.kt
index 89b7b33..e9db447 100644
--- a/cashier/src/main/java/net/taler/cashier/Response.kt
+++ b/cashier/src/main/java/net/taler/cashier/Response.kt
@@ -19,7 +19,6 @@ package net.taler.cashier
import android.content.Context
import android.util.Log
import io.ktor.client.call.body
-import io.ktor.client.call.receive
import io.ktor.client.plugins.ResponseException
import io.ktor.http.HttpStatusCode
import kotlinx.serialization.Serializable
@@ -47,7 +46,6 @@ class Response<out T> private constructor(
private suspend fun getExceptionString(e: ResponseException): String {
val response = e.response
return try {
- Log.e("TEST", "TRY RECEIVE $response")
val error: Error = response.body()
"Error ${error.code}: ${error.hint}"
} catch (ex: Exception) {
diff --git a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
index 4f624ae..45bc3af 100644
--- a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
+++ b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
@@ -23,8 +23,10 @@ data class SignedAmount(
val amount: Amount
) {
- override fun toString(): String {
- return if (positive) "$amount" else "-$amount"
- }
+ override fun toString() = toString(showSymbol = true)
+ fun toString(showSymbol: Boolean) = amount.toString(
+ showSymbol = showSymbol,
+ negative = !positive,
+ )
}
diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
index c9e87f8..3085bef 100644
--- a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
@@ -39,8 +39,8 @@ import net.taler.cashier.databinding.FragmentConfigBinding
import net.taler.common.exhaustive
import net.taler.common.showError
-private const val URL_BANK_TEST = "https://bank.demo.taler.net/demobanks/default"
-private const val URL_BANK_TEST_REGISTER = "$URL_BANK_TEST/accounts/register"
+private const val URL_BANK_TEST = "https://bank.demo.taler.net"
+private const val URL_BANK_TEST_REGISTER = "https://bank.demo.taler.net/webui/#/register"
class ConfigFragment : Fragment() {
@@ -74,9 +74,9 @@ class ConfigFragment : Fragment() {
}
ui.saveButton.setOnClickListener {
val config = Config(
- bankUrl = ui.urlView.editText!!.text.toString(),
- username = ui.usernameView.editText!!.text.toString(),
- password = ui.passwordView.editText!!.text.toString()
+ bankUrl = ui.urlView.editText!!.text.toString().trim(),
+ username = ui.usernameView.editText!!.text.toString().trim(),
+ password = ui.passwordView.editText!!.text.toString().trim()
)
if (checkConfig(config)) {
// show progress
@@ -111,9 +111,9 @@ class ConfigFragment : Fragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// for some reason automatic restore isn't working at the moment!?
- outState.putCharSequence("urlView", ui.urlView.editText?.text)
- outState.putCharSequence("usernameView", ui.usernameView.editText?.text)
- outState.putCharSequence("passwordView", ui.passwordView.editText?.text)
+ outState.putCharSequence("urlView", ui.urlView.editText?.text?.trim())
+ outState.putCharSequence("usernameView", ui.usernameView.editText?.text?.trim())
+ outState.putCharSequence("passwordView", ui.passwordView.editText?.text?.trim())
}
private fun checkConfig(config: Config): Boolean {
diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
index d850d27..50b1faf 100644
--- a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
@@ -38,12 +38,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import net.taler.cashier.BuildConfig
import net.taler.cashier.Response
import net.taler.cashier.Response.Companion.response
import net.taler.common.Version
import net.taler.common.getIncompatibleStringOrNull
-val VERSION_BANK = Version(0, 0, 0)
+val VERSION_BANK = Version.parse(BuildConfig.BACKEND_API_VERSION)!!
private const val PREF_NAME = "net.taler.cashier.prefs"
private const val PREF_KEY_BANK_URL = "bankUrl"
private const val PREF_KEY_USERNAME = "username"
@@ -55,7 +56,7 @@ private val TAG = ConfigManager::class.java.simpleName
class ConfigManager(
private val app: Application,
private val scope: CoroutineScope,
- private val httpClient: HttpClient
+ private val httpClient: HttpClient,
) {
val configDestination = ConfigFragmentDirections.actionGlobalConfigFragment()
@@ -112,8 +113,9 @@ class ConfigManager(
mConfigResult.postValue(result)
}
}
+
private suspend fun checkConfig(config: Config) = withContext(Dispatchers.IO) {
- val url = "${config.bankUrl}/integration-api/config"
+ val url = "${config.bankUrl}/config"
Log.d(TAG, "Checking config: $url")
val configResponse = response {
httpClient.get(url).body<ConfigResponse>()
@@ -124,7 +126,7 @@ class ConfigManager(
// we need to check an endpoint that requires authentication as well
// to see if the credentials are valid
val balanceResponse = response {
- val authUrl = "${config.bankUrl}/access-api/accounts/${config.username}"
+ val authUrl = "${config.bankUrl}/accounts/${config.username}"
Log.d(TAG, "Checking auth: $authUrl")
httpClient.get(authUrl) {
header(Authorization, config.basicAuth)
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
index 4f98847..c951bb8 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
@@ -22,7 +22,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import net.taler.cashier.MainViewModel
import net.taler.cashier.R
@@ -37,22 +36,22 @@ class ErrorFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
+ savedInstanceState: Bundle?,
+ ): View {
ui = FragmentErrorBinding.inflate(inflater, container, false)
return ui.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { status ->
- if (status == null) return@Observer
+ withdrawManager.withdrawStatus.observe(viewLifecycleOwner) { status ->
+ if (status == null) return@observe
if (status is WithdrawStatus.Aborted) {
ui.textView.setText(R.string.transaction_aborted)
} else if (status is WithdrawStatus.Error) {
ui.textView.text = status.msg
}
withdrawManager.completeTransaction()
- })
+ }
ui.backButton.setOnClickListener {
findNavController().popBackStack()
}
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
index ffb1539..0f606b8 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
@@ -50,21 +50,21 @@ class TransactionFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
- ): View? {
+ ): View {
ui = FragmentTransactionBinding.inflate(inflater, container, false)
return ui.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- withdrawManager.withdrawAmount.observe(viewLifecycleOwner, { amount ->
+ withdrawManager.withdrawAmount.observe(viewLifecycleOwner) { amount ->
ui.amountView.text = amount?.toString()
- })
- withdrawManager.withdrawResult.observe(viewLifecycleOwner, { result ->
+ }
+ withdrawManager.withdrawResult.observe(viewLifecycleOwner) { result ->
onWithdrawResultReceived(result)
- })
- withdrawManager.withdrawStatus.observe(viewLifecycleOwner, { status ->
+ }
+ withdrawManager.withdrawStatus.observe(viewLifecycleOwner) { status ->
onWithdrawStatusChanged(status)
- })
+ }
// change intro text depending on whether NFC is available or not
val hasNfc = NfcManager.hasNfc(requireContext())
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
index 1b809bb..487475d 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
@@ -38,17 +38,15 @@ import net.taler.common.Amount
import net.taler.common.QrCodeManager.makeQrCode
import net.taler.common.isOnline
import org.json.JSONObject
-import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS
private val TAG = WithdrawManager::class.java.simpleName
private val INTERVAL = SECONDS.toMillis(1)
-private val TIMEOUT = MINUTES.toMillis(2)
class WithdrawManager(
private val app: Application,
- private val viewModel: MainViewModel
+ private val viewModel: MainViewModel,
) {
private val scope
get() = viewModel.viewModelScope
@@ -73,11 +71,20 @@ class WithdrawManager(
private val mLastTransaction = MutableLiveData<LastTransaction>()
val lastTransaction: LiveData<LastTransaction> = mLastTransaction
+ /**
+ * Returns null if the given [amount] can't be compared to the balance
+ * e.g. due to mismatching currency.
+ */
@UiThread
- fun hasSufficientBalance(amount: Amount): Boolean {
+ fun hasSufficientBalance(amount: Amount): Boolean? {
val balanceResult = viewModel.balance.value
if (balanceResult !is BalanceResult.Success) return false
- return balanceResult.amount.positive && amount <= balanceResult.amount.amount
+ return try {
+ balanceResult.amount.positive && amount <= balanceResult.amount.amount
+ } catch (e: IllegalStateException) {
+ Log.e(TAG, "Error comparing amounts", e)
+ null
+ }
}
@UiThread
@@ -87,7 +94,7 @@ class WithdrawManager(
mWithdrawResult.value = null
mWithdrawAmount.value = amount
scope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals"
+ val url = "${config.bankUrl}/accounts/${config.username}/withdrawals"
Log.d(TAG, "Starting withdrawal at $url")
val map = mapOf("amount" to amount.toJSONString())
val body = JSONObject(map)
@@ -119,7 +126,7 @@ class WithdrawManager(
}
}
- private val timer: CountDownTimer = object : CountDownTimer(TIMEOUT, INTERVAL) {
+ private val timer: CountDownTimer = object : CountDownTimer(Long.MAX_VALUE, INTERVAL) {
override fun onTick(millisUntilFinished: Long) {
val result = withdrawResult.value
if (result is WithdrawResult.Success) {
@@ -146,31 +153,32 @@ class WithdrawManager(
}
private fun checkWithdrawStatus(withdrawalId: String) = scope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}"
+ val url =
+ "${config.bankUrl}/withdrawals/${withdrawalId}"
Log.d(TAG, "Checking withdraw status at $url")
val response = makeJsonGetRequest(url, config)
if (response !is Success) return@launch // ignore errors and continue trying
val oldStatus = mWithdrawStatus.value
try {
- when {
- response.json.getBoolean("aborted") -> {
+ when(response.json.getString("status")) {
+ "selected" -> {
+ // only update status, if there's none, yet
+ // so we don't re-notify or overwrite newer status info
+ if (oldStatus == null) {
+ mWithdrawStatus.postValue(WithdrawStatus.SelectionDone(withdrawalId))
+ }
+ }
+ "aborted" -> {
cancelWithdrawStatusCheck()
mWithdrawStatus.postValue(WithdrawStatus.Aborted)
}
- response.json.getBoolean("confirmation_done") -> {
+ "confirmed" -> {
if (oldStatus !is WithdrawStatus.Success) {
cancelWithdrawStatusCheck()
mWithdrawStatus.postValue(WithdrawStatus.Success)
viewModel.getBalance()
}
}
- response.json.getBoolean("selection_done") -> {
- // only update status, if there's none, yet
- // so we don't re-notify or overwrite newer status info
- if (oldStatus == null) {
- mWithdrawStatus.postValue(WithdrawStatus.SelectionDone(withdrawalId))
- }
- }
}
} catch (e: Exception) {
mWithdrawStatus.postValue(WithdrawStatus.Error(e.toString()))
@@ -197,7 +205,8 @@ class WithdrawManager(
}
private fun abort(withdrawalId: String) = scope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}/abort"
+ val url =
+ "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/abort"
Log.d(TAG, "Aborting withdrawal at $url")
makeJsonPostRequest(url, JSONObject(), config)
}
@@ -207,7 +216,7 @@ class WithdrawManager(
mWithdrawStatus.value = WithdrawStatus.Confirming
scope.launch(Dispatchers.IO) {
val url =
- "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}/confirm"
+ "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/confirm"
Log.d(TAG, "Confirming withdrawal at $url")
when (val response = makeJsonPostRequest(url, JSONObject(), config)) {
is Success -> {
@@ -254,5 +263,5 @@ sealed class WithdrawStatus {
data class LastTransaction(
val withdrawAmount: Amount,
- val withdrawStatus: WithdrawStatus
+ val withdrawStatus: WithdrawStatus,
)