/*
* This file is part of LibEuFin.
* Copyright (C) 2020 Taler Systems S.A.
*
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3, or
* (at your option) any later version.
*
* LibEuFin 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 Affero General
* Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with LibEuFin; see the file COPYING. If not, see
*
*/
package tech.libeufin.nexus.server
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonTypeName
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import tech.libeufin.nexus.iso20022.CamtBankAccountEntry
import tech.libeufin.nexus.iso20022.EntryStatus
import tech.libeufin.util.*
import java.math.BigDecimal
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.ChronoField
data class BackupRequestJson(
val passphrase: String
)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "paramType"
)
@JsonSubTypes(
JsonSubTypes.Type(value = EbicsStandardOrderParamsDateJson::class, name = "standard-date-range"),
JsonSubTypes.Type(value = EbicsStandardOrderParamsEmptyJson::class, name = "standard-empty"),
JsonSubTypes.Type(value = EbicsGenericOrderParamsJson::class, name = "generic")
)
abstract class EbicsOrderParamsJson {
abstract fun toOrderParams(): EbicsOrderParams
}
@JsonTypeName("generic")
class EbicsGenericOrderParamsJson(
val params: Map
) : EbicsOrderParamsJson() {
override fun toOrderParams(): EbicsOrderParams {
return EbicsGenericOrderParams(params)
}
}
@JsonTypeName("standard-empty")
class EbicsStandardOrderParamsEmptyJson : EbicsOrderParamsJson() {
override fun toOrderParams(): EbicsOrderParams {
return EbicsStandardOrderParams(null)
}
}
object EbicsDateFormat {
var fmt = DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_DATE)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.OFFSET_SECONDS, ZoneId.systemDefault().rules.getOffset(Instant.now()).totalSeconds.toLong())
.toFormatter()!!
}
@JsonTypeName("standard-date-range")
class EbicsStandardOrderParamsDateJson(
private val start: String,
private val end: String
) : EbicsOrderParamsJson() {
override fun toOrderParams(): EbicsOrderParams {
val dateRange: EbicsDateRange? =
EbicsDateRange(
ZonedDateTime.parse(this.start, EbicsDateFormat.fmt),
ZonedDateTime.parse(this.end, EbicsDateFormat.fmt)
)
return EbicsStandardOrderParams(dateRange)
}
}
data class NexusErrorDetailJson(
val type: String,
val description: String
)
data class NexusErrorJson(
val error: NexusErrorDetailJson
)
data class NexusMessage(
val message: String
)
data class ErrorResponse(
val code: Int,
val hint: String,
val detail: String,
)
data class BankConnectionInfo(
val name: String,
val type: String
)
data class BankConnectionsList(
val bankConnections: MutableList = mutableListOf()
)
data class BankConnectionDeletion(
val bankConnectionId: String
)
data class EbicsHostTestRequest(
val ebicsBaseUrl: String,
val ebicsHostId: String
)
/**
* This object is used twice: as a response to the backup request,
* and as a request to the backup restore. Note: in the second case
* the client must provide the passphrase.
*/
data class EbicsKeysBackupJson(
// Always "ebics"
val type: String,
val userID: String,
val partnerID: String,
val hostID: String,
val ebicsURL: String,
val authBlob: String,
val encBlob: String,
val sigBlob: String
)
enum class PermissionChangeAction(@get:JsonValue val jsonName: String) {
GRANT("grant"), REVOKE("revoke")
}
data class Permission(
val subjectType: String,
val subjectId: String,
val resourceType: String,
val resourceId: String,
val permissionName: String
)
data class PermissionQuery(
val resourceType: String,
val resourceId: String,
val permissionName: String,
)
data class ChangePermissionsRequest(
val action: PermissionChangeAction,
val permission: Permission
)
enum class FetchLevel(@get:JsonValue val jsonName: String) {
REPORT("report"), STATEMENT("statement"), ALL("all");
}
/**
* Instructions on what range to fetch from the bank,
* and which source(s) to use.
*
* Intended to be convenient to specify.
*/
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "rangeType"
)
@JsonSubTypes(
JsonSubTypes.Type(value = FetchSpecLatestJson::class, name = "latest"),
JsonSubTypes.Type(value = FetchSpecAllJson::class, name = "all"),
JsonSubTypes.Type(value = FetchSpecPreviousDaysJson::class, name = "previous-days"),
JsonSubTypes.Type(value = FetchSpecSinceLastJson::class, name = "since-last")
)
abstract class FetchSpecJson(
val level: FetchLevel,
val bankConnection: String?
)
@JsonTypeName("latest")
class FetchSpecLatestJson(level: FetchLevel, bankConnection: String?) : FetchSpecJson(level, bankConnection)
@JsonTypeName("all")
class FetchSpecAllJson(level: FetchLevel, bankConnection: String?) : FetchSpecJson(level, bankConnection)
@JsonTypeName("since-last")
class FetchSpecSinceLastJson(level: FetchLevel, bankConnection: String?) : FetchSpecJson(level, bankConnection)
@JsonTypeName("previous-days")
class FetchSpecPreviousDaysJson(level: FetchLevel, bankConnection: String?, val number: Int) :
FetchSpecJson(level, bankConnection)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "source"
)
@JsonSubTypes(
JsonSubTypes.Type(value = CreateBankConnectionFromBackupRequestJson::class, name = "backup"),
JsonSubTypes.Type(value = CreateBankConnectionFromNewRequestJson::class, name = "new")
)
abstract class CreateBankConnectionRequestJson(
val name: String
)
@JsonTypeName("backup")
class CreateBankConnectionFromBackupRequestJson(
name: String,
val passphrase: String?,
val data: JsonNode
) : CreateBankConnectionRequestJson(name)
@JsonTypeName("new")
class CreateBankConnectionFromNewRequestJson(
name: String,
val type: String,
val data: JsonNode
) : CreateBankConnectionRequestJson(name)
data class EbicsNewTransport(
val userID: String,
val partnerID: String,
val hostID: String,
val ebicsURL: String,
val systemID: String?
)
/** Response type of "GET /prepared-payments/{uuid}" */
data class PaymentStatus(
val paymentInitiationId: String,
val submitted: Boolean,
val creditorIban: String,
val creditorBic: String?,
val creditorName: String,
val amount: String,
val subject: String,
val submissionDate: String?,
val preparationDate: String,
// null when the payment was never acknowledged by
// the bank. For example, it was submitted but never
// seen in any report; or only created and not even
// submitted.
val status: EntryStatus?
)
data class Transactions(
val transactions: MutableList = mutableListOf()
)
data class BankProtocolsResponse(
val protocols: List
)
/** Request type of "POST /prepared-payments" */
data class CreatePaymentInitiationRequest(
val iban: String,
val bic: String,
val name: String,
val amount: String,
val subject: String
)
/** Response type of "POST /prepared-payments" */
data class PaymentInitiationResponse(
val uuid: String
)
/** Response type of "GET /user" */
data class UserResponse(
val username: String,
val superuser: Boolean,
)
/** Request type of "POST /users" */
data class CreateUserRequest(
val username: String,
val password: String
)
data class ChangeUserPassword(
val newPassword: String
)
data class UserInfo(
val username: String,
val superuser: Boolean
)
data class UsersResponse(
val users: List
)
/** Response (list's element) type of "GET /bank-accounts" */
data class BankAccount(
var ownerName: String,
var iban: String,
var bic: String,
var nexusBankAccountId: String
)
data class OfferedBankAccount(
var ownerName: String,
var iban: String,
var bic: String,
var offeredAccountId: String,
var nexusBankAccountId: String?
)
data class OfferedBankAccounts(
val accounts: MutableList = mutableListOf()
)
/** Response type of "GET /bank-accounts" */
data class BankAccounts(
var accounts: MutableList = mutableListOf()
)
data class BankMessageList(
val bankMessages: MutableList = mutableListOf()
)
data class BankMessageInfo(
val messageId: String,
val code: String,
val length: Long
)
data class FacadeShowInfo(
val name: String,
val type: String,
// Taler wire gateway API base URL.
// Different from the base URL of the facade.
val baseUrl: String,
val config: JsonNode
)
data class FacadeInfo(
val name: String,
val type: String,
val bankAccountsRead: MutableList? = mutableListOf(),
val bankAccountsWrite: MutableList? = mutableListOf(),
val bankConnectionsRead: MutableList? = mutableListOf(),
val bankConnectionsWrite: MutableList? = mutableListOf(),
val config: TalerWireGatewayFacadeConfig /* To be abstracted to Any! */
)
data class TalerWireGatewayFacadeConfig(
val bankAccount: String,
val bankConnection: String,
val reserveTransferLevel: String,
val currency: String
)
data class Pain001Data(
val creditorIban: String,
val creditorBic: String?,
val creditorName: String,
val sum: String,
val currency: String,
val subject: String
)
data class AccountTask(
val resourceType: String,
val resourceId: String,
val taskName: String,
val taskType: String,
val taskCronspec: String,
val taskParams: String,
val nextScheduledExecutionSec: Long?, // human-readable time (= Epoch when this value doesn't exist in DB)
val prevScheduledExecutionSec: Long? // human-readable time (= Epoch when this value doesn't exist in DB)
)
data class CreateAccountTaskRequest(
val name: String,
val cronspec: String,
val type: String,
val params: JsonNode
)
data class ImportBankAccount(
val offeredAccountId: String,
val nexusBankAccountId: String
)
class CurrencyAmountDeserializer(jc: Class<*> = CurrencyAmount::class.java) : StdDeserializer(jc) {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): CurrencyAmount {
if (p == null) {
throw UnsupportedOperationException();
}
val s = p.valueAsString
val components = s.split(":")
// FIXME: error handling!
return CurrencyAmount(components[0], components[1])
}
}
class CurrencyAmountSerializer(jc: Class = CurrencyAmount::class.java) : StdSerializer(jc) {
override fun serialize(value: CurrencyAmount?, gen: JsonGenerator?, provider: SerializerProvider?) {
if (gen == null) {
throw UnsupportedOperationException()
}
if (value == null) {
gen.writeNull()
} else {
gen.writeString("${value.currency}:${value.value}")
}
}
}
// FIXME: this type duplicates AmountWithCurrency.
@JsonDeserialize(using = CurrencyAmountDeserializer::class)
@JsonSerialize(using = CurrencyAmountSerializer::class)
data class CurrencyAmount(
val currency: String,
val value: String
)
fun CurrencyAmount.toPlainString(): String {
return "${this.currency}:${this.value}"
}
data class InitiatedPayments(
val initiatedPayments: MutableList = mutableListOf()
)