diff --git a/pom.xml b/pom.xml index 758b7c56b..dd57c046f 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ org.postgresql postgresql - 9.4-1206-jdbc4 + 9.4.1211 com.h2database diff --git a/src/main/scala/code/api/util/APIUtil.scala b/src/main/scala/code/api/util/APIUtil.scala index e30653427..fa729f8d9 100644 --- a/src/main/scala/code/api/util/APIUtil.scala +++ b/src/main/scala/code/api/util/APIUtil.scala @@ -93,6 +93,7 @@ object ErrorMessages { val CustomerNumberAlreadyExists = "OBP-30006: Customer Number already exists. Please specify a different value for BANK_ID or CUSTOMER_NUMBER." val CustomerAlreadyExistsForUser = "OBP-30007: The User is already linked to a Customer at the bank specified by BANK_ID" val CustomerDoNotExistsForUser = "OBP-30008: User is not linked to a Customer at the bank specified by BANK_ID" + val CounterpartyNotFoundByIban = "OBP-30009: Counterparty not found. The IBan specified does not exist on this server." val MeetingsNotSupported = "OBP-30101: Meetings are not supported on this server." val MeetingApiKeyNotConfigured = "OBP-30102: Meeting provider API Key is not configured." diff --git a/src/main/scala/code/api/v2_1_0/APIMethods210.scala b/src/main/scala/code/api/v2_1_0/APIMethods210.scala index 3d85908ed..831d5d84a 100644 --- a/src/main/scala/code/api/v2_1_0/APIMethods210.scala +++ b/src/main/scala/code/api/v2_1_0/APIMethods210.scala @@ -14,16 +14,19 @@ import code.api.v2_1_0.JSONFactory210._ import code.bankconnectors.Connector import code.entitlement.Entitlement import code.fx.fx +import code.metadata.counterparties.MappedCounterpartyMetadata import code.model.dataAccess.OBPUser import code.model.{BankId, _} import net.liftweb.http.{CurrentReq, Req} import net.liftweb.json.Extraction import net.liftweb.json.JsonAST.JValue +import net.liftweb.json.Serialization._ import net.liftweb.mapper.By import net.liftweb.util.Props import scala.collection.immutable.Nil import scala.collection.mutable.ArrayBuffer + // Makes JValue assignment to Nil work import code.util.Helper._ import net.liftweb.json.JsonDSL._ @@ -44,7 +47,6 @@ import net.liftweb.json.Serialization.{read, write} trait APIMethods210 { //needs to be a RestHelper to get access to JsonGet, JsonPost, etc. self: RestHelper => - // helper methods begin here // helper methods end here @@ -214,8 +216,8 @@ trait APIMethods210 { | |""", Extraction.decompose(TransactionRequestBodyJSON ( - TransactionRequestAccountJSON("BANK_ID", "ACCOUNT_ID"), - AmountOfMoneyJSON("EUR", "100.53"), + TransactionRequestAccountJSON("bank_id", "account_id"), + AmountOfMoneyJSON("eur", "100.53"), "A description for the transaction to be created" ) ), @@ -277,16 +279,7 @@ trait APIMethods210 { // Prevent default value for transaction request type (at least). transferCurrencyEqual <- tryo(assert(transDetailsJson.value.currency == fromAccount.currency)) ?~! {s"${ErrorMessages.InvalidTransactionRequestCurrency} From Account Currency is ${fromAccount.currency} Requested Transaction Currency is: ${transDetailsJson.value.currency}"} - transDetailsSerialized <- transactionRequestType.value match { - case "FREE_FORM" => tryo{ - implicit val formats = Serialization.formats(NoTypeHints) - write(json) - } - case _ => tryo{ - implicit val formats = Serialization.formats(NoTypeHints) - write(transDetailsJson) - } - } + amountOfMoneyJSON <- Full(AmountOfMoneyJSON(transDetails.value.currency, transDetails.value.amount)) // Note: These store in the table TransactionRequestv210 createdTransactionRequest <- transactionRequestType.value match { @@ -295,16 +288,52 @@ trait APIMethods210 { toBankId <- Full(BankId(transDetailsJson.asInstanceOf[TransactionRequestDetailsSandBoxTanJSON].to.bank_id)) toAccountId <- Full(AccountId(transDetailsJson.asInstanceOf[TransactionRequestDetailsSandBoxTanJSON].to.account_id)) toAccount <- BankAccount(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound} - + transDetailsSerialized <- tryo { + implicit val formats = Serialization.formats(NoTypeHints) + write(json) + } createdTransactionRequest <- Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Full(toAccount), transactionRequestType, transDetails, transDetailsSerialized) } yield createdTransactionRequest } case "SEPA" => { - Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Empty, transactionRequestType, transDetails, transDetailsSerialized) + for { + //for SEPA, the user do not send the Bank_ID and Acound_ID,so this will search for the bank firstly. + toIban<- Full(transDetailsJson.asInstanceOf[TransactionRequestDetailsSEPAJSON].iban) + mappedCounterpartyMetadata <- MappedCounterpartyMetadata.find(By(MappedCounterpartyMetadata.accountNumber, toIban)) ?~! {ErrorMessages.CounterpartyNotFoundByIban} + toBankId <- Full(BankId(mappedCounterpartyMetadata.thisAccountBankId)) + toAccountId <- Full(AccountId(mappedCounterpartyMetadata.thisAccountId)) + toAccount <- BankAccount(toBankId, toAccountId) ?~! {ErrorMessages.CounterpartyNotFound} + + // Following four lines: just transfer the details body ,add Bank_Id and Account_Id in the Detail part. + transactionRequestAccountJSON = TransactionRequestAccountJSON(toBankId.value, toAccountId.value) + detailDescription = transDetailsJson.asInstanceOf[TransactionRequestDetailsSEPAJSON].description + transactionRequestDetailsAddedTobankSEPAJSON = TransactionRequestDetailsSEPAResponseJSON(toIban.toString,transactionRequestAccountJSON, amountOfMoneyJSON, detailDescription.toString) + transResponseDetails = getTransactionRequestDetailsAddedTobankSEPAJSONFromJson(transactionRequestDetailsAddedTobankSEPAJSON) + + //Serialize the new format SEPA data. + transDetailsResponseSerialized <-tryo{ + implicit val formats = Serialization.formats(NoTypeHints) + write(transResponseDetails) + } + createdTransactionRequest <- Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Full(toAccount), transactionRequestType, transResponseDetails, transDetailsResponseSerialized) + } yield createdTransactionRequest } case "FREE_FORM" => { - Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Empty, transactionRequestType, transDetails, transDetailsSerialized) + for { + // Following three lines: just transfer the details body ,add Bank_Id and Account_Id in the Detail part. + transactionRequestAccountJSON <- Full(TransactionRequestAccountJSON(fromAccount.bankId.value, fromAccount.accountId.value)) + // the Free form the discription is empty, so make it "" in the following code + transactionRequestDetailsFreeFormResponseJSON = TransactionRequestDetailsFreeFormResponseJSON(transactionRequestAccountJSON,amountOfMoneyJSON,"") + transResponseDetails <- Full(getTransactionRequestDetailsFreeFormAddedTobankJson(transactionRequestDetailsFreeFormResponseJSON)) + + transDetailsResponseSerialized<-tryo{ + implicit val formats = Serialization.formats(NoTypeHints) + write(transResponseDetails) + } + createdTransactionRequest <- Connector.connector.vend.createTransactionRequestv210(u, fromAccount, Full(fromAccount), transactionRequestType, transResponseDetails, transDetailsResponseSerialized) + } yield + createdTransactionRequest } } } yield { @@ -609,7 +638,7 @@ trait APIMethods210 { u <- user ?~! ErrorMessages.UserNotLoggedIn putData <- tryo{json.extract[PutEnabledJSON]} ?~! ErrorMessages.InvalidJsonFormat hasEntitlement <- putData.enabled match { - case true => booleanToBox(hasEntitlement("", u.userId, ApiRole.CanEnableConsumers), s"$CanEnableConsumers entitlement required") + case true => booleanToBox(hasEntitlement("", u.userId, ApiRole.CanEnableConsumers), s"$CanEnableConsumers entitlement required") case false => booleanToBox(hasEntitlement("", u.userId, ApiRole.CanDisableConsumers), s"$CanDisableConsumers entitlement required") } consumer <- Consumer.find(By(Consumer.id, consumerId.toLong)) @@ -624,7 +653,6 @@ trait APIMethods210 { } - resourceDocs += ResourceDoc( addCardsForBank, apiVersion, @@ -758,7 +786,7 @@ trait APIMethods210 { | * charge : The charge to the customer for each one of these | |${authenticationRequiredMessage(getTransactionTypesIsPublic)}""", - Extraction.decompose(TransactionTypeJSON(TransactionTypeId("wuwjfuha234678"), "1", "2", "3", "4", AmountOfMoneyJSON("EUR", "123"))), + Extraction.decompose(TransactionTypeJSON(TransactionTypeId("wuwjfuha234678"), "1", "2", "3", "4", AmountOfMoneyJSON("eur", "123"))), emptyObjectJson, emptyObjectJson :: Nil, false, @@ -768,7 +796,6 @@ trait APIMethods210 { ) - lazy val createTransactionType: PartialFunction[Req, Box[User] => Box[JsonResponse]] = { case "banks" :: BankId(bankId) :: "transaction-types" :: Nil JsonPut json -> _ => { user => { diff --git a/src/main/scala/code/api/v2_1_0/JSONFactory2.1.0.scala b/src/main/scala/code/api/v2_1_0/JSONFactory2.1.0.scala index d1fae985a..8b82e4f5e 100644 --- a/src/main/scala/code/api/v2_1_0/JSONFactory2.1.0.scala +++ b/src/main/scala/code/api/v2_1_0/JSONFactory2.1.0.scala @@ -37,7 +37,7 @@ import code.api.util.ApiRole import code.api.v1_2_1.AmountOfMoneyJSON import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeJSON, TransactionRequestAccountJSON} import code.api.v2_0_0.TransactionRequestChargeJSON -import code.model.{AmountOfMoney, Consumer} +import code.model.{AmountOfMoney, Consumer, CounterpartyMetadataIban, Iban} import code.transactionrequests.TransactionRequests._ import net.liftweb.json.JValue @@ -58,15 +58,30 @@ case class TransactionRequestDetailsSandBoxTanJSON( ) extends TransactionRequestDetailsJSON case class TransactionRequestDetailsSEPAJSON( - value : AmountOfMoneyJSON, - IBAN: String, - description : String - ) extends TransactionRequestDetailsJSON + value: AmountOfMoneyJSON, + iban: String, + description: String + ) extends TransactionRequestDetailsJSON + +case class TransactionRequestDetailsSEPAResponseJSON( + iban: String, + to: TransactionRequestAccountJSON, + value: AmountOfMoneyJSON, + description: String + ) extends TransactionRequestDetailsJSON case class TransactionRequestDetailsFreeFormJSON( value : AmountOfMoneyJSON ) extends TransactionRequestDetailsJSON +case class TransactionRequestDetailsFreeFormResponseJSON( + to: TransactionRequestAccountJSON, + value: AmountOfMoneyJSON, + description: String + ) extends TransactionRequestDetailsJSON + + + case class TransactionRequestWithChargeJSON210( id: String, `type`: String, @@ -135,12 +150,35 @@ object JSONFactory210{ } def getTransactionRequestDetailsSEPAFromJson(details: TransactionRequestDetailsSEPAJSON) : TransactionRequestDetailsSEPA = { + val toAccIban = Iban ( + iban = details.iban + ) val amount = AmountOfMoney ( currency = details.value.currency, amount = details.value.amount ) - TransactionRequestDetailsSEPA ( + iban = details.iban, + value = amount, + description = details.description + ) + } + + def getTransactionRequestDetailsAddedTobankSEPAJSONFromJson(details: TransactionRequestDetailsSEPAResponseJSON) : TransactionRequestDetailsSEPAResponse = { + val toAcc = TransactionRequestAccount ( + bank_id = details.to.bank_id, + account_id = details.to.account_id + ) + val toAccIban = Iban ( + iban = details.iban + ) + val amount = AmountOfMoney ( + currency = details.value.currency, + amount = details.value.amount + ) + TransactionRequestDetailsSEPAResponse ( + iban = details.iban, + to=toAcc, value = amount, description = details.description ) @@ -157,6 +195,23 @@ object JSONFactory210{ ) } + def getTransactionRequestDetailsFreeFormAddedTobankJson(details: TransactionRequestDetailsFreeFormResponseJSON) : TransactionRequestDetailsFreeFormResponse = { + val toAcc = TransactionRequestAccount ( + bank_id = details.to.bank_id, + account_id = details.to.account_id + ) + val amount = AmountOfMoney ( + currency = details.value.currency, + amount = details.value.amount + ) + TransactionRequestDetailsFreeFormResponse ( + to=toAcc, + value = amount, + description = details.description + ) + } + + /** Creates v2.1.0 representation of a TransactionType * * @param tr An internal TransactionRequest instance diff --git a/src/main/scala/code/bankconnectors/Connector.scala b/src/main/scala/code/bankconnectors/Connector.scala index 0f8b40acd..b29b82144 100644 --- a/src/main/scala/code/bankconnectors/Connector.scala +++ b/src/main/scala/code/bankconnectors/Connector.scala @@ -5,7 +5,7 @@ import java.util.Date import code.api.util.APIUtil._ import code.api.util.ApiRole._ import code.api.util.ErrorMessages -import code.api.v2_1_0.{TransactionRequestDetailsFreeFormJSON, TransactionRequestDetailsSEPAJSON, TransactionRequestDetailsSandBoxTanJSON} +import code.api.v2_1_0.{TransactionRequestDetailsFreeFormJSON, TransactionRequestDetailsSEPAResponseJSON, TransactionRequestDetailsSandBoxTanJSON} import code.fx.fx import code.management.ImporterAPI.ImporterTransaction import code.model.{Transaction, User, _} @@ -266,7 +266,6 @@ trait Connector { //set challenge to null result = Full(result.get.copy(challenge = null)) - //save transaction_id if we have one createdTransactionId match { case Full(ti) => { @@ -395,7 +394,9 @@ trait Connector { case "SANDBOX_TAN" => Connector.connector.vend.makePaymentv200(initiator, BankAccountUID(fromAccount.bankId, fromAccount.accountId), BankAccountUID(toAccount.get.bankId, toAccount.get.accountId), BigDecimal(details.value.amount), details.asInstanceOf[TransactionRequestDetailsSandBoxTan].description) case "SEPA" => Connector.connector.vend.makePaymentv200(initiator, BankAccountUID(fromAccount.bankId, fromAccount.accountId), - BankAccountUID(toAccount.get.bankId, toAccount.get.accountId), BigDecimal(details.value.amount), details.asInstanceOf[TransactionRequestDetailsSandBoxTan].description) + BankAccountUID(toAccount.get.bankId, toAccount.get.accountId), BigDecimal(details.value.amount), details.asInstanceOf[TransactionRequestDetailsSEPAResponse].description) + case "FREE_FORM" => Connector.connector.vend.makePaymentv200(initiator, BankAccountUID(fromAccount.bankId, fromAccount.accountId), + BankAccountUID(toAccount.get.bankId, toAccount.get.accountId), BigDecimal(details.value.amount), "") } //set challenge to null diff --git a/src/main/scala/code/model/BankingData.scala b/src/main/scala/code/model/BankingData.scala index 85de07a13..72e48a0fa 100644 --- a/src/main/scala/code/model/BankingData.scala +++ b/src/main/scala/code/model/BankingData.scala @@ -112,6 +112,12 @@ object BankId { def unapply(id : String) = Some(BankId(id)) } +case class CounterpartyMetadataIban(val value : String) { +override def toString = value +} +object CounterpartyMetadataIban { + def unapply(id : String) = Some(CounterpartyMetadataIban(id)) +} case class CustomerId(val value : String) { override def toString = value @@ -658,4 +664,8 @@ class Transaction( case class AmountOfMoney ( val currency: String, val amount: String +) + +case class Iban( + val iban: String ) \ No newline at end of file diff --git a/src/main/scala/code/transactionrequests/TransactionRequests.scala b/src/main/scala/code/transactionrequests/TransactionRequests.scala index 0ebf3b573..5edc5726b 100644 --- a/src/main/scala/code/transactionrequests/TransactionRequests.scala +++ b/src/main/scala/code/transactionrequests/TransactionRequests.scala @@ -67,14 +67,28 @@ object TransactionRequests extends SimpleInjector { val description : String ) extends TransactionRequestDetails - case class TransactionRequestDetailsSEPA ( - val value : AmountOfMoney, - val description : String - ) extends TransactionRequestDetails + case class TransactionRequestDetailsSEPA( + val iban: String, + val value: AmountOfMoney, + val description: String + ) extends TransactionRequestDetails + case class TransactionRequestDetailsSEPAResponse( + val iban: String, + val to: TransactionRequestAccount, + val value: AmountOfMoney, + val description: String + ) extends TransactionRequestDetails + + case class TransactionRequestDetailsFreeForm( + val value: AmountOfMoney + ) extends TransactionRequestDetails + + case class TransactionRequestDetailsFreeFormResponse( + val to: TransactionRequestAccount, + val value: AmountOfMoney, + val description: String + ) extends TransactionRequestDetails - case class TransactionRequestDetailsFreeForm ( - val value : AmountOfMoney - ) extends TransactionRequestDetails val transactionRequestProvider = new Inject(buildOne _) {} diff --git a/src/test/scala/code/api/LocalMappedConnectorTestSetup.scala b/src/test/scala/code/api/LocalMappedConnectorTestSetup.scala index ac4a3ef5b..9424edfa7 100644 --- a/src/test/scala/code/api/LocalMappedConnectorTestSetup.scala +++ b/src/test/scala/code/api/LocalMappedConnectorTestSetup.scala @@ -9,6 +9,7 @@ import net.liftweb.common.Box import net.liftweb.mapper.MetaMapper import net.liftweb.util.Helpers._ import code.entitlement.{Entitlement, MappedEntitlement} +import code.metadata.counterparties.MappedCounterpartyMetadata import code.transaction.MappedTransaction import scala.util.Random @@ -26,6 +27,14 @@ trait LocalMappedConnectorTestSetup extends TestConnectorSetupWithStandardPermis .national_identifier(randomString(5)).saveMe } + override protected def createCounterpartyMetadata(BankId:String,AccountId:String,iBan:String):CounterpartyMetadata = { + MappedCounterpartyMetadata.create. + thisAccountBankId(BankId). + thisAccountId(AccountId). + accountNumber(iBan). + saveMe + } + // TODO: Should return an option or box so can test if the insert succeeded // or if it failed due to unique exception etc. However, we'll need to modify / lift callers so they can handle an Option // override protected def createBank(id : String) : Option[Bank] = { diff --git a/src/test/scala/code/api/TestConnectorSetup.scala b/src/test/scala/code/api/TestConnectorSetup.scala index 9dca04e43..8bed1cde8 100644 --- a/src/test/scala/code/api/TestConnectorSetup.scala +++ b/src/test/scala/code/api/TestConnectorSetup.scala @@ -2,7 +2,8 @@ package code.api import java.util.{Calendar, Date} -import code.bankconnectors.{OBPLimit, OBPOffset, Connector} +import code.bankconnectors.{Connector, OBPLimit, OBPOffset} +import code.metadata.counterparties.MappedCounterpartyMetadata import code.model._ import net.liftweb.util.Helpers._ @@ -13,6 +14,8 @@ trait TestConnectorSetup { protected def createAccount(bankId: BankId, accountId : AccountId, currency : String) : BankAccount protected def createTransaction(account : BankAccount, startDate : Date, finishDate : Date) + //TODO: Here I use the createCounterpartyMetadata.accountNumber to replace the Iban. I will fix it when new version is comming. + protected def createCounterpartyMetadata(BankId:String,AccountId:String,iBan:String):CounterpartyMetadata final protected def createAccountAndOwnerView(accountOwner: Option[User], bankId: BankId, accountId : AccountId, currency : String) : BankAccount = { val account = createAccount(bankId, accountId, currency) diff --git a/src/test/scala/code/api/v2_1_0/TransactionRequestsFreeformTest.scala b/src/test/scala/code/api/v2_1_0/TransactionRequestsFreeformTest.scala new file mode 100644 index 000000000..f8a6e2f59 --- /dev/null +++ b/src/test/scala/code/api/v2_1_0/TransactionRequestsFreeformTest.scala @@ -0,0 +1,1284 @@ +package code.api.v2_1_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole._ +import code.api.util.ErrorMessages +import code.api.v1_2_1.AmountOfMoneyJSON +import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJSON} +import code.api.v2_0_0.TransactionRequestBodyJSON +import code.api.{DefaultUsers, ServerSetupWithTestData} +import code.bankconnectors.Connector +import code.fx.fx +import code.model.{AccountId, BankAccount, TransactionRequestId} +import net.liftweb.json.JsonAST.JString +import net.liftweb.json.Serialization.write +import net.liftweb.util.Props +import org.scalatest.Tag + +class TransactionRequestsFreeformTest extends ServerSetupWithTestData with DefaultUsers with V210ServerSetup { + + object TransactionRequest extends Tag("transactionRequests") + + val transactionRequestType: String = "FREE_FORM" + + feature("we can make transaction requests") { + val view = "owner" + + 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.getTransactions(account.bankId, account.accountId).get.size + }) + } + + // No challenge, No FX (same currencies) + if ( Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at BANK_ID", TransactionRequest) {} + } else { + scenario("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at BANK_ID", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc1") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + + addEntitlement(bankId.value, obpuser3.userId, CanCreateAnyTransactionRequest.toString) + Then("We add entitlement to user3") + val hasEntitlement = code.api.util.APIUtil.hasEntitlement(bankId.value, obpuser3.userId, CanCreateAnyTransactionRequest) + hasEntitlement should equal(true) + + 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 + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + val transactionRequestBody = TransactionRequestDetailsFreeFormJSON(bodyValue) + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user3) + var response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 201 created code") + response.code should equal(201) + + println(response.body) + + //created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge + val transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction id") + transRequestId should not equal ("") + + val responseBody = response.body + + + val status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + // Challenge should be null (none required) + var challenge = (response.body \ "challenge").children + challenge.size should equal(0) + + var transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + //If user does not have access to owner or other view - they won’t be able to view transaction. Hence they can’t see the transaction_id + transaction_ids should not equal ("") + + //call getTransactionRequests, check that we really created a transaction request + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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) + + + val tr2Body = response.body + + //check transaction_ids again + transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids 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 / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + 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 + println(response.body) + /*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 rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance bigger by the amount specified to pay") + fromAccountBalance should equal((beforeFromBalance + convertedAmount)) + + /* + 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 + convertedAmount) + + And("there should now be 4 new transactions in the database (one for the sender, one for the receiver * 2") + transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 4) + } + } + + + // No challenge, No FX (same currencies) + if ( Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request without challenge, no FX (same currencies)", TransactionRequest) {} + } else { + scenario("we create a transaction request without challenge, no FX (same currencies)", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc1") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "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 + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + val transactionRequestBody = TransactionRequestDetailsFreeFormJSON(bodyValue) + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "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 and value is < 1000, we expect no challenge + val transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction id") + transRequestId should not equal ("") + + val responseBody = response.body + + + val status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + // Challenge should be null (none required) + var challenge = (response.body \ "challenge").children + challenge.size should equal(0) + + var transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should not equal ("") + + //call getTransactionRequests, check that we really created a transaction request + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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) + + + val tr2Body = response.body + + //check transaction_ids again + transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids 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 / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + 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 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 rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance bigger by the amount specified to pay") + fromAccountBalance should equal((beforeFromBalance + convertedAmount)) + + /* + 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 + convertedAmount) + + And("there should now be 2 new transactions in the database (one for the sender, one for the receiver * 2") + transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2 * 2) + } + } + + if ( Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a user without owner view access", TransactionRequest) {} + } else { + scenario("we create a transaction request with a user without owner view access", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc1") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "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 toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + val transactionRequestBody = TransactionRequestDetailsFreeFormJSON(bodyValue) + + //call createTransactionRequest with a user without owner view access + val request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user2) + val response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 400 created code") + response.code should equal(400) + + //created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge + val error: String = (response.body \ "error") match { + case JString(i) => i + case _ => "" + } + Then("We should have the error: " + ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + error should equal(ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + + } + + } + + if ( Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at a different BANK_ID", TransactionRequest) {} + } else { + scenario("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at a different BANK_ID", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val testBank2 = createBank("transactions-test-bank2") + val bankId = testBank.bankId + val bankId2 = testBank2.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc2") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR") + addEntitlement(bankId2.value, obpuser3.userId, CanCreateAnyTransactionRequest.toString) + + Then("We add entitlement to user3") + val hasEntitlement = code.api.util.APIUtil.hasEntitlement(bankId2.value, obpuser3.userId, CanCreateAnyTransactionRequest) + hasEntitlement should equal(true) + + 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 transactionRequestId = TransactionRequestId("__trans2") + val toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + val transactionRequestBody = TransactionRequestDetailsFreeFormJSON(bodyValue) + + //call createTransactionRequest + val request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user3) + val response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 400 created code") + response.code should equal(400) + + //created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge + val error: String = (response.body \ "error") match { + case JString(i) => i + case _ => "" + } + Then("We should have the error: " + ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + error should equal(ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + + + } + } + + // No challenge, with FX + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create an FX transaction request without challenge, with FX (different currencies)", TransactionRequest) {} + } else { + scenario("we create an FX transaction request without challenge, with FX (different currencies)", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1fx") + val accountId2 = AccountId("__acc1fx") + + val fromCurrency = "AED" + val toCurrency = "AED" + + val amt = BigDecimal("10.00") // This is money going out. We want to transfer this away from the From account. + + + val expectedAmtTo = amt * fx.exchangeRate(fromCurrency, toCurrency).get + + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, fromCurrency) + + 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 beforeFromCurrency = fromAccount.currency + + + val beforeToBalance = toAccount.balance + val beforeToCurrency = toAccount.currency + + // We debit the From + val expectedFromNewBalance = beforeFromBalance + amt + + // We credit the To + val expectedToNewBalance = beforeToBalance + expectedAmtTo + + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + + val bodyValue = AmountOfMoneyJSON(fromCurrency, amt.toString()) + val transactionRequestBody = TransactionRequestDetailsFreeFormJSON(bodyValue) + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user1) + var response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 201 created code") + response.code should equal(201) + + + val responseBody = response.body + + //created a transaction request, check some return values. As type is SANDBOX_TAN, we expect no challenge + val transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction request id") + transRequestId should not equal ("") + + val status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + + Then("we should not have a challenge object") + 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 / + "owner" / "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 = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val fromTransactions = response.body.children + + fromTransactions.size should equal(1) + + + // Transaction Value + val actualFromAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + + // We are debiting the amount + amt should equal(BigDecimal(actualFromAmount)) + + // New Balance + val actualFromBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedFromNewBalance should equal(BigDecimal(actualFromBalance)) + + //check that we created a new transaction (since no challenge) + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / toAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val toTransactions = response.body.children + + toTransactions.size should equal(1) + + // Transaction Value + val actualToAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedAmtTo should equal(BigDecimal(actualToAmount)) + + // New Balance + val actualToBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedToNewBalance should equal (BigDecimal(actualToBalance)) + // + + val rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance bigger by the original amount specified to pay") + fromAccountBalance should equal(beforeFromBalance + amt) + + + //val fromAccountBalance = getFromAccount.balance + //And("the from account should have a balance bigger 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 + convertedAmount) + + And("there should now be 2 new transactions in the database (one for the sender, one for the receiver)*2") + transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2 * 2) + } + } + + + // With challenge, No FX (Same currencies) + if ( Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a challenge, same currencies", 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("__acc1") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "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 transactionRequestId = TransactionRequestId("__trans1") + val toAccountJson = TransactionRequestAccountJSON(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 1000 €, so should trigger challenge request + val amt = BigDecimal("1250.00") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + val transactionRequestBody = TransactionRequestBodyJSON( + toAccountJson, + bodyValue, + "Test Transaction Request description") + + //call createTransactionRequest API method + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "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 transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + transRequestId should not equal ("") + + var status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_INITIATED) + + 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 / + "owner" / "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 / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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") //good answer, not a number + //TODO: why V4_1 does not work ?? + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + 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 / + "owner" / "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 bigger 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)*2") + transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2 * 2) + } + } + + + // With Challenge, with FX + if ( Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create an FX transaction request with challenge", TransactionRequest) {} + } else { + scenario("we create an FX transaction request with challenge", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1fx") + val accountId2 = AccountId("__acc1fx") + + val fromCurrency = "AED" + val toCurrency = "AED" + + // This value is over the "challenge threshold" i.e. a security challenge will need to be answered. + // the limited AED is 4140 = 1000 eruo + val amt = BigDecimal("5000.00") // This is money going out. We want to transfer this away from the From account. + + + val expectedAmtTo = amt * fx.exchangeRate(fromCurrency, toCurrency).get + + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, fromCurrency) + + 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 beforeFromCurrency = fromAccount.currency + + + val beforeToBalance = toAccount.balance + val beforeToCurrency = toAccount.currency + + // We debit the From + val expectedFromNewBalance = beforeFromBalance + amt + + // We credit the To + val expectedToNewBalance = beforeToBalance + expectedAmtTo + + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + + val bodyValue = AmountOfMoneyJSON(fromCurrency, amt.toString()) + val transactionRequestBody = TransactionRequestDetailsFreeFormJSON(bodyValue) + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "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 transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction id") + transRequestId should not equal ("") + + var status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_INITIATED) + + var transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids 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 = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should equal("") + + //Then("we should have a challenge object") + //challenge = (response.body \ "challenge").children + // TODO fix this path 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 = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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") //good answer, not a number + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should not equal ("") + + //call getTransactionRequests, check that we really created a transaction request + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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 not equal (0) + + //check transaction_ids again + transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should not equal ("") + + //make sure that we also get no challenges back from this url (after getting from db) + // challenge = (response.body \ "challenge").children + // TODO challenge.size should not equal(0) + + //check that we created a new transaction (since no challenge) + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val fromTransactions = response.body.children + + fromTransactions.size should equal(1) + + + // Transaction Value + val actualFromAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + + // We are debiting the amount + amt should equal(BigDecimal(actualFromAmount)) + + // New Balance + val actualFromBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedFromNewBalance should equal(BigDecimal(actualFromBalance)) + + //check that we created a new transaction + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / toAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val toTransactions = response.body.children + + toTransactions.size should equal(1) + + // Transaction Value + val actualToAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedAmtTo should equal (BigDecimal(actualToAmount)) + + // New Balance + val actualToBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedToNewBalance should equal (BigDecimal(actualToBalance)) + + + val rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance bigger by the original amount specified to pay") + fromAccountBalance should equal(beforeFromBalance + amt) + + + //val fromAccountBalance = getFromAccount.balance + //And("the from account should have a balance bigger 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 + convertedAmount) + + And("there should now be 2 new transactions in the database (one for the sender, one for the receiver)*2") + transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore + 2 * 2) + } + } + + /* + 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + val acc2 = createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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) + } */ + } +} diff --git a/src/test/scala/code/api/v2_1_0/TransactionRequestsSepaTest.scala b/src/test/scala/code/api/v2_1_0/TransactionRequestsSepaTest.scala new file mode 100644 index 000000000..635633d00 --- /dev/null +++ b/src/test/scala/code/api/v2_1_0/TransactionRequestsSepaTest.scala @@ -0,0 +1,1383 @@ +package code.api.v2_0_1 + +import code.api.util.ErrorMessages +import code.api.{DefaultUsers, ErrorMessage, ServerSetupWithTestData} +import code.api.util.APIUtil.OAuth._ +import code.api.v1_2_1.AmountOfMoneyJSON +import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJSON} +import code.bankconnectors.Connector +import code.fx.fx +import code.model.{AccountId, BankAccount, CounterpartyMetadataIban, TransactionRequestId} +import net.liftweb.json.JsonAST.JString +import net.liftweb.json.Serialization.write +import net.liftweb.util.Props +import org.scalatest.Tag +import code.api.util.ApiRole._ +import code.api.v2_0_0.TransactionRequestBodyJSON +import code.api.v2_1_0.{TransactionRequestDetailsFreeFormJSON, TransactionRequestDetailsSEPAJSON, TransactionRequestDetailsSandBoxTanJSON, V210ServerSetup} +import code.metadata.counterparties.MappedCounterpartyMetadata +import net.liftweb.mapper.By +import net.liftweb.util.Helpers._ + +class TransactionRequestsSepaTest extends ServerSetupWithTestData with DefaultUsers with V210ServerSetup { + + object TransactionRequest extends Tag("transactionRequests") + + val transactionRequestType: String = "SEPA" // SANDBOX_TAN SEPA FREE_FORM + + feature("we can make transaction requests") { + val view = "owner" + + 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.getTransactions(account.bankId, account.accountId).get.size + }) + } + + // No challenge, No FX (same currencies) + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at BANK_ID", TransactionRequest) {} + } else { + scenario("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at BANK_ID", TransactionRequest) { + + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc2") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR") + + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + addEntitlement(bankId.value, obpuser3.userId, CanCreateAnyTransactionRequest.toString) + Then("We add entitlement to user3") + val hasEntitlement = code.api.util.APIUtil.hasEntitlement(bankId.value, obpuser3.userId, CanCreateAnyTransactionRequest) + hasEntitlement should equal(true) + + 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 + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description") + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("EUR", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user3) + var response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 201 created code") + response.code should equal(201) + + println(response.body) + + //created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge + val transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction id") + transRequestId should not equal ("") + + val responseBody = response.body + + + val status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + // Challenge should be null (none required) + var challenge = (response.body \ "challenge").children + challenge.size should equal(0) + + var transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + //If user does not have access to owner or other view - they won’t be able to view transaction. Hence they can’t see the transaction_id + transaction_ids should not equal ("") + + //call getTransactionRequests, check that we really created a transaction request + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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) + + + val tr2Body = response.body + + //check transaction_ids again + transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids 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 / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + 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 + println(response.body) + /*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 rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance smaller by the amount specified to pay") + fromAccountBalance should equal((beforeFromBalance - convertedAmount)) + + /* + 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 + convertedAmount) + + 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) + } + } + + + // No challenge, No FX (same currencies) + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request without challenge, no FX (same currencies)", TransactionRequest) {} + } else { + scenario("we create a transaction request without challenge, no FX (same currencies)", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc2") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR") + + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + + 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 + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description") + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("EUR", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "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 and value is < 1000, we expect no challenge + val transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction id") + transRequestId should not equal ("") + + val responseBody = response.body + + + val status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + // Challenge should be null (none required) + var challenge = (response.body \ "challenge").children + challenge.size should equal(0) + + var transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should not equal ("") + + //call getTransactionRequests, check that we really created a transaction request + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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) + + + val tr2Body = response.body + + //check transaction_ids again + transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids 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 / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + 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 rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance smaller by the amount specified to pay") + fromAccountBalance should equal((beforeFromBalance - convertedAmount)) + + /* + 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 + convertedAmount) + + 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 (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a user without owner view access", TransactionRequest) {} + } else { + scenario("we create a transaction request with a user without owner view access", TransactionRequest) { + + + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc2") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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 toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description") + + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("EUR", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + + //call createTransactionRequest with a user without owner view access + val request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user2) + val response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 400 created code") + response.code should equal(400) + + //created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge + val error: String = (response.body \ "error") match { + case JString(i) => i + case _ => "" + } + Then("We should have the error: " + ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + error should equal(ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + + } + + } + + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at a different BANK_ID", TransactionRequest) {} + } else { + scenario("we create a transaction request with a user who doesn't have access to owner view but has CanCreateAnyTransactionRequest at a different BANK_ID", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val testBank2 = createBank("transactions-test-bank2") + val bankId = testBank.bankId + val bankId2 = testBank2.bankId + val accountId1 = AccountId("__acc1") + val accountId2 = AccountId("__acc2") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, "EUR") + addEntitlement(bankId2.value, obpuser3.userId, CanCreateAnyTransactionRequest.toString) + + Then("We add entitlement to user3") + val hasEntitlement = code.api.util.APIUtil.hasEntitlement(bankId2.value, obpuser3.userId, CanCreateAnyTransactionRequest) + hasEntitlement should equal(true) + + 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 transactionRequestId = TransactionRequestId("__trans2") + val toAccountJson = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + val amt = BigDecimal("12.50") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description") + + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("EUR", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + + //call createTransactionRequest + val request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user3) + val response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 400 created code") + response.code should equal(400) + + //created a transaction request, check some return values. As type is SANDBOX_TAN and value is < 1000, we expect no challenge + val error: String = (response.body \ "error") match { + case JString(i) => i + case _ => "" + } + Then("We should have the error: " + ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + error should equal(ErrorMessages.InsufficientAuthorisationToCreateTransactionRequest) + + + } + } + + // No challenge, with FX + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create an FX transaction request without challenge, with FX (different currencies)", TransactionRequest) {} + } else { + scenario("we create an FX transaction request without challenge, with FX (different currencies)", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1fx") + val accountId2 = AccountId("__acc2fx") + + val fromCurrency = "AED" + val toCurrency = "INR" + + val amt = BigDecimal("10.00") // This is money going out. We want to transfer this away from the From account. + + + val expectedAmtTo = amt * fx.exchangeRate(fromCurrency, toCurrency).get + + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, fromCurrency) + createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, toCurrency) + + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + 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 beforeFromCurrency = fromAccount.currency + + + val beforeToBalance = toAccount.balance + val beforeToCurrency = toAccount.currency + + // We debit the From + val expectedFromNewBalance = beforeFromBalance - amt + + // We credit the To + val expectedToNewBalance = beforeToBalance + expectedAmtTo + + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + + val bodyValue = AmountOfMoneyJSON(fromCurrency, amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description") + + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("AED", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user1) + var response = makePostRequest(request, write(transactionRequestBody)) + Then("we should get a 201 created code") + response.code should equal(201) + + + val responseBody = response.body + + //created a transaction request, check some return values. As type is SANDBOX_TAN, we expect no challenge + val transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction request id") + transRequestId should not equal ("") + + val status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + + Then("we should not have a challenge object") + 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 / + "owner" / "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 = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val fromTransactions = response.body.children + + fromTransactions.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 ("") + + // Transaction Value + val actualFromAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + + // We are debiting the amount + amt should equal(-1 * BigDecimal(actualFromAmount)) + + // New Balance + val actualFromBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedFromNewBalance should equal(BigDecimal(actualFromBalance)) + + //check that we created a new transaction (since no challenge) + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / toAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val toTransactions = response.body.children + + toTransactions.size should equal(1) + + //check that the description has been set + val toDescription = (((response.body \ "transactions") (0) \ "details") \ "description") match { + case JString(i) => i + case _ => "" + } + description should not equal ("") + + // Transaction Value + val actualToAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedAmtTo should equal(BigDecimal(actualToAmount)) + + // New Balance + val actualToBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedToNewBalance should equal(BigDecimal(actualToBalance)) + + + val rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance smaller by the original amount specified to pay") + fromAccountBalance should equal(beforeFromBalance - amt) + + + //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 + convertedAmount) + + 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) + } + } + + + // With challenge, No FX (Same currencies) + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create a transaction request with a challenge, same currencies", 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") + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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 transactionRequestId = TransactionRequestId("__trans1") + val toAccountJson = TransactionRequestAccountJSON(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 1000 €, so should trigger challenge request + val amt = BigDecimal("1250.00") + val bodyValue = AmountOfMoneyJSON("EUR", amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON( + // toAccountJson, + // bodyValue, + // "Test Transaction Request description") + + + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("EUR", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + + //call createTransactionRequest API method + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "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 transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + transRequestId should not equal ("") + + var status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_INITIATED) + + 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 / + "owner" / "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 / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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") //good answer, not a number + //TODO: why V4_1 does not work ?? + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + 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 / + "owner" / "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) + } + } + + + // With Challenge, with FX + if (Props.getBool("transactionRequests_enabled", false) == false) { + ignore("we create an FX transaction request with challenge", TransactionRequest) {} + } else { + scenario("we create an FX transaction request with challenge", TransactionRequest) { + val testBank = createBank("transactions-test-bank") + val bankId = testBank.bankId + val accountId1 = AccountId("__acc1fx") + val accountId2 = AccountId("__acc2fx") + + val fromCurrency = "AED" + val toCurrency = "INR" + + // This value is over the "challenge threshold" i.e. a security challenge will need to be answered. + // the limited AED is 4140 = 1000 eruo + val amt = BigDecimal("5000.00") // This is money going out. We want to transfer this away from the From account. + + + val expectedAmtTo = amt * fx.exchangeRate(fromCurrency, toCurrency).get + + createAccountAndOwnerView(Some(obpuser1), bankId, accountId1, fromCurrency) + createAccountAndOwnerView(Some(obpuser1), bankId, accountId2, toCurrency) + + 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 beforeFromCurrency = fromAccount.currency + + + val beforeToBalance = toAccount.balance + val beforeToCurrency = toAccount.currency + + // We debit the From + val expectedFromNewBalance = beforeFromBalance - amt + + // We credit the To + val expectedToNewBalance = beforeToBalance + expectedAmtTo + + + //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 = TransactionRequestAccountJSON(toAccount.bankId.value, toAccount.accountId.value) + + + val bodyValue = AmountOfMoneyJSON(fromCurrency, amt.toString()) + // val transactionRequestBody = TransactionRequestBodyJSON(toAccountJson, bodyValue, "Test Transaction Request description") + val counterpartyMetadataIban1 = CounterpartyMetadataIban("iBan1"); + val counterpartyMetadataIban2 = CounterpartyMetadataIban("iBan2"); + val counterpartyMetadata1 = createCounterpartyMetadata(bankId.value, accountId1.value, counterpartyMetadataIban1.value); + val counterpartyMetadata2 = createCounterpartyMetadata(bankId.value, accountId2.value, counterpartyMetadataIban2.value); + + val transactionRequestBody = TransactionRequestDetailsSEPAJSON(AmountOfMoneyJSON("AED", amt.toString()), counterpartyMetadata2.getAccountNumber, "Test Transaction Request description") + + + //call createTransactionRequest + var request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "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 transRequestId: String = (response.body \ "id") match { + case JString(i) => i + case _ => "" + } + Then("We should have some new transaction id") + transRequestId should not equal ("") + + var status: String = (response.body \ "status") match { + case JString(i) => i + case _ => "" + } + status should equal(code.transactionrequests.TransactionRequests.STATUS_INITIATED) + + var transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids 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 = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should equal("") + + //Then("we should have a challenge object") + //challenge = (response.body \ "challenge").children + // TODO fix this path 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 = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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") //good answer, not a number + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transaction-request-types" / transactionRequestType / "transaction-requests" / transRequestId / "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(code.transactionrequests.TransactionRequests.STATUS_COMPLETED) + + transaction_ids = (response.body \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should not equal ("") + + //call getTransactionRequests, check that we really created a transaction request + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "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 not equal (0) + + //check transaction_ids again + transaction_ids = (response.body \ "transaction_requests_with_charges" \ "transaction_ids") match { + case JString(i) => i + case _ => "" + } + transaction_ids should not equal ("") + + //make sure that we also get no challenges back from this url (after getting from db) + // challenge = (response.body \ "challenge").children + // TODO challenge.size should not equal(0) + + //check that we created a new transaction (since no challenge) + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val fromTransactions = response.body.children + + fromTransactions.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 ("") + + // Transaction Value + val actualFromAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + + // We are debiting the amount + amt should equal(-1 * BigDecimal(actualFromAmount)) + + // New Balance + val actualFromBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedFromNewBalance should equal(BigDecimal(actualFromBalance)) + + //check that we created a new transaction + request = (v2_1Request / "banks" / testBank.bankId.value / "accounts" / toAccount.accountId.value / + "owner" / "transactions").GET <@ (user1) + response = makeGetRequest(request) + + Then("we should get a 200 ok code") + response.code should equal(200) + + val toTransactions = response.body.children + + toTransactions.size should equal(1) + + //check that the description has been set + val toDescription = (((response.body \ "transactions") (0) \ "details") \ "description") match { + case JString(i) => i + case _ => "" + } + description should not equal ("") + + // Transaction Value + val actualToAmount = (((response.body \ "transactions") (0) \ "details") \ "value" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedAmtTo should equal(BigDecimal(actualToAmount)) + + // New Balance + val actualToBalance = (((response.body \ "transactions") (0) \ "details") \ "new_balance" \ "amount") match { + case JString(i) => i + case _ => "" + } + expectedToNewBalance should equal(BigDecimal(actualToBalance)) + + + val rate = fx.exchangeRate(fromAccount.currency, toAccount.currency) + val convertedAmount = fx.convert(amt, rate) + val fromAccountBalance = getFromAccount.balance + And("the from account should have a balance smaller by the original amount specified to pay") + fromAccountBalance should equal(beforeFromBalance - amt) + + + //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 + convertedAmount) + + 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) + } + } + + /* + 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + val acc2 = createAccountAndOwnerView(Some(obpuser1), 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(obpuser1), 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(obpuser1), bankId, accountId1, "EUR") + createAccountAndOwnerView(Some(obpuser1), 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) + } */ + } +}