summaryrefslogtreecommitdiff
path: root/app/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
blob: 3e6ee2c8b381ca2bb35ca74881f4220478bbcf1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package net.taler.merchantpos.payment

import android.os.CountDownTimer
import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.android.volley.Request.Method.GET
import com.android.volley.Request.Method.POST
import com.android.volley.RequestQueue
import com.android.volley.Response.ErrorListener
import com.android.volley.Response.Listener
import com.android.volley.VolleyError
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import net.taler.merchantpos.config.ConfigManager
import net.taler.merchantpos.config.MerchantRequest
import net.taler.merchantpos.order.Order
import net.taler.merchantpos.order.getTotalAsString
import org.json.JSONArray
import org.json.JSONObject
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS

private val TIMEOUT = MINUTES.toMillis(2)
private val CHECK_INTERVAL = SECONDS.toMillis(1)

class PaymentManager(
    private val configManager: ConfigManager,
    private val queue: RequestQueue,
    private val mapper: ObjectMapper
) {

    private val mPayment = MutableLiveData<Payment>()
    val payment: LiveData<Payment> = mPayment

    private val checkTimer = object : CountDownTimer(TIMEOUT, CHECK_INTERVAL) {
        override fun onTick(millisUntilFinished: Long) {
            val orderId = payment.value?.orderId
            if (orderId == null) cancel()
            else checkPayment(orderId)
        }

        override fun onFinish() {
            payment.value?.copy(error = true)?.let { mPayment.value = it }
        }
    }

    @UiThread
    fun createPayment(order: Order) {
        val merchantConfig = configManager.merchantConfig!!

        val currency = merchantConfig.currency!!
        val orderTotal = order.getTotalAsString()
        val amount = "$currency:$orderTotal"
        val summary = order.map {
            "${it.value} x ${it.key.description}"
        }.joinToString()

        mPayment.value = Payment(order, summary, currency)

        val body = JSONObject().apply {
            put("order", JSONObject().apply {
                put("amount", amount)
                put("summary", summary)
                // fulfillment_url needs to be unique per order
                put("fulfillment_url", "https://example.com/${order.hashCode()}")
                put("instance", "default")
                put("products", order.getProductsJson())
            })
        }

        val req = MerchantRequest(POST, merchantConfig, "order", null, body,
            Listener { onOrderCreated(it) },
            ErrorListener { onNetworkError(it) }
        )
        queue.add(req)
    }

    private fun Order.getProductsJson(): JSONArray {
        val json = JSONArray()
        forEach { product, quantity ->
            val node = mapper.valueToTree<ObjectNode>(product).apply {
                put("quantity", quantity)
            }
            json.put(JSONObject(mapper.writeValueAsString(node)))
        }
        return json
    }

    private fun onOrderCreated(orderResponse: JSONObject) {
        val orderId = orderResponse.getString("order_id")
        mPayment.value = mPayment.value!!.copy(orderId = orderId)
        checkTimer.start()
    }

    private fun checkPayment(orderId: String) {
        val merchantConfig = configManager.merchantConfig!!
        val params = mapOf(
            "order_id" to orderId,
            "instance" to merchantConfig.instance
        )

        val req = MerchantRequest(GET, merchantConfig, "check-payment", params, null,
            Listener { onPaymentChecked(it) },
            ErrorListener { onNetworkError(it) })
        queue.add(req)
    }

    /**
     * Called when the /check-payment response gave a result.
     */
    private fun onPaymentChecked(checkPaymentResponse: JSONObject) {
        val currentValue = requireNotNull(mPayment.value)
        if (checkPaymentResponse.getBoolean("paid")) {
            mPayment.value = currentValue.copy(paid = true)
            checkTimer.cancel()
        } else if (currentValue.talerPayUri == null) {
            val talerPayUri = checkPaymentResponse.getString("taler_pay_uri")
            mPayment.value = currentValue.copy(talerPayUri = talerPayUri)
        }
    }

    private fun onNetworkError(volleyError: VolleyError) {
        Log.e(PaymentManager::class.java.simpleName, volleyError.toString())
        mPayment.value = mPayment.value!!.copy(error = true)
        checkTimer.cancel()
    }

}