aboutsummaryrefslogtreecommitdiff
path: root/merchant-terminal/src/main/java/net/taler/merchantpos/config
diff options
context:
space:
mode:
Diffstat (limited to 'merchant-terminal/src/main/java/net/taler/merchantpos/config')
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt10
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt145
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt106
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt35
4 files changed, 236 insertions, 60 deletions
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
index b5b7be7..9e82a5a 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.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 com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
import com.google.android.material.snackbar.Snackbar
import net.taler.common.navigate
@@ -46,18 +45,17 @@ class ConfigFetcherFragment : Fragment() {
return ui.root
}
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
configManager.fetchConfig(configManager.config, false)
- configManager.configUpdateResult.observe(viewLifecycleOwner, Observer { result ->
+ configManager.configUpdateResult.observe(viewLifecycleOwner) { result ->
when (result) {
- null -> return@Observer
+ null -> return@observe
is ConfigUpdateResult.Error -> onNetworkError(result.msg)
is ConfigUpdateResult.Success -> {
navigate(actionConfigFetcherToOrder())
}
}
- })
+ }
}
private fun onNetworkError(msg: String) {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
index 3ee5148..d2167db 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
@@ -49,16 +49,34 @@ class ConfigFragment : Fragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
- ): View? {
+ ): View {
ui = FragmentMerchantConfigBinding.inflate(inflater, container, false)
return ui.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ ui.configToggle.check(when (configManager.config) {
+ is Config.Old -> R.id.oldConfigButton
+ is Config.New -> R.id.newConfigButton
+ })
+
+ ui.oldConfigButton.setOnClickListener {
+ showOldConfig()
+ }
+
+ ui.newConfigButton.setOnClickListener {
+ showNewConfig()
+ }
+
+ /*
+ * Old configuration (JSON)
+ */
+
ui.configUrlView.editText!!.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) checkForUrlCredentials()
}
- ui.okButton.setOnClickListener {
+
+ ui.okOldButton.setOnClickListener {
checkForUrlCredentials()
val inputUrl = ui.configUrlView.editText!!.text
val url = if (inputUrl.startsWith("http")) {
@@ -66,26 +84,55 @@ class ConfigFragment : Fragment() {
} else {
"https://$inputUrl".also { ui.configUrlView.editText!!.setText(it) }
}
- ui.progressBar.visibility = VISIBLE
- ui.okButton.visibility = INVISIBLE
- val config = Config(
+ ui.progressBarOld.visibility = VISIBLE
+ ui.okOldButton.visibility = INVISIBLE
+ val config = Config.Old(
configUrl = url,
username = ui.usernameView.editText!!.text.toString(),
password = ui.passwordView.editText!!.text.toString()
)
configManager.fetchConfig(config, true, ui.savePasswordCheckBox.isChecked)
- configManager.configUpdateResult.observe(viewLifecycleOwner, { result ->
+ configManager.configUpdateResult.observe(viewLifecycleOwner) { result ->
if (onConfigUpdate(result)) {
configManager.configUpdateResult.removeObservers(viewLifecycleOwner)
}
- })
+ }
}
+
ui.forgetPasswordButton.setOnClickListener {
configManager.forgetPassword()
ui.passwordView.editText!!.text = null
ui.forgetPasswordButton.visibility = GONE
}
+
ui.configDocsView.movementMethod = LinkMovementMethod.getInstance()
+
+ /*
+ * New configuration (Merchant)
+ */
+
+ ui.okNewButton.setOnClickListener {
+ val inputUrl = ui.merchantUrlView.editText!!.text
+ val url = if (inputUrl.startsWith("http")) {
+ inputUrl.toString()
+ } else {
+ "https://$inputUrl".also { ui.merchantUrlView.editText!!.setText(it) }
+ }
+
+ ui.progressBarNew.visibility = VISIBLE
+ ui.okNewButton.visibility = INVISIBLE
+ val config = Config.New(
+ merchantUrl = url,
+ accessToken = ui.tokenView.editText!!.text.toString(),
+ )
+ configManager.fetchConfig(config, true, ui.saveTokenCheckBox.isChecked)
+ configManager.configUpdateResult.observe(viewLifecycleOwner) { result ->
+ if (onConfigUpdate(result)) {
+ configManager.configUpdateResult.removeObservers(viewLifecycleOwner)
+ }
+ }
+ }
+
updateView(savedInstanceState == null)
}
@@ -93,28 +140,74 @@ class ConfigFragment : Fragment() {
super.onStart()
// focus password if this is the only empty field
if (ui.passwordView.editText!!.text.isBlank()
- && !ui.configUrlView.editText!!.text.isBlank()
- && !ui.usernameView.editText!!.text.isBlank()
+ && ui.configUrlView.editText!!.text.isNotBlank()
+ && ui.usernameView.editText!!.text.isNotBlank()
) {
ui.passwordView.requestFocus()
}
}
private fun updateView(isInitialization: Boolean = false) {
- val config = configManager.config
- ui.configUrlView.editText!!.setText(
- if (isInitialization && config.configUrl.isBlank()) CONFIG_URL_DEMO
- else config.configUrl
- )
- ui.usernameView.editText!!.setText(
- if (isInitialization && config.username.isBlank()) CONFIG_USERNAME_DEMO
- else config.username
- )
- ui.passwordView.editText!!.setText(
- if (isInitialization && config.password.isBlank()) CONFIG_PASSWORD_DEMO
- else config.password
- )
- ui.forgetPasswordButton.visibility = if (config.hasPassword()) VISIBLE else GONE
+ if (isInitialization) {
+ ui.configUrlView.editText!!.setText(OLD_CONFIG_URL_DEMO)
+ ui.usernameView.editText!!.setText(OLD_CONFIG_USERNAME_DEMO)
+ ui.passwordView.editText!!.setText(OLD_CONFIG_PASSWORD_DEMO)
+
+ ui.merchantUrlView.editText!!.setText(NEW_CONFIG_URL_DEMO)
+ ui.tokenView.editText!!.setText(NEW_CONFIG_ACCESS_TOKEN_DEMO)
+
+ when (val config = configManager.config) {
+ is Config.Old -> {
+ if (config.configUrl.isNotBlank()) {
+ ui.configUrlView.editText!!.setText(config.configUrl)
+ }
+
+ if (config.username.isNotBlank()) {
+ ui.usernameView.editText!!.setText(config.username)
+ }
+
+ if (config.password.isNotBlank()) {
+ ui.passwordView.editText!!.setText(config.password)
+ }
+ }
+
+ is Config.New -> {
+ if (config.merchantUrl.isNotBlank()) {
+ ui.merchantUrlView.editText!!.setText(config.merchantUrl)
+ }
+
+ if (config.accessToken.isNotBlank()) {
+ ui.tokenView.editText!!.setText(config.accessToken)
+ }
+ }
+ }
+ }
+
+ when (val config = configManager.config) {
+ is Config.Old -> {
+ ui.configToggle.check(R.id.oldConfigButton)
+ showOldConfig()
+
+ ui.forgetPasswordButton.visibility = if (config.hasPassword()) VISIBLE else GONE
+ }
+ is Config.New -> {
+ ui.configToggle.check(R.id.newConfigButton)
+ showNewConfig()
+
+ ui.tokenView.visibility = if (config.hasPassword()) VISIBLE else GONE
+ }
+ }
+
+ }
+
+ private fun showOldConfig() {
+ ui.oldConfigForm.visibility = VISIBLE
+ ui.newConfigForm.visibility = GONE
+ }
+
+ private fun showNewConfig() {
+ ui.oldConfigForm.visibility = GONE
+ ui.newConfigForm.visibility = VISIBLE
}
private fun checkForUrlCredentials() {
@@ -158,8 +251,10 @@ class ConfigFragment : Fragment() {
}
private fun onResultReceived() {
- ui.progressBar.visibility = INVISIBLE
- ui.okButton.visibility = VISIBLE
+ ui.progressBarOld.visibility = INVISIBLE
+ ui.okOldButton.visibility = VISIBLE
+ ui.progressBarNew.visibility = INVISIBLE
+ ui.okNewButton.visibility = VISIBLE
}
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
index 4327f4e..7531a4a 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -18,6 +18,7 @@ package net.taler.merchantpos.config
import android.content.Context
import android.content.Context.MODE_PRIVATE
+import android.net.Uri
import android.util.Base64.NO_WRAP
import android.util.Base64.encodeToString
import android.util.Log
@@ -40,19 +41,35 @@ import net.taler.common.getIncompatibleStringOrNull
import net.taler.merchantlib.ConfigResponse
import net.taler.merchantlib.MerchantApi
import net.taler.merchantlib.MerchantConfig
+import net.taler.merchantpos.BuildConfig
import net.taler.merchantpos.R
private const val SETTINGS_NAME = "taler-merchant-terminal"
+private const val SETTINGS_CONFIG_VERSION = "configVersion"
+
+internal const val CONFIG_VERSION_OLD = 0
+internal const val CONFIG_VERSION_NEW = 1
+
+// Old JSON config + basic auth config
+
private const val SETTINGS_CONFIG_URL = "configUrl"
private const val SETTINGS_USERNAME = "username"
private const val SETTINGS_PASSWORD = "password"
-internal const val CONFIG_URL_DEMO = "https://docs.taler.net/_static/sample-pos-config.json"
-internal const val CONFIG_USERNAME_DEMO = ""
-internal const val CONFIG_PASSWORD_DEMO = ""
+internal const val OLD_CONFIG_URL_DEMO = "https://docs.taler.net/_static/sample-pos-config.json"
+internal const val OLD_CONFIG_USERNAME_DEMO = ""
+internal const val OLD_CONFIG_PASSWORD_DEMO = ""
+
+// New merchant API + token config
+
+private const val SETTINGS_MERCHANT_URL = "merchantUrl"
+private const val SETTINGS_ACCESS_TOKEN = "accessToken"
-private val VERSION = Version(1, 0, 0)
+internal const val NEW_CONFIG_URL_DEMO = "https://backend.demo.taler.net/instances/pos"
+internal const val NEW_CONFIG_ACCESS_TOKEN_DEMO = "sandbox"
+
+private val VERSION = Version.parse(BuildConfig.BACKEND_API_VERSION)!!
private val TAG = ConfigManager::class.java.simpleName
@@ -73,14 +90,23 @@ class ConfigManager(
private val prefs = context.getSharedPreferences(SETTINGS_NAME, MODE_PRIVATE)
private val configurationReceivers = ArrayList<ConfigurationReceiver>()
- var config = Config(
- configUrl = prefs.getString(SETTINGS_CONFIG_URL, CONFIG_URL_DEMO)!!,
- username = prefs.getString(SETTINGS_USERNAME, CONFIG_USERNAME_DEMO)!!,
- password = prefs.getString(SETTINGS_PASSWORD, CONFIG_PASSWORD_DEMO)!!
- )
+ var config: Config = if (prefs.getInt(SETTINGS_CONFIG_VERSION, CONFIG_VERSION_NEW) == CONFIG_VERSION_NEW) {
+ Config.New(
+ merchantUrl = prefs.getString(SETTINGS_MERCHANT_URL, "")!!,
+ accessToken = prefs.getString(SETTINGS_ACCESS_TOKEN, NEW_CONFIG_ACCESS_TOKEN_DEMO)!!,
+ )
+ } else {
+ Config.Old(
+ configUrl = prefs.getString(SETTINGS_CONFIG_URL, "")!!,
+ username = prefs.getString(SETTINGS_USERNAME, OLD_CONFIG_USERNAME_DEMO)!!,
+ password = prefs.getString(SETTINGS_PASSWORD, OLD_CONFIG_PASSWORD_DEMO)!!,
+ )
+ }
+
@Volatile
var merchantConfig: MerchantConfig? = null
private set
+
@Volatile
var currency: String? = null
private set
@@ -96,18 +122,44 @@ class ConfigManager(
fun fetchConfig(config: Config, save: Boolean, savePassword: Boolean = false) {
mConfigUpdateResult.value = null
val configToSave = if (save) {
- if (savePassword) config else config.copy(password = "")
+ if (savePassword) config else when (val c = config) {
+ is Config.Old -> c.copy(password = "")
+ is Config.New -> c.copy(accessToken = "")
+ }
} else null
scope.launch(Dispatchers.IO) {
try {
+ val url = when(val c = config) {
+ is Config.Old -> c.configUrl
+ is Config.New -> Uri.parse(c.merchantUrl)
+ .buildUpon()
+ .appendPath("private/pos")
+ .build()
+ .toString()
+ }
+
// get PoS configuration
- val posConfig: PosConfig = httpClient.get(config.configUrl) {
- val credentials = "${config.username}:${config.password}"
- val auth = ("Basic ${encodeToString(credentials.toByteArray(), NO_WRAP)}")
- header(Authorization, auth)
+ val posConfig: PosConfig = httpClient.get(url) {
+ when (val c = config) {
+ is Config.Old -> {
+ val credentials = "${c.username}:${c.password}"
+ val auth = ("Basic ${encodeToString(credentials.toByteArray(), NO_WRAP)}")
+ header(Authorization, auth)
+ }
+ is Config.New -> {
+ val token = "secret-token:${c.accessToken}"
+ val auth = ("Bearer $token")
+ header(Authorization, auth)
+ }
+ }
}.body()
- val merchantConfig = posConfig.merchantConfig
+
+ val merchantConfig = when (val c = config) {
+ is Config.Old -> posConfig.merchantConfig!!
+ is Config.New -> MerchantConfig(c.merchantUrl, "secret-token:${c.accessToken}")
+ }
+
// get config from merchant backend API
api.getConfig(merchantConfig.baseUrl).handleSuspend(::onNetworkError) {
onMerchantConfigReceived(configToSave, posConfig, merchantConfig, it)
@@ -137,6 +189,7 @@ class ConfigManager(
val versionIncompatible =
VERSION.getIncompatibleStringOrNull(context, configResponse.version)
if (versionIncompatible != null) {
+ Log.e(TAG, "Versions incompatible $configResponse")
mConfigUpdateResult.postValue(ConfigUpdateResult.Error(versionIncompatible))
return
}
@@ -163,18 +216,29 @@ class ConfigManager(
@UiThread
fun forgetPassword() {
- config = config.copy(password = "")
+ config = when (val c = config) {
+ is Config.Old -> c.copy(password = "")
+ is Config.New -> c.copy(accessToken = "")
+ }
saveConfig(config)
merchantConfig = null
}
@UiThread
private fun saveConfig(config: Config) {
- prefs.edit()
- .putString(SETTINGS_CONFIG_URL, config.configUrl)
- .putString(SETTINGS_USERNAME, config.username)
- .putString(SETTINGS_PASSWORD, config.password)
- .apply()
+ when (val c = config) {
+ is Config.Old -> prefs.edit()
+ .putInt(SETTINGS_CONFIG_VERSION, CONFIG_VERSION_OLD)
+ .putString(SETTINGS_CONFIG_URL, c.configUrl)
+ .putString(SETTINGS_USERNAME, c.username)
+ .putString(SETTINGS_PASSWORD, c.password)
+ .apply()
+ is Config.New -> prefs.edit()
+ .putInt(SETTINGS_CONFIG_VERSION, CONFIG_VERSION_NEW)
+ .putString(SETTINGS_MERCHANT_URL, c.merchantUrl)
+ .putString(SETTINGS_ACCESS_TOKEN, c.accessToken)
+ .apply()
+ }
}
private fun onNetworkError(msg: String) = scope.launch(Dispatchers.Main) {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
index 1f1ab74..634d3a3 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
@@ -26,19 +26,38 @@ import net.taler.common.TalerUtils
import net.taler.merchantlib.MerchantConfig
import java.util.UUID
-data class Config(
- val configUrl: String,
- val username: String,
- val password: String
-) {
- fun isValid() = !configUrl.isBlank()
- fun hasPassword() = !password.isBlank()
+sealed class Config {
+ abstract fun isValid(): Boolean
+ abstract fun hasPassword(): Boolean
+
+ /**
+ * JSON config URL + user/password
+ */
+ data class Old(
+ val configUrl: String,
+ val username: String,
+ val password: String,
+ ): Config() {
+ override fun isValid() = configUrl.isNotBlank()
+ override fun hasPassword() = password.isNotBlank()
+ }
+
+ /**
+ * Merchant URL + access token
+ */
+ data class New(
+ val merchantUrl: String,
+ val accessToken: String,
+ ): Config() {
+ override fun isValid() = merchantUrl.isNotBlank()
+ override fun hasPassword() = accessToken.isNotBlank()
+ }
}
@Serializable
data class PosConfig(
@SerialName("config")
- val merchantConfig: MerchantConfig,
+ val merchantConfig: MerchantConfig? = null ,
val categories: List<Category>,
val products: List<ConfigProduct>
)