libeufin

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

WireTransferApi.kt (4378B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 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.nexus.api
     21 
     22 import io.ktor.http.*
     23 import io.ktor.server.application.*
     24 import io.ktor.server.request.*
     25 import io.ktor.server.response.*
     26 import io.ktor.server.routing.*
     27 import io.ktor.util.pipeline.*
     28 import tech.libeufin.common.*
     29 import tech.libeufin.common.crypto.CryptoUtil
     30 import tech.libeufin.nexus.NexusConfig
     31 import tech.libeufin.nexus.checkCurrency
     32 import tech.libeufin.nexus.db.Database
     33 import tech.libeufin.nexus.db.ExchangeDAO
     34 import tech.libeufin.nexus.db.ExchangeDAO.TransferResult
     35 import tech.libeufin.nexus.db.TransferDAO.RegistrationResult
     36 import tech.libeufin.nexus.db.PaymentDAO.IncomingRegistrationResult
     37 import tech.libeufin.nexus.iso20022.*
     38 import tech.libeufin.ebics.randEbicsId
     39 import java.time.Instant
     40 import java.time.Duration
     41 
     42 fun Routing.wireTransferApi(db: Database, cfg: NexusConfig) = conditional(cfg.wireTransferApiCfg) {
     43     get("/taler-wire-transfer-gateway/config") {
     44         call.respond(
     45             WireTransferConfig(
     46                 currency = cfg.currency,
     47                 supported_formats = listOf(SubjectFormat.SIMPLE, SubjectFormat.CH_QR_BILL)
     48             )
     49         )
     50     }
     51     post("/taler-wire-transfer-gateway/registration") {
     52         val req = call.receive<SubjectRequest>();
     53 
     54         if (!CryptoUtil.checkEdssaSignature(req.account_pub.raw, req.authorization_sig, req.authorization_pub))
     55             throw conflict(
     56                 "invalid signature",
     57                 TalerErrorCode.BANK_BAD_SIGNATURE
     58             )
     59 
     60         val reference = subjectFmtQrBill(req.authorization_pub)
     61 
     62         when (val result = db.transfer.register(
     63             req.type,
     64             req.account_pub,
     65             req.authorization_pub,
     66             req.authorization_sig,
     67             req.recurrent,
     68             reference,
     69             Instant.now()
     70         )) {
     71             RegistrationResult.ReservePubReuse -> throw conflict(
     72                 "reserve_pub used already",
     73                 TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT
     74             )
     75             RegistrationResult.SubjectReuse ->  throw conflict(
     76                 "subject derivation used already",
     77                 TalerErrorCode.BANK_DERIVATION_REUSE
     78             )
     79             RegistrationResult.Success -> {
     80                 call.respond(
     81                     SubjectResult(
     82                         listOf(
     83                             TransferSubject.QrBill(reference, req.credit_amount),
     84                             TransferSubject.Simple(fmtIncomingSubject(IncomingType.map, req.authorization_pub), req.credit_amount)
     85                         ),
     86                         TalerTimestamp.never()
     87                     )
     88                 )
     89             }
     90         }
     91     }
     92     delete("/taler-wire-transfer-gateway/registration") {
     93         val req = call.receive<Unregistration>();
     94 
     95         val timestamp = Instant.parse(req.timestamp)
     96 
     97         if (timestamp.isBefore(Instant.now().minus(Duration.ofMinutes(15))))
     98             throw conflict(
     99                 "timestamp too old",
    100                 TalerErrorCode.BANK_OLD_TIMESTAMP
    101             )
    102 
    103         if (!CryptoUtil.checkEdssaSignature(req.timestamp.toByteArray(), req.authorization_sig, req.authorization_pub))
    104             throw conflict(
    105                 "invalid signature",
    106                 TalerErrorCode.BANK_BAD_SIGNATURE
    107             )
    108         if (db.transfer.unregister(req.authorization_pub, Instant.now())) {
    109             call.respond(HttpStatusCode.NoContent)
    110         } else {
    111             throw notFound(
    112                 "Prepared transfer '${req.authorization_pub}' not found",
    113                 TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
    114             )
    115         }
    116     }
    117 }