mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 16:36:54 +00:00
refactor/removed v140, v200 payments: createTransactionRequest, getTransactionRequests and answerTransactionRequestChallenge
This commit is contained in:
parent
5c0107dc75
commit
dce8cd0b62
@ -696,7 +696,6 @@ object ErrorMessages {
|
||||
val InvalidConnectorResponseForGetChargeLevel = "OBP-50207: Connector did not return the set of challenge level we requested."
|
||||
val InvalidConnectorResponseForCreateTransactionRequestImpl210 = "OBP-50208: Connector did not return the set of transactions requests we requested."
|
||||
val InvalidConnectorResponseForMakePayment = "OBP-50209: Connector did not return the set of transactions we requested."
|
||||
val InvalidConnectorResponseForMakePaymentv200 = "OBP-50210: Connector did not return the set of transaction id we requested."
|
||||
val InvalidConnectorResponseForGetCheckbookOrdersFuture = "OBP-50211: Connector did not return the set of check book."
|
||||
val InvalidConnectorResponseForGetStatusOfCreditCardOrderFuture = "OBP-50212: Connector did not return the set of status of credit card."
|
||||
val InvalidConnectorResponseForCreateTransactionAfterChallengev300 = "OBP-50213: The Connector did not return a valid response for payments."
|
||||
|
||||
@ -473,199 +473,7 @@ trait APIMethods140 extends MdcLoggable with APIMethods130 with APIMethods121{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getTransactionRequests,
|
||||
apiVersion,
|
||||
"getTransactionRequests",
|
||||
"GET",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-requests",
|
||||
"Get all Transaction Requests",
|
||||
"",
|
||||
EmptyBody,
|
||||
transactionRequestJson,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
BankNotFound,
|
||||
AccountNotFound,
|
||||
"Current user does not have access to the view",
|
||||
"account not found at bank",
|
||||
"user does not have access to owner view",
|
||||
UnknownError),
|
||||
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
|
||||
|
||||
lazy val getTransactionRequests: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-requests" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- cc.user ?~ ErrorMessages.UserNotLoggedIn
|
||||
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- BankAccountX(bankId, accountId) ?~! {ErrorMessages.AccountNotFound}
|
||||
view <- APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(bankId, accountId), Some(u), callContext)
|
||||
_ <- Helper.booleanToBox(
|
||||
view.canSeeTransactionRequests,
|
||||
s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(ViewDefinition.canSeeTransactionRequests_)).dropRight(1)}` permission on the View(${viewId.value})"
|
||||
)
|
||||
transactionRequests <- Connector.connector.vend.getTransactionRequests(u, fromAccount, callContext)
|
||||
oldTransactionRequest = transactionRequests.map(transforOldTransactionRequest(_).head)
|
||||
}
|
||||
yield {
|
||||
val successJson = Extraction.decompose(oldTransactionRequest)
|
||||
successJsonResponse(successJson)
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse(TransactionRequestsNotEnabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
case class TransactionIdJson(transaction_id : String)
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
createTransactionRequest,
|
||||
apiVersion,
|
||||
"createTransactionRequest",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests",
|
||||
"Create Transaction Request",
|
||||
"""Initiate a Payment via a Transaction Request.
|
||||
|
|
||||
|This is the preferred method to create a payment and supersedes makePayment in 1.2.1.
|
||||
|
|
||||
|See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow.
|
||||
|
|
||||
|In sandbox mode, if the amount is < 100 the transaction request will create a transaction without a challenge, else a challenge will need to be answered.
|
||||
|If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created.
|
||||
|
|
||||
|Please see later versions of this call in 2.0.0 or 2.1.0.
|
||||
|""",
|
||||
transactionRequestBodyJsonV140,
|
||||
transactionRequestJson,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
InvalidJsonFormat,
|
||||
BankNotFound,
|
||||
AccountNotFound,
|
||||
CounterpartyNotFound,
|
||||
"Counterparty and holder accounts have differing currencies",
|
||||
"Request currency and holder account currency can't be different.",
|
||||
"Amount not convertible to number",
|
||||
"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}",
|
||||
"user does not have access to owner view",
|
||||
"amount ${body.value.amount} not convertible to number",
|
||||
"Cannot send payment to account with different currency",
|
||||
"Can't send a payment with a value of 0 or less.",
|
||||
TransactionRequestsNotEnabled,
|
||||
UnknownError),
|
||||
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
|
||||
|
||||
lazy val createTransactionRequest: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
/* TODO:
|
||||
* check if user has access using the view that is given (now it checks if user has access to owner view), will need some new permissions for transaction requests
|
||||
* test: functionality, error messages if user not given or invalid, if any other value is not existing
|
||||
*/
|
||||
u <- cc.user ?~ ErrorMessages.UserNotLoggedIn
|
||||
transBodyJson <- tryo{json.extract[TransactionRequestBodyJsonV140]} ?~ {ErrorMessages.InvalidJsonFormat}
|
||||
transBody <- tryo{getTransactionRequestBodyFromJson(transBodyJson)}
|
||||
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- BankAccountX(bankId, accountId) ?~! {ErrorMessages.AccountNotFound}
|
||||
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId : ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
|
||||
s"$InsufficientAuthorisationToCreateTransactionRequest " +
|
||||
s"Current ViewId(${viewId.value})," +
|
||||
s"current UserId(${u.userId})" +
|
||||
s"current ConsumerId(${callContext.map (_.consumer.map (_.consumerId.get).getOrElse ("")).getOrElse ("")})"
|
||||
}
|
||||
toBankId <- tryo(BankId(transBodyJson.to.bank_id))
|
||||
toAccountId <- tryo(AccountId(transBodyJson.to.account_id))
|
||||
toAccount <- BankAccountX(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound}
|
||||
_ <- tryo(assert(fromAccount.currency == toAccount.currency)) ?~! {"Counterparty and holder accounts have differing currencies."}
|
||||
_ <- tryo(assert(transBodyJson.value.currency == fromAccount.currency)) ?~! {"Request currency and holder account currency can't be different."}
|
||||
_ <- tryo {BigDecimal(transBodyJson.value.amount)} ?~! s"Amount ${transBodyJson.value.amount} not convertible to number"
|
||||
createdTransactionRequest <- Connector.connector.vend.createTransactionRequest(u, fromAccount, toAccount, transactionRequestType, transBody, callContext)
|
||||
oldTransactionRequest <- transforOldTransactionRequest(createdTransactionRequest)
|
||||
} yield {
|
||||
val json = Extraction.decompose(oldTransactionRequest)
|
||||
createdJsonResponse(json)
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse(TransactionRequestsNotEnabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
answerTransactionRequestChallenge,
|
||||
apiVersion,
|
||||
"answerTransactionRequestChallenge",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge",
|
||||
"Answer Transaction Request Challenge.",
|
||||
"""
|
||||
|In Sandbox mode, any string that can be converted to a possitive integer will be accepted as an answer.
|
||||
|
|
||||
""".stripMargin,
|
||||
challengeAnswerJSON,
|
||||
transactionRequestJson,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
BankNotFound,
|
||||
BankAccountNotFound,
|
||||
InvalidJsonFormat,
|
||||
"Current user does not have access to the view ",
|
||||
"Couldn't create Transaction",
|
||||
TransactionRequestsNotEnabled,
|
||||
"Need a non-empty answer",
|
||||
"Need a numeric TAN",
|
||||
"Need a positive TAN",
|
||||
"unknown challenge type",
|
||||
"Sorry, you've used up your allowed attempts.",
|
||||
"Error getting Transaction Request",
|
||||
"Transaction Request not found",
|
||||
"Couldn't create Transaction",
|
||||
UnknownError),
|
||||
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
|
||||
|
||||
lazy val answerTransactionRequestChallenge: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: TransactionRequestId(transReqId) :: "challenge" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- cc.user ?~ ErrorMessages.UserNotLoggedIn
|
||||
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! {ErrorMessages.BankNotFound}
|
||||
fromAccount <- BankAccountX(bankId, accountId) ?~! BankAccountNotFound
|
||||
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId: ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
|
||||
s"$InsufficientAuthorisationToCreateTransactionRequest " +
|
||||
s"Current ViewId(${viewId.value})," +
|
||||
s"current UserId(${u.userId})" +
|
||||
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
|
||||
}
|
||||
answerJson <- tryo{json.extract[ChallengeAnswerJSON]} ?~ InvalidJsonFormat
|
||||
//TODO check more things here
|
||||
_ <- Connector.connector.vend.answerTransactionRequestChallenge(transReqId, answerJson.answer, callContext)
|
||||
//create transaction and insert its id into the transaction request
|
||||
transactionRequest <- Connector.connector.vend.createTransactionAfterChallenge(u, transReqId, callContext)
|
||||
oldTransactionRequest <- transforOldTransactionRequest(transactionRequest)
|
||||
} yield {
|
||||
val successJson = Extraction.decompose(oldTransactionRequest)
|
||||
successJsonResponse(successJson, 202)
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse(TransactionRequestsNotEnabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
addCustomer,
|
||||
apiVersion,
|
||||
|
||||
@ -103,10 +103,7 @@ object OBPAPI1_4_0 extends OBPRestHelper with APIMethods140 with MdcLoggable wit
|
||||
Implementations1_4_0.getAtms,
|
||||
Implementations1_4_0.getProducts,
|
||||
Implementations1_4_0.getCrmEvents,
|
||||
Implementations1_4_0.createTransactionRequest,
|
||||
Implementations1_4_0.getTransactionRequests,
|
||||
Implementations1_4_0.getTransactionRequestTypes,
|
||||
Implementations1_4_0.answerTransactionRequestChallenge,
|
||||
Implementations1_4_0.testResourceDoc
|
||||
)
|
||||
|
||||
|
||||
@ -1296,257 +1296,6 @@ trait APIMethods200 {
|
||||
import net.liftweb.json.JsonAST._
|
||||
val exchangeRates = prettyRender(decompose(fx.fallbackExchangeRates))
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
createTransactionRequest,
|
||||
apiVersion,
|
||||
"createTransactionRequest",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests",
|
||||
"Create Transaction Request",
|
||||
s"""Initiate a Payment via a Transaction Request.
|
||||
|
|
||||
|This is the preferred method to create a payment and supersedes makePayment in 1.2.1.
|
||||
|
|
||||
|PSD2 Context: Third party access access to payments is a core tenent of PSD2.
|
||||
|
|
||||
|This call satisfies that requirement from several perspectives:
|
||||
|
|
||||
|1) A transaction can be initiated by a third party application.
|
||||
|
|
||||
|2) The customer is informed of the charge that will incurred.
|
||||
|
|
||||
|3) The call uses delegated authentication (OAuth)
|
||||
|
|
||||
|See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow.
|
||||
|
|
||||
|In sandbox mode, if the amount is less than 100 (any currency), the transaction request will create a transaction without a challenge, else the Transaction Request will be set to INITIALISED and a challenge will need to be answered.|
|
||||
|If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created.
|
||||
|
|
||||
|You can transfer between different currency accounts. (new in 2.0.0). The currency in body must match the sending account.
|
||||
|
|
||||
|Currently TRANSACTION_REQUEST_TYPE must be set to SANDBOX_TAN
|
||||
|
|
||||
|The following static FX rates are available in sandbox mode:
|
||||
|
|
||||
|${exchangeRates}
|
||||
|
|
||||
|
|
||||
|The payer is set in the URL. Money comes out of the BANK_ID and ACCOUNT_ID specified in the URL
|
||||
|
|
||||
|The payee is set in the request body. Money goes into the BANK_ID and ACCOUNT_IDO specified in the request body.
|
||||
|
|
||||
|
|
||||
|${authenticationRequiredMessage(true)}
|
||||
|
|
||||
|""".stripMargin,
|
||||
transactionRequestBodyJsonV200,
|
||||
transactionRequestWithChargesJson,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
InvalidJsonFormat,
|
||||
InvalidBankIdFormat,
|
||||
InvalidAccountIdFormat,
|
||||
BankNotFound,
|
||||
AccountNotFound,
|
||||
ViewNotFound,
|
||||
UserNoPermissionAccessView,
|
||||
InsufficientAuthorisationToCreateTransactionRequest,
|
||||
CounterpartyNotFound,
|
||||
InvalidTransactionRequestType,
|
||||
InvalidTransactionRequestCurrency,
|
||||
TransactionDisabled,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle),
|
||||
Some(List(canCreateAnyTransactionRequest)))
|
||||
|
||||
lazy val createTransactionRequest: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
/* TODO:
|
||||
* check if user has access using the view that is given (now it checks if user has access to owner view), will need some new permissions for transaction requests
|
||||
* test: functionality, error messages if user not given or invalid, if any other value is not existing
|
||||
*/
|
||||
u <- cc.user ?~! ErrorMessages.UserNotLoggedIn
|
||||
transBodyJson <- tryo{json.extract[TransactionRequestBodyJsonV200]} ?~! InvalidJsonFormat
|
||||
transBody <- tryo{getTransactionRequestBodyFromJson(transBodyJson)}
|
||||
_ <- tryo(assert(isValidID(bankId.value)))?~! InvalidBankIdFormat
|
||||
_ <- tryo(assert(isValidID(accountId.value)))?~! InvalidAccountIdFormat
|
||||
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound
|
||||
fromAccount <- BankAccountX(bankId, accountId) ?~! AccountNotFound
|
||||
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId: ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
|
||||
s"$InsufficientAuthorisationToCreateTransactionRequest " +
|
||||
s"Current ViewId(${viewId.value})," +
|
||||
s"current UserId(${u.userId})" +
|
||||
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
|
||||
}
|
||||
toBankId <- tryo(BankId(transBodyJson.to.bank_id))
|
||||
toAccountId <- tryo(AccountId(transBodyJson.to.account_id))
|
||||
toAccount <- BankAccountX(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound}
|
||||
// Prevent default value for transaction request type (at least).
|
||||
// Get Transaction Request Types from Props "transactionRequests_supported_types". Default is empty string
|
||||
validTransactionRequestTypes <- tryo{APIUtil.getPropsValue("transactionRequests_supported_types", "")}
|
||||
// Use a list instead of a string to avoid partial matches
|
||||
validTransactionRequestTypesList <- tryo{validTransactionRequestTypes.split(",")}
|
||||
_ <- tryo(assert(transactionRequestType.value != "TRANSACTION_REQUEST_TYPE" && validTransactionRequestTypesList.contains(transactionRequestType.value))) ?~! s"${InvalidTransactionRequestType} : Invalid value is: '${transactionRequestType.value}' Valid values are: ${validTransactionRequestTypes}"
|
||||
_ <- tryo(assert(transBodyJson.value.currency == fromAccount.currency)) ?~! InvalidTransactionRequestCurrency
|
||||
createdTransactionRequest <- Connector.connector.vend.createTransactionRequestv200(u, fromAccount, toAccount, transactionRequestType, transBody, callContext)
|
||||
} yield {
|
||||
// Explicitly format as v2.0.0 json
|
||||
val json = JSONFactory200.createTransactionRequestWithChargeJSON(createdTransactionRequest)
|
||||
createdJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse(TransactionDisabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
answerTransactionRequestChallenge,
|
||||
apiVersion,
|
||||
"answerTransactionRequestChallenge",
|
||||
"POST",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge",
|
||||
"Answer Transaction Request Challenge",
|
||||
"""
|
||||
|In Sandbox mode, any string that can be converted to a positive integer will be accepted as an answer.
|
||||
|
|
||||
""".stripMargin,
|
||||
ChallengeAnswerJSON("89123812", "123345"),
|
||||
transactionRequestWithChargeJson,
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
InvalidAccountIdFormat,
|
||||
InvalidBankIdFormat,
|
||||
BankNotFound,
|
||||
UserNoPermissionAccessView,
|
||||
InvalidJsonFormat,
|
||||
InvalidTransactionRequestId,
|
||||
TransactionRequestTypeHasChanged,
|
||||
InvalidTransactionRequestChallengeId,
|
||||
TransactionRequestStatusNotInitiated,
|
||||
TransactionDisabled,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
|
||||
|
||||
lazy val answerTransactionRequestChallenge: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" ::
|
||||
TransactionRequestType(transactionRequestType) :: "transaction-requests" :: TransactionRequestId(transReqId) :: "challenge" :: Nil JsonPost json -> _ => {
|
||||
cc =>
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- cc.user ?~! ErrorMessages.UserNotLoggedIn
|
||||
_ <- tryo(assert(isValidID(accountId.value)))?~! ErrorMessages.InvalidAccountIdFormat
|
||||
_ <- tryo(assert(isValidID(bankId.value)))?~! ErrorMessages.InvalidBankIdFormat
|
||||
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound
|
||||
fromAccount <- BankAccountX(bankId, accountId) ?~! AccountNotFound
|
||||
_ <- APIUtil.checkAuthorisationToCreateTransactionRequest(viewId: ViewId, BankIdAccountId(bankId, accountId), u: User, callContext: Option[CallContext]) ?~! {
|
||||
s"$InsufficientAuthorisationToCreateTransactionRequest " +
|
||||
s"Current ViewId(${viewId.value})," +
|
||||
s"current UserId(${u.userId})" +
|
||||
s"current ConsumerId(${callContext.map(_.consumer.map(_.consumerId.get).getOrElse("")).getOrElse("")})"
|
||||
}
|
||||
answerJson <- tryo{json.extract[ChallengeAnswerJSON]} ?~! InvalidJsonFormat
|
||||
_ <- Connector.connector.vend.answerTransactionRequestChallenge(transReqId, answerJson.answer, callContext)
|
||||
//check the transReqId validation.
|
||||
(existingTransactionRequest, callContext) <- Connector.connector.vend.getTransactionRequestImpl(transReqId, callContext) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
|
||||
|
||||
//check the input transactionRequestType is same as when the user create the existingTransactionRequest
|
||||
existingTransactionRequestType = existingTransactionRequest.`type`
|
||||
_ <- booleanToBox(existingTransactionRequestType.equals(transactionRequestType.value),s"${ErrorMessages.TransactionRequestTypeHasChanged} It should be :'$existingTransactionRequestType' ")
|
||||
|
||||
//check the challenge id is same as when the user create the existingTransactionRequest
|
||||
_ <- booleanToBox(existingTransactionRequest.challenge.id.equals(answerJson.id),{ErrorMessages.InvalidTransactionRequestChallengeId})
|
||||
|
||||
//check the challenge statue whether is initiated, only retreive INITIATED transaction requests.
|
||||
_ <- booleanToBox(existingTransactionRequest.status.equals("INITIATED"),ErrorMessages.TransactionRequestStatusNotInitiated)
|
||||
|
||||
toBankId = BankId(existingTransactionRequest.body.to_sandbox_tan.get.bank_id)
|
||||
toAccountId = AccountId(existingTransactionRequest.body.to_sandbox_tan.get.account_id)
|
||||
toAccount <- BankAccountX(toBankId, toAccountId) ?~! s"$AccountNotFound,toBankId($toBankId) and toAccountId($toAccountId) is invalid ."
|
||||
|
||||
//create transaction and insert its id into the transaction request
|
||||
transactionRequest <- Connector.connector.vend.createTransactionAfterChallengev200(fromAccount, toAccount, existingTransactionRequest, callContext)
|
||||
} yield {
|
||||
|
||||
// Format explicitly as v2.0.0 json
|
||||
val json = JSONFactory200.createTransactionRequestWithChargeJSON(transactionRequest)
|
||||
//successJsonResponse(Extraction.decompose(json))
|
||||
|
||||
val successJson = Extraction.decompose(json)
|
||||
successJsonResponse(successJson, 202)
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse(TransactionDisabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
getTransactionRequests,
|
||||
apiVersion,
|
||||
"getTransactionRequests",
|
||||
"GET",
|
||||
"/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-requests",
|
||||
"Get Transaction Requests." ,
|
||||
"""Returns transaction requests for account specified by ACCOUNT_ID at bank specified by BANK_ID.
|
||||
|
|
||||
|The VIEW_ID specified must be 'owner' and the user must have access to this view.
|
||||
|
|
||||
|Version 2.0.0 now returns charge information.
|
||||
|
|
||||
|Transaction Requests serve to initiate transactions that may or may not proceed. They contain information including:
|
||||
|
|
||||
|* Transaction Request Id
|
||||
|* Type
|
||||
|* Status (INITIATED, COMPLETED)
|
||||
|* Challenge (in order to confirm the request)
|
||||
|* From Bank / Account
|
||||
|* Body including To Account, Currency, Value, Description and other initiation information. (Could potentialy include a list of future transactions.)
|
||||
|* Related Transactions
|
||||
|
|
||||
|PSD2 Context: PSD2 requires transparency of charges to the customer.
|
||||
|This endpoint provides the charge that would be applied if the Transaction Request proceeds - and a record of that charge there after.
|
||||
|The customer can proceed with the Transaction by answering the security challenge.
|
||||
|
|
||||
""".stripMargin,
|
||||
EmptyBody,
|
||||
transactionRequestWithChargesJson,
|
||||
List(UserNotLoggedIn, BankNotFound, AccountNotFound, UserNoPermissionAccessView, UnknownError),
|
||||
List(apiTagTransactionRequest, apiTagPsd2, apiTagOldStyle))
|
||||
|
||||
lazy val getTransactionRequests: OBPEndpoint = {
|
||||
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-requests" :: Nil JsonGet _ => {
|
||||
cc =>
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false)) {
|
||||
for {
|
||||
u <- cc.user ?~! UserNotLoggedIn
|
||||
(bank, callContext ) <- BankX(bankId, Some(cc)) ?~! BankNotFound
|
||||
fromAccount <- BankAccountX(bankId, accountId) ?~! AccountNotFound
|
||||
view <-APIUtil.checkViewAccessAndReturnView(viewId, BankIdAccountId(fromAccount.bankId, fromAccount.accountId), Some(u), callContext)
|
||||
_ <- Helper.booleanToBox(view.canSeeTransactionRequests,
|
||||
s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(ViewDefinition.canSeeTransactionRequests_)).dropRight(1)}` permission on the View(${viewId.value} )")
|
||||
transactionRequests <- Connector.connector.vend.getTransactionRequests(u, fromAccount, callContext)
|
||||
}
|
||||
yield {
|
||||
// Format the data as V2.0.0 json
|
||||
val json = JSONFactory200.createTransactionRequestJSONs(transactionRequests)
|
||||
successJsonResponse(Extraction.decompose(json))
|
||||
}
|
||||
} else {
|
||||
Full(errorJsonResponse(TransactionDisabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resourceDocs += ResourceDoc(
|
||||
createUser,
|
||||
apiVersion,
|
||||
|
||||
@ -146,9 +146,6 @@ object OBPAPI2_0_0 extends OBPRestHelper with APIMethods130 with APIMethods140 w
|
||||
Implementations2_0_0.corePrivateAccountsAtOneBank, // this is /my accounts
|
||||
Implementations2_0_0.privateAccountsAtOneBank, // This was missing for a while from v2.0.0
|
||||
Implementations2_0_0.publicAccountsAtOneBank,
|
||||
Implementations2_0_0.createTransactionRequest,
|
||||
Implementations2_0_0.answerTransactionRequestChallenge,
|
||||
Implementations2_0_0.getTransactionRequests, // Now has charges information
|
||||
// Updated in 2.0.0 (added sorting and better guards / error messages)
|
||||
Implementations2_0_0.accountById,
|
||||
Implementations2_0_0.getPermissionsForBankAccount,
|
||||
|
||||
@ -671,37 +671,6 @@ trait Connector extends MdcLoggable {
|
||||
callContext: Option[CallContext]
|
||||
): OBPReturnType[Box[PhysicalCardTrait]] = Future{(Failure{setUnimplementedError(nameOf(updatePhysicalCard _))}, callContext)}
|
||||
|
||||
/**
|
||||
* \
|
||||
*
|
||||
* @param initiator The user attempting to make the payment
|
||||
* @param fromAccountUID The unique identifier of the account sending money
|
||||
* @param toAccountUID The unique identifier of the account receiving money
|
||||
* @param amt The amount of money to send ( > 0 )
|
||||
* @return The id of the sender's new transaction,
|
||||
*/
|
||||
def makePayment(initiator : User, fromAccountUID : BankIdAccountId, toAccountUID : BankIdAccountId,
|
||||
amt : BigDecimal, description : String, transactionRequestType: TransactionRequestType, callContext: Option[CallContext]) : Box[TransactionId] =
|
||||
Failure(setUnimplementedError(nameOf(makePayment _)))
|
||||
|
||||
/**
|
||||
* \
|
||||
*
|
||||
* @param fromAccount The unique identifier of the account sending money
|
||||
* @param toAccount The unique identifier of the account receiving money
|
||||
* @param amount The amount of money to send ( > 0 )
|
||||
* @param transactionRequestType user input: SEPA, SANDBOX_TAN, FREE_FORM, COUNTERPARTY
|
||||
* @return The id of the sender's new transaction,
|
||||
*/
|
||||
def makePaymentv200(fromAccount: BankAccount,
|
||||
toAccount: BankAccount,
|
||||
transactionRequestCommonBody: TransactionRequestCommonBodyJSON,
|
||||
amount: BigDecimal,
|
||||
description: String,
|
||||
transactionRequestType: TransactionRequestType,
|
||||
chargePolicy: String,
|
||||
callContext: Option[CallContext]): Box[TransactionId] =
|
||||
Failure(setUnimplementedError(nameOf(makePayment _)))
|
||||
|
||||
//Note: introduce v210 here, is for kafka connectors, use callContext and return Future.
|
||||
def makePaymentv210(fromAccount: BankAccount,
|
||||
@ -721,20 +690,6 @@ trait Connector extends MdcLoggable {
|
||||
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]]= Future{(Failure(setUnimplementedError(nameOf(saveDoubleEntryBookTransaction _))), callContext)}
|
||||
def getBalancingTransaction(transactionId: TransactionId,
|
||||
callContext: Option[CallContext]): OBPReturnType[Box[DoubleEntryTransaction]]= Future{(Failure(setUnimplementedError(nameOf(getBalancingTransaction _))), callContext)}
|
||||
|
||||
|
||||
/*
|
||||
Transaction Requests
|
||||
*/
|
||||
|
||||
|
||||
// This is used for 1.4.0 See createTransactionRequestv200 for 2.0.0
|
||||
def createTransactionRequest(initiator : User, fromAccount : BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody, callContext: Option[CallContext]) : Box[TransactionRequest] =
|
||||
(Failure(setUnimplementedError(nameOf(createTransactionRequest _))), callContext)
|
||||
|
||||
|
||||
def createTransactionRequestv200(initiator : User, fromAccount : BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody,
|
||||
callContext: Option[CallContext]) : Box[TransactionRequest] = (Failure(setUnimplementedError(nameOf(createTransactionRequestv200 _))), callContext)
|
||||
|
||||
// Set initial status
|
||||
def getStatus(challengeThresholdAmount: BigDecimal, transactionRequestCommonBodyAmount: BigDecimal, transactionRequestType: TransactionRequestType, callContext: Option[CallContext]): OBPReturnType[Box[TransactionRequestStatus.Value]] =
|
||||
@ -838,18 +793,6 @@ trait Connector extends MdcLoggable {
|
||||
|
||||
def getTransactionRequestTypes(initiator : User, fromAccount : BankAccount, callContext: Option[CallContext]) : Box[List[TransactionRequestType]] =Failure(setUnimplementedError(nameOf(createChallengesC3 _)))
|
||||
|
||||
//Note: Now we use validateChallengeAnswer instead, new methods validate over kafka, and move the allowed_attempts guard into API level.
|
||||
//It is only used for V140 and V200, has been deprecated from V210.
|
||||
@deprecated
|
||||
def answerTransactionRequestChallenge(transReqId: TransactionRequestId, answer: String, callContext: Option[CallContext]) :Box[Boolean] =
|
||||
Failure(setUnimplementedError(nameOf(answerTransactionRequestChallenge _)))
|
||||
|
||||
def createTransactionAfterChallenge(initiator: User, transReqId: TransactionRequestId, callContext: Option[CallContext]) : Box[TransactionRequest] =
|
||||
Failure(setUnimplementedError(nameOf(getBalancingTransaction _)))
|
||||
|
||||
def createTransactionAfterChallengev200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]): Box[TransactionRequest] =
|
||||
Failure(setUnimplementedError(nameOf(createTransactionAfterChallengev200 _)))
|
||||
|
||||
def createTransactionAfterChallengeV210(fromAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]) : OBPReturnType[Box[TransactionRequest]] =
|
||||
Future{(Failure(setUnimplementedError(nameOf(createTransactionAfterChallengeV210 _))), callContext)}
|
||||
|
||||
|
||||
@ -382,10 +382,7 @@ object ConnectorBuilderUtil {
|
||||
*/
|
||||
val specialMethods = List(
|
||||
"getPhysicalCards",
|
||||
"makePayment",
|
||||
"makePaymentv200",
|
||||
"createTransactionRequest",
|
||||
"createTransactionRequestv200",
|
||||
"getStatus",
|
||||
"getChargeValue",
|
||||
"saveTransactionRequestTransaction",
|
||||
@ -394,7 +391,6 @@ object ConnectorBuilderUtil {
|
||||
"getTransactionRequestStatuses",
|
||||
"getTransactionRequestTypes",
|
||||
"createTransactionAfterChallenge",
|
||||
"createTransactionAfterChallengev200",
|
||||
"updateAccountLabel",
|
||||
"getProduct",
|
||||
"createOrUpdateBranch",
|
||||
|
||||
@ -4196,219 +4196,11 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
val result: Box[(Transaction, Option[CallContext])] = getTransactionLegacy(bankId, accountId, transactionId, callContext)
|
||||
Future(result.map(_._1), result.map(_._2).getOrElse(callContext))
|
||||
}
|
||||
|
||||
//Payments api: just return Failure("not supported") from makePaymentImpl if you don't want to implement it
|
||||
/**
|
||||
* \
|
||||
*
|
||||
* @param initiator The user attempting to make the payment
|
||||
* @param fromAccountUID The unique identifier of the account sending money
|
||||
* @param toAccountUID The unique identifier of the account receiving money
|
||||
* @param amt The amount of money to send ( > 0 )
|
||||
* @return The id of the sender's new transaction,
|
||||
*/
|
||||
override def makePayment(initiator: User, fromAccountUID: BankIdAccountId, toAccountUID: BankIdAccountId,
|
||||
amt: BigDecimal, description: String, transactionRequestType: TransactionRequestType,
|
||||
callContext: Option[CallContext]): Box[TransactionId] = {
|
||||
for {
|
||||
(fromAccount, callContext) <- getBankAccountLegacy(fromAccountUID.bankId, fromAccountUID.accountId,callContext)?~
|
||||
s"$BankAccountNotFound Account ${fromAccountUID.accountId} not found at bank ${fromAccountUID.bankId}"
|
||||
(toAccount, callContext)<- getBankAccountLegacy(toAccountUID.bankId, toAccountUID.accountId, callContext) ?~
|
||||
s"$BankAccountNotFound Account ${toAccountUID.accountId} not found at bank ${toAccountUID.bankId}"
|
||||
sameCurrency <- booleanToBox(fromAccount.currency == toAccount.currency, {
|
||||
s"$InvalidTransactionRequestCurrency, Cannot send payment to account with different currency (From ${fromAccount.currency} to ${toAccount.currency}"
|
||||
})
|
||||
isPositiveAmtToSend <- booleanToBox(amt > BigDecimal("0"), s"$NotPositiveAmount Can't send a payment with a value of 0 or less. ($amt)")
|
||||
//TODO: verify the amount fits with the currency -> e.g. 12.543 EUR not allowed, 10.00 JPY not allowed, 12.53 EUR allowed
|
||||
// Note for 'new MappedCounterparty()' in the following :
|
||||
// We update the makePaymentImpl in V210, added the new parameter 'toCounterparty: CounterpartyTrait' for V210
|
||||
// But in V200 or before, we do not used the new parameter toCounterparty. So just keep it empty.
|
||||
transactionId <- LocalMappedConnectorInternal.makePaymentImpl(fromAccount,
|
||||
toAccount,
|
||||
transactionRequestCommonBody = null, //Note transactionRequestCommonBody started to use in V210
|
||||
amt,
|
||||
description,
|
||||
transactionRequestType,
|
||||
"",
|
||||
callContext) //Note chargePolicy started to use in V210
|
||||
} yield transactionId
|
||||
}
|
||||
|
||||
/**
|
||||
* \
|
||||
*
|
||||
* @param fromAccount The unique identifier of the account sending money
|
||||
* @param toAccount The unique identifier of the account receiving money
|
||||
* @param amount The amount of money to send ( > 0 )
|
||||
* @param transactionRequestType user input: SEPA, SANDBOX_TAN, FREE_FORM, COUNTERPARTY
|
||||
* @return The id of the sender's new transaction,
|
||||
*/
|
||||
override def makePaymentv200(fromAccount: BankAccount,
|
||||
toAccount: BankAccount,
|
||||
transactionRequestCommonBody: TransactionRequestCommonBodyJSON,
|
||||
amount: BigDecimal,
|
||||
description: String,
|
||||
transactionRequestType: TransactionRequestType,
|
||||
chargePolicy: String,
|
||||
callContext: Option[CallContext]): Box[TransactionId] = {
|
||||
for {
|
||||
transactionId <- LocalMappedConnectorInternal.makePaymentImpl(fromAccount, toAccount, transactionRequestCommonBody, amount, description, transactionRequestType, chargePolicy, callContext) ?~! InvalidConnectorResponseForMakePayment
|
||||
} yield transactionId
|
||||
}
|
||||
|
||||
|
||||
override def saveTransactionRequestChallenge(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge, callContext: Option[CallContext]): OBPReturnType[Box[Boolean]] ={
|
||||
Future{(TransactionRequests.transactionRequestProvider.vend.saveTransactionRequestChallengeImpl(transactionRequestId, challenge), callContext)}
|
||||
}
|
||||
|
||||
// This is used for 1.4.0 See createTransactionRequestv200 for 2.0.0
|
||||
override def createTransactionRequest(initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody,
|
||||
callContext: Option[CallContext]): Box[TransactionRequest] = {
|
||||
//set initial status
|
||||
//for sandbox / testing: depending on amount, we ask for challenge or not
|
||||
val status =
|
||||
if (transactionRequestType.value == TransactionRequestTypes.SANDBOX_TAN.toString && BigDecimal(body.value.amount) < 100) {
|
||||
TransactionRequestStatus.COMPLETED
|
||||
} else {
|
||||
TransactionRequestStatus.INITIATED
|
||||
}
|
||||
|
||||
//create a new transaction request
|
||||
val request = for {
|
||||
(fromAccountType,callContext) <- getBankAccountLegacy(fromAccount.bankId, fromAccount.accountId, callContext) ?~
|
||||
s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
|
||||
(toAccountType, callContext) <- getBankAccountLegacy(toAccount.bankId, toAccount.accountId, callContext) ?~
|
||||
s"account ${toAccount.accountId} not found at bank ${toAccount.bankId}"
|
||||
rawAmt <- tryo {
|
||||
BigDecimal(body.value.amount)
|
||||
} ?~! s"amount ${body.value.amount} not convertible to number"
|
||||
sameCurrency <- booleanToBox(fromAccount.currency == toAccount.currency, {
|
||||
s"Cannot send payment to account with different currency (From ${fromAccount.currency} to ${toAccount.currency}"
|
||||
})
|
||||
isPositiveAmtToSend <- booleanToBox(rawAmt > BigDecimal("0"), s"Can't send a payment with a value of 0 or less. (${rawAmt})")
|
||||
// Version 200 below has more support for charge
|
||||
charge = TransactionRequestCharge("Charge for completed transaction", AmountOfMoney(body.value.currency, "0.00"))
|
||||
transactionRequest <- TransactionRequests.transactionRequestProvider.vend.createTransactionRequestImpl(
|
||||
TransactionRequestId(generateUUID()),
|
||||
transactionRequestType,
|
||||
fromAccount,
|
||||
toAccount,
|
||||
body,
|
||||
status.toString,
|
||||
charge
|
||||
)
|
||||
} yield transactionRequest
|
||||
|
||||
//make sure we get something back
|
||||
var result = request.openOrThrowException("Exception: Couldn't create transactionRequest")
|
||||
|
||||
//if no challenge necessary, create transaction immediately and put in data store and object to return
|
||||
if (status == TransactionRequestStatus.COMPLETED) {
|
||||
val createdTransactionId = Connector.connector.vend.makePayment(initiator, BankIdAccountId(fromAccount.bankId, fromAccount.accountId),
|
||||
BankIdAccountId(toAccount.bankId, toAccount.accountId), BigDecimal(body.value.amount), body.description, transactionRequestType,
|
||||
callContext)
|
||||
|
||||
//set challenge to null
|
||||
result = result.copy(challenge = null)
|
||||
|
||||
//save transaction_id if we have one
|
||||
createdTransactionId match {
|
||||
case Full(ti) => {
|
||||
if (!createdTransactionId.isEmpty) {
|
||||
saveTransactionRequestTransaction(result.id, ti, callContext)
|
||||
result = result.copy(transaction_ids = ti.value)
|
||||
}
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
//if challenge necessary, create a new one
|
||||
val challenge = TransactionRequestChallenge(id = generateUUID(), allowed_attempts = 3, challenge_type = ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString)
|
||||
saveTransactionRequestChallenge(result.id, challenge, callContext)
|
||||
result = result.copy(challenge = challenge)
|
||||
}
|
||||
|
||||
Full(result)
|
||||
}
|
||||
|
||||
override def createTransactionRequestv200(initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody,
|
||||
callContext: Option[CallContext]): Box[TransactionRequest] = {
|
||||
//set initial status
|
||||
//for sandbox / testing: depending on amount, we ask for challenge or not
|
||||
val status =
|
||||
if (transactionRequestType.value == TransactionRequestTypes.SANDBOX_TAN.toString && BigDecimal(body.value.amount) < 1000) {
|
||||
TransactionRequestStatus.COMPLETED
|
||||
} else {
|
||||
TransactionRequestStatus.INITIATED
|
||||
}
|
||||
|
||||
|
||||
// Always create a new Transaction Request
|
||||
val request = for {
|
||||
(fromAccountType,callContext) <- getBankAccountLegacy(fromAccount.bankId, fromAccount.accountId, callContext) ?~ s"account ${fromAccount.accountId} not found at bank ${fromAccount.bankId}"
|
||||
(toAccountType, callContext) <- getBankAccountLegacy(toAccount.bankId, toAccount.accountId, callContext) ?~ s"account ${toAccount.accountId} not found at bank ${toAccount.bankId}"
|
||||
rawAmt <- tryo {
|
||||
BigDecimal(body.value.amount)
|
||||
} ?~! s"amount ${body.value.amount} not convertible to number"
|
||||
// isValidTransactionRequestType is checked at API layer. Maybe here too.
|
||||
isPositiveAmtToSend <- booleanToBox(rawAmt > BigDecimal("0"), s"Can't send a payment with a value of 0 or less. (${rawAmt})")
|
||||
|
||||
// For now, arbitary charge value to demonstrate PSD2 charge transparency principle. Eventually this would come from Transaction Type? 10 decimal places of scaling so can add small percentage per transaction.
|
||||
chargeValue <- tryo {
|
||||
(BigDecimal(body.value.amount) * 0.0001).setScale(10, BigDecimal.RoundingMode.HALF_UP).toDouble
|
||||
} ?~! s"could not create charge for ${body.value.amount}"
|
||||
charge = TransactionRequestCharge("Total charges for completed transaction", AmountOfMoney(body.value.currency, chargeValue.toString()))
|
||||
|
||||
transactionRequest <- TransactionRequests.transactionRequestProvider.vend.createTransactionRequestImpl(
|
||||
TransactionRequestId(generateUUID()),
|
||||
transactionRequestType,
|
||||
fromAccount,
|
||||
toAccount,
|
||||
body,
|
||||
status.toString,
|
||||
charge
|
||||
)
|
||||
} yield transactionRequest
|
||||
|
||||
//make sure we get something back
|
||||
var result = request.openOrThrowException("Exception: Couldn't create transactionRequest")
|
||||
|
||||
// If no challenge necessary, create Transaction immediately and put in data store and object to return
|
||||
if (status == TransactionRequestStatus.COMPLETED) {
|
||||
// Note for 'new MappedCounterparty()' in the following :
|
||||
// We update the makePaymentImpl in V210, added the new parameter 'toCounterparty: CounterpartyTrait' for V210
|
||||
// But in V200 or before, we do not used the new parameter toCounterparty. So just keep it empty.
|
||||
val createdTransactionId = Connector.connector.vend.makePaymentv200(fromAccount,
|
||||
toAccount,
|
||||
transactionRequestCommonBody = null, //Note chargePolicy only support in V210
|
||||
BigDecimal(body.value.amount),
|
||||
body.description,
|
||||
transactionRequestType,
|
||||
"",
|
||||
callContext) //Note chargePolicy only support in V210
|
||||
|
||||
//set challenge to null
|
||||
result = result.copy(challenge = null)
|
||||
|
||||
//save transaction_id if we have one
|
||||
createdTransactionId match {
|
||||
case Full(ti) => {
|
||||
if (!createdTransactionId.isEmpty) {
|
||||
saveTransactionRequestTransaction(result.id, ti, callContext)
|
||||
result = result.copy(transaction_ids = ti.value)
|
||||
}
|
||||
}
|
||||
case Failure(message, exception, chain) => return Failure(message, exception, chain)
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
//if challenge necessary, create a new one
|
||||
val challenge = TransactionRequestChallenge(id = generateUUID(), allowed_attempts = 3, challenge_type = ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString)
|
||||
saveTransactionRequestChallenge(result.id, challenge, callContext)
|
||||
result = result.copy(challenge = challenge)
|
||||
}
|
||||
|
||||
Full(result)
|
||||
}
|
||||
|
||||
// Set initial status
|
||||
override def getStatus(challengeThresholdAmount: BigDecimal, transactionRequestCommonBodyAmount: BigDecimal, transactionRequestType: TransactionRequestType, callContext: Option[CallContext]): OBPReturnType[Box[TransactionRequestStatus.Value]] = {
|
||||
Future(Full(
|
||||
@ -4882,73 +4674,6 @@ object LocalMappedConnector extends Connector with MdcLoggable {
|
||||
Full(APIUtil.getPropsValue("transactionRequests_supported_types", "").split(",").map(x => TransactionRequestType(x)).toList)
|
||||
}
|
||||
|
||||
//Note: Now we use validateChallengeAnswer instead, new methods validate over kafka, and move the allowed_attempts guard into API level.
|
||||
//It is only used for V140 and V200, has been deprecated from V210.
|
||||
@deprecated
|
||||
override def answerTransactionRequestChallenge(transReqId: TransactionRequestId, answer: String, callContext: Option[CallContext]) : Box[Boolean]= {
|
||||
val tr = getTransactionRequestImpl(transReqId, None) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
|
||||
|
||||
tr.map(_._1) match {
|
||||
case Full(tr: TransactionRequest) =>
|
||||
if (tr.challenge.allowed_attempts > 0) {
|
||||
if (tr.challenge.challenge_type == ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString) {
|
||||
//check if answer supplied is correct (i.e. for now, TAN -> some number and not empty)
|
||||
for {
|
||||
nonEmpty <- booleanToBox(answer.nonEmpty) ?~ "Need a non-empty answer"
|
||||
answerToNumber <- tryo(BigInt(answer)) ?~! "Need a numeric TAN"
|
||||
positive <- booleanToBox(answerToNumber > 0) ?~ "Need a positive TAN"
|
||||
} yield true
|
||||
|
||||
//TODO: decrease allowed attempts value
|
||||
}
|
||||
//else if (tr.challenge.challenge_type == ...) {}
|
||||
else {
|
||||
Failure("unknown challenge type")
|
||||
}
|
||||
} else {
|
||||
Failure("Sorry, you've used up your allowed attempts.")
|
||||
}
|
||||
case Failure(f, Empty, Empty) => Failure(f)
|
||||
case _ => Failure("Error getting Transaction Request")
|
||||
}
|
||||
}
|
||||
|
||||
override def createTransactionAfterChallenge(initiator: User, transReqId: TransactionRequestId, callContext: Option[CallContext]): Box[TransactionRequest] = {
|
||||
for {
|
||||
(tr, callContext) <- getTransactionRequestImpl(transReqId, None) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
|
||||
transId <- makePayment(initiator, BankIdAccountId(BankId(tr.from.bank_id), AccountId(tr.from.account_id)),
|
||||
BankIdAccountId(BankId(tr.body.to_sandbox_tan.get.bank_id), AccountId(tr.body.to_sandbox_tan.get.account_id)), BigDecimal(tr.body.value.amount), tr.body.description, TransactionRequestType(tr.`type`),
|
||||
callContext) ?~! InvalidConnectorResponseForMakePayment
|
||||
_ = saveTransactionRequestTransaction(transReqId, transId, callContext)
|
||||
_ = NewStyle.function.saveTransactionRequestStatusImpl(transReqId, TransactionRequestStatus.COMPLETED.toString, callContext)
|
||||
//get transaction request again now with updated values
|
||||
(tr, callContext) <- getTransactionRequestImpl(transReqId, None) ?~! s"${ErrorMessages.InvalidTransactionRequestId} : $transReqId"
|
||||
} yield {
|
||||
tr
|
||||
}
|
||||
}
|
||||
|
||||
override def createTransactionAfterChallengev200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]): Box[TransactionRequest] = {
|
||||
for {
|
||||
transRequestId <- Full(transactionRequest.id)
|
||||
transactionId <- makePaymentv200(
|
||||
fromAccount,
|
||||
toAccount,
|
||||
transactionRequestCommonBody = null, //Note transactionRequestCommonBody started to use from V210
|
||||
BigDecimal(transactionRequest.body.value.amount),
|
||||
transactionRequest.body.description,
|
||||
TransactionRequestType(transactionRequest.`type`),
|
||||
"", //Note chargePolicy started to use from V210
|
||||
callContext) ?~! InvalidConnectorResponseForMakePayment
|
||||
_ = saveTransactionRequestTransaction(transRequestId, transactionId, None)
|
||||
_ = NewStyle.function.saveTransactionRequestStatusImpl(transRequestId, TransactionRequestStatus.COMPLETED.toString, None)
|
||||
|
||||
transactionRequestUpdated <- Full(transactionRequest.copy(transaction_ids = transactionId.value, status = TransactionRequestStatus.COMPLETED.toString))
|
||||
} yield {
|
||||
transactionRequestUpdated
|
||||
}
|
||||
}
|
||||
|
||||
override def createTransactionAfterChallengeV210(fromAccount: BankAccount, transactionRequest: TransactionRequest, callContext: Option[CallContext]): OBPReturnType[Box[TransactionRequest]] = {
|
||||
for {
|
||||
body <- Future(transactionRequest.body)
|
||||
|
||||
@ -578,28 +578,6 @@ object LocalMappedConnectorInternal extends MdcLoggable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def makePaymentImpl(fromAccount: BankAccount,
|
||||
toAccount: BankAccount,
|
||||
transactionRequestCommonBody: TransactionRequestCommonBodyJSON,
|
||||
amount: BigDecimal,
|
||||
description: String,
|
||||
transactionRequestType: TransactionRequestType,
|
||||
chargePolicy: String,
|
||||
callContext: Option[CallContext]): Box[TransactionId] = {
|
||||
for {
|
||||
//def exchangeRate --> do not return any exception, but it may return NONO there.
|
||||
rate <- Full (fx.exchangeRate(fromAccount.currency, toAccount.currency, Some(fromAccount.bankId.value), callContext))
|
||||
_ <- booleanToBox(rate.isDefined) ?~! s"$InvalidCurrency The requested currency conversion (${fromAccount.currency} to ${fromAccount.currency}) is not supported."
|
||||
|
||||
fromTransAmt = -amount //from fromAccount balance should decrease
|
||||
toTransAmt = fx.convert(amount, rate)
|
||||
sentTransactionId <- saveTransaction(fromAccount, toAccount, transactionRequestCommonBody, fromTransAmt, description, transactionRequestType, chargePolicy)
|
||||
_sentTransactionId <- saveTransaction(toAccount, fromAccount, transactionRequestCommonBody, toTransAmt, description, transactionRequestType, chargePolicy)
|
||||
} yield {
|
||||
sentTransactionId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a transaction with @amount, @toAccount and @transactionRequestType for @fromAccount and @toCounterparty. <br>
|
||||
|
||||
@ -1,576 +0,0 @@
|
||||
package code.api.v1_4_0
|
||||
|
||||
import code.api.Constant._
|
||||
import code.api.util.APIUtil
|
||||
import code.api.util.APIUtil.OAuth._
|
||||
import com.openbankproject.commons.model.AmountOfMoneyJsonV121
|
||||
import code.api.v1_4_0.JSONFactory1_4_0._
|
||||
import code.bankconnectors.Connector
|
||||
import code.setup.DefaultUsers
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestTypes._
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestTypes
|
||||
import com.openbankproject.commons.model.enums.{ChallengeType, TransactionRequestStatus}
|
||||
import net.liftweb.json.JsonAST.JString
|
||||
import net.liftweb.json.Serialization.write
|
||||
import org.scalatest.Tag
|
||||
import code.api.util.ErrorMessages._
|
||||
import code.model.BankAccountX
|
||||
import com.openbankproject.commons.model.enums.TransactionRequestTypes.SANDBOX_TAN
|
||||
import com.openbankproject.commons.model.{AccountId, BankAccount, TransactionRequestId}
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
class TransactionRequestsTest extends V140ServerSetup with DefaultUsers {
|
||||
|
||||
object TransactionRequest extends Tag("transactionRequests")
|
||||
|
||||
feature("we can make transaction requests") {
|
||||
val view = SYSTEM_OWNER_VIEW_ID
|
||||
|
||||
def transactionCount(accounts: BankAccount*) : Int = {
|
||||
accounts.foldLeft(0)((accumulator, account) => {
|
||||
//TODO: might be nice to avoid direct use of the connector, but if we use an api call we need to do
|
||||
//it with the correct account owners, and be sure that we don't even run into pagination problems
|
||||
accumulator + Connector.connector.vend.getTransactionsLegacy(account.bankId, account.accountId, None).map(_._1).openOrThrowException(attemptedToOpenAnEmptyBox).size
|
||||
})
|
||||
}
|
||||
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
|
||||
ignore("we create a transaction request without challenge", TransactionRequest) {}
|
||||
} else {
|
||||
scenario("we create a transaction request without challenge", TransactionRequest) {
|
||||
val testBank = createBank("transactions-test-bank")
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
createAccountRelevantResource(Some(resourceUser1), bankId, accountId1, "EUR")
|
||||
createAccountRelevantResource(Some(resourceUser1), bankId, accountId2, "EUR")
|
||||
|
||||
def getFromAccount: BankAccount = {
|
||||
BankAccountX(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount: BankAccount = {
|
||||
BankAccountX(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
//Create a transaction (request)
|
||||
//1. get possible challenge types for from account
|
||||
//2. create transaction request to to-account with one of the possible challenges
|
||||
//3. answer challenge
|
||||
//4. have a new transaction
|
||||
|
||||
val transactionRequestId = TransactionRequestId("__trans1")
|
||||
val toAccountJson = TransactionRequestAccountJsonV140(toAccount.bankId.value, toAccount.accountId.value)
|
||||
|
||||
val amt = BigDecimal("12.50")
|
||||
val bodyValue = AmountOfMoneyJsonV121("EUR", amt.toString())
|
||||
val transactionRequestBody = TransactionRequestBodyJsonV140(toAccountJson, bodyValue, "Test Transaction Request description", "")
|
||||
|
||||
//call createTransactionRequest
|
||||
var request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests").POST <@(user1)
|
||||
var response = makePostRequest(request, write(transactionRequestBody))
|
||||
Then("we should get a 201 created code")
|
||||
response.code should equal(201)
|
||||
|
||||
//created a transaction request, check some return values. As type is SANDBOX_TAN, we expect no challenge
|
||||
val transId: String = (response.body \ "id" \ "value") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
Then("We should have some new transaction id")
|
||||
transId should not equal ("")
|
||||
|
||||
val status: String = (response.body \ "status") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
status should equal (TransactionRequestStatus.COMPLETED.toString)
|
||||
|
||||
var challenge = (response.body \ "challenge").children
|
||||
challenge.size should equal(0)
|
||||
|
||||
var transaction_id = (response.body \ "transaction_ids") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transaction_id should not equal("")
|
||||
|
||||
//call getTransactionRequests, check that we really created a transaction request
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-requests").GET <@(user1)
|
||||
response = makeGetRequest(request)
|
||||
|
||||
Then("we should get a 200 ok code")
|
||||
response.code should equal(200)
|
||||
val transactionRequests = response.body.children
|
||||
transactionRequests.size should not equal(0)
|
||||
|
||||
//check transaction_ids again
|
||||
transaction_id = (response.body \ "transaction_ids") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transaction_id should not equal("")
|
||||
|
||||
//make sure that we also get no challenges back from this url (after getting from db)
|
||||
challenge = (response.body \ "challenge").children
|
||||
challenge.size should equal(0)
|
||||
|
||||
//check that we created a new transaction (since no challenge)
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transactions").GET <@(user1)
|
||||
response = makeGetRequest(request, List(("from_date", APIUtil.epochTimeString),("to_date", APIUtil.DefaultToDateString)))
|
||||
|
||||
Then("we should get a 200 ok code")
|
||||
response.code should equal(200)
|
||||
val transactions = response.body.children
|
||||
|
||||
transactions.size should equal(1)
|
||||
|
||||
//check that the description has been set
|
||||
val description = (((response.body \ "transactions")(0) \ "details") \ "description") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
description should not equal ("")
|
||||
|
||||
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
|
||||
//(do it here even though the payments test does test makePayment already)
|
||||
|
||||
val fromAccountBalance = getFromAccount.balance
|
||||
And("the from account should have a balance smaller by the amount specified to pay")
|
||||
fromAccountBalance should equal((beforeFromBalance - amt))
|
||||
|
||||
/*
|
||||
And("the newest transaction for the account receiving the payment should have the proper amount")
|
||||
newestToAccountTransaction.details.value.amount should equal(amt.toString)
|
||||
*/
|
||||
|
||||
And("the account receiving the payment should have a new balance plus the amount paid")
|
||||
val toAccountBalance = getToAccount.balance
|
||||
toAccountBalance should equal(beforeToBalance + amt)
|
||||
|
||||
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
|
||||
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
|
||||
}
|
||||
}
|
||||
|
||||
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
|
||||
ignore("we create a transaction request with a challenge", TransactionRequest) {}
|
||||
} else {
|
||||
scenario("we create a transaction request with a challenge", TransactionRequest) {
|
||||
//setup accounts
|
||||
val testBank = createBank("transactions-test-bank")
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
createAccountRelevantResource(Some(resourceUser1), bankId, accountId1, "EUR")
|
||||
createAccountRelevantResource(Some(resourceUser1), bankId, accountId2, "EUR")
|
||||
|
||||
def getFromAccount: BankAccount = {
|
||||
BankAccountX(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount: BankAccount = {
|
||||
BankAccountX(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
val transactionRequestId = TransactionRequestId("__trans1")
|
||||
val toAccountJson = TransactionRequestAccountJsonV140(toAccount.bankId.value, toAccount.accountId.value)
|
||||
|
||||
//1. TODO: get possible challenge types from account
|
||||
|
||||
//2. create transaction request to to-account with one of the possible challenges
|
||||
|
||||
//amount over 100 €, so should trigger challenge request
|
||||
val amt = BigDecimal("1250.00")
|
||||
val bodyValue = AmountOfMoneyJsonV121("EUR", amt.toString())
|
||||
val transactionRequestBody = TransactionRequestBodyJsonV140(toAccountJson, bodyValue, "Test Transaction Request description", ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString)
|
||||
|
||||
//call createTransactionRequest API method
|
||||
var request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests").POST <@ (user1)
|
||||
var response = makePostRequest(request, write(transactionRequestBody))
|
||||
Then("we should get a 201 created code")
|
||||
response.code should equal(201)
|
||||
|
||||
//ok, created a transaction request, check some return values. As type is SANDBOX_TAN but over 100€, we expect a challenge
|
||||
val transId: String = (response.body \ "id" \ "value") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transId should not equal ("")
|
||||
|
||||
var status: String = (response.body \ "status") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
status should equal(TransactionRequestStatus.INITIATED.toString)
|
||||
|
||||
var transaction_id = (response.body \ "transaction_ids") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transaction_id should equal ("")
|
||||
|
||||
var challenge = (response.body \ "challenge").children
|
||||
challenge.size should not equal(0)
|
||||
|
||||
val challenge_id = (response.body \ "challenge" \ "id") match {
|
||||
case JString(s) => s
|
||||
case _ => ""
|
||||
}
|
||||
challenge_id should not equal("")
|
||||
|
||||
//call getTransactionRequests, check that we really created a transaction request
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-requests").GET <@ (user1)
|
||||
response = makeGetRequest(request)
|
||||
|
||||
Then("we should get a 200 ok code")
|
||||
response.code should equal(200)
|
||||
var transactionRequests = response.body.children
|
||||
|
||||
transactionRequests.size should equal(1)
|
||||
transaction_id = (response.body \ "transaction_ids") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transaction_id should equal ("")
|
||||
|
||||
challenge = (response.body \ "challenge").children
|
||||
challenge.size should not equal(0)
|
||||
|
||||
//3. answer challenge and check if transaction is being created
|
||||
//call answerTransactionRequestChallenge, give a false answer
|
||||
var answerJson = ChallengeAnswerJSON(id = challenge_id, answer = "hello") //wrong answer, not a number
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests" / transId / "challenge").POST <@ (user1)
|
||||
response = makePostRequest(request, write(answerJson))
|
||||
Then("we should get a 400 bad request code")
|
||||
response.code should equal(400)
|
||||
|
||||
//TODO: check if allowed_attempts is decreased
|
||||
|
||||
//call answerTransactionRequestChallenge again, give a good answer
|
||||
answerJson = ChallengeAnswerJSON(id = challenge_id, answer = "12345") //wrong answer, not a number
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-request-types" / SANDBOX_TAN.toString / "transaction-requests" / transId / "challenge").POST <@ (user1)
|
||||
response = makePostRequest(request, write(answerJson))
|
||||
Then("we should get a 202 accepted code")
|
||||
response.code should equal(202)
|
||||
|
||||
//check if returned data includes new transaction's id
|
||||
status = (response.body \ "status") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
status should equal(TransactionRequestStatus.COMPLETED.toString)
|
||||
|
||||
transaction_id = (response.body \ "transaction_ids") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transaction_id should not equal ("")
|
||||
|
||||
//call getTransactionRequests, check that we really created a transaction
|
||||
request = (v1_4Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
|
||||
SYSTEM_OWNER_VIEW_ID / "transaction-requests").GET <@ (user1)
|
||||
response = makeGetRequest(request)
|
||||
|
||||
Then("we should get a 200 ok code")
|
||||
response.code should equal(200)
|
||||
transactionRequests = response.body.children
|
||||
|
||||
transactionRequests.size should equal(1)
|
||||
transaction_id = (response.body \ "transaction_ids") match {
|
||||
case JString(i) => i
|
||||
case _ => ""
|
||||
}
|
||||
transaction_id should not equal ("")
|
||||
|
||||
challenge = (response.body \ "challenge").children
|
||||
challenge.size should not equal(0)
|
||||
|
||||
//check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least)
|
||||
//(do it here even though the payments test does test makePayment already)
|
||||
|
||||
val fromAccountBalance = getFromAccount.balance
|
||||
And("the from account should have a balance smaller by the amount specified to pay")
|
||||
fromAccountBalance should equal((beforeFromBalance - amt))
|
||||
|
||||
/*
|
||||
And("the newest transaction for the account receiving the payment should have the proper amount")
|
||||
newestToAccountTransaction.details.value.amount should equal(amt.toString)
|
||||
*/
|
||||
|
||||
And("the account receiving the payment should have a new balance plus the amount paid")
|
||||
val toAccountBalance = getToAccount.balance
|
||||
toAccountBalance should equal(beforeToBalance + amt)
|
||||
|
||||
And("there should now be 2 new transactions in the database (one for the sender, one for the receiver")
|
||||
transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2)
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO Make this tests functional
|
||||
scenario("we can't make a payment without access to the owner view", Payments) {
|
||||
val testBank = createPaymentTestBank()
|
||||
val bankId = testBank.bankId
|
||||
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
|
||||
|
||||
def getFromAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
val amt = BigDecimal("12.33")
|
||||
|
||||
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
|
||||
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user2)
|
||||
|
||||
Then("we should get a 400")
|
||||
postResult.code should equal(400)
|
||||
|
||||
And("the number of transactions for each account should remain unchanged")
|
||||
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
|
||||
|
||||
And("the balances of each account should remain unchanged")
|
||||
beforeFromBalance should equal(getFromAccount.balance)
|
||||
beforeToBalance should equal(getToAccount.balance)
|
||||
}
|
||||
|
||||
scenario("we can't make a payment without an oauth user", Payments) {
|
||||
val testBank = createPaymentTestBank()
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
|
||||
|
||||
def getFromAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
val amt = BigDecimal("12.33")
|
||||
|
||||
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
|
||||
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, None)
|
||||
|
||||
Then("we should get a 400")
|
||||
postResult.code should equal(400)
|
||||
|
||||
And("the number of transactions for each account should remain unchanged")
|
||||
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
|
||||
|
||||
And("the balances of each account should remain unchanged")
|
||||
beforeFromBalance should equal(getFromAccount.balance)
|
||||
beforeToBalance should equal(getToAccount.balance)
|
||||
}
|
||||
|
||||
scenario("we can't make a payment of zero units of currency", Payments) {
|
||||
When("we try to make a payment with amount = 0")
|
||||
|
||||
val testBank = createPaymentTestBank()
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
|
||||
|
||||
def getFromAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
val amt = BigDecimal("0")
|
||||
|
||||
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
|
||||
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
|
||||
|
||||
Then("we should get a 400")
|
||||
postResult.code should equal(400)
|
||||
|
||||
And("the number of transactions for each account should remain unchanged")
|
||||
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
|
||||
|
||||
And("the balances of each account should remain unchanged")
|
||||
beforeFromBalance should equal(getFromAccount.balance)
|
||||
beforeToBalance should equal(getToAccount.balance)
|
||||
}
|
||||
|
||||
scenario("we can't make a payment with a negative amount of money", Payments) {
|
||||
|
||||
val testBank = createPaymentTestBank()
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
val acc1 = createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
|
||||
val acc2 = createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "EUR")
|
||||
|
||||
When("we try to make a payment with amount < 0")
|
||||
|
||||
def getFromAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
val amt = BigDecimal("-20.30")
|
||||
|
||||
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
|
||||
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
|
||||
|
||||
Then("we should get a 400")
|
||||
postResult.code should equal(400)
|
||||
|
||||
And("the number of transactions for each account should remain unchanged")
|
||||
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
|
||||
|
||||
And("the balances of each account should remain unchanged")
|
||||
beforeFromBalance should equal(getFromAccount.balance)
|
||||
beforeToBalance should equal(getToAccount.balance)
|
||||
}
|
||||
|
||||
scenario("we can't make a payment to an account that doesn't exist", Payments) {
|
||||
|
||||
val testBank = createPaymentTestBank()
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val acc1 = createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
|
||||
|
||||
When("we try to make a payment to an account that doesn't exist")
|
||||
|
||||
def getFromAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
|
||||
val amt = BigDecimal("17.30")
|
||||
|
||||
val payJson = MakePaymentJson(bankId.value, "ACCOUNTTHATDOESNOTEXIST232321321", amt.toString)
|
||||
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
|
||||
|
||||
Then("we should get a 400")
|
||||
postResult.code should equal(400)
|
||||
|
||||
And("the number of transactions for the sender's account should remain unchanged")
|
||||
totalTransactionsBefore should equal(transactionCount(fromAccount))
|
||||
|
||||
And("the balance of the sender's account should remain unchanged")
|
||||
beforeFromBalance should equal(getFromAccount.balance)
|
||||
}
|
||||
|
||||
scenario("we can't make a payment between accounts with different currencies", Payments) {
|
||||
When("we try to make a payment to an account that has a different currency")
|
||||
val testBank = createPaymentTestBank()
|
||||
val bankId = testBank.bankId
|
||||
val accountId1 = AccountId("__acc1")
|
||||
val accountId2 = AccountId("__acc2")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId1, "EUR")
|
||||
createAccountAndOwnerView(Some(authuser1), bankId, accountId2, "GBP")
|
||||
|
||||
def getFromAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId1).getOrElse(fail("couldn't get from account"))
|
||||
}
|
||||
|
||||
def getToAccount : BankAccount = {
|
||||
BankAccount(bankId, accountId2).getOrElse(fail("couldn't get to account"))
|
||||
}
|
||||
|
||||
val fromAccount = getFromAccount
|
||||
val toAccount = getToAccount
|
||||
|
||||
val totalTransactionsBefore = transactionCount(fromAccount, toAccount)
|
||||
|
||||
val beforeFromBalance = fromAccount.balance
|
||||
val beforeToBalance = toAccount.balance
|
||||
|
||||
val amt = BigDecimal("4.95")
|
||||
|
||||
val payJson = MakePaymentJson(toAccount.bankId.value, toAccount.accountId.value, amt.toString)
|
||||
val postResult = postTransaction(fromAccount.bankId.value, fromAccount.accountId.value, view, payJson, user1)
|
||||
|
||||
Then("we should get a 400")
|
||||
postResult.code should equal(400)
|
||||
|
||||
And("the number of transactions for each account should remain unchanged")
|
||||
totalTransactionsBefore should equal(transactionCount(fromAccount, toAccount))
|
||||
|
||||
And("the balances of each account should remain unchanged")
|
||||
beforeFromBalance should equal(getFromAccount.balance)
|
||||
beforeToBalance should equal(getToAccount.balance)
|
||||
} */
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1054,26 +1054,13 @@ case class InBoundGetPhysicalCardsForBankLegacy(status: Status, data: List[Physi
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundMakePayment(outboundAdapterCallContext: OutboundAdapterCallContext,initiator: User, fromAccountUID: BankIdAccountId, toAccountUID: BankIdAccountId, amt: BigDecimal, description: String, transactionRequestType: TransactionRequestType) extends TopicTrait
|
||||
case class InBoundMakePayment(status: Status, data: TransactionId) extends InBoundTrait[TransactionId] {
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundMakePaymentv200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequestCommonBody: TransactionRequestCommonBodyJSON, amount: BigDecimal, description: String, transactionRequestType: TransactionRequestType, chargePolicy: String) extends TopicTrait
|
||||
case class InBoundMakePaymentv200(status: Status, data: TransactionId) extends InBoundTrait[TransactionId] {
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundCreateTransactionRequest(outboundAdapterCallContext: OutboundAdapterCallContext,initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody) extends TopicTrait
|
||||
case class InBoundCreateTransactionRequest(status: Status, data: TransactionRequest) extends InBoundTrait[TransactionRequest] {
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundCreateTransactionRequestv200(outboundAdapterCallContext: OutboundAdapterCallContext,initiator: User, fromAccount: BankAccount, toAccount: BankAccount, transactionRequestType: TransactionRequestType, body: TransactionRequestBody) extends TopicTrait
|
||||
case class InBoundCreateTransactionRequestv200(status: Status, data: TransactionRequest) extends InBoundTrait[TransactionRequest] {
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundGetStatus(challengeThresholdAmount: BigDecimal, transactionRequestCommonBodyAmount: BigDecimal, transactionRequestType: TransactionRequestType) extends TopicTrait
|
||||
case class InBoundGetStatus(status: Status, statusValue: String) extends InBoundTrait[TransactionRequestStatus.Value] {
|
||||
|
||||
@ -1164,11 +1151,6 @@ case class InBoundCreateTransactionAfterChallenge(status: Status, data: Transact
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundCreateTransactionAfterChallengev200(fromAccount: BankAccount, toAccount: BankAccount, transactionRequest: TransactionRequest) extends TopicTrait
|
||||
case class InBoundCreateTransactionAfterChallengev200(status: Status, data: TransactionRequest) extends InBoundTrait[TransactionRequest] {
|
||||
override val inboundAdapterCallContext: InboundAdapterCallContext = InboundAdapterCallContext()
|
||||
}
|
||||
|
||||
case class OutBoundAddBankAccount(outboundAdapterCallContext: OutboundAdapterCallContext, bankId: BankId, accountType: String, accountLabel: String, currency: String, initialBalance: BigDecimal, accountHolderName: String, branchId: String, accountRoutings: List[AccountRouting]) extends TopicTrait
|
||||
case class InBoundAddBankAccount(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: BankAccountCommons) extends InBoundTrait[BankAccountCommons]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user