libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

TalerMessage.kt (16513B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2024, 2025, 2026 Taler Systems S.A.
      4  *
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9  *
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14  *
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 package tech.libeufin.common
     21 
     22 import java.nio.ByteBuffer
     23 import java.nio.ByteOrder
     24 import java.security.MessageDigest
     25 import io.github.smiley4.schemakenerator.core.annotations.Description
     26 import kotlinx.serialization.SerialName
     27 import kotlinx.serialization.Serializable
     28 import tech.libeufin.common.crypto.CryptoUtil.hashStringNbo
     29 import tech.libeufin.common.crypto.NBO
     30 
     31 enum class IncomingType {
     32     reserve,
     33     kyc,
     34     map
     35 }
     36 
     37 @Description("State of a wire transfer")
     38 enum class TransferStatusState {
     39     pending,
     40     transient_failure,
     41     permanent_failure,
     42     success
     43 }
     44 
     45 /** Response GET /taler-wire-gateway/config */
     46 @Serializable
     47 @Description("Wire gateway configuration response")
     48 data class WireGatewayConfig(
     49     @Description("Currency supported by the gateway")
     50     val currency: String,
     51     @Description("Whether account check is supported")
     52     val support_account_check: Boolean
     53 ) {
     54     @Description("API name identifier")
     55     val name: String = "taler-wire-gateway"
     56     @Description("API version string")
     57     val version: String = WIRE_GATEWAY_API_VERSION
     58 }
     59 
     60 /** Request POST /taler-wire-gateway/transfer */
     61 @Serializable
     62 @Description("Wire transfer request")
     63 data class TransferRequest(
     64     @Description("Unique identifier for this request")
     65     val request_uid: HashCode,
     66     @Description("Amount to transfer")
     67     val amount: TalerAmount,
     68     @Description("Base URL of the exchange")
     69     val exchange_base_url: BaseURL,
     70     @Description("Wire transfer identifier")
     71     val wtid: ShortHashCode,
     72     @Description("Payto URI of the credit account")
     73     val credit_account: Payto,
     74     @Description("Optional transfer metadata")
     75     val metadata: String? = null,
     76 ) {
     77     init {
     78         if (metadata != null && !METADATA_REGEX.matches(metadata))
     79             throw badRequest("metadata '$metadata' is malformed, must match [a-zA-Z0-9-.+:]{1,40}")
     80     }
     81 
     82     companion object {
     83         private val METADATA_REGEX = Regex("^[a-zA-Z0-9-.:]{1,40}$")
     84     }
     85 }
     86 
     87 /** Response POST /taler-wire-gateway/transfer */
     88 @Serializable
     89 @Description("Wire transfer response")
     90 data class TransferResponse(
     91     @Description("Timestamp of the transfer")
     92     val timestamp: TalerTimestamp,
     93     @Description("Database row identifier")
     94     val row_id: Long
     95 )
     96 
     97 /** Request GET /taler-wire-gateway/transfers */
     98 @Serializable
     99 @Description("List of wire transfers")
    100 data class TransferList(
    101     @Description("List of transfer statuses")
    102     val transfers: List<TransferListStatus>,
    103     @Description("Payto URI of the debit account")
    104     val debit_account: String
    105 )
    106 
    107 @Serializable
    108 @Description("Transfer status in a list response")
    109 data class TransferListStatus(
    110     @Description("Database row identifier")
    111     val row_id: Long,
    112     @Description("Current transfer status")
    113     val status: TransferStatusState,
    114     @Description("Transfer amount")
    115     val amount: TalerAmount,
    116     @Description("Payto URI of the credit account")
    117     val credit_account: String,
    118     @Description("Timestamp of the transfer")
    119     val timestamp: TalerTimestamp
    120 )
    121 
    122 /** Request GET /taler-wire-gateway/transfers/{ROW_iD} */
    123 @Serializable
    124 @Description("Detailed status of a single transfer")
    125 data class TransferStatus(
    126     @Description("Current transfer status")
    127     val status: TransferStatusState,
    128     @Description("Optional status message")
    129     val status_msg: String? = null,
    130     @Description("Transfer amount")
    131     val amount: TalerAmount,
    132     @Description("URL of the originating exchange")
    133     val origin_exchange_url: String,
    134     @Description("Optional transfer metadata")
    135     val metadata: String? = null,
    136     @Description("Wire transfer identifier")
    137     val wtid: ShortHashCode,
    138     @Description("Payto URI of the credit account")
    139     val credit_account: String,
    140     @Description("Timestamp of the transfer")
    141     val timestamp: TalerTimestamp
    142 )
    143 
    144 /** Request POST /taler-wire-gateway/admin/add-incoming */
    145 @Serializable
    146 @Description("Request to add an incoming transaction")
    147 data class AddIncomingRequest(
    148     @Description("Amount of the incoming transaction")
    149     val amount: TalerAmount,
    150     @Description("Reserve public key")
    151     val reserve_pub: EddsaPublicKey,
    152     @Description("Payto URI of the debit account")
    153     val debit_account: Payto
    154 )
    155 
    156 /** Response POST /taler-wire-gateway/admin/add-incoming */
    157 @Serializable
    158 @Description("Response to adding an incoming transaction")
    159 data class AddIncomingResponse(
    160     @Description("Timestamp of the transaction")
    161     val timestamp: TalerTimestamp,
    162     @Description("Database row identifier")
    163     val row_id: Long
    164 )
    165 
    166 /** Request POST /taler-wire-gateway/admin/add-kycauth */
    167 @Serializable
    168 @Description("Request to add a KYC auth transaction")
    169 data class AddKycauthRequest(
    170     @Description("Amount of the KYC auth transaction")
    171     val amount: TalerAmount,
    172     @Description("Account public key for KYC")
    173     val account_pub: EddsaPublicKey,
    174     @Description("Payto URI of the debit account")
    175     val debit_account: Payto
    176 )
    177 
    178 /** Request POST /taler-wire-gateway/admin/add-mapped */
    179 @Serializable
    180 data class AddMappedRequest(
    181     val amount: TalerAmount,
    182     val authorization_pub: EddsaPublicKey,
    183     val debit_account: Payto
    184 )
    185 
    186 /** Response GET /taler-wire-gateway/history/incoming */
    187 @Serializable
    188 @Description("History of incoming transactions")
    189 data class IncomingHistory(
    190     @Description("List of incoming transactions")
    191     val incoming_transactions: List<IncomingBankTransaction>,
    192     @Description("Payto URI of the credit account")
    193     val credit_account: String
    194 )
    195 
    196 /** Inner response GET /taler-wire-gateway/history/incoming */
    197 @Serializable
    198 @Description("Incoming bank transaction details")
    199 sealed interface IncomingBankTransaction {
    200     val row_id: Long
    201     val date: TalerTimestamp
    202     val amount: TalerAmount
    203     val debit_account: String
    204     val credit_fee: TalerAmount?
    205 }
    206 
    207 @Serializable
    208 @SerialName("KYCAUTH")
    209 @Description("Incoming KYC authentication transaction")
    210 data class IncomingKycAuthTransaction(
    211     @Description("Database row identifier")
    212     override val row_id: Long,
    213     @Description("Timestamp of the transaction")
    214     override val date: TalerTimestamp,
    215     @Description("Transaction amount")
    216     override val amount: TalerAmount,
    217     @Description("Optional credit fee")
    218     override val credit_fee: TalerAmount? = null,
    219     @Description("Payto URI of the debit account")
    220     override val debit_account: String,
    221     @Description("Account public key for KYC")
    222     val account_pub: EddsaPublicKey,
    223     @Description("Optional authorization public key")
    224     val authorization_pub: EddsaPublicKey? = null,
    225     @Description("Optional authorization signature")
    226     val authorization_sig: EddsaSignature? = null,
    227 ) : IncomingBankTransaction
    228 
    229 @Serializable
    230 @SerialName("RESERVE")
    231 @Description("Incoming reserve transaction")
    232 data class IncomingReserveTransaction(
    233     @Description("Database row identifier")
    234     override val row_id: Long,
    235     @Description("Timestamp of the transaction")
    236     override val date: TalerTimestamp,
    237     @Description("Transaction amount")
    238     override val amount: TalerAmount,
    239     @Description("Optional credit fee")
    240     override val credit_fee: TalerAmount? = null,
    241     @Description("Payto URI of the debit account")
    242     override val debit_account: String,
    243     @Description("Reserve public key")
    244     val reserve_pub: EddsaPublicKey,
    245     @Description("Optional authorization public key")
    246     val authorization_pub: EddsaPublicKey? = null,
    247     @Description("Optional authorization signature")
    248     val authorization_sig: EddsaSignature? = null,
    249 ) : IncomingBankTransaction
    250 
    251 @Serializable
    252 @SerialName("WAD")
    253 @Description("Incoming WAD transaction")
    254 data class IncomingWadTransaction(
    255     @Description("Database row identifier")
    256     override val row_id: Long,
    257     @Description("Timestamp of the transaction")
    258     override val date: TalerTimestamp,
    259     @Description("Transaction amount")
    260     override val amount: TalerAmount,
    261     @Description("Optional credit fee")
    262     override val credit_fee: TalerAmount? = null,
    263     @Description("Payto URI of the debit account")
    264     override val debit_account: String,
    265     @Description("URL of the originating exchange")
    266     val origin_exchange_url: String,
    267     @Description("WAD identifier")
    268     val wad_id: String // TODO 24 bytes Base32
    269 ) : IncomingBankTransaction
    270 
    271 /** Response GET /taler-wire-gateway/history/outgoing */
    272 @Serializable
    273 @Description("History of outgoing transactions")
    274 data class OutgoingHistory(
    275     @Description("List of outgoing transactions")
    276     val outgoing_transactions: List<OutgoingTransaction>,
    277     @Description("Payto URI of the debit account")
    278     val debit_account: String
    279 )
    280 
    281 /** Inner response GET /taler-wire-gateway/history/outgoing */
    282 @Serializable
    283 @Description("Single outgoing transaction details")
    284 data class OutgoingTransaction(
    285     @Description("Database row identifier")
    286     val row_id: Long, // DB row ID of the payment.
    287     @Description("Timestamp of the transaction")
    288     val date: TalerTimestamp,
    289     @Description("Transaction amount")
    290     val amount: TalerAmount,
    291     @Description("Payto URI of the credit account")
    292     val credit_account: String,
    293     @Description("Wire transfer identifier")
    294     val wtid: ShortHashCode,
    295     @Description("Base URL of the exchange")
    296     val exchange_base_url: String,
    297     @Description("Optional transfer metadata")
    298     val metadata: String? = null,
    299     @Description("Optional debit fee")
    300     val debit_fee: TalerAmount? = null
    301 )
    302 
    303 /** Response GET /taler-wire-gateway/account/check */
    304 @Serializable
    305 @Description("Account information response")
    306 class AccountInfo()
    307 
    308 /** Response GET /taler-prepared-transfer/config */
    309 @Serializable
    310 @Description("Prepared transfer configuration")
    311 data class PreparedTransferConfig(
    312     @Description("Currency supported")
    313     val currency: String,
    314     @Description("List of supported subject formats")
    315     val supported_formats: List<SubjectFormat>
    316 ) {
    317     @Description("API name identifier")
    318     val name: String = "taler-prepared-transfer"
    319     @Description("API version string")
    320     val version: String = WIRE_TRANSFER_API_VERSION
    321 }
    322 
    323 
    324 /** Inner response GET /taler-prepared-transfer/registration */
    325 @Serializable
    326 @Description("Transfer subject information")
    327 sealed interface TransferSubject {
    328     @Serializable
    329     @SerialName("SIMPLE")
    330     @Description("Simple text transfer subject")
    331     data class Simple(
    332         @Description("Plain text transfer subject")
    333         val subject: String,
    334         @Description("Credit amount for the transfer")
    335         val credit_amount: TalerAmount
    336     ) : TransferSubject
    337 
    338     @Serializable
    339     @SerialName("URI")
    340     @Description("URI-based transfer subject")
    341     data class Uri(
    342         @Description("Taler URI for the transfer")
    343         val uri: String,
    344         @Description("Credit amount for the transfer")
    345         val credit_amount: TalerAmount
    346     ) : TransferSubject
    347 
    348     @Serializable
    349     @SerialName("CH_QR_BILL")
    350     @Description("Swiss QR bill transfer subject")
    351     data class QrBill(
    352         @Description("QR reference number for the bill")
    353         val qr_reference_number: String,
    354         @Description("Credit amount for the transfer")
    355         val credit_amount: TalerAmount,
    356     ) : TransferSubject
    357 }
    358 
    359 @Serializable
    360 @Description("Supported transfer subject format")
    361 enum class SubjectFormat {
    362     SIMPLE,
    363     URI,
    364     CH_QR_BILL
    365 }
    366 
    367 @Serializable
    368 @Description("Public key algorithm")
    369 enum class PublicKeyAlg {
    370     EdDSA
    371 }
    372 
    373 @Serializable
    374 @Description("Type of wire transfer")
    375 enum class TransferType {
    376     reserve,
    377     kyc
    378 }
    379 
    380 @Serializable
    381 @Description("Request to generate a transfer subject")
    382 data class SubjectRequest(
    383     @Description("Payto URI of the credit account")
    384     val credit_account: Payto,
    385     @Description("Type of transfer")
    386     val type: TransferType,
    387     @Description("Whether subject is recurrent")
    388     val recurrent: Boolean,
    389     @Description("Credit amount for the transfer")
    390     val credit_amount: TalerAmount,
    391     @Description("Public key algorithm")
    392     val alg: PublicKeyAlg,
    393     @Description("Account public key")
    394     val account_pub: EddsaPublicKey,
    395     @Description("Authorization public key")
    396     val authorization_pub: EddsaPublicKey,
    397     @Description("Authorization signature")
    398     val authorization_sig: EddsaSignature,
    399 ) : NBO {
    400     /* Network bytes */
    401     override fun nbo(): ByteArray = ByteBuffer.allocate(104).apply {
    402         order(ByteOrder.BIG_ENDIAN)
    403         putInt(capacity())
    404         putInt(1224)
    405         put(hashStringNbo(credit_account.toString()))
    406         put(credit_amount.nbo())
    407         putInt(when (type) {
    408             TransferType.reserve -> 1
    409             TransferType.kyc -> 2
    410         })
    411         putShort(if (recurrent) 2 else 1)
    412         putShort(when (alg) {
    413             PublicKeyAlg.EdDSA -> 1
    414         })
    415         put(account_pub.raw)
    416     }.array()
    417 
    418     fun sign(priv: ByteArray): SubjectRequest =
    419         this.copy(authorization_sig = this.signNbo(priv))
    420 
    421     fun verify(): Boolean =
    422         this.verifyNbo(this.authorization_sig, this.authorization_pub)
    423 }
    424 
    425 @Serializable
    426 @Description("Result of subject generation")
    427 data class SubjectResult(
    428     @Description("List of generated transfer subjects")
    429     val subjects: List<TransferSubject>,
    430     @Description("Expiration timestamp of the subjects")
    431     val expiration: TalerTimestamp
    432 )
    433 
    434 @Serializable
    435 @Description("Request to unregister a subject")
    436 data class Unregistration(
    437     @Description("Timestamp of the unregistration")
    438     val timestamp: TalerTimestamp,
    439     @Description("Authorization public key")
    440     val authorization_pub: EddsaPublicKey,
    441     @Description("Authorization signature")
    442     val authorization_sig: EddsaSignature
    443 ) : NBO {
    444     /* Network bytes */
    445     override fun nbo(): ByteArray = ByteBuffer.allocate(16).apply {
    446         order(ByteOrder.BIG_ENDIAN)
    447         putInt(capacity())
    448         putInt(1225)
    449         putLong(timestamp.instant.epochSecond * 1000 * 1000)
    450     }.array()
    451 
    452     fun sign(priv: ByteArray): Unregistration =
    453         this.copy(authorization_sig = this.signNbo(priv))
    454 
    455     fun verify(): Boolean =
    456         this.verifyNbo(this.authorization_sig, this.authorization_pub)
    457 }
    458 
    459 /** Response GET /taler-revenue/config */
    460 @Serializable
    461 @Description("Revenue API configuration response")
    462 data class RevenueConfig(
    463     @Description("Currency supported by the API")
    464     val currency: String
    465 ) {
    466     @Description("API name identifier")
    467     val name: String = "taler-revenue"
    468     @Description("API version string")
    469     val version: String = REVENUE_API_VERSION
    470 }
    471 
    472 /** Request GET /taler-revenue/history */
    473 @Serializable
    474 @Description("History of revenue incoming transactions")
    475 data class RevenueIncomingHistory(
    476     @Description("List of incoming revenue transactions")
    477     val incoming_transactions: List<RevenueIncomingBankTransaction>,
    478     @Description("Payto URI of the credit account")
    479     val credit_account: String
    480 )
    481 
    482 /** Inner request GET /taler-revenue/history */
    483 @Serializable
    484 @Description("Single revenue incoming bank transaction")
    485 data class RevenueIncomingBankTransaction(
    486     @Description("Database row identifier")
    487     val row_id: Long,
    488     @Description("Timestamp of the transaction")
    489     val date: TalerTimestamp,
    490     @Description("Transaction amount")
    491     val amount: TalerAmount,
    492     @Description("Optional credit fee")
    493     val credit_fee: TalerAmount? = null,
    494     @Description("Payto URI of the debit account")
    495     val debit_account: String,
    496     @Description("Transaction subject line")
    497     val subject: String
    498 )
    499 
    500 /** Response GET /taler-observability/config */
    501 @Serializable
    502 @Description("Observability API configuration response")
    503 class TalerObservabilityConfig() {
    504     @Description("API name identifier")
    505     val name: String = "taler-observability"
    506     @Description("API version string")
    507     val version: String = OBSERVABILITY_API_VERSION
    508 }