taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

RefundManager.kt (5675B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2020 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.merchantpos.refund
     18 
     19 import androidx.annotation.UiThread
     20 import androidx.lifecycle.LiveData
     21 import androidx.lifecycle.MutableLiveData
     22 import kotlinx.coroutines.CoroutineScope
     23 import kotlinx.coroutines.Job
     24 import kotlinx.coroutines.delay
     25 import kotlinx.coroutines.launch
     26 import net.taler.common.Amount
     27 import net.taler.lib.android.assertUiThread
     28 import net.taler.merchantlib.OrderHistoryEntry
     29 import net.taler.merchantlib.MerchantApi
     30 import net.taler.merchantlib.RefundRequest
     31 import net.taler.merchantpos.config.ConfigManager
     32 
     33 sealed class RefundResult {
     34     class Error(val msg: String) : RefundResult()
     35     object PastDeadline : RefundResult()
     36     object AlreadyRefunded : RefundResult()
     37     class Success(
     38         val refundUri: String,
     39         val item: OrderHistoryEntry,
     40         val amount: Amount,
     41         val reason: String
     42     ) : RefundResult()
     43 }
     44 
     45 class RefundManager(
     46     private val configManager: ConfigManager,
     47     private val scope: CoroutineScope,
     48     private val api: MerchantApi
     49 ) {
     50     private var refundStatusJob: Job? = null
     51 
     52     var toBeRefunded: OrderHistoryEntry? = null
     53         private set
     54 
     55     private val mRefundResult = MutableLiveData<RefundResult?>()
     56     internal val refundResult: LiveData<RefundResult?> = mRefundResult
     57     private val mPendingRefundOrderId = MutableLiveData<String?>(null)
     58     internal val pendingRefundOrderId: LiveData<String?> = mPendingRefundOrderId
     59     private val mRefundReceived = MutableLiveData(false)
     60     internal val refundReceived: LiveData<Boolean> = mRefundReceived
     61 
     62     @UiThread
     63     internal fun startRefund(item: OrderHistoryEntry) {
     64         refundStatusJob?.cancel()
     65         toBeRefunded = item
     66         mRefundResult.value = null
     67         mPendingRefundOrderId.value = null
     68         mRefundReceived.value = false
     69     }
     70 
     71     @UiThread
     72     internal fun abortRefund() {
     73         refundStatusJob?.cancel()
     74         toBeRefunded = null
     75         mRefundResult.value = null
     76         mPendingRefundOrderId.value = null
     77         mRefundReceived.value = false
     78     }
     79 
     80     @UiThread
     81     internal fun completeRefund() {
     82         refundStatusJob?.cancel()
     83         toBeRefunded = null
     84         mRefundResult.value = null
     85         mPendingRefundOrderId.value = null
     86         mRefundReceived.value = false
     87     }
     88 
     89     @UiThread
     90     internal fun debugSetRefundResult(result: RefundResult) {
     91         mRefundResult.value = result
     92     }
     93 
     94     @UiThread
     95     internal fun resumeRefund(item: OrderHistoryEntry): Boolean {
     96         val current = mRefundResult.value as? RefundResult.Success ?: return false
     97         if (current.item.orderId != item.orderId) return false
     98         toBeRefunded = item
     99         mPendingRefundOrderId.value = item.orderId
    100         if (mRefundReceived.value != true) {
    101             observeRefundStatus(item.orderId)
    102         }
    103         return true
    104     }
    105 
    106     @UiThread
    107     internal fun refund(item: OrderHistoryEntry, amount: Amount, reason: String) = scope.launch {
    108         val merchantConfig = configManager.merchantConfig!!
    109         val request = RefundRequest(amount, reason)
    110         api.giveRefund(merchantConfig, item.orderId, request).handle(::onRefundError) {
    111             assertUiThread()
    112             mRefundResult.value = RefundResult.Success(
    113                 refundUri = it.talerRefundUri,
    114                 item = item,
    115                 amount = amount,
    116                 reason = reason
    117             )
    118             mPendingRefundOrderId.value = item.orderId
    119             mRefundReceived.value = false
    120             observeRefundStatus(item.orderId)
    121         }
    122     }
    123 
    124     @UiThread
    125     private fun onRefundError(msg: String) {
    126         assertUiThread()
    127         refundStatusJob?.cancel()
    128         mPendingRefundOrderId.postValue(null)
    129         mRefundReceived.postValue(false)
    130         if (msg.contains("2602")) {
    131             mRefundResult.postValue(RefundResult.AlreadyRefunded)
    132         } else mRefundResult.postValue(RefundResult.Error(msg))
    133     }
    134 
    135     @UiThread
    136     private fun observeRefundStatus(orderId: String) {
    137         refundStatusJob?.cancel()
    138         refundStatusJob = scope.launch {
    139             val merchantConfig = configManager.merchantConfig ?: return@launch
    140             while (true) {
    141                 var wasRefunded = false
    142                 api.checkOrder(merchantConfig, orderId).handle(null) { response ->
    143                     assertUiThread()
    144                     val paidResponse = response as? net.taler.merchantlib.CheckPaymentResponse.Paid
    145                     if (
    146                         paidResponse != null &&
    147                         paidResponse.refunded &&
    148                         !paidResponse.refundPending &&
    149                         paidResponse.refundAmount?.isZero() == false
    150                     ) {
    151                         mRefundReceived.value = true
    152                         wasRefunded = true
    153                     }
    154                 }
    155                 if (wasRefunded || mRefundReceived.value == true) break
    156                 delay(2_000)
    157             }
    158         }
    159     }
    160 }