mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 13:07:02 +00:00
feature/add HOLD transaction request type and related models for v6.0.0
This commit is contained in:
parent
f4253b652c
commit
121ab9edae
@ -362,7 +362,7 @@ transactionRequests_enabled=false
|
||||
transactionRequests_connector=mapped
|
||||
|
||||
## Transaction Request Types that are supported on this server. Possible values might include SANDBOX_TAN, COUNTERPARTY, SEPA, FREE_FORM
|
||||
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE
|
||||
transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE,HOLD
|
||||
|
||||
## Transaction request challenge threshold. Level at which challenge is created and needs to be answered.
|
||||
## The Currency is EUR unless set with transactionRequests_challenge_currency.
|
||||
@ -1071,6 +1071,7 @@ database_messages_scheduler_interval=3600
|
||||
# -- SCA (Strong Customer Authentication) method for OTP challenge-------
|
||||
# ACCOUNT_OTP_INSTRUCTION_TRANSPORT=DUMMY
|
||||
# SIMPLE_OTP_INSTRUCTION_TRANSPORT=DUMMY
|
||||
# HOLD_OTP_INSTRUCTION_TRANSPORT=DUMMY
|
||||
# SEPA_OTP_INSTRUCTION_TRANSPORT=DUMMY
|
||||
# FREE_FORM_OTP_INSTRUCTION_TRANSPORT=DUMMY
|
||||
# COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=DUMMY
|
||||
|
||||
@ -5811,6 +5811,12 @@ object SwaggerDefinitionsJSON {
|
||||
description = descriptionExample.value
|
||||
)
|
||||
|
||||
// HOLD sample (V600)
|
||||
lazy val transactionRequestBodyHoldJsonV600 = TransactionRequestBodyHoldJsonV600(
|
||||
value = amountOfMoneyJsonV121,
|
||||
description = descriptionExample.value
|
||||
)
|
||||
|
||||
//The common error or success format.
|
||||
//Just some helper format to use in Json
|
||||
case class NotSupportedYet()
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package code.api.v6_0_0
|
||||
|
||||
import code.api.{APIFailureNewStyle, ObpApiFailure}
|
||||
import code.api.ObpApiFailure
|
||||
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._
|
||||
import code.api.util.APIUtil._
|
||||
import code.api.util.ApiRole.{canDeleteRateLimiting, canReadCallLimits, canSetCallLimits}
|
||||
import code.api.util.ApiTag._
|
||||
import code.api.util.ErrorMessages.{$UserNotLoggedIn, InvalidDateFormat, InvalidJsonFormat, UnknownError, _}
|
||||
import code.api.util.FutureUtil.EndpointContext
|
||||
import code.api.util.{NewStyle, RateLimitingUtil}
|
||||
import code.api.util.NewStyle.HttpCode
|
||||
import code.api.util.{NewStyle, RateLimitingUtil}
|
||||
import code.api.v6_0_0.JSONFactory600.{createActiveCallLimitsJsonV600, createCallLimitJsonV600, createCurrentUsageJson}
|
||||
import code.bankconnectors.LocalMappedConnectorInternal
|
||||
import code.bankconnectors.LocalMappedConnectorInternal._
|
||||
@ -19,11 +19,10 @@ import com.github.dwickern.macros.NameOf.nameOf
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
|
||||
import net.liftweb.common.{Box, Empty, Full}
|
||||
import net.liftweb.common.Full
|
||||
import net.liftweb.http.rest.RestHelper
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import scala.collection.immutable.{List, Nil}
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.Future
|
||||
@ -45,6 +44,46 @@ trait APIMethods600 {
|
||||
val codeContext = CodeContext(staticResourceDocs, apiRelations)
|
||||
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
createTransactionRequestHold,
|
||||
implementedInApiVersion,
|
||||
nameOf(createTransactionRequestHold),
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/owner/transaction-request-types/HOLD/transaction-requests",
|
||||
"Create Transaction Request (HOLD)",
|
||||
s"""
|
||||
|
|
||||
|Create a transaction request to move funds from the account to its Holding Account.
|
||||
|If the Holding Account does not exist, it will be created automatically.
|
||||
|
|
||||
|${transactionRequestGeneralText}
|
||||
|
|
||||
""".stripMargin,
|
||||
transactionRequestBodyHoldJsonV600,
|
||||
transactionRequestWithChargeJSON400,
|
||||
List(
|
||||
$UserNotLoggedIn,
|
||||
$BankNotFound,
|
||||
$BankAccountNotFound,
|
||||
InsufficientAuthorisationToCreateTransactionRequest,
|
||||
InvalidTransactionRequestType,
|
||||
InvalidJsonFormat,
|
||||
NotPositiveAmount,
|
||||
InvalidTransactionRequestCurrency,
|
||||
TransactionDisabled,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2)
|
||||
)
|
||||
|
||||
lazy val createTransactionRequestHold: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
"HOLD" :: "transaction-requests" :: Nil JsonPost json -> _ =>
|
||||
cc => implicit val ec = EndpointContext(Some(cc))
|
||||
val transactionRequestType = TransactionRequestType("HOLD")
|
||||
LocalMappedConnectorInternal.createTransactionRequest(bankId, accountId, viewId , transactionRequestType, json)
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getCurrentCallsLimit,
|
||||
implementedInApiVersion,
|
||||
|
||||
@ -35,7 +35,6 @@ import code.api.v3_1_0.{RateLimit, RedisCallLimitJson}
|
||||
import code.entitlement.Entitlement
|
||||
import code.util.Helper.MdcLoggable
|
||||
import com.openbankproject.commons.model._
|
||||
import java.util.Date
|
||||
|
||||
case class CardanoPaymentJsonV600(
|
||||
address: String,
|
||||
@ -122,6 +121,12 @@ case class TransactionRequestBodyEthSendRawTransactionJsonV600(
|
||||
description: String
|
||||
)
|
||||
|
||||
// ---------------- HOLD models (V600) ----------------
|
||||
case class TransactionRequestBodyHoldJsonV600(
|
||||
value: AmountOfMoneyJsonV121,
|
||||
description: String
|
||||
) extends TransactionRequestCommonBodyJSON
|
||||
|
||||
case class UserJsonV600(
|
||||
user_id: String,
|
||||
email : String,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package code.bankconnectors
|
||||
|
||||
import code.accountattribute.AccountAttributeX
|
||||
import code.api.ChargePolicy
|
||||
import code.api.Constant._
|
||||
import code.api.berlin.group.ConstantsBG
|
||||
@ -13,7 +14,7 @@ import code.api.util.newstyle.ViewNewStyle
|
||||
import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140
|
||||
import code.api.v2_1_0._
|
||||
import code.api.v4_0_0._
|
||||
import code.api.v6_0_0.{TransactionRequestBodyCardanoJsonV600, TransactionRequestBodyEthSendRawTransactionJsonV600, TransactionRequestBodyEthereumJsonV600}
|
||||
import code.api.v6_0_0.{TransactionRequestBodyCardanoJsonV600, TransactionRequestBodyEthSendRawTransactionJsonV600, TransactionRequestBodyEthereumJsonV600, TransactionRequestBodyHoldJsonV600}
|
||||
import code.bankconnectors.ethereum.DecodeRawTx
|
||||
import code.branches.MappedBranch
|
||||
import code.fx.fx
|
||||
@ -801,6 +802,10 @@ object LocalMappedConnectorInternal extends MdcLoggable {
|
||||
description = transactionRequestBodyEthSendRawTransactionJsonV600.description
|
||||
)
|
||||
} yield (transactionRequestBodyEthereum)
|
||||
case HOLD =>
|
||||
NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $TransactionRequestBodyHoldJsonV600 ", 400, callContext) {
|
||||
json.extract[TransactionRequestBodyHoldJsonV600]
|
||||
}
|
||||
case _ =>
|
||||
NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $TransactionRequestBodyCommonJSON ", 400, callContext) {
|
||||
json.extract[TransactionRequestBodyCommonJSON]
|
||||
@ -823,6 +828,30 @@ object LocalMappedConnectorInternal extends MdcLoggable {
|
||||
})
|
||||
|
||||
(createdTransactionRequest, callContext) <- transactionRequestTypeValue match {
|
||||
case HOLD => {
|
||||
for {
|
||||
holdBody <- NewStyle.function.tryons(s"$InvalidJsonFormat It should be $TransactionRequestBodyHoldJsonV600 json format", 400, callContext) {
|
||||
json.extract[TransactionRequestBodyHoldJsonV600]
|
||||
}
|
||||
(holdingAccount, callContext) <- getOrCreateHoldingAccount(bankId, fromAccount, callContext)
|
||||
transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) {
|
||||
write(holdBody)(Serialization.formats(NoTypeHints))
|
||||
}
|
||||
(createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(
|
||||
u,
|
||||
viewId,
|
||||
fromAccount,
|
||||
holdingAccount,
|
||||
transactionRequestType,
|
||||
holdBody,
|
||||
transDetailsSerialized,
|
||||
sharedChargePolicy.toString,
|
||||
Some(OBP_TRANSACTION_REQUEST_CHALLENGE),
|
||||
getScaMethodAtInstance(transactionRequestType.value).toOption,
|
||||
None,
|
||||
callContext)
|
||||
} yield (createdTransactionRequest, callContext)
|
||||
}
|
||||
case REFUND => {
|
||||
for {
|
||||
transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) {
|
||||
@ -1560,5 +1589,72 @@ object LocalMappedConnectorInternal extends MdcLoggable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create a Holding Account for the given parent account and link via account attributes.
|
||||
* Rules:
|
||||
* - Holding account uses the same currency as parent.
|
||||
* - Holding account type: "HOLDING".
|
||||
* - Attributes on holding: ACCOUNT_ROLE=HOLDING, PARENT_ACCOUNT_ID=parentAccountId
|
||||
* - Optional reverse link on parent: HOLDING_ACCOUNT_ID=holdingAccountId
|
||||
*/
|
||||
private def getOrCreateHoldingAccount(
|
||||
bankId: BankId,
|
||||
parentAccount: BankAccount,
|
||||
callContext: Option[CallContext]
|
||||
): Future[(BankAccount, Option[CallContext])] = {
|
||||
val params = Map("PARENT_ACCOUNT_ID" -> List(parentAccount.accountId.value))
|
||||
for {
|
||||
// Query by attribute to find accounts that link to the parent
|
||||
accountIdsBox <- AccountAttributeX.accountAttributeProvider.vend.getAccountIdsByParams(bankId, params)
|
||||
accountIds = accountIdsBox.getOrElse(Nil).map(id => AccountId(id))
|
||||
// Try to find an existing holding account among them
|
||||
existingOpt <- {
|
||||
def firstHolding(ids: List[AccountId]): Future[Option[(BankAccount, Option[CallContext])]] = ids match {
|
||||
case Nil => Future.successful(None)
|
||||
case id :: tail =>
|
||||
NewStyle.function.getBankAccount(bankId, id, callContext).flatMap { case (acc, cc) =>
|
||||
if (acc.accountType == "HOLDING") Future.successful(Some((acc, cc)))
|
||||
else firstHolding(tail)
|
||||
}
|
||||
}
|
||||
firstHolding(accountIds)
|
||||
}
|
||||
result <- existingOpt match {
|
||||
case Some((acc, cc)) => Future.successful((acc, cc))
|
||||
case None =>
|
||||
val newAccountId = AccountId(APIUtil.generateUUID())
|
||||
for {
|
||||
// Create holding account with same currency and zero balance
|
||||
(holding, cc1) <- NewStyle.function.createBankAccount(
|
||||
bankId = bankId,
|
||||
accountId = newAccountId,
|
||||
accountType = "HOLDING",
|
||||
accountLabel = s"Holding account for ${parentAccount.accountId.value}",
|
||||
currency = parentAccount.currency,
|
||||
initialBalance = BigDecimal(0),
|
||||
accountHolderName = Option(parentAccount.accountHolder).getOrElse(""),
|
||||
branchId = parentAccount.branchId,
|
||||
accountRoutings= Nil,
|
||||
callContext = callContext
|
||||
)
|
||||
// Link attributes on holding account
|
||||
_ <- NewStyle.function.createOrUpdateAccountAttribute(
|
||||
bankId, holding.accountId, ProductCode("HOLDING"),
|
||||
None, "ACCOUNT_ROLE", AccountAttributeType.STRING, "HOLDING", None, cc1
|
||||
)
|
||||
_ <- NewStyle.function.createOrUpdateAccountAttribute(
|
||||
bankId, holding.accountId, ProductCode("HOLDING"),
|
||||
None, "PARENT_ACCOUNT_ID", AccountAttributeType.STRING, parentAccount.accountId.value, None, cc1
|
||||
)
|
||||
// Optional reverse link on parent account
|
||||
_ <- NewStyle.function.createOrUpdateAccountAttribute(
|
||||
bankId, parentAccount.accountId, ProductCode(parentAccount.accountType),
|
||||
None, "HOLDING_ACCOUNT_ID", AccountAttributeType.STRING, holding.accountId.value, None, cc1
|
||||
)
|
||||
} yield (holding, cc1)
|
||||
}
|
||||
} yield result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -102,6 +102,7 @@ object TransactionRequestTypes extends OBPEnumeration[TransactionRequestTypes]{
|
||||
object SEPA extends Value
|
||||
object FREE_FORM extends Value
|
||||
object SIMPLE extends Value
|
||||
object HOLD extends Value
|
||||
object CARD extends Value
|
||||
object TRANSFER_TO_PHONE extends Value
|
||||
object TRANSFER_TO_ATM extends Value
|
||||
|
||||
Loading…
Reference in New Issue
Block a user