/*
* This file is part of GNU Taler
* (C) 2020 Taler Systems S.A.
*
* GNU Taler is free software; you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3, or (at your option) any later version.
*
* GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* GNU Taler; see the file COPYING. If not, see
*/
package net.taler.wallet.transactions
import android.content.Context
import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.taler.common.ContractMerchant
import net.taler.common.ContractProduct
import net.taler.lib.common.Amount
import net.taler.lib.common.Timestamp
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
import net.taler.wallet.transactions.WithdrawalDetails.ManualTransfer
import net.taler.wallet.transactions.WithdrawalDetails.TalerBankIntegrationApi
@Serializable
data class Transactions(val transactions: List)
@Serializable
sealed class Transaction {
abstract val transactionId: String
abstract val timestamp: Timestamp
abstract val pending: Boolean
abstract val error: TransactionError?
abstract val amountRaw: Amount
abstract val amountEffective: Amount
@get:DrawableRes
abstract val icon: Int
@get:LayoutRes
abstract val detailPageLayout: Int
abstract val amountType: AmountType
abstract fun getTitle(context: Context): String
@get:StringRes
abstract val generalTitleRes: Int
}
sealed class AmountType {
object Positive : AmountType()
object Negative : AmountType()
object Neutral : AmountType()
}
@Serializable
data class TransactionError(
private val ec: Int,
private val hint: String? = null,
) {
val text get() = if (hint == null) "$ec" else "$ec $hint"
}
@Serializable
@SerialName("withdrawal")
class TransactionWithdrawal(
override val transactionId: String,
override val timestamp: Timestamp,
override val pending: Boolean,
val exchangeBaseUrl: String,
val withdrawalDetails: WithdrawalDetails,
override val error: TransactionError? = null,
override val amountRaw: Amount,
override val amountEffective: Amount
) : Transaction() {
override val icon = R.drawable.transaction_withdrawal
override val detailPageLayout = R.layout.fragment_transaction_withdrawal
@Transient
override val amountType = AmountType.Positive
override fun getTitle(context: Context) = cleanExchange(exchangeBaseUrl)
override val generalTitleRes = R.string.withdraw_title
val confirmed: Boolean
get() = !pending && (
(withdrawalDetails is TalerBankIntegrationApi && withdrawalDetails.confirmed) ||
withdrawalDetails is ManualTransfer
)
}
@Serializable
sealed class WithdrawalDetails {
@Serializable
@SerialName("manual-transfer")
class ManualTransfer(
/**
* Payto URIs that the exchange supports.
*
* Already contains the amount and message.
*/
val exchangePaytoUris: List
) : WithdrawalDetails()
@Serializable
@SerialName("taler-bank-integration-api")
class TalerBankIntegrationApi(
/**
* Set to true if the bank has confirmed the withdrawal, false if not.
* An unconfirmed withdrawal usually requires user-input
* and should be highlighted in the UI.
* See also bankConfirmationUrl below.
*/
val confirmed: Boolean,
/**
* If the withdrawal is unconfirmed, this can include a URL for user-initiated confirmation.
*/
val bankConfirmationUrl: String? = null,
) : WithdrawalDetails()
}
@Serializable
@SerialName("payment")
class TransactionPayment(
override val transactionId: String,
override val timestamp: Timestamp,
override val pending: Boolean,
val info: TransactionInfo,
val status: PaymentStatus,
override val error: TransactionError? = null,
override val amountRaw: Amount,
override val amountEffective: Amount
) : Transaction() {
override val icon = R.drawable.ic_cash_usd_outline
override val detailPageLayout = R.layout.fragment_transaction_payment
@Transient
override val amountType = AmountType.Negative
override fun getTitle(context: Context) = info.merchant.name
override val generalTitleRes = R.string.payment_title
}
@Serializable
class TransactionInfo(
val orderId: String,
val merchant: ContractMerchant,
val summary: String,
@SerialName("summary_i18n")
val summaryI18n: Map? = null,
val products: List,
val fulfillmentUrl: String
)
@Serializable
enum class PaymentStatus {
@SerialName("aborted")
Aborted,
@SerialName("failed")
Failed,
@SerialName("paid")
Paid,
@SerialName("accepted")
Accepted
}
@Serializable
@SerialName("refund")
class TransactionRefund(
override val transactionId: String,
override val timestamp: Timestamp,
override val pending: Boolean,
val refundedTransactionId: String,
val info: TransactionInfo,
/**
* Part of the refund that couldn't be applied because the refund permissions were expired
*/
val amountInvalid: Amount? = null,
override val error: TransactionError? = null,
@SerialName("amountEffective") // TODO remove when fixed in wallet-core
override val amountRaw: Amount,
@SerialName("amountRaw") // TODO remove when fixed in wallet-core
override val amountEffective: Amount
) : Transaction() {
override val icon = R.drawable.transaction_refund
override val detailPageLayout = R.layout.fragment_transaction_payment
@Transient
override val amountType = AmountType.Positive
override fun getTitle(context: Context): String {
return context.getString(R.string.transaction_refund_from, info.merchant.name)
}
override val generalTitleRes = R.string.refund_title
}
@Serializable
@SerialName("tip")
class TransactionTip(
override val transactionId: String,
override val timestamp: Timestamp,
override val pending: Boolean,
// TODO status: TipStatus,
val exchangeBaseUrl: String,
val merchant: ContractMerchant,
override val error: TransactionError? = null,
override val amountRaw: Amount,
override val amountEffective: Amount
) : Transaction() {
override val icon = R.drawable.transaction_tip_accepted // TODO different when declined
override val detailPageLayout = R.layout.fragment_transaction_payment
@Transient
override val amountType = AmountType.Positive
override fun getTitle(context: Context): String {
return context.getString(R.string.transaction_tip_from, merchant.name)
}
override val generalTitleRes = R.string.tip_title
}
@Serializable
@SerialName("refresh")
class TransactionRefresh(
override val transactionId: String,
override val timestamp: Timestamp,
override val pending: Boolean,
val exchangeBaseUrl: String,
override val error: TransactionError? = null,
override val amountRaw: Amount,
override val amountEffective: Amount
) : Transaction() {
override val icon = R.drawable.transaction_refresh
override val detailPageLayout = R.layout.fragment_transaction_withdrawal
@Transient
override val amountType = AmountType.Negative
override fun getTitle(context: Context): String {
return context.getString(R.string.transaction_refresh)
}
override val generalTitleRes = R.string.transaction_refresh
}