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 }