summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-08-22 23:37:54 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-08-22 23:38:08 +0200
commit2a457a1c46a5da4985035d8bff1db914de161049 (patch)
tree28a95fa5de18eeb4a8a46d8ee293dac0d50ed505
parenteb9594079589a0126c3fb5f9a2f6ecd56950eebc (diff)
downloadmerchant-terminal-android-2a457a1c46a5da4985035d8bff1db914de161049.tar.gz
merchant-terminal-android-2a457a1c46a5da4985035d8bff1db914de161049.tar.bz2
merchant-terminal-android-2a457a1c46a5da4985035d8bff1db914de161049.zip
UX improvements / prototype support for NFC tunneling
-rw-r--r--.idea/codeStyles/Project.xml109
-rw-r--r--app/build.gradle2
-rw-r--r--app/src/main/AndroidManifest.xml7
-rw-r--r--app/src/main/java/net/taler/merchantpos/CreatePayment.kt65
-rw-r--r--app/src/main/java/net/taler/merchantpos/MainActivity.kt251
-rw-r--r--app/src/main/java/net/taler/merchantpos/PaymentSuccess.kt29
-rw-r--r--app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt9
-rw-r--r--app/src/main/java/net/taler/merchantpos/ProcessPayment.kt126
-rw-r--r--app/src/main/res/layout/fragment_create_payment.xml7
-rw-r--r--app/src/main/res/layout/fragment_payment_success.xml38
-rw-r--r--app/src/main/res/layout/fragment_process_payment.xml3
-rw-r--r--app/src/main/res/navigation/nav_graph.xml12
-rw-r--r--build.gradle2
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
14 files changed, 518 insertions, 146 deletions
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 1bec35e..ce889bd 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -3,6 +3,115 @@
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
+ <codeStyleSettings language="XML">
+ <arrangement>
+ <rules>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:android</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:id</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>style</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>ANDROID_ATTRIBUTE_ORDER</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>.*</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ </rules>
+ </arrangement>
+ </codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
diff --git a/app/build.gradle b/app/build.gradle
index 22225ac..0f36545 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,7 +10,7 @@ android {
buildToolsVersion "29.0.1"
defaultConfig {
applicationId "net.taler.merchantpos"
- minSdkVersion 28
+ minSdkVersion 27
targetSdkVersion 29
versionCode 1
versionName "1.0"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 52f77ef..0920771 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="net.taler.merchantpos">
+ <uses-permission android:name="android.permission.NFC" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-feature android:name="android.hardware.nfc"
+ android:required="true" />
+
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -20,7 +25,5 @@
</intent-filter>
</activity>
</application>
-
- <uses-permission android:name="android.permission.INTERNET"/>
</manifest> \ No newline at end of file
diff --git a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
index 330b9e8..e07802f 100644
--- a/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
+++ b/app/src/main/java/net/taler/merchantpos/CreatePayment.kt
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
+import android.widget.EditText
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.android.volley.Request
@@ -34,19 +35,11 @@ private const val ARG_PARAM2 = "param2"
*
*/
class CreatePayment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
- private var listener: OnFragmentInteractionListener? = null
private lateinit var queue: RequestQueue
private lateinit var model: PosTerminalViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
model = activity?.run {
ViewModelProviders.of(this)[PosTerminalViewModel::class.java]
@@ -56,11 +49,14 @@ class CreatePayment : Fragment() {
}
private fun onRequestPayment() {
- val amount = "TESTKUDOS:10.00"
+ val amountValStr = activity!!.findViewById<EditText>(R.id.edit_payment_amount).text
+ val amount = "TESTKUDOS:${amountValStr}"
model.activeAmount = amount
+ model.activeSubject = activity!!.findViewById<EditText>(R.id.edit_payment_subject).text
+
var order = JSONObject().also {
it.put("amount", amount)
- it.put("summary", "hello world")
+ it.put("summary", model.activeSubject!!)
it.put("fulfillment_url", "https://example.com")
it.put("instance", "default")
}
@@ -119,53 +115,4 @@ class CreatePayment : Fragment() {
return view
}
- override fun onAttach(context: Context) {
- super.onAttach(context)
- if (context is OnFragmentInteractionListener) {
- listener = context
- } else {
- throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
- }
- }
-
- override fun onDetach() {
- super.onDetach()
- listener = null
- }
-
- /**
- * This interface must be implemented by activities that contain this
- * fragment to allow an interaction in this fragment to be communicated
- * to the activity and potentially other fragments contained in that
- * activity.
- *
- *
- * See the Android Training lesson [Communicating with Other Fragments]
- * (http://developer.android.com/training/basics/fragments/communicating.html)
- * for more information.
- */
- interface OnFragmentInteractionListener {
- // TODO: Update argument type and name
- fun onFragmentInteraction(uri: Uri)
- }
-
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment CreatePayment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- CreatePayment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
- }
}
diff --git a/app/src/main/java/net/taler/merchantpos/MainActivity.kt b/app/src/main/java/net/taler/merchantpos/MainActivity.kt
index 4f67f9c..23d0417 100644
--- a/app/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/app/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -1,7 +1,10 @@
package net.taler.merchantpos
-import android.net.Uri
+import android.nfc.NfcAdapter
+import android.nfc.Tag
+import android.nfc.tech.IsoDep
import android.os.Bundle
+import android.util.Log
import androidx.core.view.GravityCompat
import android.view.MenuItem
import androidx.drawerlayout.widget.DrawerLayout
@@ -14,16 +17,243 @@ import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
+import net.taler.merchantpos.Utils.Companion.hexStringToByteArray
+import org.json.JSONObject
+import java.io.ByteArrayOutputStream
+import java.net.URL
+import javax.net.ssl.HttpsURLConnection
+
+
+class Utils {
+ companion object {
+ private val HEX_CHARS = "0123456789ABCDEF"
+ fun hexStringToByteArray(data: String): ByteArray {
+
+ val result = ByteArray(data.length / 2)
+
+ for (i in 0 until data.length step 2) {
+ val firstIndex = HEX_CHARS.indexOf(data[i]);
+ val secondIndex = HEX_CHARS.indexOf(data[i + 1]);
+
+ val octet = firstIndex.shl(4).or(secondIndex)
+ result.set(i.shr(1), octet.toByte())
+ }
+
+ return result
+ }
+
+ private val HEX_CHARS_ARRAY = "0123456789ABCDEF".toCharArray()
+ fun toHex(byteArray: ByteArray): String {
+ val result = StringBuffer()
+
+ byteArray.forEach {
+ val octet = it.toInt()
+ val firstIndex = (octet and 0xF0).ushr(4)
+ val secondIndex = octet and 0x0F
+ result.append(HEX_CHARS_ARRAY[firstIndex])
+ result.append(HEX_CHARS_ARRAY[secondIndex])
+ }
+
+ return result.toString()
+ }
+ }
+}
+
+val TALER_AID = "A0000002471001"
+
+
+fun writeApduLength(stream: ByteArrayOutputStream, size: Int) {
+ when {
+ size == 0 -> {
+ // No size field needed!
+ }
+ size <= 255 -> // One byte size field
+ stream.write(size)
+ size <= 65535 -> {
+ stream.write(0)
+ // FIXME: is this supposed to be little or big endian?
+ stream.write(size and 0xFF)
+ stream.write((size ushr 8) and 0xFF)
+ }
+ else -> throw Error("payload too big")
+ }
+}
+
+fun apduSelectFile(): ByteArray {
+ return hexStringToByteArray("00A4040007A0000002471001")
+}
+
+
+fun apduPutData(payload: ByteArray): ByteArray {
+ val stream = ByteArrayOutputStream()
+
+ // Class
+ stream.write(0x00)
+
+ // Instruction 0xDA = put data
+ stream.write(0xDA)
+
+ // Instruction parameters
+ // (proprietary encoding)
+ stream.write(0x01)
+ stream.write(0x00)
+
+ writeApduLength(stream, payload.size)
+
+ stream.write(payload)
+
+ return stream.toByteArray()
+}
+
+fun apduPutTalerData(talerInst: Int, payload: ByteArray): ByteArray {
+ val realPayload = ByteArrayOutputStream()
+ realPayload.write(talerInst)
+ realPayload.write(payload)
+ return apduPutData(realPayload.toByteArray())
+}
+
+fun apduGetData(): ByteArray {
+ val stream = ByteArrayOutputStream()
+
+ // Class
+ stream.write(0x00)
+
+ // Instruction 0xCA = get data
+ stream.write(0xCA)
+
+ // Instruction parameters
+ // (proprietary encoding)
+ stream.write(0x01)
+ stream.write(0x00)
+
+ // Max expected response size, two
+ // zero bytes denotes 65536
+ stream.write(0x0)
+ stream.write(0x0)
+
+ return stream.toByteArray()
+}
+
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener,
- CreatePayment.OnFragmentInteractionListener, ProcessPayment.OnFragmentInteractionListener {
- override fun onFragmentInteraction(uri: Uri) {
- TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
+ NfcAdapter.ReaderCallback {
+
+ companion object {
+ const val TAG = "taler-merchant"
+ }
+
+ private lateinit var model: PosTerminalViewModel
+ private var nfcAdapter: NfcAdapter? = null
+
+ private var currentTag: IsoDep? = null
+
+ override fun onTagDiscovered(tag: Tag?) {
+
+ Log.v(TAG, "tag discovered")
+
+ val isoDep = IsoDep.get(tag)
+ isoDep.connect()
+
+ currentTag = isoDep
+
+ isoDep.transceive(apduSelectFile())
+
+ val contractUri: String? = model.activeContractUri
+
+ if (contractUri != null) {
+ isoDep.transceive(apduPutTalerData(1, contractUri.toByteArray()))
+ }
+
+ // FIXME: use better pattern for sleeps in between requests
+ // -> start with fast polling, poll more slowly if no requests are coming
+
+ while (true) {
+ try {
+ val reqFrame = isoDep.transceive(apduGetData())
+ if (reqFrame.size < 2) {
+ Log.v(TAG, "request frame too small")
+ break
+ }
+ val req = ByteArray(reqFrame.size - 2)
+ if (req.isEmpty()) {
+ continue
+ }
+ reqFrame.copyInto(req, 0, 0, reqFrame.size - 2)
+ val jsonReq = JSONObject(req.toString(Charsets.UTF_8))
+ val reqId = jsonReq.getInt("id")
+ Log.v(TAG, "got request $jsonReq")
+ val jsonInnerReq = jsonReq.getJSONObject("request")
+ val method = jsonInnerReq.getString("method")
+ val urlStr = jsonInnerReq.getString("url")
+ Log.v(TAG, "url '$urlStr'")
+ Log.v(TAG, "method '$method'")
+ val url = URL(urlStr)
+ val conn: HttpsURLConnection = url.openConnection() as HttpsURLConnection
+ conn.setRequestProperty("Accept", "application/json")
+ conn.connectTimeout = 5000
+ conn.doInput = true
+ when (method) {
+ "get" -> {
+ conn.requestMethod = "GET"
+ }
+ "postJson" -> {
+ conn.requestMethod = "POST"
+ conn.doOutput = true
+ conn.setRequestProperty("Content-Type", "application/json; utf-8")
+ val body = jsonInnerReq.getString("body")
+ conn.outputStream.write(body.toByteArray(Charsets.UTF_8))
+ }
+ else -> {
+ throw Exception("method not supported")
+ }
+ }
+ Log.v(TAG, "connecting")
+ conn.connect()
+ Log.v(TAG, "connected")
+
+ val statusCode = conn.responseCode
+ val tunnelResp = JSONObject()
+ tunnelResp.put("id", reqId)
+ tunnelResp.put("status", conn.responseCode)
+
+ if (statusCode == 200) {
+ val stream = conn.inputStream
+ val httpResp = stream.buffered().readBytes()
+ tunnelResp.put("responseJson", JSONObject(httpResp.toString(Charsets.UTF_8)))
+ }
+
+ Log.v(TAG, "sending: $tunnelResp")
+
+ isoDep.transceive(apduPutTalerData(2, tunnelResp.toString().toByteArray()))
+ } catch (e: Exception) {
+ Log.v(TAG, "exception during NFC loop: ${e}")
+ break
+ }
+ }
+
+ isoDep.close()
+ }
+
+ public override fun onResume() {
+ super.onResume()
+ nfcAdapter?.enableReaderMode(
+ this, this,
+ NfcAdapter.FLAG_READER_NFC_A or
+ NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK,
+ null
+ )
}
+ public override fun onPause() {
+ super.onPause()
+ nfcAdapter?.disableReaderMode(this)
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ nfcAdapter = NfcAdapter.getDefaultAdapter(this)
+
setContentView(R.layout.activity_main)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
@@ -35,13 +265,20 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration =
- AppBarConfiguration(setOf(R.id.createPayment, R.id.merchantSettings, R.id.merchantHistory), drawerLayout)
+ AppBarConfiguration(
+ setOf(
+ R.id.createPayment,
+ R.id.merchantSettings,
+ R.id.merchantHistory
+ ), drawerLayout
+ )
findViewById<Toolbar>(R.id.toolbar)
.setupWithNavController(navController, appBarConfiguration)
- val model = ViewModelProviders.of(this)[PosTerminalViewModel::class.java]
- model.merchantConfig = MerchantConfig("https://backend.test.taler.net", "default", "sandbox")
+ model = ViewModelProviders.of(this)[PosTerminalViewModel::class.java]
+ model.merchantConfig =
+ MerchantConfig("https://backend.test.taler.net", "default", "sandbox")
}
diff --git a/app/src/main/java/net/taler/merchantpos/PaymentSuccess.kt b/app/src/main/java/net/taler/merchantpos/PaymentSuccess.kt
new file mode 100644
index 0000000..20b6ed1
--- /dev/null
+++ b/app/src/main/java/net/taler/merchantpos/PaymentSuccess.kt
@@ -0,0 +1,29 @@
+package net.taler.merchantpos
+
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.navigation.findNavController
+
+/**
+ * A simple [Fragment] subclass.
+ */
+class PaymentSuccess : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.fragment_payment_success, container, false)
+ view.findViewById<Button>(R.id.button_success_back).setOnClickListener {
+ activity!!.findNavController(R.id.nav_host_fragment).navigateUp()
+ }
+ return view
+ }
+
+
+}
diff --git a/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt b/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt
index 1e174ef..bcbc0c8 100644
--- a/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt
+++ b/app/src/main/java/net/taler/merchantpos/PosTerminalViewModel.kt
@@ -1,15 +1,18 @@
package net.taler.merchantpos
+import android.text.Editable
import androidx.lifecycle.ViewModel
class PosTerminalViewModel : ViewModel() {
+ var activeSubject: Editable? = null
var merchantConfig: MerchantConfig? = null
var activeOrderId: String? = null
var activeAmount: String? = null
var activeContractUri: String? = null
fun activeAmountPretty(): String? {
- val a = activeAmount ?: return null
- return a.replace(":", " ")
+ val amount = activeAmount ?: return null
+ val components = amount.split(":")
+ return "${components[1]} ${components[0]}"
}
-} \ No newline at end of file
+}
diff --git a/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt b/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
index c98a3e4..26ef1c7 100644
--- a/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
+++ b/app/src/main/java/net/taler/merchantpos/ProcessPayment.kt
@@ -14,6 +14,8 @@ import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter
import android.opengl.ETC1.getWidth
import android.opengl.ETC1.getHeight
+import android.os.Handler
+import android.util.Log
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
@@ -22,7 +24,14 @@ import androidx.activity.addCallback
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
+import com.android.volley.Request
+import com.android.volley.RequestQueue
+import com.android.volley.Response
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.Volley
import com.google.android.material.snackbar.Snackbar
+import org.json.JSONObject
+import java.net.URLEncoder
// TODO: Rename parameter arguments, choose names that match
@@ -32,32 +41,68 @@ private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
- * Activities that contain this fragment must implement the
- * [ProcessPayment.OnFragmentInteractionListener] interface
- * to handle interaction events.
- * Use the [ProcessPayment.newInstance] factory method to
- * create an instance of this fragment.
*
*/
class ProcessPayment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
- private var listener: OnFragmentInteractionListener? = null
+ private var paused: Boolean = true
+ private lateinit var queue: RequestQueue
private lateinit var model: PosTerminalViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
model = activity?.run {
ViewModelProviders.of(this)[PosTerminalViewModel::class.java]
} ?: throw Exception("Invalid Activity")
+ queue = Volley.newRequestQueue(context)
+
+ }
+
+ private fun onCheckPayment(checkPaymentResponse: JSONObject) {
+ if (paused) {
+ return
+ }
+ //Log.v("taler-merchant", "got check payment result ${checkPaymentResponse}")
+ if (checkPaymentResponse.getBoolean("paid")) {
+ queue.cancelAll { true }
+ findNavController().navigate(R.id.action_processPayment_to_paymentSuccess)
+ return
+ }
+ }
+
+ private fun onNetworkError(volleyError: VolleyError?) {
+ val mySnackbar = Snackbar.make(view!!, "Network Error", Snackbar.LENGTH_SHORT)
+ mySnackbar.show()
+ }
+
+ private fun checkPaid() {
+ if (paused) {
+ return
+ }
+ //Log.v("taler-merchant", "checkig if payment happened")
+ val params = mapOf("order_id" to model.activeOrderId!!, "instance" to model.merchantConfig!!.instance)
+ var req = MerchantInternalRequest(Request.Method.GET, model.merchantConfig!!, "check-payment", params, null,
+ Response.Listener { onCheckPayment(it) }, Response.ErrorListener { onNetworkError(it) })
+ queue.add(req)
+ val handler = Handler()
+ handler.postDelayed({
+ checkPaid()
+ }, 500)
+
+ }
+
+ override fun onResume() {
+ this.paused = false
+ checkPaid()
+ super.onResume()
+ }
+
+ override fun onPause() {
+ this.paused = true
+ super.onPause()
+ queue.cancelAll { true }
}
override fun onCreateView(
@@ -67,7 +112,8 @@ class ProcessPayment : Fragment() {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_process_payment, container, false)
val img = view.findViewById<ImageView>(R.id.qrcode)
- val myBitmap = makeQrCode(model.activeContractUri!!)
+ val talerPayUrl = "talerpay:" + URLEncoder.encode(model.activeContractUri!!, "utf-8")
+ val myBitmap = makeQrCode(talerPayUrl)
img.setImageBitmap(myBitmap)
val cancelPaymentButton = view.findViewById<Button>(R.id.button_cancel_payment)
cancelPaymentButton.setOnClickListener {
@@ -99,56 +145,6 @@ class ProcessPayment : Fragment() {
bmp.setPixel(x, y, if (bitMatrix.get(x, y)) Color.BLACK else Color.WHITE)
}
}
- return bmp;
- }
-
- override fun onAttach(context: Context) {
- super.onAttach(context)
- if (context is OnFragmentInteractionListener) {
- listener = context
- } else {
- throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
- }
- }
-
- override fun onDetach() {
- super.onDetach()
- listener = null
- }
-
- /**
- * This interface must be implemented by activities that contain this
- * fragment to allow an interaction in this fragment to be communicated
- * to the activity and potentially other fragments contained in that
- * activity.
- *
- *
- * See the Android Training lesson [Communicating with Other Fragments]
- * (http://developer.android.com/training/basics/fragments/communicating.html)
- * for more information.
- */
- interface OnFragmentInteractionListener {
- // TODO: Update argument type and name
- fun onFragmentInteraction(uri: Uri)
- }
-
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment ProcessPayment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- ProcessPayment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
+ return bmp
}
}
diff --git a/app/src/main/res/layout/fragment_create_payment.xml b/app/src/main/res/layout/fragment_create_payment.xml
index 2549e91..f72f263 100644
--- a/app/src/main/res/layout/fragment_create_payment.xml
+++ b/app/src/main/res/layout/fragment_create_payment.xml
@@ -28,10 +28,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
- android:text="Payment Subject"
+ android:text="Demo Payment"
android:ems="10"
android:layout_gravity="top"
- android:id="@+id/editText5"/>
+ android:id="@+id/edit_payment_subject"/>
<TextView
android:text="Amount (TESTKUDOS)"
android:layout_width="match_parent"
@@ -42,7 +42,8 @@
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:ems="10"
- android:id="@+id/editText6" android:layout_weight="0" android:text="10"/>
+ android:id="@+id/edit_payment_amount" android:layout_weight="0"
+ android:text="1"/>
<Space
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/app/src/main/res/layout/fragment_payment_success.xml b/app/src/main/res/layout/fragment_payment_success.xml
new file mode 100644
index 0000000..8db64df
--- /dev/null
+++ b/app/src/main/res/layout/fragment_payment_success.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="15dp"
+ tools:context=".PaymentSuccessful">
+
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Space android:layout_width="match_parent" android:layout_height="0dp"
+ android:layout_weight="1"/>
+
+ <TextView
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:textAlignment="center"
+ android:layout_height="50dp"
+ android:text="Payment Received"
+ android:autoSizeTextType="uniform"
+ android:textColor="@android:color/holo_green_dark"/>
+
+
+ <Space android:layout_width="match_parent" android:layout_height="0dp"
+ android:layout_weight="1"/>
+ <Button
+ android:text="Go Back"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/button_success_back"/>
+
+ </LinearLayout>
+
+</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_process_payment.xml b/app/src/main/res/layout/fragment_process_payment.xml
index 0ff8257..3f1764e 100644
--- a/app/src/main/res/layout/fragment_process_payment.xml
+++ b/app/src/main/res/layout/fragment_process_payment.xml
@@ -23,8 +23,7 @@
android:layout_height="275dp" tools:src="@tools:sample/backgrounds/scenic[16]"
android:id="@+id/qrcode" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="32dp"
- android:background="#CEC02424"/>
+ app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="32dp" />
<TextView
android:text="Please scan QR Code or use NFC to pay"
android:layout_width="wrap_content"
diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml
index b6345a1..3a5b470 100644
--- a/app/src/main/res/navigation/nav_graph.xml
+++ b/app/src/main/res/navigation/nav_graph.xml
@@ -9,7 +9,12 @@
<action android:id="@+id/action_createPayment_to_processPayment" app:destination="@id/processPayment"/>
</fragment>
<fragment android:id="@+id/processPayment" android:name="net.taler.merchantpos.ProcessPayment"
- android:label="Payment Prompt" tools:layout="@layout/fragment_process_payment"/>
+ android:label="Payment Prompt" tools:layout="@layout/fragment_process_payment">
+ <action
+ android:id="@+id/action_processPayment_to_paymentSuccess"
+ app:destination="@id/paymentSuccess"
+ app:popUpTo="@id/createPayment"/>
+ </fragment>
<fragment android:id="@+id/merchantHistory" android:name="net.taler.merchantpos.MerchantHistory"
android:label="Payment History" tools:layout="@layout/fragment_merchant_history"/>
<action android:id="@+id/action_global_merchantHistory" app:destination="@id/merchantHistory"/>
@@ -17,4 +22,9 @@
<fragment android:id="@+id/merchantSettings" android:name="net.taler.merchantpos.MerchantSettings"
android:label="Merchant Settings" tools:layout="@layout/fragment_merchant_settings"/>
<action android:id="@+id/action_global_merchantSettings" app:destination="@id/merchantSettings"/>
+ <fragment
+ android:id="@+id/paymentSuccess"
+ android:name="net.taler.merchantpos.PaymentSuccess"
+ android:label="Payment Received"
+ tools:layout="@layout/fragment_payment_success" />
</navigation> \ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 438d280..4420c59 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.2'
+ classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a20e04a..344e98a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sat Aug 10 13:25:15 CEST 2019
+#Tue Aug 20 23:27:29 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip