diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 55e25fc50c..2190e6c321 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -166,7 +166,11 @@ jwt.use.ssl=false # Bypass TPP signature validation # bypass_tpp_signature_validation = false -## Reject Berlin Group consents with status "received" after a defined time (in seconds) +## Reject Berlin Group TRANSACTIONS with status "received" after a defined time (in seconds) +# berlin_group_outdated_transactions_time_in_seconds = 300 +# berlin_group_outdated_transactions_interval_in_seconds = + +## Reject Berlin Group CONSENTS with status "received" after a defined time (in seconds) # berlin_group_outdated_consents_time_in_seconds = 300 # berlin_group_outdated_consents_interval_in_seconds = @@ -795,8 +799,14 @@ display_internal_errors=false # - redirection_with_dedicated_start_of_authorization # - embedded # - decoupled +# # In case that "psu_authentication_method = redirection" you must define +# Please note that in case that redirect_url_value contains special word PLACEHOLDER it will be replaced with actual ID +# http://127.0.0.1:8080/confirm-bg-consent-request?CONSENT_ID=PLACEHOLDER # psu_authentication_method_sca_redirect_url = redirect_url_value +# +# Please note that in case that redirect_url_value contains special word PLACEHOLDER it will be replaced with actual ID +# http://127.0.0.1:8080/confirm-bg-consent-request?PAYMENT_ID=PLACEHOLDER # psu_make_payment_sca_redirect_url = redirect_url_value # -------------------------------------------------------------- Authentication methods -- diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 7ecef624f1..3f96a4e4f3 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -107,7 +107,7 @@ import code.productfee.ProductFee import code.products.MappedProduct import code.ratelimiting.RateLimiting import code.regulatedentities.MappedRegulatedEntity -import code.scheduler.{ConsentScheduler, DataBaseCleanerScheduler, DatabaseDriverScheduler, JobScheduler, MetricsArchiveScheduler} +import code.scheduler.{ConsentScheduler, DataBaseCleanerScheduler, DatabaseDriverScheduler, JobScheduler, MetricsArchiveScheduler, TransactionScheduler} import code.scope.{MappedScope, MappedUserScope} import code.signingbaskets.{MappedSigningBasket, MappedSigningBasketConsent, MappedSigningBasketPayment} import code.snippet.{OAuthAuthorisation, OAuthWorkedThanks} @@ -137,6 +137,7 @@ import code.regulatedentities.attribute.RegulatedEntityAttribute import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.Functions.Implicits._ import com.openbankproject.commons.util.{ApiVersion, Functions} + import javax.mail.internet.MimeMessage import net.liftweb.common._ import net.liftweb.db.{DB, DBLogEntry} @@ -730,6 +731,7 @@ class Boot extends MdcLoggable { case _ => // Do not start it } ConsentScheduler.startAll() + TransactionScheduler.startAll() APIUtil.getPropsAsBoolValue("enable_metrics_scheduler", true) match { diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index a3f51da3f8..7675b9fc0d 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -448,6 +448,8 @@ respectively the OAuth2 access token. //The card contains the account object, it mean the card account. (_, callContext) <- NewStyle.function.getPhysicalCardsForUser(u, callContext) (accounts, callContext) <- NewStyle.function.getBankAccounts(availablePrivateAccounts, callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) //also see `getAccountList` endpoint bankAccountsFiltered = accounts.filter(bankAccount => bankAccount.attributes.toList.flatten.find(attribute=> @@ -456,7 +458,14 @@ respectively the OAuth2 access token. attribute.value.equalsIgnoreCase("card") ).isDefined) } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson(bankAccountsFiltered, u), callContext) + (JSONFactory_BERLIN_GROUP_1_3.createCardAccountListJson( + bankAccountsFiltered, + canReadBalancesAccounts, + canReadTransactionsAccounts, + u + ), + callContext + ) } } } @@ -986,9 +995,19 @@ Give detailed information about the addressed account together with balance info (Full(u), callContext) <- authenticatedAccess(cc) _ <- passesPsd2Aisp(callContext) (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) } yield { - (JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson(account, u), callContext) + ( + JSONFactory_BERLIN_GROUP_1_3.createAccountDetailsJson( + account, + canReadBalancesAccounts, + canReadTransactionsAccounts, + u + ), + callContext + ) } } } @@ -1040,9 +1059,11 @@ respectively the OAuth2 access token. (Full(u), callContext) <- authenticatedAccess(cc) _ <- passesPsd2Aisp(callContext) (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) + (canReadBalancesAccounts, callContext) <- NewStyle.function.getAccountCanReadBalancesOfBerlinGroup(u, callContext) + (canReadTransactionsAccounts, callContext) <- NewStyle.function.getAccountCanReadTransactionsOfBerlinGroup(u, callContext) _ <- checkAccountAccess(ViewId(SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID), u, account, callContext) } yield { - (JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(account, u), callContext) + (JSONFactory_BERLIN_GROUP_1_3.createCardAccountDetailsJson(account, canReadBalancesAccounts, canReadTransactionsAccounts, u), callContext) } } } diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala index 44315adbb6..8f6b83a18f 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/JSONFactory_BERLIN_GROUP_1_3.scala @@ -47,16 +47,16 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { ) case class CoreAccountLinksJsonV13( - balances: LinkHrefJson, + balances: Option[LinkHrefJson] = None, transactions: Option[LinkHrefJson] = None // These links are only supported, when the corresponding consent has been already granted. ) - case class CoreAccountBalancesJson( - balanceAmount:AmountOfMoneyV13 = AmountOfMoneyV13("EUR","123"), - balanceType: String = "closingBooked", - lastChangeDateTime: String = "2019-01-28T06:26:52.185Z", - referenceDate: String = "2020-07-02", - lastCommittedTransaction: String = "string", + case class CoreAccountBalanceJson( + balanceAmount:AmountOfMoneyV13,// = AmountOfMoneyV13("EUR","123"), + balanceType: String //= "closingBooked", +// lastChangeDateTime: String = "2019-01-28T06:26:52.185Z", +// referenceDate: String = "2020-07-02", +// lastCommittedTransaction: String = "string", ) case class CoreAccountJsonV13( resourceId: String, @@ -70,7 +70,7 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { // linkedAccounts: String ="string", // usage: String ="PRIV", // details: String ="", -// balances: CoreAccountBalancesJson,// We put this under the _links, not need to show it here. + balances: Option[List[CoreAccountBalanceJson]] = None, _links: CoreAccountLinksJsonV13, ) @@ -78,8 +78,8 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { case class CoreCardAccountsJsonV13(cardAccounts: List[CoreAccountJsonV13]) case class AccountDetailsLinksJsonV13( - balances: LinkHrefJson, - transactions: LinkHrefJson + balances: Option[LinkHrefJson], + transactions: Option[LinkHrefJson] ) case class AccountJsonV13( @@ -315,9 +315,13 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { val (iBan: String, bBan: String) = getIbanAndBban(x) val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}" val balanceRef = LinkHrefJson(s"/$commonPath/balances") + val canReadBalances = canReadBalancesAccounts.map(_.accountId.value).contains(x.accountId.value) val transactionRef = LinkHrefJson(s"/$commonPath/transactions") val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(x.accountId.value) - + val accountBalances = Some(List(CoreAccountBalanceJson( + balanceAmount = AmountOfMoneyV13(x.currency, x.balance.toString), + balanceType = "closingBooked" + ))) CoreAccountJsonV13( resourceId = x.accountId.value, @@ -327,8 +331,9 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { name = x.name, cashAccountType = x.accountType, product = x.accountType, + balances = accountBalances, _links = CoreAccountLinksJsonV13( - balances = balanceRef, + balances = if(canReadBalances) Some(balanceRef) else None, transactions = if(canReadTransactions) Some(transactionRef) else None, ) ) @@ -336,10 +341,23 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { ) } - def createCardAccountListJson(bankAccounts: List[BankAccount], user: User): CoreCardAccountsJsonV13 = { + def createCardAccountListJson(bankAccounts: List[BankAccount], + canReadBalancesAccounts: List[BankIdAccountId], + canReadTransactionsAccounts: List[BankIdAccountId], + user: User): CoreCardAccountsJsonV13 = { CoreCardAccountsJsonV13(bankAccounts.map { x => val (iBan: String, bBan: String) = getIbanAndBban(x) + val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}" + val balanceRef = LinkHrefJson(s"/$commonPath/balances") + val canReadBalances = canReadBalancesAccounts.map(_.accountId.value).contains(x.accountId.value) + val transactionRef = LinkHrefJson(s"/$commonPath/transactions") + val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(x.accountId.value) + + val accountBalances = Some(List(CoreAccountBalanceJson( + balanceAmount = AmountOfMoneyV13(x.currency, x.balance.toString), + balanceType = "closingBooked" + ))) CoreAccountJsonV13( resourceId = x.accountId.value, @@ -349,19 +367,34 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { name = x.name, cashAccountType = x.accountType, product = x.accountType, - _links = CoreAccountLinksJsonV13(LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${x.accountId.value}/balances")) + balances = accountBalances, + _links = CoreAccountLinksJsonV13( + balances = if (canReadBalances) Some(balanceRef) else None, + transactions = if (canReadTransactions) Some(transactionRef) else None, + ) ) } ) } - def createCardAccountDetailsJson(bankAccount: BankAccount, user: User): CardAccountDetailsJsonV13 = { - val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount: BankAccount, user: User) + def createCardAccountDetailsJson(bankAccount: BankAccount, + canReadBalancesAccounts: List[BankIdAccountId], + canReadTransactionsAccounts: List[BankIdAccountId], + user: User): CardAccountDetailsJsonV13 = { + val accountDetailsJsonV13 = createAccountDetailsJson(bankAccount, canReadBalancesAccounts, canReadTransactionsAccounts, user) CardAccountDetailsJsonV13(accountDetailsJsonV13.account) } - def createAccountDetailsJson(bankAccount: BankAccount, user: User): AccountDetailsJsonV13 = { + def createAccountDetailsJson(bankAccount: BankAccount, + canReadBalancesAccounts: List[BankIdAccountId], + canReadTransactionsAccounts: List[BankIdAccountId], + user: User): AccountDetailsJsonV13 = { val (iBan: String, bBan: String) = getIbanAndBban(bankAccount) + val commonPath = s"${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}" + val balanceRef = LinkHrefJson(s"/$commonPath/balances") + val canReadBalances = canReadBalancesAccounts.map(_.accountId.value).contains(bankAccount.accountId.value) + val transactionRef = LinkHrefJson(s"/$commonPath/transactions") + val canReadTransactions = canReadTransactionsAccounts.map(_.accountId.value).contains(bankAccount.accountId.value) val account = AccountJsonV13( resourceId = bankAccount.accountId.value, iban = iBan, @@ -370,8 +403,8 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { cashAccountType = bankAccount.accountType, product = bankAccount.accountType, _links = AccountDetailsLinksJsonV13( - LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}/balances"), - LinkHrefJson(s"/${OBP_BERLIN_GROUP_1_3.apiVersion.urlPrefix}/${OBP_BERLIN_GROUP_1_3.version}/accounts/${bankAccount.accountId.value}/transactions") + balances = if (canReadBalances) Some(balanceRef) else None, + transactions = if (canReadTransactions) Some(transactionRef) else None, ) ) AccountDetailsJsonV13(account) @@ -644,8 +677,13 @@ object JSONFactory_BERLIN_GROUP_1_3 extends CustomJsonFormats { // Remark: This code may be //map OBP transactionRequestId to BerlinGroup PaymentId val paymentId = transactionRequest.id.value - val scaRedirectUrl = getPropsValue("psu_make_payment_sca_redirect_url") + val scaRedirectUrlPattern = getPropsValue("psu_make_payment_sca_redirect_url") .openOr(MissingPropsValueAtThisInstance + "psu_make_payment_sca_redirect_url") + val scaRedirectUrl = + if (scaRedirectUrlPattern.contains("PLACEHOLDER")) + scaRedirectUrlPattern.replace("PLACEHOLDER", paymentId) + else + s"$scaRedirectUrlPattern/${paymentId}" InitiatePaymentResponseJson( transactionStatus = mapTransactionStatus(transactionRequest.status), paymentId = paymentId, diff --git a/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala b/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala index 466455a1da..ce97f3451a 100644 --- a/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala +++ b/obp-api/src/main/scala/code/api/util/BerlinGroupSigning.scala @@ -148,9 +148,9 @@ object BerlinGroupSigning extends MdcLoggable { */ def verifySignedRequest(body: Box[String], verb: String, url: String, reqHeaders: List[HTTPParam], forwardResult: (Box[User], Option[CallContext])): (Box[User], Option[CallContext]) = { def checkRequestIsSigned(requestHeaders: List[HTTPParam]): Boolean = { - requestHeaders.exists(_.name == RequestHeader.`TPP-Signature-Certificate`) && - requestHeaders.exists(_.name == RequestHeader.Signature) && - requestHeaders.exists(_.name == RequestHeader.Digest) + requestHeaders.exists(_.name.toLowerCase() == RequestHeader.`TPP-Signature-Certificate`.toLowerCase()) && + requestHeaders.exists(_.name.toLowerCase() == RequestHeader.Signature.toLowerCase()) && + requestHeaders.exists(_.name.toLowerCase() == RequestHeader.Digest.toLowerCase()) } checkRequestIsSigned(forwardResult._2.map(_.requestHeaders).getOrElse(Nil)) match { case false => @@ -161,26 +161,29 @@ object BerlinGroupSigning extends MdcLoggable { X509.validateCertificate(certificate) match { case Full(true) => // PEM certificate is ok val digest = generateDigest(body.getOrElse("")) - - val signatureHeaderValue = getHeaderValue(RequestHeader.Signature, requestHeaders) - val signature = parseSignatureHeader(signatureHeaderValue).getOrElse("signature", "NONE") - val headersToSign = parseSignatureHeader(signatureHeaderValue).getOrElse("headers", "").split(" ").toList - val headers = headersToSign.map(h => - if (h.toLowerCase() == RequestHeader.Digest.toLowerCase()) { - s"$h: $digest" - } else { - s"$h: ${getHeaderValue(h, requestHeaders)}" + if(digest == getHeaderValue(RequestHeader.Digest, requestHeaders)) { // Verifying the Hash in the Digest Field + val signatureHeaderValue = getHeaderValue(RequestHeader.Signature, requestHeaders) + val signature = parseSignatureHeader(signatureHeaderValue).getOrElse("signature", "NONE") + val headersToSign = parseSignatureHeader(signatureHeaderValue).getOrElse("headers", "").split(" ").toList + val headers = headersToSign.map(h => + if (h.toLowerCase() == RequestHeader.Digest.toLowerCase()) { + s"$h: $digest" + } else { + s"$h: ${getHeaderValue(h, requestHeaders)}" + } + ) + val signingString = headers.mkString("\n") + val isVerified = verifySignature(signingString, signature, certificate.getPublicKey) + val isValidated = CertificateVerifier.validateCertificate(certificate) + val bypassValidation = APIUtil.getPropsAsBoolValue("bypass_tpp_signature_validation", defaultValue = false) + (isVerified, isValidated) match { + case (true, true) => forwardResult + case (true, false) if bypassValidation => forwardResult + case (true, false) => (Failure(ErrorMessages.X509PublicKeyCannotBeValidated), forwardResult._2) + case (false, _) => (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2) } - ) - val signingString = headers.mkString("\n") - val isVerified = verifySignature(signingString, signature, certificate.getPublicKey) - val isValidated = CertificateVerifier.validateCertificate(certificate) - val bypassValidation = APIUtil.getPropsAsBoolValue("bypass_tpp_signature_validation", defaultValue = false) - (isVerified, isValidated) match { - case (true, true) => forwardResult - case (true, false) if bypassValidation => forwardResult - case (true, false) => (Failure(ErrorMessages.X509PublicKeyCannotBeValidated), forwardResult._2) - case (false, _) => (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2) + } else { // The two DIGEST hashes do NOT match, the integrity of the request body is NOT confirmed. + (Failure(ErrorMessages.X509PublicKeyCannotVerify), forwardResult._2) } case Failure(msg, t, c) => (Failure(msg, t, c), forwardResult._2) // PEM certificate is not valid case _ => (Failure(ErrorMessages.X509GeneralError), forwardResult._2) // PEM certificate cannot be validated diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index ea74ba5bc3..48afde5e0c 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -307,7 +307,7 @@ object ErrorMessages { val CustomerNumberAlreadyExists = "OBP-30006: Customer Number already exists. Please specify a different value for BANK_ID or CUSTOMER_NUMBER." val CustomerAlreadyExistsForUser = "OBP-30007: The User is already linked to a Customer at the bank specified by BANK_ID" val UserCustomerLinksNotFoundForUser = "OBP-30008: User Customer Link not found by USER_ID" - val AtmNotFoundByAtmId = "OBP-30009: ATM not found. ATMPlease specify a valid value for ATM_ID." + val AtmNotFoundByAtmId = "OBP-30009: ATM not found. Please specify a valid value for ATM_ID." val BranchNotFoundByBranchId = "OBP-300010: Branch not found. Please specify a valid value for BRANCH_ID. Or License may not be set. meta.license.id and meta.license.name can not be empty" val ProductNotFoundByProductCode = "OBP-30011: Product not found. Please specify a valid value for PRODUCT_CODE." val CounterpartyNotFoundByIban = "OBP-30012: Counterparty not found. Please specify a valid value for IBAN." diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 100576bf77..3c1449092b 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -1489,7 +1489,7 @@ trait APIMethods510 { i => connectorEmptyResponse(i, cc.callContext) } _ <- Helper.booleanToFuture(ConsentUserAlreadyAdded, cc = cc.callContext) { - consent.userId != null + Option(consent.userId).forall(_.isBlank) // checks whether userId is not populated } consent <- Future(Consents.consentProvider.vend.updateConsentUser(consentId, user)) map { i => connectorEmptyResponse(i, cc.callContext) diff --git a/obp-api/src/main/scala/code/scheduler/TransactionScheduler.scala b/obp-api/src/main/scala/code/scheduler/TransactionScheduler.scala new file mode 100644 index 0000000000..381b1a680a --- /dev/null +++ b/obp-api/src/main/scala/code/scheduler/TransactionScheduler.scala @@ -0,0 +1,56 @@ +package code.scheduler + +import code.api.berlin.group.v1_3.model.TransactionStatus +import code.api.util.APIUtil +import code.transactionrequests.MappedTransactionRequest +import code.util.Helper.MdcLoggable +import net.liftweb.common.Full +import net.liftweb.mapper.{By, By_<} + +import scala.util.{Failure, Success, Try} + + +object TransactionScheduler extends MdcLoggable { + + // Starts multiple scheduled tasks with different intervals + def startAll(): Unit = { + var initialDelay = 0 + + // Berlin Group + APIUtil.getPropsAsIntValue("berlin_group_outdated_transactions_interval_in_seconds") match { + case Full(interval) if interval > 0 => + val time = APIUtil.getPropsAsIntValue("berlin_group_outdated_transactions_time_in_seconds", 300) + SchedulerUtil.startTask(interval = interval, () => outdatedBerlinGroupTransactions(time)) // Runs periodically + initialDelay = initialDelay + 10 + case _ => + logger.warn("|---> Skipping outdatedBerlinGroupTransactions task: berlin_group_outdated_transactions_interval_in_seconds not set or invalid") + } + } + + private def outdatedBerlinGroupTransactions(seconds: Int): Unit = { + Try { + logger.debug("|---> Checking for OUTDATED Berlin Group TRANSACTIONS...") + + val outdatedTransactions = MappedTransactionRequest.findAll( + By(MappedTransactionRequest.mStatus, TransactionStatus.RCVD.toString), + By_<(MappedTransactionRequest.updatedAt, SchedulerUtil.someSecondsAgo(seconds)) + ) + + logger.debug(s"|---> Found ${outdatedTransactions.size} outdated transactions") + + outdatedTransactions.foreach { transaction => + Try { + transaction.mStatus(TransactionStatus.RJCT.toString).save + logger.warn(s"|---> Changed status to ${TransactionStatus.RJCT.toString} for transaction ID: ${transaction.id}") + } match { + case Failure(ex) => logger.error(s"Failed to update transaction ID: ${transaction.id}", ex) + case Success(_) => // Already logged + } + } + } match { + case Failure(ex) => logger.error("Error in outdatedBerlinGroupTransactions!", ex) + case Success(_) => logger.debug("|---> Task executed successfully") + } + } + +}