/* * 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() )