WireTransferApi.kt (4322B)
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.bank.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.bank.* 29 import tech.libeufin.bank.auth.pathUsername 30 import tech.libeufin.bank.db.Database 31 import tech.libeufin.bank.db.TransferDAO 32 import tech.libeufin.bank.db.TransferDAO.RegistrationResult 33 import tech.libeufin.common.* 34 import tech.libeufin.common.crypto.CryptoUtil 35 import java.time.Instant 36 import java.time.Duration 37 38 fun Routing.wireTransferApi(db: Database, cfg: BankConfig) { 39 get("/accounts/{USERNAME}/taler-wire-transfer-gateway/config") { 40 call.respond( 41 WireTransferConfig( 42 currency = cfg.regionalCurrency, 43 supported_formats = listOf(SubjectFormat.SIMPLE) 44 ) 45 ) 46 } 47 post("/accounts/{USERNAME}/taler-wire-transfer-gateway/registration") { 48 val username = call.pathUsername 49 val req = call.receive<SubjectRequest>(); 50 cfg.checkRegionalCurrency(req.credit_amount) 51 52 if (!CryptoUtil.checkEdssaSignature(req.account_pub.raw, req.authorization_sig, req.authorization_pub)) 53 throw conflict( 54 "invalid signature", 55 TalerErrorCode.BANK_BAD_SIGNATURE 56 ) 57 58 when (val result = db.transfer.register( 59 username, 60 req.type, 61 req.account_pub, 62 req.authorization_pub, 63 req.authorization_sig, 64 req.recurrent, 65 req.credit_amount, 66 Instant.now() 67 )) { 68 RegistrationResult.UnknownAccount -> throw unknownAccount(username) 69 RegistrationResult.NotExchange -> throw notExchange(username) 70 RegistrationResult.ReservePubReuse -> throw conflict( 71 "reserve_pub used already", 72 TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT 73 ) 74 is RegistrationResult.Success -> { 75 val subjects = mutableListOf<TransferSubject>() 76 if (result.uuid != null) 77 subjects.add(TransferSubject.Uri(cfg.talerWithdrawUri(result.uuid), req.credit_amount)) 78 subjects.add(TransferSubject.Simple(fmtIncomingSubject(IncomingType.map, req.authorization_pub), req.credit_amount)) 79 call.respond( 80 SubjectResult( 81 subjects, 82 TalerTimestamp.never() 83 ) 84 ) 85 } 86 } 87 } 88 delete("/accounts/{USERNAME}/taler-wire-transfer-gateway/registration") { 89 val req = call.receive<Unregistration>(); 90 91 val timestamp = Instant.parse(req.timestamp) 92 93 if (timestamp.isBefore(Instant.now().minus(Duration.ofMinutes(15)))) 94 throw conflict( 95 "timestamp too old", 96 TalerErrorCode.BANK_OLD_TIMESTAMP 97 ) 98 99 if (!CryptoUtil.checkEdssaSignature(req.timestamp.toByteArray(), req.authorization_sig, req.authorization_pub)) 100 throw conflict( 101 "invalid signature", 102 TalerErrorCode.BANK_BAD_SIGNATURE 103 ) 104 105 if (db.transfer.unregister(req.authorization_pub, Instant.now())) { 106 call.respond(HttpStatusCode.NoContent) 107 } else { 108 throw notFound( 109 "Prepared transfer '${req.authorization_pub}' not found", 110 TalerErrorCode.BANK_TRANSACTION_NOT_FOUND 111 ) 112 } 113 } 114 }