From 6d0e316dc62186ef531116f37cabde240d935f8d Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 14 Nov 2024 12:12:40 +0100 Subject: [PATCH 01/14] feature/OBPv510 added new endpoints:createAgent,getAgent and createTransactionRequestAgent --- .../SwaggerDefinitionsJSON.scala | 26 + .../main/scala/code/api/util/ApiRole.scala | 9 + .../scala/code/api/util/ErrorMessages.scala | 2 + .../scala/code/api/util/ExampleValue.scala | 6 + .../main/scala/code/api/util/NewStyle.scala | 6 + .../scala/code/api/v4_0_0/APIMethods400.scala | 1046 +++++++++-------- .../scala/code/api/v5_0_0/APIMethods500.scala | 2 +- .../scala/code/api/v5_1_0/APIMethods510.scala | 180 ++- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 43 +- .../commons/model/enums/Enumerations.scala | 1 + 10 files changed, 808 insertions(+), 513 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 582a3aff9..8297bb1f7 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5515,6 +5515,32 @@ object SwaggerDefinitionsJSON { val consumersJsonV510 = ConsumersJsonV510( List(consumerJsonV510) ) + + val agentIdJson = AgentIdJson("") + + val transactionRequestBodyAgentJsonV510 = TransactionRequestBodyAgentJsonV510( + to = agentIdJson, + value = amountOfMoneyJsonV121, + description = descriptionExample.value, + charge_policy = chargePolicyExample.value, + future_date = Some(futureDateExample.value) + ) + + val postAgentJsonV510 = PostAgentJsonV510( + legal_name = legalNameExample.value, + mobile_phone_number = mobilePhoneNumberExample.value, + agent_number = agentNumberExample.value, + currency = currencyExample.value + ) + + val agentJsonV510 = AgentJsonV510( + agent_id = agentIdExample.value, + legal_name = legalNameExample.value, + mobile_phone_number = mobilePhoneNumberExample.value, + agent_number = agentNumberExample.value, + currency = currencyExample.value + ) + //The common error or success format. //Just some helper format to use in Json case class NotSupportedYet() diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 3aaa5eb97..91defb78f 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -104,6 +104,12 @@ object ApiRole extends MdcLoggable{ case class CanCreateCustomer(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateCustomer = CanCreateCustomer() + case class CanCreateAgent(requiresBankId: Boolean = true) extends ApiRole + lazy val canCreateAgent = CanCreateAgent() + + case class CanGetAgent(requiresBankId: Boolean = true) extends ApiRole + lazy val canGetAgent = CanGetAgent() + case class CanUpdateCustomerEmail(requiresBankId: Boolean = true) extends ApiRole lazy val canUpdateCustomerEmail = CanUpdateCustomerEmail() @@ -134,6 +140,9 @@ object ApiRole extends MdcLoggable{ case class CanCreateCustomerAtAnyBank(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateCustomerAtAnyBank = CanCreateCustomerAtAnyBank() + case class CanCreateAgentAtAnyBank(requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateAgentAtAnyBank = CanCreateAgentAtAnyBank() + case class CanGetCorrelatedUsersInfo(requiresBankId: Boolean = true) extends ApiRole lazy val canGetCorrelatedUsersInfo = CanGetCorrelatedUsersInfo() diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 3fc845f73..7006390be 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -452,6 +452,7 @@ object ErrorMessages { val UpdateProductFeeError = "OBP-30119: Could not update the Product Fee." val InvalidCardNumber = "OBP-30200: Card not found. Please specify a valid value for CARD_NUMBER. " + val AgentNotFound = "OBP-30201: Agent not found. Please specify a valid value for AGENT_ID. " val CustomerAccountLinkNotFound = "OBP-30204: Customer Account Link not found" @@ -523,6 +524,7 @@ object ErrorMessages { val GetChargeValueError = "OBP-30323: Could not get the Charge Value." val GetTransactionRequestTypeChargesError = "OBP-30324: Could not get Transaction Request Type Charges." + val AgentAccountLinkNotFound = "OBP-30325: Agent Account Link not found." // Branch related messages val BranchesNotFoundLicense = "OBP-32001: No branches available. License may not be set." diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 8d09a2ec3..9d32c4727 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -65,6 +65,9 @@ object ExampleValue { lazy val customerIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"A non human friendly string that identifies the customer and is used in URLs. This SHOULD NOT be the customer number. The combination of customerId and bankId MUST be unique on an OBP instance. customerId SHOULD be unique on an OBP instance. Ideally customerId is a UUID. A mapping between customer number and customer id is kept in OBP.") glossaryItems += makeGlossaryItem("Customer.customerId", customerIdExample) + + lazy val agentIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"A non human friendly string that identifies the agent and is used in URLs. This SHOULD NOT be the agent number. The combination of agentId and bankId MUST be unique on an OBP instance. AgentId SHOULD be unique on an OBP instance. Ideally agentId is a UUID. A mapping between agent number and agent id is kept in OBP.") + glossaryItems += makeGlossaryItem("Agent.agent_id", agentIdExample) lazy val customerAccountLinkIdExample = ConnectorField("xyz8a7e4-6d02-40e3-a129-0b2bf89de8uh", s"A non human friendly string that identifies the Customer Account Link and is used in URLs. ") glossaryItems += makeGlossaryItem("Customer.customerAccountLinkId", customerAccountLinkIdExample) @@ -114,6 +117,9 @@ object ExampleValue { lazy val customerNumberExample = ConnectorField("5987953", s"The human friendly customer identifier that MUST uniquely identify the Customer at the Bank ID. Customer Number is NOT used in URLs.") glossaryItems += makeGlossaryItem("Customer.customerNumber", customerNumberExample) + lazy val agentNumberExample = ConnectorField("5987953", s"The human friendly agent identifier that MUST uniquely identify the Agent at the Bank ID. Agent Number is NOT used in URLs.") + glossaryItems += makeGlossaryItem("Agent.agent_number", agentNumberExample) + lazy val licenseIdExample = ConnectorField("ODbL-1.0", s"") glossaryItems += makeGlossaryItem("License.id", licenseIdExample) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 342a6584a..80342ac23 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -748,6 +748,12 @@ object NewStyle extends MdcLoggable{ unboxFullOrFail(_, callContext, s"$CustomerNotFoundByCustomerId. Current CustomerId($customerId)", 404) } } + + def getAgentByAgentId(agentId : String, callContext: Option[CallContext]): OBPReturnType[Customer] = { + Connector.connector.vend.getCustomerByCustomerId(agentId, callContext) map { + unboxFullOrFail(_, callContext, s"$AgentNotFound. Current AGENT_ID($agentId)", 404) + } + } def checkCustomerNumberAvailable(bankId: BankId, customerNumber: String, callContext: Option[CallContext]): OBPReturnType[Boolean] = { Connector.connector.vend.checkCustomerNumberAvailable(bankId: BankId, customerNumber: String, callContext: Option[CallContext]) map { i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse", 400), i._2) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index d3be8f537..d8eee9ed1 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -44,7 +44,9 @@ import code.api.dynamic.endpoint.helper._ import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} import code.api.util.FutureUtil.EndpointContext +import code.api.v4_0_0.APIMethods400.{createTransactionRequest, exchangeRates, lowAmount, sharedChargePolicy, transactionRequestGeneralText} import code.api.v5_0_0.OBPAPI5_0_0 +import code.api.v5_1_0.TransactionRequestBodyAgentJsonV510 import code.api.{ChargePolicy, Constant, JsonResponseException} import code.apicollection.MappedApiCollectionsProvider import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider @@ -595,71 +597,6 @@ trait APIMethods400 extends MdcLoggable { } } - val exchangeRates = - APIUtil.getPropsValue("webui_api_explorer_url", "") + - "/more?version=OBPv4.0.0&list-all-banks=false&core=&psd2=&obwg=#OBPv2_2_0-getCurrentFxRate" - - - // This text is used in the various Create Transaction Request resource docs - val transactionRequestGeneralText = - s"""Initiate a Payment via creating a Transaction Request. - | - |In OBP, a `transaction request` may or may not result in a `transaction`. However, a `transaction` only has one possible state: completed. - | - |A `Transaction Request` can have one of several states: INITIATED, NEXT_CHALLENGE_PENDING etc. - | - |`Transactions` are modeled on items in a bank statement that represent the movement of money. - | - |`Transaction Requests` are requests to move money which may or may not succeed and thus result in a `Transaction`. - | - |A `Transaction Request` might create a security challenge that needs to be answered before the `Transaction Request` proceeds. - |In case 1 person needs to answer security challenge we have next flow of state of an `transaction request`: - | INITIATED => COMPLETED - |In case n persons needs to answer security challenge we have next flow of state of an `transaction request`: - | INITIATED => NEXT_CHALLENGE_PENDING => ... => NEXT_CHALLENGE_PENDING => COMPLETED - | - |The security challenge is bound to a user i.e. in case of right answer and the user is different than expected one the challenge will fail. - | - |Rule for calculating number of security challenges: - |If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges - |(one for every user that has a View where permission "can_add_transaction_request_to_any_account"=true) - |In case REQUIRED_CHALLENGE_ANSWERS is not defined as an account attribute default value is 1. - | - |Transaction Requests contain charge information giving the client the opportunity to proceed or not (as long as the challenge level is appropriate). - | - |Transaction Requests can have one of several Transaction Request Types which expect different bodies. The escaped body is returned in the details key of the GET response. - |This provides some commonality and one URL for many different payment or transfer types with enough flexibility to validate them differently. - | - |The payer is set in the URL. Money comes out of the BANK_ID and ACCOUNT_ID specified in the URL. - | - |In sandbox mode, TRANSACTION_REQUEST_TYPE is commonly set to ACCOUNT. See getTransactionRequestTypesSupportedByBank for all supported types. - | - |In sandbox mode, if the amount is less than 1000 EUR (any currency, unless it is set differently on this server), the transaction request will create a transaction without a challenge, else the Transaction Request will be set to INITIALISED and a challenge will need to be answered. - | - |If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created. - | - |You can transfer between different currency accounts. (new in 2.0.0). The currency in body must match the sending account. - | - |The following static FX rates are available in sandbox mode: - | - |${exchangeRates} - | - | - |Transaction Requests satisfy PSD2 requirements thus: - | - |1) A transaction can be initiated by a third party application. - | - |2) The customer is informed of the charge that will incurred. - | - |3) The call supports delegated authentication (OAuth) - | - |See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow. - | - |There is further documentation [here](https://github.com/OpenBankProject/OBP-API/wiki/Transaction-Requests) - | - |""" - - // ACCOUNT. (we no longer create a resource doc for the general case) staticResourceDocs += ResourceDoc( createTransactionRequestAccount, @@ -807,10 +744,6 @@ trait APIMethods400 extends MdcLoggable { ), List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2)) - - val lowAmount = AmountOfMoneyJsonV121("EUR", "12.50") - val sharedChargePolicy = ChargePolicy.withName("SHARED") - // Transaction Request (SEPA) staticResourceDocs += ResourceDoc( createTransactionRequestSepa, @@ -925,443 +858,6 @@ trait APIMethods400 extends MdcLoggable { ), List(apiTagTransactionRequest, apiTagPSD2PIS), Some(List(canCreateAnyTransactionRequest))) - - - def createTransactionRequest(bankId: BankId, accountId: AccountId, viewId: ViewId, transactionRequestType: TransactionRequestType, json: JValue): Future[(TransactionRequestWithChargeJSON400, Option[CallContext])] = { - for { - (Full(u), callContext) <- SS.user - - transactionRequestTypeValue <- NewStyle.function.tryons(s"$InvalidTransactionRequestType: '${transactionRequestType.value}'. OBP does not support it.", 400, callContext) { - TransactionRequestTypes.withName(transactionRequestType.value) - } - - (fromAccount, callContext) <- transactionRequestTypeValue match { - case CARD => - for{ - transactionRequestBodyCard <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $CARD json format", 400, callContext) { - json.extract[TransactionRequestBodyCardJsonV400] - } - // 1.1 get Card from card_number - (cardFromCbs,callContext) <- NewStyle.function.getPhysicalCardByCardNumber(transactionRequestBodyCard.card.card_number, callContext) - - // 1.2 check card name/expire month. year. - calendar = Calendar.getInstance - _ = calendar.setTime(cardFromCbs.expires) - yearFromCbs = calendar.get(Calendar.YEAR).toString - monthFromCbs = calendar.get(Calendar.MONTH).toString - nameOnCardFromCbs= cardFromCbs.nameOnCard - cvvFromCbs= cardFromCbs.cvv.getOrElse("") - brandFromCbs= cardFromCbs.brand.getOrElse("") - - _ <- Helper.booleanToFuture(s"$InvalidJsonValue brand is not matched", cc=callContext) { - transactionRequestBodyCard.card.brand.equalsIgnoreCase(brandFromCbs) - } - - dateFromJsonBody <- NewStyle.function.tryons(s"$InvalidDateFormat year should be 'yyyy', " + - s"eg: 2023, but current expiry_year(${transactionRequestBodyCard.card.expiry_year}), " + - s"month should be 'xx', eg: 02, but current expiry_month(${transactionRequestBodyCard.card.expiry_month})", 400, callContext) { - DateWithMonthFormat.parse(s"${transactionRequestBodyCard.card.expiry_year}-${transactionRequestBodyCard.card.expiry_month}") - } - _ <- Helper.booleanToFuture(s"$InvalidJsonValue your credit card is expired.", cc=callContext) { - org.apache.commons.lang3.time.DateUtils.addMonths(new Date(), 1).before(dateFromJsonBody) - } - - _ <- Helper.booleanToFuture(s"$InvalidJsonValue expiry_year is not matched", cc=callContext) { - transactionRequestBodyCard.card.expiry_year.equalsIgnoreCase(yearFromCbs) - } - _ <- Helper.booleanToFuture(s"$InvalidJsonValue expiry_month is not matched", cc=callContext) { - transactionRequestBodyCard.card.expiry_month.toInt.equals(monthFromCbs.toInt+1) - } - - _ <- Helper.booleanToFuture(s"$InvalidJsonValue name_on_card is not matched", cc=callContext) { - transactionRequestBodyCard.card.name_on_card.equalsIgnoreCase(nameOnCardFromCbs) - } - _ <- Helper.booleanToFuture(s"$InvalidJsonValue cvv is not matched", cc=callContext) { - HashUtil.Sha256Hash(transactionRequestBodyCard.card.cvv).equals(cvvFromCbs) - } - - } yield{ - (cardFromCbs.account, callContext) - } - - case _ => NewStyle.function.getBankAccount(bankId,accountId, callContext) - } - _ <- NewStyle.function.isEnabledTransactionRequests(callContext) - _ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext) { - isValidID(fromAccount.accountId.value) - } - _ <- Helper.booleanToFuture(InvalidBankIdFormat, cc=callContext) { - isValidID(fromAccount.bankId.value) - } - - _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, BankIdAccountId(fromAccount.bankId, fromAccount.accountId), u, callContext) - - _ <- Helper.booleanToFuture(s"${InvalidTransactionRequestType}: '${transactionRequestType.value}'. Current Sandbox does not support it. ", cc=callContext) { - APIUtil.getPropsValue("transactionRequests_supported_types", "").split(",").contains(transactionRequestType.value) - } - - // Check the input JSON format, here is just check the common parts of all four types - transDetailsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $TransactionRequestBodyCommonJSON ", 400, callContext) { - json.extract[TransactionRequestBodyCommonJSON] - } - - transactionAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${transDetailsJson.value.amount} ", 400, callContext) { - BigDecimal(transDetailsJson.value.amount) - } - - _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${transactionAmountNumber}'", cc=callContext) { - transactionAmountNumber > BigDecimal("0") - } - - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { - isValidCurrencyISOCode(transDetailsJson.value.currency) - } - - // Prevent default value for transaction request type (at least). - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { - isValidCurrencyISOCode(transDetailsJson.value.currency) - } - - (createdTransactionRequest, callContext) <- transactionRequestTypeValue match { - case REFUND => { - for { - transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { - json.extract[TransactionRequestBodyRefundJsonV400] - } - - transactionId = TransactionId(transactionRequestBodyRefundJson.refund.transaction_id) - - (fromAccount, toAccount, transaction, callContext) <- transactionRequestBodyRefundJson.to match { - case Some(refundRequestTo) if refundRequestTo.account_id.isDefined && refundRequestTo.bank_id.isDefined => - val toBankId = BankId(refundRequestTo.bank_id.get) - val toAccountId = AccountId(refundRequestTo.account_id.get) - for { - (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) - (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) - } yield (fromAccount, toAccount, transaction, callContext) - - case Some(refundRequestTo) if refundRequestTo.counterparty_id.isDefined => - val toCounterpartyId = CounterpartyId(refundRequestTo.counterparty_id.get) - for { - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(toCounterpartyId, callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, isOutgoingAccount = true, callContext) - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) - } yield (fromAccount, toAccount, transaction, callContext) - - case None if transactionRequestBodyRefundJson.from.isDefined => - val fromCounterpartyId = CounterpartyId(transactionRequestBodyRefundJson.from.get.counterparty_id) - val toAccount = fromAccount - for { - (fromCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(fromCounterpartyId, callContext) - fromAccount <- NewStyle.function.getBankAccountFromCounterparty(fromCounterparty, isOutgoingAccount = false, callContext) - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - fromCounterparty.isBeneficiary - } - (transaction, callContext) <- NewStyle.function.getTransaction(toAccount.bankId, toAccount.accountId, transactionId, callContext) - } yield (fromAccount, toAccount, transaction, callContext) - } - - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyRefundJson)(Serialization.formats(NoTypeHints)) - } - - _ <- Helper.booleanToFuture(s"${RefundedTransaction} Current input amount is: '${transDetailsJson.value.amount}'. It can not be more than the original amount(${(transaction.amount).abs})", cc=callContext) { - (transaction.amount).abs >= transactionAmountNumber - } - //TODO, we need additional field to guarantee the transaction is refunded... - // _ <- Helper.booleanToFuture(s"${RefundedTransaction}") { - // !((transaction.description.toString contains(" Refund to ")) && (transaction.description.toString contains(" and transaction_id("))) - // } - - //we add the extra info (counterparty name + transaction_id) for this special Refund endpoint. - newDescription = s"${transactionRequestBodyRefundJson.description} - Refund for transaction_id: (${transactionId.value}) to ${transaction.otherAccount.counterpartyName}" - - //This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money. - refundToAccount = fromAccount - //This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money. - refundFromAccount = toAccount - - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - refundFromAccount, - refundToAccount, - transactionRequestType, - transactionRequestBodyRefundJson.copy(description = newDescription), - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) //in ACCOUNT, ChargePolicy set default "SHARED" - - _ <- NewStyle.function.createOrUpdateTransactionRequestAttribute( - bankId = bankId, - transactionRequestId = createdTransactionRequest.id, - transactionRequestAttributeId = None, - name = "original_transaction_id", - attributeType = TransactionRequestAttributeType.withName("STRING"), - value = transactionId.value, - callContext = callContext - ) - - refundReasonCode = transactionRequestBodyRefundJson.refund.reason_code - _ <- if (refundReasonCode.nonEmpty) { - NewStyle.function.createOrUpdateTransactionRequestAttribute( - bankId = bankId, - transactionRequestId = createdTransactionRequest.id, - transactionRequestAttributeId = None, - name = "refund_reason_code", - attributeType = TransactionRequestAttributeType.withName("STRING"), - value = refundReasonCode, - callContext = callContext) - } else Future.successful() - - (newTransactionRequestStatus, callContext) <- NewStyle.function.notifyTransactionRequest(refundFromAccount, refundToAccount, createdTransactionRequest, callContext) - _ <- NewStyle.function.saveTransactionRequestStatusImpl(createdTransactionRequest.id, newTransactionRequestStatus.toString, callContext) - createdTransactionRequest <- Future(createdTransactionRequest.copy(status = newTransactionRequestStatus.toString)) - - } yield (createdTransactionRequest, callContext) - } - case ACCOUNT | SANDBOX_TAN => { - for { - transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { - json.extract[TransactionRequestBodySandBoxTanJSON] - } - - toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) - toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) - (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) - - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) - } - - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodySandboxTan, - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) //in ACCOUNT, ChargePolicy set default "SHARED" - } yield (createdTransactionRequest, callContext) - } - case ACCOUNT_OTP => { - for { - transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { - json.extract[TransactionRequestBodySandBoxTanJSON] - } - - toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) - toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) - (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) - - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) - } - - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodySandboxTan, - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) //in ACCOUNT, ChargePolicy set default "SHARED" - } yield (createdTransactionRequest, callContext) - } - case COUNTERPARTY => { - for { - //For COUNTERPARTY, Use the counterpartyId to find the toCounterparty and set up the toAccount - transactionRequestBodyCounterparty <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $COUNTERPARTY json format", 400, callContext) { - json.extract[TransactionRequestBodyCounterpartyJSON] - } - toCounterpartyId = transactionRequestBodyCounterparty.to.counterparty_id - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - // Check we can send money to it. - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = transactionRequestBodyCounterparty.charge_policy - _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { - ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) - } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyCounterparty)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodyCounterparty, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) - } yield (createdTransactionRequest, callContext) - } - case CARD => { - for { - //2rd: get toAccount from counterpartyId - transactionRequestBodyCard <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $CARD json format", 400, callContext) { - json.extract[TransactionRequestBodyCardJsonV400] - } - toCounterpartyId = transactionRequestBodyCard.to.counterparty_id - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - // Check we can send money to it. - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = ChargePolicy.RECEIVER.toString - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyCard)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodyCard, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) - } yield (createdTransactionRequest, callContext) - - } - case SIMPLE => { - for { - //For SAMPLE, we will create/get toCounterparty on site and set up the toAccount - transactionRequestBodySimple <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SIMPLE json format", 400, callContext) { - json.extract[TransactionRequestBodySimpleJsonV400] - } - (toCounterparty, callContext) <- NewStyle.function.getOrCreateCounterparty( - name = transactionRequestBodySimple.to.name, - description = transactionRequestBodySimple.to.description, - currency = transactionRequestBodySimple.value.currency, - createdByUserId = u.userId, - thisBankId = bankId.value, - thisAccountId = accountId.value, - thisViewId = viewId.value, - otherBankRoutingScheme = transactionRequestBodySimple.to.other_bank_routing_scheme, - otherBankRoutingAddress = transactionRequestBodySimple.to.other_bank_routing_address, - otherBranchRoutingScheme = transactionRequestBodySimple.to.other_branch_routing_scheme, - otherBranchRoutingAddress = transactionRequestBodySimple.to.other_branch_routing_address, - otherAccountRoutingScheme = transactionRequestBodySimple.to.other_account_routing_scheme, - otherAccountRoutingAddress = transactionRequestBodySimple.to.other_account_routing_address, - otherAccountSecondaryRoutingScheme = transactionRequestBodySimple.to.other_account_secondary_routing_scheme, - otherAccountSecondaryRoutingAddress = transactionRequestBodySimple.to.other_account_secondary_routing_address, - callContext: Option[CallContext], - ) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - // Check we can send money to it. - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = transactionRequestBodySimple.charge_policy - _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { - ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) - } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodySimple)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transactionRequestBodySimple, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) - } yield (createdTransactionRequest, callContext) - - } - case SEPA => { - for { - //For SEPA, Use the IBAN to find the toCounterparty and set up the toAccount - transDetailsSEPAJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SEPA json format", 400, callContext) { - json.extract[TransactionRequestBodySEPAJsonV400] - } - toIban = transDetailsSEPAJson.to.iban - (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByIbanAndBankAccountId(toIban, fromAccount.bankId, fromAccount.accountId, callContext) - toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) - _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { - toCounterparty.isBeneficiary - } - chargePolicy = transDetailsSEPAJson.charge_policy - _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { - ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) - } - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transDetailsSEPAJson)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - toAccount, - transactionRequestType, - transDetailsSEPAJson, - transDetailsSerialized, - chargePolicy, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - transDetailsSEPAJson.reasons.map(_.map(_.transform)), - callContext) - } yield (createdTransactionRequest, callContext) - } - case FREE_FORM => { - for { - transactionRequestBodyFreeForm <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $FREE_FORM json format", 400, callContext) { - json.extract[TransactionRequestBodyFreeFormJSON] - } - // Following lines: just transfer the details body, add Bank_Id and Account_Id in the Detail part. This is for persistence and 'answerTransactionRequestChallenge' - transactionRequestAccountJSON = TransactionRequestAccountJsonV140(bankId.value, accountId.value) - transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { - write(transactionRequestBodyFreeForm)(Serialization.formats(NoTypeHints)) - } - (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, - viewId, - fromAccount, - fromAccount, - transactionRequestType, - transactionRequestBodyFreeForm, - transDetailsSerialized, - sharedChargePolicy.toString, - Some(OBP_TRANSACTION_REQUEST_CHALLENGE), - getScaMethodAtInstance(transactionRequestType.value).toOption, - None, - callContext) - } yield - (createdTransactionRequest, callContext) - } - } - (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(createdTransactionRequest.id.value, callContext) - } yield { - (JSONFactory400.createTransactionRequestWithChargeJSON(createdTransactionRequest, challenges), HttpCode.`201`(callContext)) - } - } lazy val createTransactionRequestAccount: OBPEndpoint = { case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: @@ -12718,5 +12214,543 @@ object APIMethods400 extends RestHelper with APIMethods400 { lazy val newStyleEndpoints: List[(String, String)] = Implementations4_0_0.resourceDocs.map { rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString()) }.toList + + val exchangeRates = + APIUtil.getPropsValue("webui_api_explorer_url", "") + + "/more?version=OBPv4.0.0&list-all-banks=false&core=&psd2=&obwg=#OBPv2_2_0-getCurrentFxRate" + + + // This text is used in the various Create Transaction Request resource docs + val transactionRequestGeneralText = + s"""Initiate a Payment via creating a Transaction Request. + | + |In OBP, a `transaction request` may or may not result in a `transaction`. However, a `transaction` only has one possible state: completed. + | + |A `Transaction Request` can have one of several states: INITIATED, NEXT_CHALLENGE_PENDING etc. + | + |`Transactions` are modeled on items in a bank statement that represent the movement of money. + | + |`Transaction Requests` are requests to move money which may or may not succeed and thus result in a `Transaction`. + | + |A `Transaction Request` might create a security challenge that needs to be answered before the `Transaction Request` proceeds. + |In case 1 person needs to answer security challenge we have next flow of state of an `transaction request`: + | INITIATED => COMPLETED + |In case n persons needs to answer security challenge we have next flow of state of an `transaction request`: + | INITIATED => NEXT_CHALLENGE_PENDING => ... => NEXT_CHALLENGE_PENDING => COMPLETED + | + |The security challenge is bound to a user i.e. in case of right answer and the user is different than expected one the challenge will fail. + | + |Rule for calculating number of security challenges: + |If product Account attribute REQUIRED_CHALLENGE_ANSWERS=N then create N challenges + |(one for every user that has a View where permission "can_add_transaction_request_to_any_account"=true) + |In case REQUIRED_CHALLENGE_ANSWERS is not defined as an account attribute default value is 1. + | + |Transaction Requests contain charge information giving the client the opportunity to proceed or not (as long as the challenge level is appropriate). + | + |Transaction Requests can have one of several Transaction Request Types which expect different bodies. The escaped body is returned in the details key of the GET response. + |This provides some commonality and one URL for many different payment or transfer types with enough flexibility to validate them differently. + | + |The payer is set in the URL. Money comes out of the BANK_ID and ACCOUNT_ID specified in the URL. + | + |In sandbox mode, TRANSACTION_REQUEST_TYPE is commonly set to ACCOUNT. See getTransactionRequestTypesSupportedByBank for all supported types. + | + |In sandbox mode, if the amount is less than 1000 EUR (any currency, unless it is set differently on this server), the transaction request will create a transaction without a challenge, else the Transaction Request will be set to INITIALISED and a challenge will need to be answered. + | + |If a challenge is created you must answer it using Answer Transaction Request Challenge before the Transaction is created. + | + |You can transfer between different currency accounts. (new in 2.0.0). The currency in body must match the sending account. + | + |The following static FX rates are available in sandbox mode: + | + |${exchangeRates} + | + | + |Transaction Requests satisfy PSD2 requirements thus: + | + |1) A transaction can be initiated by a third party application. + | + |2) The customer is informed of the charge that will incurred. + | + |3) The call supports delegated authentication (OAuth) + | + |See [this python code](https://github.com/OpenBankProject/Hello-OBP-DirectLogin-Python/blob/master/hello_payments.py) for a complete example of this flow. + | + |There is further documentation [here](https://github.com/OpenBankProject/OBP-API/wiki/Transaction-Requests) + | + |""" + + val lowAmount = AmountOfMoneyJsonV121("EUR", "12.50") + + val sharedChargePolicy = ChargePolicy.withName("SHARED") + + def createTransactionRequest(bankId: BankId, accountId: AccountId, viewId: ViewId, transactionRequestType: TransactionRequestType, json: JValue): Future[(TransactionRequestWithChargeJSON400, Option[CallContext])] = { + for { + (Full(u), callContext) <- SS.user + + transactionRequestTypeValue <- NewStyle.function.tryons(s"$InvalidTransactionRequestType: '${transactionRequestType.value}'. OBP does not support it.", 400, callContext) { + TransactionRequestTypes.withName(transactionRequestType.value) + } + + (fromAccount, callContext) <- transactionRequestTypeValue match { + case CARD => + for{ + transactionRequestBodyCard <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $CARD json format", 400, callContext) { + json.extract[TransactionRequestBodyCardJsonV400] + } + // 1.1 get Card from card_number + (cardFromCbs,callContext) <- NewStyle.function.getPhysicalCardByCardNumber(transactionRequestBodyCard.card.card_number, callContext) + + // 1.2 check card name/expire month. year. + calendar = Calendar.getInstance + _ = calendar.setTime(cardFromCbs.expires) + yearFromCbs = calendar.get(Calendar.YEAR).toString + monthFromCbs = calendar.get(Calendar.MONTH).toString + nameOnCardFromCbs= cardFromCbs.nameOnCard + cvvFromCbs= cardFromCbs.cvv.getOrElse("") + brandFromCbs= cardFromCbs.brand.getOrElse("") + + _ <- Helper.booleanToFuture(s"$InvalidJsonValue brand is not matched", cc=callContext) { + transactionRequestBodyCard.card.brand.equalsIgnoreCase(brandFromCbs) + } + + dateFromJsonBody <- NewStyle.function.tryons(s"$InvalidDateFormat year should be 'yyyy', " + + s"eg: 2023, but current expiry_year(${transactionRequestBodyCard.card.expiry_year}), " + + s"month should be 'xx', eg: 02, but current expiry_month(${transactionRequestBodyCard.card.expiry_month})", 400, callContext) { + DateWithMonthFormat.parse(s"${transactionRequestBodyCard.card.expiry_year}-${transactionRequestBodyCard.card.expiry_month}") + } + _ <- Helper.booleanToFuture(s"$InvalidJsonValue your credit card is expired.", cc=callContext) { + org.apache.commons.lang3.time.DateUtils.addMonths(new Date(), 1).before(dateFromJsonBody) + } + + _ <- Helper.booleanToFuture(s"$InvalidJsonValue expiry_year is not matched", cc=callContext) { + transactionRequestBodyCard.card.expiry_year.equalsIgnoreCase(yearFromCbs) + } + _ <- Helper.booleanToFuture(s"$InvalidJsonValue expiry_month is not matched", cc=callContext) { + transactionRequestBodyCard.card.expiry_month.toInt.equals(monthFromCbs.toInt+1) + } + + _ <- Helper.booleanToFuture(s"$InvalidJsonValue name_on_card is not matched", cc=callContext) { + transactionRequestBodyCard.card.name_on_card.equalsIgnoreCase(nameOnCardFromCbs) + } + _ <- Helper.booleanToFuture(s"$InvalidJsonValue cvv is not matched", cc=callContext) { + HashUtil.Sha256Hash(transactionRequestBodyCard.card.cvv).equals(cvvFromCbs) + } + + } yield{ + (cardFromCbs.account, callContext) + } + + case _ => NewStyle.function.getBankAccount(bankId,accountId, callContext) + } + _ <- NewStyle.function.isEnabledTransactionRequests(callContext) + _ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext) { + isValidID(fromAccount.accountId.value) + } + _ <- Helper.booleanToFuture(InvalidBankIdFormat, cc=callContext) { + isValidID(fromAccount.bankId.value) + } + + _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, BankIdAccountId(fromAccount.bankId, fromAccount.accountId), u, callContext) + + _ <- Helper.booleanToFuture(s"${InvalidTransactionRequestType}: '${transactionRequestType.value}'. Current Sandbox does not support it. ", cc=callContext) { + APIUtil.getPropsValue("transactionRequests_supported_types", "").split(",").contains(transactionRequestType.value) + } + + // Check the input JSON format, here is just check the common parts of all four types + transDetailsJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $TransactionRequestBodyCommonJSON ", 400, callContext) { + json.extract[TransactionRequestBodyCommonJSON] + } + + transactionAmountNumber <- NewStyle.function.tryons(s"$InvalidNumber Current input is ${transDetailsJson.value.amount} ", 400, callContext) { + BigDecimal(transDetailsJson.value.amount) + } + + _ <- Helper.booleanToFuture(s"${NotPositiveAmount} Current input is: '${transactionAmountNumber}'", cc=callContext) { + transactionAmountNumber > BigDecimal("0") + } + + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { + isValidCurrencyISOCode(transDetailsJson.value.currency) + } + + // Prevent default value for transaction request type (at least). + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { + isValidCurrencyISOCode(transDetailsJson.value.currency) + } + + (createdTransactionRequest, callContext) <- transactionRequestTypeValue match { + case REFUND => { + for { + transactionRequestBodyRefundJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { + json.extract[TransactionRequestBodyRefundJsonV400] + } + + transactionId = TransactionId(transactionRequestBodyRefundJson.refund.transaction_id) + + (fromAccount, toAccount, transaction, callContext) <- transactionRequestBodyRefundJson.to match { + case Some(refundRequestTo) if refundRequestTo.account_id.isDefined && refundRequestTo.bank_id.isDefined => + val toBankId = BankId(refundRequestTo.bank_id.get) + val toAccountId = AccountId(refundRequestTo.account_id.get) + for { + (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) + (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) + } yield (fromAccount, toAccount, transaction, callContext) + + case Some(refundRequestTo) if refundRequestTo.counterparty_id.isDefined => + val toCounterpartyId = CounterpartyId(refundRequestTo.counterparty_id.get) + for { + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(toCounterpartyId, callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, isOutgoingAccount = true, callContext) + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + (transaction, callContext) <- NewStyle.function.getTransaction(fromAccount.bankId, fromAccount.accountId, transactionId, callContext) + } yield (fromAccount, toAccount, transaction, callContext) + + case None if transactionRequestBodyRefundJson.from.isDefined => + val fromCounterpartyId = CounterpartyId(transactionRequestBodyRefundJson.from.get.counterparty_id) + val toAccount = fromAccount + for { + (fromCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(fromCounterpartyId, callContext) + fromAccount <- NewStyle.function.getBankAccountFromCounterparty(fromCounterparty, isOutgoingAccount = false, callContext) + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + fromCounterparty.isBeneficiary + } + (transaction, callContext) <- NewStyle.function.getTransaction(toAccount.bankId, toAccount.accountId, transactionId, callContext) + } yield (fromAccount, toAccount, transaction, callContext) + } + + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyRefundJson)(Serialization.formats(NoTypeHints)) + } + + _ <- Helper.booleanToFuture(s"${RefundedTransaction} Current input amount is: '${transDetailsJson.value.amount}'. It can not be more than the original amount(${(transaction.amount).abs})", cc=callContext) { + (transaction.amount).abs >= transactionAmountNumber + } + //TODO, we need additional field to guarantee the transaction is refunded... + // _ <- Helper.booleanToFuture(s"${RefundedTransaction}") { + // !((transaction.description.toString contains(" Refund to ")) && (transaction.description.toString contains(" and transaction_id("))) + // } + + //we add the extra info (counterparty name + transaction_id) for this special Refund endpoint. + newDescription = s"${transactionRequestBodyRefundJson.description} - Refund for transaction_id: (${transactionId.value}) to ${transaction.otherAccount.counterpartyName}" + + //This is the refund endpoint, the original fromAccount is the `toAccount` which will receive money. + refundToAccount = fromAccount + //This is the refund endpoint, the original toAccount is the `fromAccount` which will lose money. + refundFromAccount = toAccount + + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + refundFromAccount, + refundToAccount, + transactionRequestType, + transactionRequestBodyRefundJson.copy(description = newDescription), + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) //in ACCOUNT, ChargePolicy set default "SHARED" + + _ <- NewStyle.function.createOrUpdateTransactionRequestAttribute( + bankId = bankId, + transactionRequestId = createdTransactionRequest.id, + transactionRequestAttributeId = None, + name = "original_transaction_id", + attributeType = TransactionRequestAttributeType.withName("STRING"), + value = transactionId.value, + callContext = callContext + ) + + refundReasonCode = transactionRequestBodyRefundJson.refund.reason_code + _ <- if (refundReasonCode.nonEmpty) { + NewStyle.function.createOrUpdateTransactionRequestAttribute( + bankId = bankId, + transactionRequestId = createdTransactionRequest.id, + transactionRequestAttributeId = None, + name = "refund_reason_code", + attributeType = TransactionRequestAttributeType.withName("STRING"), + value = refundReasonCode, + callContext = callContext) + } else Future.successful() + + (newTransactionRequestStatus, callContext) <- NewStyle.function.notifyTransactionRequest(refundFromAccount, refundToAccount, createdTransactionRequest, callContext) + _ <- NewStyle.function.saveTransactionRequestStatusImpl(createdTransactionRequest.id, newTransactionRequestStatus.toString, callContext) + createdTransactionRequest <- Future(createdTransactionRequest.copy(status = newTransactionRequestStatus.toString)) + + } yield (createdTransactionRequest, callContext) + } + case ACCOUNT | SANDBOX_TAN => { + for { + transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { + json.extract[TransactionRequestBodySandBoxTanJSON] + } + + toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) + toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) + (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) + + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) + } + + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodySandboxTan, + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) //in ACCOUNT, ChargePolicy set default "SHARED" + } yield (createdTransactionRequest, callContext) + } + case ACCOUNT_OTP => { + for { + transactionRequestBodySandboxTan <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $ACCOUNT json format", 400, callContext) { + json.extract[TransactionRequestBodySandBoxTanJSON] + } + + toBankId = BankId(transactionRequestBodySandboxTan.to.bank_id) + toAccountId = AccountId(transactionRequestBodySandboxTan.to.account_id) + (toAccount, callContext) <- NewStyle.function.checkBankAccountExists(toBankId, toAccountId, callContext) + + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodySandboxTan)(Serialization.formats(NoTypeHints)) + } + + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodySandboxTan, + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) //in ACCOUNT, ChargePolicy set default "SHARED" + } yield (createdTransactionRequest, callContext) + } + case COUNTERPARTY => { + for { + //For COUNTERPARTY, Use the counterpartyId to find the toCounterparty and set up the toAccount + transactionRequestBodyCounterparty <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $COUNTERPARTY json format", 400, callContext) { + json.extract[TransactionRequestBodyCounterpartyJSON] + } + toCounterpartyId = transactionRequestBodyCounterparty.to.counterparty_id + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + // Check we can send money to it. + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = transactionRequestBodyCounterparty.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyCounterparty)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodyCounterparty, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) + } yield (createdTransactionRequest, callContext) + } + case AGENT_CASH_WITHDRAWAL => { + for { + //For Agent, Use the agentId to find the agent and set up the toAccount + transactionRequestBodyAgent <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $AGENT_CASH_WITHDRAWAL json format", 400, callContext) { + json.extract[TransactionRequestBodyAgentJsonV510] + } + toAgentId = transactionRequestBodyAgent.to.agent_id + (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(toAgentId, callContext) + customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { + customerAccountLinks.head + } + (toAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) + chargePolicy = transactionRequestBodyAgent.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyAgent)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodyAgent, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) + } yield (createdTransactionRequest, callContext) + } + case CARD => { + for { + //2rd: get toAccount from counterpartyId + transactionRequestBodyCard <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $CARD json format", 400, callContext) { + json.extract[TransactionRequestBodyCardJsonV400] + } + toCounterpartyId = transactionRequestBodyCard.to.counterparty_id + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + // Check we can send money to it. + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = ChargePolicy.RECEIVER.toString + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyCard)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodyCard, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) + } yield (createdTransactionRequest, callContext) + + } + case SIMPLE => { + for { + //For SAMPLE, we will create/get toCounterparty on site and set up the toAccount + transactionRequestBodySimple <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SIMPLE json format", 400, callContext) { + json.extract[TransactionRequestBodySimpleJsonV400] + } + (toCounterparty, callContext) <- NewStyle.function.getOrCreateCounterparty( + name = transactionRequestBodySimple.to.name, + description = transactionRequestBodySimple.to.description, + currency = transactionRequestBodySimple.value.currency, + createdByUserId = u.userId, + thisBankId = bankId.value, + thisAccountId = accountId.value, + thisViewId = viewId.value, + otherBankRoutingScheme = transactionRequestBodySimple.to.other_bank_routing_scheme, + otherBankRoutingAddress = transactionRequestBodySimple.to.other_bank_routing_address, + otherBranchRoutingScheme = transactionRequestBodySimple.to.other_branch_routing_scheme, + otherBranchRoutingAddress = transactionRequestBodySimple.to.other_branch_routing_address, + otherAccountRoutingScheme = transactionRequestBodySimple.to.other_account_routing_scheme, + otherAccountRoutingAddress = transactionRequestBodySimple.to.other_account_routing_address, + otherAccountSecondaryRoutingScheme = transactionRequestBodySimple.to.other_account_secondary_routing_scheme, + otherAccountSecondaryRoutingAddress = transactionRequestBodySimple.to.other_account_secondary_routing_address, + callContext: Option[CallContext], + ) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + // Check we can send money to it. + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = transactionRequestBodySimple.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodySimple)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transactionRequestBodySimple, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) + } yield (createdTransactionRequest, callContext) + + } + case SEPA => { + for { + //For SEPA, Use the IBAN to find the toCounterparty and set up the toAccount + transDetailsSEPAJson <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $SEPA json format", 400, callContext) { + json.extract[TransactionRequestBodySEPAJsonV400] + } + toIban = transDetailsSEPAJson.to.iban + (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByIbanAndBankAccountId(toIban, fromAccount.bankId, fromAccount.accountId, callContext) + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) + _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { + toCounterparty.isBeneficiary + } + chargePolicy = transDetailsSEPAJson.charge_policy + _ <- Helper.booleanToFuture(s"$InvalidChargePolicy", cc=callContext) { + ChargePolicy.values.contains(ChargePolicy.withName(chargePolicy)) + } + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transDetailsSEPAJson)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + toAccount, + transactionRequestType, + transDetailsSEPAJson, + transDetailsSerialized, + chargePolicy, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + transDetailsSEPAJson.reasons.map(_.map(_.transform)), + callContext) + } yield (createdTransactionRequest, callContext) + } + case FREE_FORM => { + for { + transactionRequestBodyFreeForm <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $FREE_FORM json format", 400, callContext) { + json.extract[TransactionRequestBodyFreeFormJSON] + } + // Following lines: just transfer the details body, add Bank_Id and Account_Id in the Detail part. This is for persistence and 'answerTransactionRequestChallenge' + transactionRequestAccountJSON = TransactionRequestAccountJsonV140(bankId.value, accountId.value) + transDetailsSerialized <- NewStyle.function.tryons(UnknownError, 400, callContext) { + write(transactionRequestBodyFreeForm)(Serialization.formats(NoTypeHints)) + } + (createdTransactionRequest, callContext) <- NewStyle.function.createTransactionRequestv400(u, + viewId, + fromAccount, + fromAccount, + transactionRequestType, + transactionRequestBodyFreeForm, + transDetailsSerialized, + sharedChargePolicy.toString, + Some(OBP_TRANSACTION_REQUEST_CHALLENGE), + getScaMethodAtInstance(transactionRequestType.value).toOption, + None, + callContext) + } yield + (createdTransactionRequest, callContext) + } + } + (challenges, callContext) <- NewStyle.function.getChallengesByTransactionRequestId(createdTransactionRequest.id.value, callContext) + } yield { + (JSONFactory400.createTransactionRequestWithChargeJSON(createdTransactionRequest, challenges), HttpCode.`201`(callContext)) + } + } + } diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 74beb10c3..e123717a0 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -1290,7 +1290,7 @@ trait APIMethods500 { case "banks" :: BankId(bankId) :: "customers" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { - postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostCustomerJsonV310 ", 400, cc.callContext) { + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostCustomerJsonV500 ", 400, cc.callContext) { json.extract[PostCustomerJsonV500] } _ <- Helper.booleanToFuture(failMsg = InvalidJsonContent + s" The field dependants(${postedData.dependants.getOrElse(0)}) not equal the length(${postedData.dob_of_dependants.getOrElse(Nil).length }) of dob_of_dependants array", 400, cc.callContext) { diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index bbf7e0976..a3024f616 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -18,15 +18,13 @@ import code.api.util.newstyle.RegulatedEntityNewStyle.{createRegulatedEntityNewS import code.api.v2_1_0.ConsumerRedirectUrlJSON import code.api.v3_0_0.JSONFactory300 import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson -import code.api.v3_1_0.ConsentJsonV310 +import code.api.v3_1_0.{ConsentJsonV310, JSONFactory310} import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson +import code.api.v4_0_0.APIMethods400.{createTransactionRequest, transactionRequestGeneralText} import code.api.v4_0_0.JSONFactory400.{createAccountBalancesJson, createBalancesJson, createNewCoreBankAccountJson} import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400, RevokedJsonV400} -import code.api.v5_0_0.{JSONFactory500, PostConsentRequestJsonV500} -import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createConsentsJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson} -import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson} import code.api.v5_0_0.JSONFactory500 -import code.api.v5_1_0.JSONFactory510.{createRegulatedEntitiesJson, createRegulatedEntityJson} +import code.api.v5_1_0.JSONFactory510.{createConsentsInfoJsonV510, createConsentsJsonV510, createRegulatedEntitiesJson, createRegulatedEntityJson} import code.atmattribute.AtmAttribute import code.bankconnectors.Connector import code.consent.{ConsentRequests, Consents} @@ -345,6 +343,178 @@ trait APIMethods510 { } } } + + staticResourceDocs += ResourceDoc( + createAgent, + implementedInApiVersion, + nameOf(createAgent), + "POST", + "/banks/BANK_ID/agents", + "Create Agent", + s""" + |The Customer resource stores the customer number (which is set by the backend), legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc. + |Dates need to be in the format 2013-01-21T23:08:00Z + | + |Note: If you need to set a specific customer number, use the Update Customer Number endpoint after this call. + | + |${authenticationRequiredMessage(true)} + |""", + postAgentJsonV510, + agentJsonV510, + List( + $UserNotLoggedIn, + $BankNotFound, + InvalidJsonFormat, + CustomerNumberAlreadyExists, + UserNotFoundById, + CustomerAlreadyExistsForUser, + CreateConsumerError, + UnknownError + ), + List(apiTagCustomer, apiTagPerson) + ) + + lazy val createAgent : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "agents" :: Nil JsonPost json -> _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostAgentJsonV510 ", 400, cc.callContext) { + json.extract[PostAgentJsonV510] + } + (_, callContext) <- NewStyle.function.checkCustomerNumberAvailable(bankId, postedData.agent_number, cc.callContext) + (customer, callContext) <- NewStyle.function.createCustomerC2( + bankId, + postedData.legal_name, + postedData.agent_number, + postedData.mobile_phone_number, + "", + CustomerFaceImage(null, ""), + null, + "", + 0, + Nil, + "", + "", + false, + null, + None, + None, + "", + "", + "", + callContext, + ) + (bankAccount, callContext) <- NewStyle.function.createBankAccount( + bankId, + AccountId(APIUtil.generateUUID()), + "AGENT", + "AGENT", + postedData.currency, + 0, + postedData.legal_name, + null, + Nil, + callContext + ) + (_, callContext) <- NewStyle.function.createCustomerAccountLink(customer.customerId, bankAccount.bankId.value, bankAccount.accountId.value, "Owner", callContext) + } yield { + (JSONFactory510.createAgentJson(customer, bankAccount), HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getAgent, + implementedInApiVersion, + nameOf(getAgent), + "GET", + "/banks/BANK_ID/agents/AGENT_ID", + "Get Agent", + s"""Get Agent. + | + |${authenticationRequiredMessage(true)} + |""".stripMargin, + EmptyBody, + agentJsonV510, + List( + $UserNotLoggedIn, + $BankNotFound, + UnknownError + ), + List(apiTagAccount) + ) + + lazy val getAgent: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "agents" :: agentId :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (agent, callContext) <- NewStyle.function.getAgentByAgentId(agentId, callContext) + (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agentId, callContext) + customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, cc.callContext) { + customerAccountLinks.head + } + (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) + } yield { + (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + createTransactionRequestAgent, + implementedInApiVersion, + nameOf(createTransactionRequestAgent), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/AGENT/transaction-requests", + "Create Transaction Request (AGENT)", + s""" + | + |Either the `from` or the `to` field must be filled. Those fields refers to the information about the party that will be refunded. + | + |In case the `from` object is used, it means that the refund comes from the part that sent you a transaction. + |In the `from` object, you have two choices : + |- Use `bank_id` and `account_id` fields if the other account is registered on the OBP-API + |- Use the `counterparty_id` field in case the counterparty account is out of the OBP-API + | + |In case the `to` object is used, it means you send a request to a counterparty to ask for a refund on a previous transaction you sent. + |(This case is not managed by the OBP-API and require an external adapter) + | + | + |$transactionRequestGeneralText + | + """.stripMargin, + transactionRequestBodyAgentJsonV510, + transactionRequestWithChargeJSON400, + List( + $UserNotLoggedIn, + InvalidBankIdFormat, + InvalidAccountIdFormat, + InvalidJsonFormat, + $BankNotFound, + AccountNotFound, + $BankAccountNotFound, + InsufficientAuthorisationToCreateTransactionRequest, + InvalidTransactionRequestType, + InvalidJsonFormat, + InvalidNumber, + NotPositiveAmount, + InvalidTransactionRequestCurrency, + TransactionDisabled, + UnknownError + ), + List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2) + ) + + lazy val createTransactionRequestAgent: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "AGENT-CASH-WITHDRAWAL" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + val transactionRequestType = TransactionRequestType("ACCOUNT") + createTransactionRequest(bankId, accountId, viewId, transactionRequestType, json) + } + staticResourceDocs += ResourceDoc( createNonPersonalUserAttribute, implementedInApiVersion, diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index cc456d665..8e7146921 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -40,7 +40,7 @@ import code.atmattribute.AtmAttribute import code.atms.Atms.Atm import code.users.{UserAttribute, Users} import code.views.system.{AccountAccess, ViewDefinition} -import com.openbankproject.commons.model.{AccountRoutingJsonV121, Address, AtmId, AtmT, BankId, BankIdAccountId, BranchRoutingJsonV141, CreateViewJson, Customer, Location, Meta, RegulatedEntityTrait, UpdateViewJSON, View} +import com.openbankproject.commons.model.{AccountRoutingJsonV121, Address, AmountOfMoneyJsonV121, AtmId, AtmT, BankAccount, BankId, BankIdAccountId, BranchRoutingJsonV141, CreateViewJson, Customer, Location, Meta, RegulatedEntityTrait, TransactionRequestCommonBodyJSON, UpdateViewJSON, View} import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} import java.util.Date @@ -317,6 +317,38 @@ case class UserAttributesResponseJsonV510( ) case class CustomerIdJson(id: String) +case class AgentJson( + id: String, + name:String +) + +case class AgentIdJson( + agent_id: String +) + +case class TransactionRequestBodyAgentJsonV510( + to: AgentIdJson, + value: AmountOfMoneyJsonV121, + description: String, + charge_policy: String, + future_date: Option[String] = None +) extends TransactionRequestCommonBodyJSON + +case class PostAgentJsonV510( + legal_name: String, + mobile_phone_number: String, + agent_number: String, + currency: String +) + +case class AgentJsonV510( + agent_id: String, + legal_name: String, + mobile_phone_number: String, + agent_number: String, + currency: String +) + case class CustomersIdsJsonV510(customers: List[CustomerIdJson]) case class PostCustomerLegalNameJsonV510(legal_name: String) @@ -912,6 +944,15 @@ object JSONFactory510 extends CustomJsonFormats { ConsumersJsonV510(consumers.map(createConsumerJSON(_,None))) } + def createAgentJson(customer: Customer, bankAccount: BankAccount): AgentJsonV510 = { + AgentJsonV510( + agent_id = customer.customerId, + legal_name = customer.legalName, + mobile_phone_number = customer.mobileNumber, + agent_number = customer.number, + currency = bankAccount.currency + ) + } } diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala index e65d502d3..0372048c8 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/enums/Enumerations.scala @@ -105,6 +105,7 @@ object TransactionRequestTypes extends OBPEnumeration[TransactionRequestTypes]{ object TARGET_2_PAYMENTS extends Value object CROSS_BORDER_CREDIT_TRANSFERS extends Value object REFUND extends Value + object AGENT_CASH_WITHDRAWAL extends Value } sealed trait StrongCustomerAuthentication extends EnumValue From 7854d4fedff71f36aceb21c19ba6da03ef389f59 Mon Sep 17 00:00:00 2001 From: hongwei Date: Mon, 18 Nov 2024 10:45:14 +0100 Subject: [PATCH 02/14] refactor/tweaked the InMemory level to trace --- obp-api/src/main/scala/code/api/cache/InMemory.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/obp-api/src/main/scala/code/api/cache/InMemory.scala b/obp-api/src/main/scala/code/api/cache/InMemory.scala index 959d88066..2b45437cf 100644 --- a/obp-api/src/main/scala/code/api/cache/InMemory.scala +++ b/obp-api/src/main/scala/code/api/cache/InMemory.scala @@ -17,12 +17,12 @@ object InMemory extends MdcLoggable { implicit val scalaCache = ScalaCache(GuavaCache(underlyingGuavaCache)) def memoizeSyncWithInMemory[A](cacheKey: Option[String])(@cacheKeyExclude ttl: Duration)(@cacheKeyExclude f: => A): A = { - logger.debug(s"InMemory.memoizeSyncWithInMemory.underlyingGuavaCache size ${underlyingGuavaCache.size()}, current cache key is $cacheKey") + logger.trace(s"InMemory.memoizeSyncWithInMemory.underlyingGuavaCache size ${underlyingGuavaCache.size()}, current cache key is $cacheKey") memoizeSync(ttl)(f) } def memoizeWithInMemory[A](cacheKey: Option[String])(@cacheKeyExclude ttl: Duration)(@cacheKeyExclude f: => Future[A])(implicit @cacheKeyExclude m: Manifest[A]): Future[A] = { - logger.debug(s"InMemory.memoizeWithInMemory.underlyingGuavaCache size ${underlyingGuavaCache.size()}, current cache key is $cacheKey") + logger.trace(s"InMemory.memoizeWithInMemory.underlyingGuavaCache size ${underlyingGuavaCache.size()}, current cache key is $cacheKey") memoize(ttl)(f) } } From cc8a963063f37b25e745da95c5f8ff76f752b5b9 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 19 Nov 2024 10:29:49 +0100 Subject: [PATCH 03/14] refactor/tweaked default setting for rabbitMq --- .../Adapter/MockedRabbitMqAdapter.scala | 3 ++- .../rabbitmq/RabbitMQUtils.scala | 24 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala index 662a1c651..bfd67dbe1 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/Adapter/MockedRabbitMqAdapter.scala @@ -31,6 +31,7 @@ class ServerCallback(val ch: Channel) extends DeliverCallback with MdcLoggable{ val replyProps = new BasicProperties.Builder() .correlationId(delivery.getProperties.getCorrelationId) .contentType("application/json") + .expiration("60000") .messageId(obpMessageId) .build val message = new String(delivery.getBody, "UTF-8") @@ -3099,7 +3100,7 @@ object MockedRabbitMqAdapter extends App with MdcLoggable{ connection = factory.newConnection() channel = connection.createChannel() - channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null) + channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, RabbitMQUtils.args) channel.basicQos(1) // stop after one consumed message since this is example code val serverCallback = new ServerCallback(channel) diff --git a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQUtils.scala b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQUtils.scala index 0fa125cb5..7b238a8c5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQUtils.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQUtils.scala @@ -34,6 +34,12 @@ object RabbitMQUtils extends MdcLoggable{ val keystorePassword = APIUtil.getPropsValue("keystore.password").getOrElse(APIUtil.initPasswd) val truststorePath = APIUtil.getPropsValue("truststore.path").getOrElse("") val truststorePassword = APIUtil.getPropsValue("keystore.password").getOrElse(APIUtil.initPasswd) + + val args = new util.HashMap[String, AnyRef]() + //60s It sets the time (in milliseconds) after which the queue will + // automatically be deleted if it is not used, i.e., if no consumer is connected to it during that time. + args.put("x-expires", Integer.valueOf(60000)) + args.put("x-message-ttl", Integer.valueOf(60000)) private implicit val formats = code.api.util.CustomJsonFormats.nullTolerateFormats @@ -74,15 +80,9 @@ object RabbitMQUtils extends MdcLoggable{ val rabbitRequestJsonString: String = write(outBound) // convert OutBound to json string - val args = new util.HashMap[String, AnyRef]() - //60s It sets the time (in milliseconds) after which the queue will - // automatically be deleted if it is not used, i.e., if no consumer is connected to it during that time. - args.put("x-expires", Integer.valueOf(60000)) - - val connection = RabbitMQConnectionPool.borrowConnection() val channel = connection.createChannel() // channel is not thread safe, so we always create new channel for each message. - channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null) + channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, args) val replyQueueName:String = channel.queueDeclare( "", // Queue name false, // durable: non-persistent @@ -106,6 +106,16 @@ object RabbitMQUtils extends MdcLoggable{ val responseCallback = new ResponseCallback(rabbitMQCorrelationId, channel) channel.basicConsume(replyQueueName, true, responseCallback, cancelCallback) +// // Add a timeout mechanism here: +// val timeout = 10 // seconds +// val start = System.currentTimeMillis() +// while (true) { +// Thread.sleep(100) +// if (System.currentTimeMillis() - start > timeout * 1000) { +// println("Request timed out") +// channel.close(); +// } +// } responseCallback.take() From a871f79794a97978ef2c76ad9d225fdd762efb11 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 19 Nov 2024 11:16:01 +0100 Subject: [PATCH 04/14] feature/OBPv510 added updateAgentstatus and V400 added createTransactionRequestAgent --- .../SwaggerDefinitionsJSON.scala | 18 ++- .../main/scala/code/api/util/APIUtil.scala | 9 +- .../main/scala/code/api/util/ApiRole.scala | 6 + .../main/scala/code/api/util/NewStyle.scala | 14 ++ .../scala/code/api/v4_0_0/APIMethods400.scala | 61 +++++++- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 12 ++ .../scala/code/api/v5_1_0/APIMethods510.scala | 141 +++++++++++------- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 34 +++-- .../scala/code/bankconnectors/Connector.scala | 7 + .../bankconnectors/LocalMappedConnector.scala | 46 +++++- .../KafkaMappedConnector_vSept2018.scala | 11 +- .../customer/MappedCustomerProvider.scala | 12 +- .../MappedTransactionRequestProvider.scala | 16 ++ .../commons/model/CommonModel.scala | 28 +--- .../commons/model/CustomerDataModel.scala | 3 + 15 files changed, 310 insertions(+), 108 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 8297bb1f7..89f43a279 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5516,9 +5516,9 @@ object SwaggerDefinitionsJSON { List(consumerJsonV510) ) - val agentIdJson = AgentIdJson("") + val agentIdJson = AgentIdJson(agentIdExample.value) - val transactionRequestBodyAgentJsonV510 = TransactionRequestBodyAgentJsonV510( + val transactionRequestBodyAgentJsonV400 = TransactionRequestBodyAgentJsonV400( to = agentIdJson, value = amountOfMoneyJsonV121, description = descriptionExample.value, @@ -5533,6 +5533,11 @@ object SwaggerDefinitionsJSON { currency = currencyExample.value ) + val putAgentJsonV510 = PutAgentJsonV510( + is_pending_agent = true, + is_confirmed_agent = true + ) + val agentJsonV510 = AgentJsonV510( agent_id = agentIdExample.value, legal_name = legalNameExample.value, @@ -5541,6 +5546,15 @@ object SwaggerDefinitionsJSON { currency = currencyExample.value ) + val agentMinimalJsonV510 = AgentMinimalJsonV510( + agent_id = agentIdExample.value, + legal_name = legalNameExample.value, + ) + + val agentMinimalsJsonV510 = AgentMinimalsJsonV510( + agents = List(agentMinimalJsonV510) + ) + //The common error or success format. //Just some helper format to use in Json case class NotSupportedYet() diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index 855a4d0bb..66b9d5aee 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -938,7 +938,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ case _ => null } - //started -- Filtering and Paging revelent methods//////////////////////////// + //started -- Filtering and Paging relevant methods//////////////////////////// def parseObpStandardDate(date: String): Box[Date] = { val parsedDate = tryo{DateWithMsFormat.parse(date)} @@ -1280,7 +1280,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val queryStrings = urlAndQueryString.split("&").map(_.split("=")).flatten //Full(from_date, $DateWithMsExampleString, to_date, $DateWithMsExampleString) if (queryStrings.contains(name)&& queryStrings.length > queryStrings.indexOf(name)+1) queryStrings(queryStrings.indexOf(name)+1) else ""//Full($DateWithMsExampleString) } - //ended -- Filtering and Paging revelent methods //////////////////////////// + //ended -- Filtering and Paging relevant methods //////////////////////////// /** Import this object's methods to add signing operators to dispatch.Request */ @@ -2117,7 +2117,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ |eg1:?limit=100&offset=0 |""". stripMargin - val sortDirectionParameters = + val sortDirectionParameters = if (containsSortDirection) { s""" | |* sort_direction=ASC/DESC ==> default value: DESC. @@ -2125,6 +2125,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ |eg2:?limit=100&offset=0&sort_direction=ASC | |""". stripMargin + }else{ + "" + } val dateParameter = if(containsDate){ s""" diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 91defb78f..137b2712a 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -109,6 +109,12 @@ object ApiRole extends MdcLoggable{ case class CanGetAgent(requiresBankId: Boolean = true) extends ApiRole lazy val canGetAgent = CanGetAgent() + + case class CanUpdateAgentStatusAtAnyBank(requiresBankId: Boolean = false) extends ApiRole + lazy val canUpdateAgentStatusAtAnyBank = CanUpdateAgentStatusAtAnyBank() + + case class CanUpdateAgentStatusAtOneBank(requiresBankId: Boolean = true) extends ApiRole + lazy val canUpdateAgentStatusAtOneBank = CanUpdateAgentStatusAtOneBank() case class CanUpdateCustomerEmail(requiresBankId: Boolean = true) extends ApiRole lazy val canUpdateCustomerEmail = CanUpdateCustomerEmail() diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 80342ac23..feb909f77 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2838,6 +2838,20 @@ object NewStyle extends MdcLoggable{ callContext) map { i => (unboxFullOrFail(i._1, callContext, UpdateCustomerError), i._2) } + + def updateAgentStatus( + agentId: String, + is_pending_agent: Boolean, + is_confirmed_agent: Boolean, + callContext: Option[CallContext]): OBPReturnType[Customer] = + Connector.connector.vend.updateAgentStatus( + agentId: String, + is_pending_agent: Boolean, + is_confirmed_agent: Boolean, + callContext: Option[CallContext] + ) map { + i => (unboxFullOrFail(i._1, callContext, UpdateCustomerError), i._2) + } def updateCustomerCreditData(customerId: String, creditRating: Option[String], creditSource: Option[String], diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index d8eee9ed1..5924f92bf 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -45,8 +45,7 @@ import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} import code.api.util.FutureUtil.EndpointContext import code.api.v4_0_0.APIMethods400.{createTransactionRequest, exchangeRates, lowAmount, sharedChargePolicy, transactionRequestGeneralText} -import code.api.v5_0_0.OBPAPI5_0_0 -import code.api.v5_1_0.TransactionRequestBodyAgentJsonV510 +import code.api.v4_0_0.TransactionRequestBodyAgentJsonV400 import code.api.{ChargePolicy, Constant, JsonResponseException} import code.apicollection.MappedApiCollectionsProvider import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider @@ -858,6 +857,61 @@ trait APIMethods400 extends MdcLoggable { ), List(apiTagTransactionRequest, apiTagPSD2PIS), Some(List(canCreateAnyTransactionRequest))) + + + staticResourceDocs += ResourceDoc( + createTransactionRequestAgentCashWithDrawal, + implementedInApiVersion, + nameOf(createTransactionRequestAgentCashWithDrawal), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/AGENT_CASH_WITHDRAWAL/transaction-requests", + "Create Transaction Request (AGENT_CASH_WITHDRAWAL)", + s""" + | + |Either the `from` or the `to` field must be filled. Those fields refers to the information about the party that will be refunded. + | + |In case the `from` object is used, it means that the refund comes from the part that sent you a transaction. + |In the `from` object, you have two choices : + |- Use `bank_id` and `account_id` fields if the other account is registered on the OBP-API + |- Use the `counterparty_id` field in case the counterparty account is out of the OBP-API + | + |In case the `to` object is used, it means you send a request to a counterparty to ask for a refund on a previous transaction you sent. + |(This case is not managed by the OBP-API and require an external adapter) + | + | + |$transactionRequestGeneralText + | + """.stripMargin, + transactionRequestBodyAgentJsonV400, + transactionRequestWithChargeJSON400, + List( + $UserNotLoggedIn, + InvalidBankIdFormat, + InvalidAccountIdFormat, + InvalidJsonFormat, + $BankNotFound, + AccountNotFound, + $BankAccountNotFound, + InsufficientAuthorisationToCreateTransactionRequest, + InvalidTransactionRequestType, + InvalidJsonFormat, + InvalidNumber, + NotPositiveAmount, + InvalidTransactionRequestCurrency, + TransactionDisabled, + UnknownError + ), + List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2) + ) + + lazy val createTransactionRequestAgentCashWithDrawal: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: + "AGENT_CASH_WITHDRAWAL" :: "transaction-requests" :: Nil JsonPost json -> _ => + cc => + implicit val ec = EndpointContext(Some(cc)) + val transactionRequestType = TransactionRequestType("AGENT_CASH_WITHDRAWAL") + createTransactionRequest(bankId, accountId, viewId, transactionRequestType, json) + } lazy val createTransactionRequestAccount: OBPEndpoint = { case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: @@ -12575,9 +12629,10 @@ object APIMethods400 extends RestHelper with APIMethods400 { for { //For Agent, Use the agentId to find the agent and set up the toAccount transactionRequestBodyAgent <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $AGENT_CASH_WITHDRAWAL json format", 400, callContext) { - json.extract[TransactionRequestBodyAgentJsonV510] + json.extract[TransactionRequestBodyAgentJsonV400] } toAgentId = transactionRequestBodyAgent.to.agent_id + (agent, callContext) <- NewStyle.function.getAgentByAgentId(toAgentId, callContext) (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(toAgentId, callContext) customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { customerAccountLinks.head diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index dd9645c16..fec521ac8 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -391,6 +391,18 @@ case class ConsentInfoJsonV400(consent_id: String, api_version: String) case class ConsentInfosJsonV400(consents: List[ConsentInfoJsonV400]) +case class AgentIdJson( + agent_id: String +) + +case class TransactionRequestBodyAgentJsonV400( + to: AgentIdJson, + value: AmountOfMoneyJsonV121, + description: String, + charge_policy: String, + future_date: Option[String] = None +) extends TransactionRequestCommonBodyJSON + case class TransactionRequestBodySEPAJsonV400( value: AmountOfMoneyJsonV121, to: IbanJson, diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index a3024f616..eab0e6b8f 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -10,6 +10,7 @@ import code.api.util.ErrorMessages.{$UserNotLoggedIn, BankNotFound, ConsentNotFo import code.api.util.FutureUtil.{EndpointContext, EndpointTimeout} import code.api.util.JwtUtil.{getSignedPayloadAsJson, verifyJwt} import code.api.util.NewStyle.HttpCode +import code.api.util.NewStyle.function.extractQueryParams import code.api.util.X509.{getCommonName, getEmailAddress, getOrganization} import code.api.util._ import code.api.util.newstyle.BalanceNewStyle @@ -422,6 +423,57 @@ trait APIMethods510 { } } } + + staticResourceDocs += ResourceDoc( + updateAgentstatus, + implementedInApiVersion, + nameOf(updateAgentstatus), + "PUT", + "/banks/BANK_ID/agents/AGENT_ID", + "Update Agent status", + s""" + |${authenticationRequiredMessage(true)} + |""", + putAgentJsonV510, + agentJsonV510, + List( + $UserNotLoggedIn, + $BankNotFound, + InvalidJsonFormat, + CustomerNumberAlreadyExists, + UserNotFoundById, + CustomerAlreadyExistsForUser, + CreateConsumerError, + UnknownError + ), + List(apiTagCustomer, apiTagPerson), + Some(canUpdateAgentStatusAtAnyBank :: canUpdateAgentStatusAtOneBank :: Nil) + ) + + lazy val updateAgentstatus : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "agents" :: agentId :: Nil JsonPost json -> _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostAgentJsonV510 ", 400, cc.callContext) { + json.extract[PutAgentJsonV510] + } + (agent, callContext) <- NewStyle.function.getAgentByAgentId(agentId, cc.callContext) + (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agentId, callContext) + customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { + customerAccountLinks.head + } + (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) + (customer, callContext) <- NewStyle.function.updateCustomerScaData( + agentId, + Some(postedData.is_pending_agent.toString), + None, + None, + callContext) + } yield { + (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`201`(callContext)) + } + } + } staticResourceDocs += ResourceDoc( getAgent, @@ -451,7 +503,7 @@ trait APIMethods510 { (Full(u), callContext) <- SS.user (agent, callContext) <- NewStyle.function.getAgentByAgentId(agentId, callContext) (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agentId, callContext) - customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, cc.callContext) { + customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { customerAccountLinks.head } (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) @@ -460,60 +512,6 @@ trait APIMethods510 { } } } - - staticResourceDocs += ResourceDoc( - createTransactionRequestAgent, - implementedInApiVersion, - nameOf(createTransactionRequestAgent), - "POST", - "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/AGENT/transaction-requests", - "Create Transaction Request (AGENT)", - s""" - | - |Either the `from` or the `to` field must be filled. Those fields refers to the information about the party that will be refunded. - | - |In case the `from` object is used, it means that the refund comes from the part that sent you a transaction. - |In the `from` object, you have two choices : - |- Use `bank_id` and `account_id` fields if the other account is registered on the OBP-API - |- Use the `counterparty_id` field in case the counterparty account is out of the OBP-API - | - |In case the `to` object is used, it means you send a request to a counterparty to ask for a refund on a previous transaction you sent. - |(This case is not managed by the OBP-API and require an external adapter) - | - | - |$transactionRequestGeneralText - | - """.stripMargin, - transactionRequestBodyAgentJsonV510, - transactionRequestWithChargeJSON400, - List( - $UserNotLoggedIn, - InvalidBankIdFormat, - InvalidAccountIdFormat, - InvalidJsonFormat, - $BankNotFound, - AccountNotFound, - $BankAccountNotFound, - InsufficientAuthorisationToCreateTransactionRequest, - InvalidTransactionRequestType, - InvalidJsonFormat, - InvalidNumber, - NotPositiveAmount, - InvalidTransactionRequestCurrency, - TransactionDisabled, - UnknownError - ), - List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2) - ) - - lazy val createTransactionRequestAgent: OBPEndpoint = { - case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: ViewId(viewId) :: "transaction-request-types" :: - "AGENT-CASH-WITHDRAWAL" :: "transaction-requests" :: Nil JsonPost json -> _ => - cc => - implicit val ec = EndpointContext(Some(cc)) - val transactionRequestType = TransactionRequestType("ACCOUNT") - createTransactionRequest(bankId, accountId, viewId, transactionRequestType, json) - } staticResourceDocs += ResourceDoc( createNonPersonalUserAttribute, @@ -972,6 +970,39 @@ trait APIMethods510 { } } } + staticResourceDocs += ResourceDoc( + getAgents, + implementedInApiVersion, + nameOf(getAgents), + "GET", + "/banks/BANK_ID/agents", + "Get Agents at Bank", + s"""Get Agents at Bank. + | + |${authenticationRequiredMessage(false)} + | + |${urlParametersDocument(true, true)} + |""".stripMargin, + EmptyBody, + agentMinimalsJsonV510, + List( + $BankNotFound, + UnknownError + ), + List(apiTagAccount) + ) + + lazy val getAgents: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "agents" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (requestParams, callContext) <- extractQueryParams(cc.url, List("limit","offset","sort_direction"), cc.callContext) + customers <- NewStyle.function.getCustomers(bankId, callContext, requestParams) + } yield { + (JSONFactory510.createAgentMinimalsJson(customers), HttpCode.`200`(callContext)) + } + } + } staticResourceDocs += ResourceDoc( getAtmAttributes, diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 8e7146921..823290b63 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -322,18 +322,6 @@ case class AgentJson( name:String ) -case class AgentIdJson( - agent_id: String -) - -case class TransactionRequestBodyAgentJsonV510( - to: AgentIdJson, - value: AmountOfMoneyJsonV121, - description: String, - charge_policy: String, - future_date: Option[String] = None -) extends TransactionRequestCommonBodyJSON - case class PostAgentJsonV510( legal_name: String, mobile_phone_number: String, @@ -341,6 +329,11 @@ case class PostAgentJsonV510( currency: String ) +case class PutAgentJsonV510( + is_pending_agent: Boolean, + is_confirmed_agent: Boolean +) + case class AgentJsonV510( agent_id: String, legal_name: String, @@ -349,6 +342,14 @@ case class AgentJsonV510( currency: String ) +case class AgentMinimalJsonV510( + agent_id: String, + legal_name: String, +) +case class AgentMinimalsJsonV510( + agents: List[AgentMinimalJsonV510] +) + case class CustomersIdsJsonV510(customers: List[CustomerIdJson]) case class PostCustomerLegalNameJsonV510(legal_name: String) @@ -953,6 +954,15 @@ object JSONFactory510 extends CustomJsonFormats { currency = bankAccount.currency ) } + def createAgentMinimalsJson(customers: List[Customer]): AgentMinimalsJsonV510 = { + AgentMinimalsJsonV510( + customers + .filter(_.isConfirmedAgent == Some(true)) + .map(customer => AgentMinimalJsonV510( + agent_id = customer.customerId, + legal_name = customer.legalName + ))) + } } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 6a0f210fd..647454e43 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -1112,6 +1112,13 @@ trait Connector extends MdcLoggable { customerNumber: Option[String], callContext: Option[CallContext]): OBPReturnType[Box[Customer]] = Future{(Failure(setUnimplementedError(nameOf(updateCustomerScaData _))), callContext)} + + def updateAgentStatus( + agentId: String, + is_pending_agent: Boolean, + is_confirmed_agent: Boolean, + callContext: Option[CallContext] + ): OBPReturnType[Box[Customer]] = Future{(Failure(setUnimplementedError(nameOf(updateCustomerScaData _))), callContext)} def updateCustomerCreditData(customerId: String, creditRating: Option[String], diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 8b83ae3db..89f06f315 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -15,7 +15,7 @@ import code.api.util.ErrorMessages._ import code.api.util._ import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140 import code.api.v2_1_0._ -import code.api.v4_0_0.{PostSimpleCounterpartyJson400, TransactionRequestBodySimpleJsonV400} +import code.api.v4_0_0.{AgentIdJson, PostSimpleCounterpartyJson400, TransactionRequestBodyAgentJsonV400, TransactionRequestBodySimpleJsonV400} import code.atmattribute.{AtmAttribute, AtmAttributeX} import code.atms.{Atms, MappedAtm} import code.bankattribute.{BankAttribute, BankAttributeX} @@ -4679,6 +4679,42 @@ object LocalMappedConnector extends Connector with MdcLoggable { } yield { (transactionId, callContext) } + case AGENT_CASH_WITHDRAWAL => + for { + bodyToAgent <- NewStyle.function.tryons(s"$TransactionRequestDetailsExtractException It can not extract to $TransactionRequestBodyAgentJsonV400", 400, callContext) { + body.to_agent.get + } + + toAgentId = bodyToAgent.agent_id + (agent, callContext) <- NewStyle.function.getAgentByAgentId(toAgentId, callContext) + (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(toAgentId, callContext) + customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { + customerAccountLinks.head + } + (toAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) + + agentRequestJsonBody = TransactionRequestBodyAgentJsonV400( + to = AgentIdJson(toAgentId), + value = AmountOfMoneyJsonV121(body.value.currency, body.value.amount), + description = body.description, + charge_policy = transactionRequest.charge_policy, + future_date = transactionRequest.future_date + ) + + (transactionId, callContext) <- NewStyle.function.makePaymentv210( + fromAccount, + toAccount, + transactionRequest.id, + transactionRequestCommonBody = agentRequestJsonBody, + BigDecimal(agentRequestJsonBody.value.amount), + agentRequestJsonBody.description, + TransactionRequestType(transactionRequestType), + transactionRequest.charge_policy, + callContext + ) + } yield { + (transactionId, callContext) + } case SIMPLE => for { bodyToSimple <- NewStyle.function.tryons(s"$TransactionRequestDetailsExtractException It can not extract to $TransactionRequestBodyCounterpartyJSON", 400, callContext) { @@ -4844,11 +4880,11 @@ object LocalMappedConnector extends Connector with MdcLoggable { case transactionRequestType => Future((throw new Exception(s"${InvalidTransactionRequestType}: '${transactionRequestType}'. Not supported in this version.")), callContext) } - _ = saveTransactionRequestTransaction(transactionRequestId, transactionId, callContext) + didSaveTransId <- saveTransactionRequestTransaction(transactionRequestId, transactionId, callContext) + + didSaveStatus <- NewStyle.function.saveTransactionRequestStatusImpl(transactionRequestId, TransactionRequestStatus.COMPLETED.toString, callContext) - _ <- NewStyle.function.saveTransactionRequestStatusImpl(transactionRequestId, TransactionRequestStatus.COMPLETED.toString, callContext) - - //After `makePaymentv200` and update data for request, we get the new requqest from database again. + //After `makePaymentv210` and update data for request, we get the new request from database . (transactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transactionRequestId, callContext) } yield { diff --git a/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala b/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala index a0686a1eb..d0960d832 100644 --- a/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala +++ b/obp-api/src/main/scala/code/bankconnectors/vSept2018/KafkaMappedConnector_vSept2018.scala @@ -3076,24 +3076,27 @@ object KafkaMappedConnector_vSept2018 extends KafkaMappedConnector_vSept2018{ ) } def createObpCustomer(customer : InternalCustomer) : Customer = { - ObpCustomer( + CustomerCommons( customerId = customer.customerId, bankId = customer.bankId, number = customer.number, legalName = customer.legalName, mobileNumber = customer.mobileNumber, email = customer.email, - faceImage = customer.faceImage, + faceImage = CustomerFaceImage(customer.faceImage.date,customer.faceImage.url), dateOfBirth = customer.dateOfBirth, relationshipStatus = customer.relationshipStatus, dependents = customer.dependents, dobOfDependents = customer.dobOfDependents, highestEducationAttained = customer.highestEducationAttained, employmentStatus = customer.employmentStatus, - creditRating = customer.creditRating, - creditLimit = customer.creditLimit, + creditRating = CreditRating(customer.creditRating.rating, customer.creditRating.source), + creditLimit = CreditLimit(customer.creditLimit.amount,customer.creditLimit.currency), kycStatus = customer.kycStatus, lastOkDate = customer.lastOkDate, + title = "", + branchId = "", + nameSuffix = "" ) } diff --git a/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala b/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala index 630b2f66e..1d290fbb9 100644 --- a/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala +++ b/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala @@ -196,6 +196,8 @@ object MappedCustomerProvider extends CustomerProvider with MdcLoggable { .mTitle(title) .mBranchId(branchId) .mNameSuffix(nameSuffix) + .mIsPendingAgent(true) + .mIsConfirmedAgent(false) .saveMe() // This is especially for OneToMany table, to save a List to database. @@ -361,7 +363,12 @@ class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with object mTitle extends MappedString(this, 255) object mBranchId extends MappedString(this, 255) object mNameSuffix extends MappedString(this, 255) - + object mIsPendingAgent extends MappedBoolean(this){ + override def defaultValue = true + } + object mIsConfirmedAgent extends MappedBoolean(this){ + override def defaultValue = false + } override def customerId: String = mCustomerId.get // id.toString override def bankId: String = mBank.get override def number: String = mNumber.get @@ -395,6 +402,9 @@ class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with override def title: String = mTitle.get override def branchId: String = mBranchId.get override def nameSuffix: String = mNameSuffix.get + + override def isConfirmedAgent = Some(mIsConfirmedAgent.get) + override def isPendingAgent = Some(mIsPendingAgent.get) } object MappedCustomer extends MappedCustomer with LongKeyedMetaMapper[MappedCustomer] { diff --git a/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala b/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala index cbc4b7aa4..aaf9f0b50 100644 --- a/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala +++ b/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala @@ -3,6 +3,7 @@ package code.transactionrequests import code.api.util.APIUtil.DateWithMsFormat import code.api.util.CustomJsonFormats import code.api.util.ErrorMessages._ +import code.api.v4_0_0.TransactionRequestBodyAgentJsonV400 import code.bankconnectors.LocalMappedConnectorInternal import code.model._ import code.util.{AccountIdString, UUIDString} @@ -365,6 +366,20 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest] Some(parsedDetails.extract[TransactionRequestTransferToAccount]) else None + + val t_to_agent = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.AGENT_CASH_WITHDRAWAL && details.nonEmpty) { + val agentIdList: List[String] = for { + JObject(child) <- parsedDetails + JField("agent_id", JString(agentId)) <- child + } yield + agentId + val agentIdValue = if (agentIdList.isEmpty) "" else agentIdList.head + Some(TransactionRequestAgentId(agent_id = agentIdValue)) + } + else + None + + //This is Berlin Group Types: val t_to_sepa_credit_transfers = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.SEPA_CREDIT_TRANSFERS && details.nonEmpty) Some(parsedDetails.extract[SepaCreditTransfers]) //TODO, here may need a internal case class, but for now, we used it from request json body. @@ -380,6 +395,7 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest] to_transfer_to_atm = t_to_transfer_to_atm, to_transfer_to_account = t_to_transfer_to_account, to_sepa_credit_transfers = t_to_sepa_credit_transfers, + to_agent = t_to_agent, value = t_amount, description = mBody_Description.get ) diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala index 12e208057..f1d9b0384 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala @@ -840,6 +840,9 @@ case class TransactionRequestTransferToAtm( //For COUNTERPARTY, it needs the counterparty_id to find the toCounterparty--> toBankAccount case class TransactionRequestCounterpartyId (counterparty_id : String) +//For AGENT_CASH_WITHDRAWAL, it needs the agent_id to find the toAgent--> toBankAccount +case class TransactionRequestAgentId (agent_id : String) + case class TransactionRequestSimple ( otherBankRoutingScheme: String, otherBankRoutingAddress: String, @@ -951,6 +954,8 @@ case class TransactionRequestBodyAllTypes ( to_transfer_to_account: Option[TransactionRequestTransferToAccount]= None,//TODO not stable @optional to_sepa_credit_transfers: Option[SepaCreditTransfers]= None,//TODO not stable, from berlin Group + @optional + to_agent: Option[TransactionRequestAgentId]= None, value: AmountOfMoney, description: String @@ -1161,29 +1166,6 @@ case class AuthInfo( authViews: List[AuthView] = Nil, ) -case class ObpCustomer( - customerId: String, - bankId: String, - number: String, - legalName: String, - mobileNumber: String, - email: String, - faceImage: CustomerFaceImage, - dateOfBirth: Date, - relationshipStatus: String, - dependents: Integer, - dobOfDependents: List[Date], - highestEducationAttained: String, - employmentStatus: String, - creditRating: CreditRating, - creditLimit: CreditLimit, - kycStatus: lang.Boolean, - lastOkDate: Date, - title: String = "", //These new fields for V310, not from Connector for now. - branchId: String = "", //These new fields for V310, not from Connector for now. - nameSuffix: String = "", //These new fields for V310, not from Connector for now. -) extends Customer - case class InternalCustomer( customerId: String, bankId: String, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala index 3611e2cb5..c6757c651 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala @@ -53,6 +53,9 @@ trait Customer { def title: String def branchId: String def nameSuffix: String + + def isConfirmedAgent: Option[Boolean] = None //this is for agent + def isPendingAgent: Option[Boolean]= None // this is for agent } trait CustomerFaceImageTrait { From 377f3dd618bcde16c66cd5a51c5e31db19fa6a20 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 19 Nov 2024 15:07:42 +0100 Subject: [PATCH 05/14] feature/Added Agent Trait --- .../scala/code/api/util/ErrorMessages.scala | 4 + .../main/scala/code/api/util/NewStyle.scala | 44 +++++- .../scala/code/api/v5_1_0/APIMethods510.scala | 45 ++----- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 41 +++--- .../scala/code/bankconnectors/Connector.scala | 23 +++- .../customer/MappedCustomerProvider.scala | 13 +- .../code/customer/agent/AgentProvider.scala | 55 ++++++++ .../customer/agent/MappedAgentProvider.scala | 127 ++++++++++++++++++ .../commons/model/CommonModel.scala | 10 ++ .../commons/model/CustomerDataModel.scala | 13 +- 10 files changed, 304 insertions(+), 71 deletions(-) create mode 100644 obp-api/src/main/scala/code/customer/agent/AgentProvider.scala create mode 100644 obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 7006390be..41ca284f0 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -453,6 +453,8 @@ object ErrorMessages { val InvalidCardNumber = "OBP-30200: Card not found. Please specify a valid value for CARD_NUMBER. " val AgentNotFound = "OBP-30201: Agent not found. Please specify a valid value for AGENT_ID. " + val CreateAgentError = "OBP-30202: Could not create Agent." + val UpdateAgentError = "OBP-30203: Could not update Agent." val CustomerAccountLinkNotFound = "OBP-30204: Customer Account Link not found" @@ -525,6 +527,8 @@ object ErrorMessages { val GetChargeValueError = "OBP-30323: Could not get the Charge Value." val GetTransactionRequestTypeChargesError = "OBP-30324: Could not get Transaction Request Type Charges." val AgentAccountLinkNotFound = "OBP-30325: Agent Account Link not found." + val AgentsNotFound = "OBP-30326: Agents not found." + val CreateAgentAccountLinkError = "OBP-30327: Could not create the agent account link." // Branch related messages val BranchesNotFoundLicense = "OBP-32001: No branches available. License may not be set." diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index feb909f77..3ee618fec 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -749,11 +749,6 @@ object NewStyle extends MdcLoggable{ } } - def getAgentByAgentId(agentId : String, callContext: Option[CallContext]): OBPReturnType[Customer] = { - Connector.connector.vend.getCustomerByCustomerId(agentId, callContext) map { - unboxFullOrFail(_, callContext, s"$AgentNotFound. Current AGENT_ID($agentId)", 404) - } - } def checkCustomerNumberAvailable(bankId: BankId, customerNumber: String, callContext: Option[CallContext]): OBPReturnType[Boolean] = { Connector.connector.vend.checkCustomerNumberAvailable(bankId: BankId, customerNumber: String, callContext: Option[CallContext]) map { i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse", 400), i._2) @@ -2839,19 +2834,49 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, UpdateCustomerError), i._2) } + def createAgent( + bankId: String, + legalName : String, + mobileNumber : String, + number : String, + callContext: Option[CallContext] + ): OBPReturnType[Agent] = + Connector.connector.vend.createAgent( + bankId: String, + legalName : String, + mobileNumber : String, + number : String, + callContext: Option[CallContext] + ) map { + i => (unboxFullOrFail(i._1, callContext, CreateAgentError), i._2) + } + + def getAgents(bankId : String, queryParams: List[OBPQueryParam], callContext: Option[CallContext]): OBPReturnType[List[Agent]] = { + Connector.connector.vend.getAgents(bankId : String, queryParams: List[OBPQueryParam], callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, s"$AgentsNotFound."), i._2) + } + } + + def getAgentByAgentId(agentId : String, callContext: Option[CallContext]): OBPReturnType[Agent] = { + Connector.connector.vend.getAgentByAgentId(agentId : String, callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, s"$AgentNotFound. Current AGENT_ID($agentId)"), i._2) + } + } + def updateAgentStatus( agentId: String, is_pending_agent: Boolean, is_confirmed_agent: Boolean, - callContext: Option[CallContext]): OBPReturnType[Customer] = + callContext: Option[CallContext]): OBPReturnType[Agent] = Connector.connector.vend.updateAgentStatus( agentId: String, is_pending_agent: Boolean, is_confirmed_agent: Boolean, callContext: Option[CallContext] ) map { - i => (unboxFullOrFail(i._1, callContext, UpdateCustomerError), i._2) + i => (unboxFullOrFail(i._1, callContext, UpdateAgentError), i._2) } + def updateCustomerCreditData(customerId: String, creditRating: Option[String], creditSource: Option[String], @@ -4103,6 +4128,11 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, CreateCustomerAccountLinkError), i._2) } + def createAgentAccountLink(agentId: String, bankId: String, accountId: String, relationshipType: String, callContext: Option[CallContext]): OBPReturnType[CustomerAccountLinkTrait] = + Connector.connector.vend.createCustomerAccountLink(agentId: String, bankId, accountId: String, relationshipType: String, callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, CreateAgentAccountLinkError), i._2) + } + def getCustomerAccountLinksByCustomerId(customerId: String, callContext: Option[CallContext]): OBPReturnType[List[CustomerAccountLinkTrait]] = Connector.connector.vend.getCustomerAccountLinksByCustomerId(customerId: String, callContext: Option[CallContext]) map { i => (unboxFullOrFail(i._1, callContext, GetCustomerAccountLinksError), i._2) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index eab0e6b8f..c37fde46e 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -353,11 +353,6 @@ trait APIMethods510 { "/banks/BANK_ID/agents", "Create Agent", s""" - |The Customer resource stores the customer number (which is set by the backend), legal name, email, phone number, their date of birth, relationship status, education attained, a url for a profile image, KYC status etc. - |Dates need to be in the format 2013-01-21T23:08:00Z - | - |Note: If you need to set a specific customer number, use the Update Customer Number endpoint after this call. - | |${authenticationRequiredMessage(true)} |""", postAgentJsonV510, @@ -383,26 +378,11 @@ trait APIMethods510 { json.extract[PostAgentJsonV510] } (_, callContext) <- NewStyle.function.checkCustomerNumberAvailable(bankId, postedData.agent_number, cc.callContext) - (customer, callContext) <- NewStyle.function.createCustomerC2( - bankId, - postedData.legal_name, - postedData.agent_number, - postedData.mobile_phone_number, - "", - CustomerFaceImage(null, ""), - null, - "", - 0, - Nil, - "", - "", - false, - null, - None, - None, - "", - "", - "", + (agent, callContext) <- NewStyle.function.createAgent( + bankId = bankId.value, + legalName = postedData.legal_name, + mobileNumber = postedData.mobile_phone_number, + number = postedData.agent_number, callContext, ) (bankAccount, callContext) <- NewStyle.function.createBankAccount( @@ -417,9 +397,9 @@ trait APIMethods510 { Nil, callContext ) - (_, callContext) <- NewStyle.function.createCustomerAccountLink(customer.customerId, bankAccount.bankId.value, bankAccount.accountId.value, "Owner", callContext) + (_, callContext) <- NewStyle.function.createCustomerAccountLink(agent.agentId, bankAccount.bankId.value, bankAccount.accountId.value, "Owner", callContext) } yield { - (JSONFactory510.createAgentJson(customer, bankAccount), HttpCode.`201`(callContext)) + (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`201`(callContext)) } } } @@ -463,11 +443,10 @@ trait APIMethods510 { customerAccountLinks.head } (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) - (customer, callContext) <- NewStyle.function.updateCustomerScaData( + (agent, callContext) <- NewStyle.function.updateAgentStatus( agentId, - Some(postedData.is_pending_agent.toString), - None, - None, + postedData.is_pending_agent, + postedData.is_confirmed_agent, callContext) } yield { (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`201`(callContext)) @@ -997,9 +976,9 @@ trait APIMethods510 { cc => implicit val ec = EndpointContext(Some(cc)) for { (requestParams, callContext) <- extractQueryParams(cc.url, List("limit","offset","sort_direction"), cc.callContext) - customers <- NewStyle.function.getCustomers(bankId, callContext, requestParams) + (agents, callContext) <- NewStyle.function.getAgents(bankId.value, requestParams, callContext) } yield { - (JSONFactory510.createAgentMinimalsJson(customers), HttpCode.`200`(callContext)) + (JSONFactory510.createAgentMinimalsJson(agents), HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 823290b63..b8954659d 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -27,33 +27,30 @@ package code.api.v5_1_0 import code.api.Constant -import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil, Role} import code.api.util.APIUtil.{DateWithDay, DateWithSeconds, gitCommit, stringOrNull} +import code.api.util._ import code.api.v1_2_1.BankRoutingJsonV121 import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, transformToLocationFromV140, transformToMetaFromV140} import code.api.v2_1_0.ResourceUserJSON import code.api.v3_0_0.JSONFactory300.{createLocationJson, createMetaJson, transformToAddressFromV300} -import code.api.v3_0_0.{AccountIdJson, AccountsIdsJsonV300, AddressJsonV300, OpeningTimesV300, ViewJsonV300} -import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400, PostViewJsonV400} +import code.api.v3_0_0.{AddressJsonV300, OpeningTimesV300} +import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400} import code.api.v5_0_0.PostConsentRequestJsonV500 import code.atmattribute.AtmAttribute import code.atms.Atms.Atm -import code.users.{UserAttribute, Users} -import code.views.system.{AccountAccess, ViewDefinition} -import com.openbankproject.commons.model.{AccountRoutingJsonV121, Address, AmountOfMoneyJsonV121, AtmId, AtmT, BankAccount, BankId, BankIdAccountId, BranchRoutingJsonV141, CreateViewJson, Customer, Location, Meta, RegulatedEntityTrait, TransactionRequestCommonBodyJSON, UpdateViewJSON, View} -import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} - -import java.util.Date import code.consent.MappedConsent import code.metrics.APIMetric import code.model.Consumer -import com.openbankproject.commons.model.enums.ConsentType +import code.users.{UserAttribute, Users} +import code.views.system.{AccountAccess, ViewDefinition} +import com.openbankproject.commons.model._ +import com.openbankproject.commons.util.ApiVersion import net.liftweb.common.{Box, Full} import net.liftweb.json import net.liftweb.json.{JString, JValue, parse, parseOpt} import java.text.SimpleDateFormat -import scala.collection.immutable.List +import java.util.Date import scala.util.Try @@ -945,22 +942,22 @@ object JSONFactory510 extends CustomJsonFormats { ConsumersJsonV510(consumers.map(createConsumerJSON(_,None))) } - def createAgentJson(customer: Customer, bankAccount: BankAccount): AgentJsonV510 = { + def createAgentJson(agent: Agent, bankAccount: BankAccount): AgentJsonV510 = { AgentJsonV510( - agent_id = customer.customerId, - legal_name = customer.legalName, - mobile_phone_number = customer.mobileNumber, - agent_number = customer.number, + agent_id = agent.agentId, + legal_name = agent.legalName, + mobile_phone_number = agent.mobileNumber, + agent_number = agent.number, currency = bankAccount.currency ) } - def createAgentMinimalsJson(customers: List[Customer]): AgentMinimalsJsonV510 = { + def createAgentMinimalsJson(agents: List[Agent]): AgentMinimalsJsonV510 = { AgentMinimalsJsonV510( - customers - .filter(_.isConfirmedAgent == Some(true)) - .map(customer => AgentMinimalJsonV510( - agent_id = customer.customerId, - legal_name = customer.legalName + agents + .filter(_.isConfirmedAgent == true) + .map(agent => AgentMinimalJsonV510( + agent_id = agent.agentId, + legal_name = agent.legalName ))) } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 647454e43..3926f3514 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -1113,12 +1113,33 @@ trait Connector extends MdcLoggable { callContext: Option[CallContext]): OBPReturnType[Box[Customer]] = Future{(Failure(setUnimplementedError(nameOf(updateCustomerScaData _))), callContext)} + def getAgentByAgentId( + agentId : String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(getAgentByAgentId _))), callContext)} + + + def getAgents( + bankId : String, + queryParams: List[OBPQueryParam], + callContext: Option[CallContext] + ): OBPReturnType[Box[List[Agent]]] = Future{(Failure(setUnimplementedError(nameOf(getAgents _))), callContext)} + + def updateAgentStatus( agentId: String, is_pending_agent: Boolean, is_confirmed_agent: Boolean, callContext: Option[CallContext] - ): OBPReturnType[Box[Customer]] = Future{(Failure(setUnimplementedError(nameOf(updateCustomerScaData _))), callContext)} + ): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(updateAgentStatus _))), callContext)} + + def createAgent( + bankId: String, + legalName : String, + mobileNumber : String, + number : String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(createAgent _))), callContext)} def updateCustomerCreditData(customerId: String, creditRating: Option[String], diff --git a/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala b/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala index 1d290fbb9..5ce838796 100644 --- a/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala +++ b/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala @@ -32,7 +32,7 @@ object MappedCustomerProvider extends CustomerProvider with MdcLoggable { Full(MappedCustomer.findAll(mapperParams:_*)) } - private def getOptionalParams(queryParams: List[OBPQueryParam]) = { + def getOptionalParams(queryParams: List[OBPQueryParam]) = { val limit = queryParams.collect { case OBPLimit(value) => MaxRows[MappedCustomer](value) }.headOption val offset = queryParams.collect { case OBPOffset(value) => StartAt[MappedCustomer](value) }.headOption val fromDate = queryParams.collect { case OBPFromDate(date) => By_>=(MappedCustomer.updatedAt, date) }.headOption @@ -333,7 +333,7 @@ object MappedCustomerProvider extends CustomerProvider with MdcLoggable { } -class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with IdPK with CreatedUpdated { +class MappedCustomer extends Customer with Agent with LongKeyedMapper[MappedCustomer] with IdPK with CreatedUpdated { def getSingleton = MappedCustomer @@ -402,9 +402,12 @@ class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with override def title: String = mTitle.get override def branchId: String = mBranchId.get override def nameSuffix: String = mNameSuffix.get - - override def isConfirmedAgent = Some(mIsConfirmedAgent.get) - override def isPendingAgent = Some(mIsPendingAgent.get) + + override def isConfirmedAgent: Boolean = mIsConfirmedAgent.get //This is for Agent + + override def isPendingAgent: Boolean = mIsPendingAgent.get //This is for Agent + + override def agentId: String = mCustomerId.get //this is for Agent } object MappedCustomer extends MappedCustomer with LongKeyedMetaMapper[MappedCustomer] { diff --git a/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala b/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala new file mode 100644 index 000000000..8bfb02ae8 --- /dev/null +++ b/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala @@ -0,0 +1,55 @@ +package code.customer.agent + +import code.api.util.{CallContext, OBPQueryParam} +import com.openbankproject.commons.model._ +import net.liftweb.common.Box +import net.liftweb.util.SimpleInjector + +import scala.concurrent.Future + + +object AgentX extends SimpleInjector { + + val agentProvider = new Inject(buildOne _) {} + + def buildOne: AgentProvider = MappedAgentProvider + +} + +trait AgentProvider { + def getAgentsAtAllBanks(queryParams: List[OBPQueryParam]): Future[Box[List[Agent]]] + + def getAgentsFuture(bankId: BankId, queryParams: List[OBPQueryParam]): Future[Box[List[Agent]]] + + def getAgentsByAgentPhoneNumber(bankId: BankId, phoneNumber: String): Future[Box[List[Agent]]] + + def getAgentsByAgentLegalName(bankId: BankId, legalName: String): Future[Box[List[Agent]]] + + def getAgentByAgentId(agentId: String): Box[Agent] + + def getAgentByAgentIdFuture(agentId: String): Future[Box[Agent]] + + def getBankIdByAgentId(agentId: String): Box[String] + + def getAgentByAgentNumber(agentNumber: String, bankId: BankId): Box[Agent] + + def getAgentByAgentNumberFuture(agentNumber: String, bankId: BankId): Future[Box[Agent]] + + def checkAgentNumberAvailable(bankId: BankId, agentNumber: String): Boolean + + def createAgent( + bankId: String, + legalName : String, + mobileNumber : String, + number : String, + callContext: Option[CallContext] + ): Future[Box[Agent]] + + def updateAgentStatus( + agentId: String, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, + callContext: Option[CallContext] + ): Future[Box[Agent]] + +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala b/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala new file mode 100644 index 000000000..bded8cb9d --- /dev/null +++ b/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala @@ -0,0 +1,127 @@ +package code.customer.agent + +import code.api.util._ +import code.customer.{MappedCustomer, MappedCustomerProvider} +import code.util.Helper.MdcLoggable +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model._ +import net.liftweb.common.{Box, Full} +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo + +import scala.concurrent.Future + + +object MappedAgentProvider extends AgentProvider with MdcLoggable { + + override def getAgentsAtAllBanks(queryParams: List[OBPQueryParam]): Future[Box[List[Agent]]] = Future { + val mapperParams = MappedCustomerProvider.getOptionalParams(queryParams) + Full(MappedCustomer.findAll(mapperParams: _*)) + } + + override def getAgentsFuture(bankId: BankId, queryParams: List[OBPQueryParam]): Future[Box[List[Agent]]] = Future { + val mapperParams = Seq(By(MappedCustomer.mBank, bankId.value)) ++ MappedCustomerProvider.getOptionalParams(queryParams) + Full(MappedCustomer.findAll(mapperParams: _*)) + } + + + override def getAgentsByAgentPhoneNumber(bankId: BankId, phoneNumber: String): Future[Box[List[Agent]]] = Future { + val result = MappedCustomer.findAll( + By(MappedCustomer.mBank, bankId.value), + Like(MappedCustomer.mMobileNumber, phoneNumber) + ) + Full(result) + } + + override def getAgentsByAgentLegalName(bankId: BankId, legalName: String): Future[Box[List[Agent]]] = Future { + val result = MappedCustomer.findAll( + By(MappedCustomer.mBank, bankId.value), + Like(MappedCustomer.mLegalName, legalName) + ) + Full(result) + } + + + override def checkAgentNumberAvailable(bankId: BankId, customerNumber: String): Boolean = { + val customers = MappedCustomer.findAll( + By(MappedCustomer.mBank, bankId.value), + By(MappedCustomer.mNumber, customerNumber) + ) + + val available: Boolean = customers.size match { + case 0 => true + case _ => false + } + + available + } + + override def getAgentByAgentId(customerId: String): Box[Agent] = { + MappedCustomer.find( + By(MappedCustomer.mCustomerId, customerId) + ) + } + + override def getBankIdByAgentId(customerId: String): Box[String] = { + val customer: Box[MappedCustomer] = MappedCustomer.find( + By(MappedCustomer.mCustomerId, customerId) + ) + for (c <- customer) yield { + c.mBank.get + } + } + + override def getAgentByAgentNumber(customerNumber: String, bankId: BankId): Box[Agent] = { + MappedCustomer.find( + By(MappedCustomer.mNumber, customerNumber), + By(MappedCustomer.mBank, bankId.value) + ) + } + + override def getAgentByAgentNumberFuture(customerNumber: String, bankId: BankId): Future[Box[Agent]] = { + Future(getAgentByAgentNumber(customerNumber, bankId)) + } + + + override def createAgent( + bankId: String, + legalName: String, + mobileNumber: String, + number: String, + callContext: Option[CallContext] + ): Future[Box[Agent]] = Future { + tryo { + MappedCustomer + .create + .mBank(bankId) + .mLegalName(legalName) + .mMobileNumber(mobileNumber) + .mNumber(number) + .mIsPendingAgent(true) //default value + .mIsConfirmedAgent(false) // default value + .saveMe() + + } + + } + + override def updateAgentStatus( + agentId: String, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, + callContext: Option[CallContext] + ): Future[Box[Agent]] = Future { + MappedCustomer.find( + By(MappedCustomer.mCustomerId, agentId) + ) map { + c => + c.mIsPendingAgent(isPendingAgent) + c.mIsConfirmedAgent(isConfirmedAgent) + c.saveMe() + } + } + + override def getAgentByAgentIdFuture(agentId: String): Future[Box[Agent]] = Future { + getAgentByAgentId(agentId: String) + } +} \ No newline at end of file diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala index f1d9b0384..14d785f23 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala @@ -170,6 +170,16 @@ case class CustomerCommons( object CustomerCommons extends Converter[Customer, CustomerCommons] +case class AgentCommons( + agentId: String, + bankId: String, + number: String, + legalName: String, + mobileNumber: String, + isConfirmedAgent: Boolean, + isPendingAgent: Boolean, +) extends Agent +object AgentCommons extends Converter[Agent, AgentCommons] case class CustomerAddressCommons( customerId :String, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala index c6757c651..524d2993a 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CustomerDataModel.scala @@ -53,9 +53,16 @@ trait Customer { def title: String def branchId: String def nameSuffix: String - - def isConfirmedAgent: Option[Boolean] = None //this is for agent - def isPendingAgent: Option[Boolean]= None // this is for agent +} + +trait Agent { + def agentId : String + def bankId : String + def number : String + def legalName : String + def mobileNumber : String + def isConfirmedAgent: Boolean + def isPendingAgent: Boolean } trait CustomerFaceImageTrait { From ea47a4490551fc9f441c44edb3b54788c7372264 Mon Sep 17 00:00:00 2001 From: hongwei Date: Tue, 19 Nov 2024 15:34:48 +0100 Subject: [PATCH 06/14] feature/Added LocalMappedConnector methods --- .../main/scala/code/api/util/NewStyle.scala | 8 +-- .../scala/code/api/v5_1_0/APIMethods510.scala | 8 +-- .../scala/code/bankconnectors/Connector.scala | 4 +- .../bankconnectors/LocalMappedConnector.scala | 51 +++++++++++++++++++ 4 files changed, 61 insertions(+), 10 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 3ee618fec..ae008ba89 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2865,13 +2865,13 @@ object NewStyle extends MdcLoggable{ def updateAgentStatus( agentId: String, - is_pending_agent: Boolean, - is_confirmed_agent: Boolean, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, callContext: Option[CallContext]): OBPReturnType[Agent] = Connector.connector.vend.updateAgentStatus( agentId: String, - is_pending_agent: Boolean, - is_confirmed_agent: Boolean, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, callContext: Option[CallContext] ) map { i => (unboxFullOrFail(i._1, callContext, UpdateAgentError), i._2) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index c37fde46e..8c52ff949 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -405,9 +405,9 @@ trait APIMethods510 { } staticResourceDocs += ResourceDoc( - updateAgentstatus, + updateAgentStatus, implementedInApiVersion, - nameOf(updateAgentstatus), + nameOf(updateAgentStatus), "PUT", "/banks/BANK_ID/agents/AGENT_ID", "Update Agent status", @@ -430,8 +430,8 @@ trait APIMethods510 { Some(canUpdateAgentStatusAtAnyBank :: canUpdateAgentStatusAtOneBank :: Nil) ) - lazy val updateAgentstatus : OBPEndpoint = { - case "banks" :: BankId(bankId) :: "agents" :: agentId :: Nil JsonPost json -> _ => { + lazy val updateAgentStatus : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "agents" :: agentId :: Nil JsonPut json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostAgentJsonV510 ", 400, cc.callContext) { diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 3926f3514..57a28978c 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -1128,8 +1128,8 @@ trait Connector extends MdcLoggable { def updateAgentStatus( agentId: String, - is_pending_agent: Boolean, - is_confirmed_agent: Boolean, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, callContext: Option[CallContext] ): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(updateAgentStatus _))), callContext)} diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 89f06f315..c1715d4a9 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -25,6 +25,7 @@ import code.cards.MappedPhysicalCard import code.context.{UserAuthContextProvider, UserAuthContextUpdateProvider} import code.counterpartylimit.CounterpartyLimitProvider import code.customer._ +import code.customer.agent.AgentX import code.customeraccountlinks.CustomerAccountLinkX import com.openbankproject.commons.model.CustomerAccountLinkTrait import code.customeraddress.CustomerAddressX @@ -1623,7 +1624,57 @@ object LocalMappedConnector extends Connector with MdcLoggable { transactionRequestId: TransactionRequestId ).map((_, callContext)) } + + override def createAgent( + bankId: String, + legalName : String, + mobileNumber : String, + number : String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = { + AgentX.agentProvider.vend.createAgent( + bankId: String, + legalName : String, + mobileNumber : String, + number : String, + callContext: Option[CallContext] + ).map((_, callContext)) + } + override def updateAgentStatus( + agentId: String, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = { + AgentX.agentProvider.vend.updateAgentStatus( + agentId: String, + isPendingAgent: Boolean, + isConfirmedAgent: Boolean, + callContext: Option[CallContext] + ).map((_, callContext)) + } + + override def getAgentByAgentId( + agentId : String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = { + AgentX.agentProvider.vend.getAgentByAgentIdFuture( + agentId : String + ).map((_, callContext)) + } + + override def getAgents( + bankId : String, + queryParams: List[OBPQueryParam], + callContext: Option[CallContext] + ): OBPReturnType[Box[List[Agent]]] = { + AgentX.agentProvider.vend.getAgentsFuture( + BankId(bankId), + queryParams: List[OBPQueryParam] + ).map((_, callContext)) + } + override def getTransactionRequestAttributes(bankId: BankId, transactionRequestId: TransactionRequestId, callContext: Option[CallContext]): OBPReturnType[Box[List[TransactionRequestAttributeTrait]]] = { From 9c5372faf89891b6d6548d71c6396105c0bcfc52 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 20 Nov 2024 13:51:56 +0100 Subject: [PATCH 07/14] test/added the tests for agent endpoints --- .../props/test.default.props.template | 4 +- .../SwaggerDefinitionsJSON.scala | 6 +- .../scala/code/api/v5_1_0/APIMethods510.scala | 2 +- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 8 +- .../api/v4_0_0/TransactionRequestsTest.scala | 91 ++++++---- .../code/api/v4_0_0/V400ServerSetup.scala | 1 + .../scala/code/api/v5_1_0/AgentTest.scala | 170 ++++++++++++++++++ .../test/scala/code/setup/ServerSetup.scala | 3 +- 8 files changed, 239 insertions(+), 46 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala diff --git a/obp-api/src/main/resources/props/test.default.props.template b/obp-api/src/main/resources/props/test.default.props.template index 8f5102df6..64308d35d 100644 --- a/obp-api/src/main/resources/props/test.default.props.template +++ b/obp-api/src/main/resources/props/test.default.props.template @@ -109,13 +109,15 @@ sandbox_data_import_secret=change_me allow_account_deletion=true # This needs to be a list all the types of transaction_requests that we have tests for. Else those tests will fail -transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE +transactionRequests_supported_types=SANDBOX_TAN,COUNTERPARTY,SEPA,ACCOUNT_OTP,ACCOUNT,SIMPLE,AGENT_CASH_WITHDRAWAL,CARD ACCOUNT_OTP_INSTRUCTION_TRANSPORT=dummy SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy SEPA_OTP_INSTRUCTION_TRANSPORT=dummy FREE_FORM_OTP_INSTRUCTION_TRANSPORT=dummy COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=dummy SEPA_CREDIT_TRANSFERS_OTP_INSTRUCTION_TRANSPORT=dummy +AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT=dummy +CARD_OTP_INSTRUCTION_TRANSPORT=dummy # control the create and access to public views. diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 89f43a279..ada743b45 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5534,7 +5534,7 @@ object SwaggerDefinitionsJSON { ) val putAgentJsonV510 = PutAgentJsonV510( - is_pending_agent = true, + is_pending_agent = false, is_confirmed_agent = true ) @@ -5543,7 +5543,9 @@ object SwaggerDefinitionsJSON { legal_name = legalNameExample.value, mobile_phone_number = mobilePhoneNumberExample.value, agent_number = agentNumberExample.value, - currency = currencyExample.value + currency = currencyExample.value, + is_confirmed_agent = false, + is_pending_agent = true ) val agentMinimalJsonV510 = AgentMinimalJsonV510( diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 8c52ff949..8be53ade5 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -449,7 +449,7 @@ trait APIMethods510 { postedData.is_confirmed_agent, callContext) } yield { - (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`201`(callContext)) + (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index b8954659d..4c38bab18 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -336,7 +336,9 @@ case class AgentJsonV510( legal_name: String, mobile_phone_number: String, agent_number: String, - currency: String + currency: String, + is_confirmed_agent: Boolean, + is_pending_agent: Boolean ) case class AgentMinimalJsonV510( @@ -948,7 +950,9 @@ object JSONFactory510 extends CustomJsonFormats { legal_name = agent.legalName, mobile_phone_number = agent.mobileNumber, agent_number = agent.number, - currency = bankAccount.currency + currency = bankAccount.currency, + is_confirmed_agent = agent.isConfirmedAgent, + is_pending_agent = agent.isPendingAgent ) } def createAgentMinimalsJson(agents: List[Agent]): AgentMinimalsJsonV510 = { diff --git a/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala index efac10492..b566a6270 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala @@ -5,7 +5,7 @@ import java.util.{Date, UUID} import code.api.ChargePolicy import code.api.Constant._ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON -import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createPhysicalCardJsonV500 +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{createPhysicalCardJsonV500, postAgentJsonV510} import code.api.util.APIUtil.OAuth._ import code.api.util.APIUtil.extractErrorMessageCode import code.api.util.ApiRole.{CanCreateAnyTransactionRequest, CanCreateCardsForBank} @@ -16,6 +16,7 @@ import code.api.v2_0_0.TransactionRequestBodyJsonV200 import code.api.v2_1_0._ import code.api.v4_0_0.APIMethods400.Implementations4_0_0 import code.api.v5_0_0.PhysicalCardJsonV500 +import code.api.v5_1_0.AgentJsonV510 import code.bankconnectors.Connector import code.entitlement.Entitlement import code.fx.fx @@ -52,6 +53,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { object ApiEndpoint8 extends Tag(nameOf(Implementations4_0_0.answerTransactionRequestChallenge)) object ApiEndpoint9 extends Tag(nameOf(Implementations4_0_0.createTransactionRequestSimple)) object ApiEndpoint10 extends Tag(nameOf(Implementations4_0_0.createTransactionRequestCard)) + object ApiEndpoint11 extends Tag(nameOf(Implementations4_0_0.createTransactionRequestAgentCashWithDrawal)) def transactionCount(accounts: BankAccount*): Int = { accounts.foldLeft(0)((accumulator, account) => { @@ -59,9 +61,9 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { }) } - def defaultSetup(transactionRequestTypeInput : String= ACCOUNT.toString) = new DefaultSetup(transactionRequestTypeInput) + def defaultSetup(transactionRequestTypeInput : String) = new DefaultSetup(transactionRequestTypeInput) - class DefaultSetup(transactionRequestTypeInput : String= ACCOUNT.toString) { + class DefaultSetup(transactionRequestTypeInput : String) { val sharedChargePolicy = ChargePolicy.withName("SHARED").toString var transactionRequestType: String = transactionRequestTypeInput @@ -129,8 +131,13 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { //For Counterpart local mapper, the mOtherAccountRoutingScheme='OBP' and mOtherBankRoutingScheme = 'OBP' val counterpartyCounterparty = createCounterparty(bankId.value, accountId1.value, accountId2.value, true, UUID.randomUUID.toString); + val request = (v5_1_0_Request / "banks" / bankId.value / "agents").POST <@ (user1) + val response = makePostRequest(request, write(postAgentJsonV510.copy(currency=fromAccount.currency))) + val agentCashWithdrawalAgent = response.body.extract[AgentJsonV510] + var transactionRequestBodySEPA = TransactionRequestBodySEPAJSON(bodyValue, IbanJson(counterpartySEPA.otherAccountSecondaryRoutingAddress), description, sharedChargePolicy) + var transactionRequestBodyAgentCashWithdrawal = TransactionRequestBodyAgentJsonV400(AgentIdJson(agentCashWithdrawalAgent.agent_id), bodyValue, description, sharedChargePolicy) var transactionRequestBodyCounterparty = TransactionRequestBodyCounterpartyJSON(CounterpartyIdJson(counterpartyCounterparty.counterpartyId), bodyValue, description, sharedChargePolicy) var transactionRequestBodySimple = TransactionRequestBodySimpleJsonV400(SwaggerDefinitionsJSON.postSimpleCounterpartyJson400.copy( other_account_routing_address = counterpartyCounterparty.otherAccountRoutingAddress, @@ -193,6 +200,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { def makeCreateTransReqRequest: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBody)) def makeCreateTransReqRequestSEPA: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodySEPA)) def makeCreateTransReqRequestCounterparty: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodyCounterparty)) + def makeCreateTransReqRequestAgentCashWithdrawal: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodyAgentCashWithdrawal)) def makeCreateTransReqRequestSimple: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodySimple)) def makeCreateTransReqRequestCard: APIResponse = makePostRequest(createTransReqRequestCard, write(transactionRequestBodyCard)) @@ -305,6 +313,11 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { fromAccountBalance should equal((beforeFromBalance)) And("there should now be 2 new transactions in the database") transactionCount(fromAccount, toAccount) should equal(totalTransactionsBefore+2) + } else if(transactionRequestTypeInput.equals(AGENT_CASH_WITHDRAWAL.toString)){ + Then(s"$AGENT_CASH_WITHDRAWAL can only check fromAccount, agent account can not be checked here.") + fromAccountBalance should equal((beforeFromBalance - amt)) + And("there should now be 1 new transactions in the database") + transactionCount(fromAccount) should equal(totalTransactionsBefore+1) } else { Then("check that the balances have been properly decreased/increased (since we handle that logic for sandbox accounts at least) ") fromAccountBalance should equal((beforeFromBalance - amt)) @@ -313,7 +326,6 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { 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) } - } else { Then("No transaction, it should be the same as before ") fromAccountBalance should equal((beforeFromBalance)) @@ -356,7 +368,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("No login user", ApiEndpoint1) { - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) Then("We call the 'Create Transaction Request.' without the login user") var request = (v4_0_0_Request / "banks" / helper.fromAccount.bankId.value / "accounts" / helper.fromAccount.accountId.value / @@ -378,7 +390,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("No owner view, No CanCreateAnyTransactionRequest role", ApiEndpoint1) { - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) Then("We used the login user2, but it does not have the owner view and CreateTransactionRequest role ") val request = (v4_0_0_Request / "banks" / helper.testBank.bankId.value / "accounts" / helper.fromAccount.accountId.value / @@ -399,7 +411,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("No owner view, With CanCreateAnyTransactionRequest role", ApiEndpoint1) { - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) Then("We grant the CanCreateAnyTransactionRequest role to user3") addEntitlement(helper.bankId.value, resourceUser3.userId, CanCreateAnyTransactionRequest.toString) @@ -420,7 +432,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("Invalid transactionRequestType", ApiEndpoint1) { - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) Then("We grant the CanCreateAnyTransactionRequest role to user3") addEntitlement(helper.bankId.value, resourceUser3.userId, CanCreateAnyTransactionRequest.toString) @@ -449,7 +461,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { scenario("No challenge, No FX (same currencies)", ApiEndpoint1) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) Then("we call the 'V400 Create Transaction Request' endpoint") val createTransactionRequestResponse = helper.makeCreateTransReqRequest @@ -479,7 +491,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { scenario("No challenge, With FX ", ApiEndpoint1) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" @@ -518,7 +530,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("With challenge, No FX ", ApiEndpoint1, ApiEndpoint2) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" val toCurrency = "AED" @@ -560,7 +572,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { scenario("With challenge, No FX, test the allowed_attempts times ", ApiEndpoint1, ApiEndpoint2) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" val toCurrency = "AED" @@ -599,7 +611,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("With challenge, With FX ", ApiEndpoint1, ApiEndpoint2) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup() + val helper = defaultSetup(ACCOUNT.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" @@ -1234,19 +1246,20 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } } - - feature("we can create transaction requests -- SIMPLE") { + + + feature(s"we can create transaction requests -- $AGENT_CASH_WITHDRAWAL") { if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) { - ignore("No challenge, No FX ", ApiEndpoint1) {} + ignore("No challenge, No FX ", ApiEndpoint11) {} } else { - scenario("No challenge, No FX ", ApiEndpoint1) { + scenario("No challenge, No FX ", ApiEndpoint11) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup(SIMPLE.toString) + val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) Then("we call the 'V400 Create Transaction Request' endpoint") - val createTransactionRequestResponse = helper.makeCreateTransReqRequestSimple + val createTransactionRequestResponse = helper.makeCreateTransReqRequestAgentCashWithdrawal Then("We checked all the fields of createTransact dionRequestResponse body ") helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, false) @@ -1268,24 +1281,24 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) { - ignore("No challenge, With FX ", ApiEndpoint1) {} + ignore("No challenge, With FX ", ApiEndpoint11) {} } else { - scenario("No challenge, With FX ", ApiEndpoint1) { + scenario("No challenge, With FX ", ApiEndpoint11) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup(SIMPLE.toString) + val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) - Then("we call the 'V400 Create Transaction Request' endpoint") + And("We set the special conditions for different currencies") val fromCurrency = "AED" val toCurrency = "INR" val amt = "10" helper.setCurrencyAndAmt(fromCurrency, toCurrency, amt) And("We set the special input JSON values for 'V400 Create Transaction Request' endpoint") helper.bodyValue = AmountOfMoneyJsonV121(fromCurrency, amt.toString()) - helper.transactionRequestBodySimple = helper.transactionRequestBodySimple.copy(value=helper.bodyValue) + helper.transactionRequestBodyAgentCashWithdrawal = helper.transactionRequestBodyAgentCashWithdrawal.copy(value=helper.bodyValue) Then("we call the 'V400 Create Transaction Request' endpoint") - val createTransactionRequestResponse = helper.makeCreateTransReqRequestSimple + val createTransactionRequestResponse = helper.makeCreateTransReqRequestAgentCashWithdrawal Then("We checked all the fields of createTransactionRequestResponse body ") helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, false) @@ -1308,11 +1321,11 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) { - ignore("With challenge, No FX ", ApiEndpoint1) {} + ignore("With challenge, No FX ", ApiEndpoint11) {} } else { - scenario("With challenge, No FX ", ApiEndpoint1) { + scenario("With challenge, No FX ", ApiEndpoint11) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup(SIMPLE.toString) + val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" val toCurrency = "AED" @@ -1320,10 +1333,10 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { helper.setCurrencyAndAmt(fromCurrency, toCurrency, amt) And("We set the special input JSON values for 'V400 Create Transaction Request' endpoint") helper.bodyValue = AmountOfMoneyJsonV121(fromCurrency, amt.toString()) - helper.transactionRequestBodySimple = helper.transactionRequestBodySimple.copy(value=helper.bodyValue) + helper.transactionRequestBodyAgentCashWithdrawal = helper.transactionRequestBodyAgentCashWithdrawal.copy(value=helper.bodyValue) Then("we call the 'V400 Create Transaction Request' endpoint") - val createTransactionRequestResponse = helper.makeCreateTransReqRequestSimple + val createTransactionRequestResponse = helper.makeCreateTransReqRequestAgentCashWithdrawal And("We checked all the fields of createTransactionRequestResponse body ") helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true) @@ -1354,11 +1367,11 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) { - ignore("With challenge, With FX", ApiEndpoint1) {} + ignore("With challenge, With FX", ApiEndpoint11) {} } else { - scenario("With challenge, With FX", ApiEndpoint1) { + scenario("With challenge, With FX", ApiEndpoint11) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup(SIMPLE.toString) + val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" @@ -1368,10 +1381,10 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { And("We set the special input JSON values for 'V400 Create Transaction Request' endpoint") helper.bodyValue = AmountOfMoneyJsonV121(fromCurrency, amt.toString()) - helper.transactionRequestBodySimple = helper.transactionRequestBodySimple.copy(value=helper.bodyValue) + helper.transactionRequestBodyAgentCashWithdrawal = helper.transactionRequestBodyAgentCashWithdrawal.copy(value=helper.bodyValue) Then("we call the 'V400 Create Transaction Request' endpoint") - val createTransactionRequestResponse = helper.makeCreateTransReqRequestSimple + val createTransactionRequestResponse = helper.makeCreateTransReqRequestAgentCashWithdrawal Then("We checked all the fields of createTransactionRequestResponse body ") helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true) @@ -1404,11 +1417,11 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) { - ignore("With N challenges, With FX", ApiEndpoint1) {} + ignore("With N challenges, With FX", ApiEndpoint11) {} } else { scenario("With N challenges, With FX", ApiEndpoint1) { When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") - val helper = defaultSetup(SIMPLE.toString) + val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) And("We set the special conditions for different currencies") val fromCurrency = "AED" @@ -1418,7 +1431,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { And("We set the special input JSON values for 'V400 Create Transaction Request' endpoint") helper.bodyValue = AmountOfMoneyJsonV121(fromCurrency, amt.toString()) - helper.transactionRequestBodySimple = helper.transactionRequestBodySimple.copy(value=helper.bodyValue) + helper.transactionRequestBodyAgentCashWithdrawal = helper.transactionRequestBodyAgentCashWithdrawal.copy(value=helper.bodyValue) createAccountAttributeViaEndpoint( helper.bankId.value, @@ -1438,7 +1451,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { ) Then("we call the 'V400 Create Transaction Request' endpoint") - val createTransactionRequestResponse = helper.makeCreateTransReqRequestSimple + val createTransactionRequestResponse = helper.makeCreateTransReqRequestAgentCashWithdrawal val createTransactionRequestJsonResponse = createTransactionRequestResponse.body.extract[TransactionRequestWithChargeJSON400] createTransactionRequestJsonResponse.status should equal(TransactionRequestStatus.INITIATED.toString) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/V400ServerSetup.scala b/obp-api/src/test/scala/code/api/v4_0_0/V400ServerSetup.scala index 8b42fb359..86d5ec111 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/V400ServerSetup.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/V400ServerSetup.scala @@ -37,6 +37,7 @@ trait V400ServerSetup extends ServerSetupWithTestData with DefaultUsers { def v4_0_0_Request: Req = baseRequest / "obp" / "v4.0.0" def v5_0_0_Request: Req = baseRequest / "obp" / "v5.0.0" + def v5_1_0_Request: Req = baseRequest / "obp" / "v5.1.0" def dynamicEndpoint_Request: Req = baseRequest / "obp" / ApiShortVersions.`dynamic-endpoint`.toString def dynamicEntity_Request: Req = baseRequest / "obp" / ApiShortVersions.`dynamic-entity`.toString diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala new file mode 100644 index 000000000..ec23c9d1b --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala @@ -0,0 +1,170 @@ +package code.api.v5_1_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{postAgentJsonV510, putAgentJsonV510} +import code.api.util.ErrorMessages.{BankNotFound, UserHasMissingRoles, UserNotLoggedIn} +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.{canUpdateAgentStatusAtAnyBank, canUpdateAgentStatusAtOneBank} +import code.entitlement.Entitlement + +class AgentTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object CreateAgent extends Tag(nameOf(Implementations5_1_0.createAgent)) + object UpdateAgentStatus extends Tag(nameOf(Implementations5_1_0.updateAgentStatus)) + object GetAgent extends Tag(nameOf(Implementations5_1_0.getAgent)) + object GetAgents extends Tag(nameOf(Implementations5_1_0.getAgents)) + + feature(s"test all endpoints") { + scenario(s"We will test all endpoints logins", CreateAgent, UpdateAgentStatus,GetAgent, GetAgents, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents").POST + val response = makePostRequest(request, write(postAgentJsonV510)) + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents"/ "agentId").PUT + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + + { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents").GET + val response = makeGetRequest(request) + response.code should equal(404) + response.body.extract[ErrorMessage].message contains (BankNotFound) shouldBe(true) + } + + { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents"/"agentId").GET + val response = makeGetRequest(request) + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + scenario(s"We will test all endpoints wrong Bankid", CreateAgent, UpdateAgentStatus,GetAgent, GetAgents, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents").POST <@ (user1) + val response = makePostRequest(request, write(postAgentJsonV510)) + response.code should equal(404) + response.body.extract[ErrorMessage].message contains (BankNotFound) shouldBe(true) + + { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents"/ "agentId").PUT <@ (user1) + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(404) + response.body.extract[ErrorMessage].message contains (BankNotFound) shouldBe(true) + } + + { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents").GET <@ (user1) + val response = makeGetRequest(request) + response.code should equal(404) + response.body.extract[ErrorMessage].message contains (BankNotFound) shouldBe(true) + } + + { + val request = (v5_1_0_Request / "banks" / "BANK_ID" / "agents"/"agentId").GET <@ (user1) + val response = makeGetRequest(request) + response.code should equal(404) + response.body.extract[ErrorMessage].message contains (BankNotFound) shouldBe(true) + } + } + + scenario(s"We will test all endpoints roles", UpdateAgentStatus) { + val bankId =testBankId1.value + val bankId2 =testBankId2.value + val request = (v5_1_0_Request / "banks" / bankId / "agents").POST <@ (user1) + val response = makePostRequest(request, write(postAgentJsonV510)) + response.code should equal(201) + val agentId = response.body.extract[AgentJsonV510].agent_id + + { + val request = (v5_1_0_Request / "banks" / bankId / "agents"/ agentId).PUT <@user1 + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(403) + response.body.extract[ErrorMessage].message contains UserHasMissingRoles shouldBe(true) + } + + Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, canUpdateAgentStatusAtOneBank.toString) + + { + val request = (v5_1_0_Request / "banks" / bankId / "agents"/ agentId).PUT <@user1 + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(200) + } + + { + val request = (v5_1_0_Request / "banks" / bankId2 / "agents"/ agentId).PUT <@user1 + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(403) + response.body.extract[ErrorMessage].message contains UserHasMissingRoles shouldBe(true) + } + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateAgentStatusAtAnyBank.toString) + + { + val request = (v5_1_0_Request / "banks" / bankId2 / "agents"/ agentId).PUT <@user1 + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(200) + } + } + + scenario(s"We will test all endpoints successful cases", UpdateAgentStatus) { + val bankId =randomBankId + val request = (v5_1_0_Request / "banks" / bankId / "agents").POST <@ (user1) + val response = makePostRequest(request, write(postAgentJsonV510)) + response.code should equal(201) + val agentId = response.body.extract[AgentJsonV510].agent_id + + { + val request = (v5_1_0_Request / "banks" / bankId / "agents"/ agentId).GET <@ (user1) + val response = makeGetRequest(request) + response.code should equal(200) + response.body.extract[AgentJsonV510].agent_id should equal(agentId) + response.body.extract[AgentJsonV510].legal_name should equal(postAgentJsonV510.legal_name) + response.body.extract[AgentJsonV510].currency should equal(postAgentJsonV510.currency) + response.body.extract[AgentJsonV510].mobile_phone_number should equal(postAgentJsonV510.mobile_phone_number) + response.body.extract[AgentJsonV510].is_pending_agent should equal(true) + response.body.extract[AgentJsonV510].is_confirmed_agent should equal(false) + } + + { + val request = (v5_1_0_Request / "banks" / bankId / "agents").GET + val response = makeGetRequest(request) + response.code should equal(200) + response.body.extract[AgentMinimalsJsonV510].agents.length shouldBe(0) + } + + { + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, canUpdateAgentStatusAtAnyBank.toString) + val request = (v5_1_0_Request / "banks" / bankId / "agents"/ agentId).PUT <@user1 + val response = makePutRequest(request, write(putAgentJsonV510)) + response.code should equal(200) + response.body.extract[AgentJsonV510].is_pending_agent should equal(putAgentJsonV510.is_pending_agent) + response.body.extract[AgentJsonV510].is_confirmed_agent should equal(putAgentJsonV510.is_confirmed_agent) + } + + //After updated the status, we can get the agents back ; + { + val request = (v5_1_0_Request / "banks" / bankId / "agents").GET + val response = makeGetRequest(request) + response.code should equal(200) + response.body.extract[AgentMinimalsJsonV510].agents.length shouldBe(1) + } + + + } + } + +} \ No newline at end of file diff --git a/obp-api/src/test/scala/code/setup/ServerSetup.scala b/obp-api/src/test/scala/code/setup/ServerSetup.scala index b3a4e25fc..02dae2c24 100644 --- a/obp-api/src/test/scala/code/setup/ServerSetup.scala +++ b/obp-api/src/test/scala/code/setup/ServerSetup.scala @@ -51,8 +51,9 @@ trait ServerSetup extends FeatureSpec with SendServerRequests setPropsValues("dauth.host" -> "127.0.0.1") setPropsValues("jwt.token_secret"->"your-at-least-256-bit-secret-token") setPropsValues("jwt.public_key_rsa" -> "src/test/resources/cert/public_dauth.pem") - setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD") + setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD,AGENT_CASH_WITHDRAWAL") setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") + setPropsValues("AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") setPropsValues("api_instance_id" -> "1_final") setPropsValues("starConnector_supported_types" -> "mapped,internal") setPropsValues("connector" -> "star") From 97592507f9dca61cf5cc5e47a9141a21f304197a Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 20 Nov 2024 17:49:48 +0100 Subject: [PATCH 08/14] test/fixed the failed tests --- .../src/test/resources/frozen_type_meta_data | Bin 135363 -> 135878 bytes .../RestConnector_vMar2019_frozen_meta_data | Bin 123706 -> 123875 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/obp-api/src/test/resources/frozen_type_meta_data b/obp-api/src/test/resources/frozen_type_meta_data index 5b3c8f81d05cb96bcec3c91264079ce8fdcb3afd..93f5b18ed70cb782a6d139b03ba6799b6766c7c6 100644 GIT binary patch delta 7704 zcmaJ`c|cTU*Y}L@y^8xMb$8`GhAk6|%R~ zGR4V8UrZrfQyk4q%O&?$(-89wcYS})ox#lfzJKnWx%YX_InP;sXStOnE_?swvU#*G zIhe=z&UBTPHfyt5?rel-~+52Sqrls8%%VFpsR}jUAFyq!aF)#^L&Qrwm*#cFMP}cjEY# z6d(#Zuf|HhE_(^Lvt5nyHl({7Zv5&H^5>itq!D}G_Y$JVSkj7HJ-T5*o1SU)vEa;f znjjwao&ko1k+WTOrg~19X{qUY_N*mukv-B$)I_?7Hho?O<57Ji`+a?SVY^e5FSeT- z_cN$)n-Tpc(Ogl!JX(YgYyq-QV#!Zr419?7>4PS@X+c(R%1la6<*F%=*fFLE2VWU; z58@QX7GYJ`;1PIII^3Uo^(Q~}Zbd!C&B0SZdqUhE@&o~woV@Iuv^>+K+a4+H?PVK_K)6;rSf(0nC31}vqMID)&yR<0~!b$ z8>}`CG-hWRB}bjmjGYEH^T%d9HS?y&6enWdm@ZFu!B1R#BM8JODG{5enLeQ(+Zop-sjcuca4$6zmejU(k=>-`Tha#M zopum5{59tGM@)gf1oeL!wPo38_-(%F6@J|E5_-p)@3jr3bTkXS!) zKPcoCbf<=bI)*yhG+c7uPuwoh;tjl4DwYkCDTNp2Qb%!TQdc~Wm|P7)AyYE(d-0Su z&`QM6VDZb8BzL&=Q>TPFbx?Kk)F3;l3Bw}9zNstm&oFJFd%&|x#lvZ%oZ&={S=lC0 zvC~`R6t>3^*A^~|L5zMq(}w6)!v9L3SfP;qjpRDa3)$9a#lCO#?s+~6*rnI&aUnR(zkl}i8gDR!ItrBe#Es!@dI3&mMjLn z%_X_Gj_MP{Gj!xGs*+s9&`%^T&-!ESIw5Q`uB ziy8k)wQIBiudb${=2t%xFpiQX#P!d=Bi-`-)QH!3ldm|l4$%*5C@yV&ef=zH#nT(w zN}o_~e9unD*#hu&Bd(qb*ZuaZ{@8MQ!zgporjyXP=u-L?q%STVuLk<6(o+==2IZ)pL6~_DtCxCaoSn3&#Dqk2}YOl9LE5)9{yvNuvVH z-v0Ll5NKD@jFU!^t5{GLj=g1NgRs|mhrt223FTUnDbX=QpOTg#%ExL&aYYDTR8&ln z>gZW{N@bVUmp!et)Vyi$WSI|NI`9U8_Pp;$;hOud&I=NUU814=F+bpB_n` z=B`J}sLn*9dP}Hy<9>|DIo1=KiVeY{>evp@oBv}B#5?h0vcmvVy^efdqw*1P$6Gw3 z=*7odD=?Fnox!x3e8h_5-r~~nc978brzC0O{1fv@yX>S4l$?`O?JU*i+b4UG#>JGK zq?c8Ysa}+LygO8sclB~q$|7@{U(%r<)*qnuV*jc4usi;AKB(?J-Sw|?=i{+BJ033d z_^gaL{jVc%t-0*${z_rrzOaP_zlxI@*k zF}z+)qs7sS-9TM)DHnPvywn%CzPWplICm)k)bC&Fho_O3x8wTYa+(CCD+A%7UtU>{ zYhh%t$p6oF$P<1w3agD*V<3L{RltWW5jg&~0gpHTX224k>(g<~N%IlwuD4SnB0zjT z;Wg3uMvuQ8!&R)g(FMd$-gr`n9A|znz}Df_ov>|D^<1@rm<^Rc5*6)zcu6U&5`ELd zt^ik?4`ls84R}QY1&ZREvmtqln#31@cs$>CArEn-#uq#u)=0-ozqMC7aJ&DyN(b)t z=Q!vL719Xv{XaWOmk+;#sM3njcRzrfHFsAkr|Bi)=Xr`#_q;%+`ku_o;rG|zxAp!| z{BHi>cAa$M{DJ`CVd^1_55EQ#o7V@5>5n>))iS6PHB^u!OjJMakL^-n1D{AR%zBb; zC*i0ZP~3SU>6iB$Qgn@4A<8|8df5;baba0YG)Tt&O%+LefyeW>`UH?s@R55sPzwag z-A2@vbJn5|8J(-)Ee_NeXKmP=-es#VHMhfZZjK*+;7Gt~3(AXT(rJ%5k>PpwGUEAG zXENHsl-nLBBhNce-T9^qwXgF|1tY|!z47egMms@rpBovVRSwfo5elpdjXcG2cbe>F zOJK2pra%ZsMOIgTb2~z^PRYBGewHrX0#ey;5&ZOnyp)qt;ie5 z$sbU2u53Xe@^~oujljZS5i;MG>$fD}3lD5bD{zBvg$$^rB_Le}$MQ(ia_ zrFa0zzai6_|=$`nz4+ZDbEw@jcW zQ1J8w7*1(nP#aj#7SATHXG>~ZOUOr)s2Sf)q+kdMO!4Nhq0|)HV~3Ij^c{!M25g6O zhmgIX=AVW`!vX9yf|v$~^9FDJ7HJCm&yAoMyg4^7(e=Zgd`9YaW0oO}=JaTrU0W=rN+YN>KSbrr(?dk~@B3&_LC z-rh>QsL62^*(vV)Ac_26*yj20;2Y$*+?gCWHko8um6r_g)bY+_O4Uf&QuJ99jHY@` z#L{wK(eQ`^_t#T6OgT{xjn;CN9$pf_Q7M%ETyl~fq&NSXLY^RZD+NAf<*^1tvI6|< zol04Dy+rcrIrJ@m)16+k%ul0EE?8NSN$bExJsZTw-X$+C%Azn3s>njL>Nq@G(UU2g zVxQNO@HF}eO}-mfXA_!V3m|8w49j10sS1BL=Fv9vgZ!q6(r`OuqAW->-Q0?2)K{rF zoXClHt-0@3oS0(u84n@*xHRFD(n4m3M5 zt?ZRW@^El`Z5kDT#?EOndeJYnhK1C;q);Xq^op$n;36VR8_8h~@0yLM((#?y z^k&_b+;{0^P+9Y?#BIxmb0}O>=h}srsWFHAL5|D!kqg%@qkzA6tV#vaPS7iex!j(9 zJWWt*rLD}$&Po%dvEE!Rke=%hk$pd)_rZ3_Of+IUKcJ5CkgfUAL27L~VJ%9!X57CB zK4P_06j7hL8rF$(H&HK+X8IV^pn^k^%axHZPefLkPKxUIUpV6|(DBh+efSDhq+yK?w?a`rb41f7oU+Sw@m zmExJc0lJ2)r)ZE(U;h*$)qLa}g<3o}AQ9t$(O)3std?D0(03{iz#N)LQ$3AoT)Hty zoVyXmhqfWVp8bjzV$p<6vOa@*ZKjlG(ru`Syyh#WF7oE)rPSjEpf3w6Sx4*aR&=w~ z(Dw4VDZ6ihtjgF*Nhmd2s7V93fRoWMF3mPiL~VvrJi<(YPPH;)&*dRzlomQZVg@+s zxZl?_9=Fo3DG@j1;vlhfkUNKOrLK0K&SK{F5X+LSu)G4gZ>OhC1n4&BY&Y`dQ!4V{ zBi|s^)=EGe#&H(viQkJXh!h<=Z$r|0^*+}0-cMeBas;T@NLpyPUH3|6j#3P$e#U^p zGc^dFs$}X!H&sWq%}%zm1H(ywF5D&yY_Z^BOR4NoEUw)~32-_&800k%$k#G)J1vz! zq{L-N8O34Iv+>lBSCs)6pfhFI#R+R8!y{goY7&hS)TAsR}KEv~72V||KB!k#!H6J>l zbTz#-Na;|Lnj3#lfpxCJ%Hz@BlNlQu9HjONPNgGHi(U7$oOF=&#!6$D%XYA!|t#_`;JBz0Q;1GNUV&>zsmK*>K)Um2>lp@}UVJdYvgYBBls z=L^RG>sD^@Bh`SU9QdPGvbfnkfVk96a@gS~`tARU)u0nfWgc?^hp6SvCukYi%BbSx zld=ZobteImvX>83x;U*!TI9!B7GP`G&nVGrIqGLZ|Hv1AhLurX`~nJAuK9&7*mdlP z8ri@-P9rnwc>HO=xsKPL20JUepP{dnrZ<33ok3U!aKE#Jnvyr4rAx>y4^~o3e%OTi zv+5iTuunK;`}7(hiqFvqM2ZEjiTTG|4txZm-@Zrz@UDcF&DeaAy5RMVi%4TKIW}JQ zWn(2Z*4OgGd(@lfT~_jU#br3MZCF?6hI(aDH_~w06^gLy(HS5qN-F&>AdtF#+zA6}(N_LF+rz)l$&t*HErIOaN?fg8MZ0|};<=iWd#XgRVqoTAO|G!n;! zz5{sm@92sGq_fCQA*NbJrRBqFiaLjaZ^M~T+}x&rNx}X8BoqDuY@(&Yj@Qxv@D0ABIPUv*XbZ^J)rc6n z^76ZsY8N5wH2gR09!*xhY5}Yb6P|w6;^IR#h{`h(7q9-bUSka$U%b~Y^LfD8xmum zbUN|k7;moaSoC zH=43D)ADSoA2V!c&N%^e$#GWo!#as-q7GUwJF8F)f8(OsC^1P6&iR~1r54-I1}%3} zIU?N9Nd$8XH`N7fby2IvKjQ~pyjrb_!1E(&l~G}>Pfg8BGaAz*>i{t+(T}HVRPA4c zFOm0J^VxjZ#iD8CnIJsJk>*m&oq{gJirI94>eHLeFMa|ir$U5 JOGDLy{{f^^w|@Wt delta 7885 zcma)Bd3;Rg`u7~kWM3x9NFpQ|u~SLf)J}tHEfI-bDzYQ8k(nSRA+=rCrFg|tR7vSo zyBIo_AZ~iKwh0&2DiTy_OGMkWw(|SFXC|uJem=i{&Y3yqeV^@pp6|1qqaQg|lsN8u zOGgb&V{}FKCYOMwIvU7_vdLFOhIx}k3=2E$V3}&ypg{vC@!F5UeDDUfGffG9wVm>8n>1Y6lKjQLJAHtaeLEi|++4dF#e=!fKPbu5xB;D z*pxjzNW&-X$V2S!-PKudY@kWXHcU;+723E)qRUJd5!A;AJR|z(8sawKjTDgLu-_=u zbh%$AqWPj`ML!Wgpaq^5Mnm~k10I7i56mZ>&_OjOU}HN8(9o^i9xO zIrzV%)fpQ&PRh-hl$LAEpWL8O!KRr$!gZI6SRAXzhMlnkv0>4v2cm9BKDgx!9cI(b zjkmR+W~SRi_2gA&Y+#q2l$9na%(s$RH`15gI+7PZ`kcH(-0+s*STsCd8m=_jPy9OK zEsyz7cBnBoJ$t-$OVY^Bc)Mz3Ir)jVM*Txw*-|){^%Nyp^F+7oC|(jr zo~F>Z782fkl-!o}`{rW(Cfkez#dMNL9u8$)~6>fi~j6N|8Zb7EorV^LHVD`q8a!v40& z(SKvfHOVb(>z#Prb@CCHlMN6cHf075OnaOf@o7E1B($jm;X>gB)Kze5TPK`~lAa1x z_$op4YpLNjBF@t2EoqbJm39IKbsKj~de(&rm!t`f>eYj*PS@j^GV!XaSS!&1PLy$6 zD_UjvfR?4yUkuL}XZv+NV=8tI$~2O`_y{!>Rhhl9w^`OS5MP}YAbqWahJNNAtq4RO zIf=uQv~c1xlL|n5@ZoMW8T^v8m|4v96W5kpgEkV|AJ9&R)ljvBpLFu#nlBJOqUiUz&NJv z3+E|R$6H5H8*zJ49B90`_-i+vG$-QD&X_jMsD^sXY8MgxZioli+h-;vr)3%&G#ox8 zN`#*X5^LY>fPG<`RGe$Xo=1T%-b-eK^}9=b#r7qwpM}DiK0%z*mYjuV>F3yjL#l^H zFJZszsGDz4If&!VC~iwdxpgdRc_VPnA?Si+7>SDlth0xl8e6 z_?Qj+d`Oh9cpoq0xxGA&f4+AZmK?BB9>@NbopJqXWhR~^t~!8glhw(j=OaJE$0G;0 ziA}3}VZjfpNBkiQ=e{3li^64Vy5W8IHH$Q=#dM-_&A;%f)J18_GtQA#B(8N3r;ByG zR#EJ&3IozQs@^(ZxUTJ3pAZ6Xkp_#K!g&hUp0k6Od2`7^cxd{%8+bOjWHI<$DVdCG z%!diM?)LW!b?${Ut4(*z(bM+evr`&n?%* zj@;Q|=Efq(MIT=T%}XB-g`L7SeI*^kc}oZBa$UB~$DbqHCgYiK-V(>Bw^9$&@g1wB zp~sbuhj5nCi7s_25~)GRcLLKiMQyH z*u1|j_Ef$DWN94d#+Ej)X#WGQ?gI=t*&|*FR~<*u3;lr%&>342B6c3Y=`TCnRy6%i zE1H*i+T;op>qG}rdReGlz1#A-X7K1w${&D!_-yj#Huj2EoIBDOg5NzdU0N%n;%mjW z96?>NMAY{9r)m6WfIN$7)UmMymgeJexO$zC^~1~)3t<OH=9RYM<;rj;X|bsj(sI*8aB)jx!&nHqpu{6b-V=j@MC^+BZxzj5<-`Z75mYb8AW-vBr#YI!G<Vc=fzYQkE{Me5u*R4xmZ$pDGxOI zUhZNw#4bVI9UCvjkc?(xi{1lb@gNV zv*V^jYen*PUubU$rXa5Ug2r%bS9(jteA5+77JdV45x;%&I(e+P43tQ?;SV19H{QV0 z@*Debef!%qoKW?*1K{oLZf?SLYp)>j^UZw_Zu_l1SpDOzD6I25{gtRH@D-uocgH_j z?9BOoGv0>&kb;%shnZ@XXD}ui^9;)}TJgGAauYGP9n^^P7w0qj^7Jbd!XF$UAB{xC zhR^AmmEj@+?{o(9k$0ZfgVUIrX`p<*rXxtW-u;J*idi0N&`ONw)_LGiephF+l9TEu z`zzs4O`It}c-A2w*QU7qUXM)^SJ%9*v-19PT8fcnuDeLPrvumN_oVyYx_1<{f@#Ok zPBJ3))lGorMZ-h*Zazhr@_&ht;j;bLcJk%L`^zDH&V$vezCA_Z0<8#Q2OjHB8dY7y za@NC05ZL~3Ef_>U8iwENAN^D>J!_*VP#k#tgB2LnvcgH+{;e-r%*Q*DCvjE*wG&xS z-^A`cPdkCnou}!x-S$%V;wdC4*Yp^|6Np;Dt(Fk=1UVJo2Y*g3+~G3>=#^i+REa>i znL-1BjGWz&fP8A@Ch?hXHY8x6TDkFFJ8DgSeASL%5`MQi&E@Du)STBnCOewX;jPdh zcxwpgVn$IFBHe$81Esv^ss=oda3lk?mMuoPBUNKZi4$#r70qwBPzvt6*_DbxG}28K ze5M<{?PbLUB=?k@RJ9bBRZml~Il-BKn?nxV#hn_VB#6Py8E5T~oRpcAox;?S0(r4J z$m;n=cS-{LVIDLFyh=T21bC_GJXlL#VnMYtHRVaMpmx|%(Qt?t^~KY4FPd0?JZiIb zq70R-r#F>@-x+TbjzD^~7*mU2p5;R?;Q)5V>iB&VtZnh3K^)<^^Z!!-M8r}v*J2baT~G~*s#rBB zp2XtZWyjc?R}2AIsi=VF!ID(%i52F@pH8CM4FW12L zo+2*y3`2}q_~bA$L*zNbX*0A!&;^sNhgp`k-XB)02kZ19q}I?$V6 z8%05oGhq}(;T`&mX||J8>mv+1#wzSThPul3$6h)SKhaS;zFbA_ynGD6#=$yh~rK;sia#D5%%9lW{J&$ucnna;E<6n|s<~oi_hBx`~f08Nv#WX6`B>V8# z6w-p$q!jp`g)gNL<}B9QZdod!qcc+)^>o6k$ZYx;uS(5e@vfWV#XdRI4m%@rfB|~m zo4xC5#ovpp`PMb=reI72(5jOTya9t-r(W061u-oBx35rE*Hs-53M9WD`?1; zJQsVex+^{$dQdv%Di!7`R?%cty$NW?<=~NrtfnA2g|auM8#B{n#pTapHAUM(-IWuw z$;bS7HHr@q?6!t7T>wDOlEtnyv=4e^en2HJMiJjF284H7Oa8Dz>{==!XYkPg1{2a# zC93!d`|teNwFHSw&s|C=UIh{vvno!Cb*G*AKnYT!#eBbnI-~XH@bxNkA6<{CMb9A{ z2<4~wUmIwe%vAOpsftt@Ntx+M;^0$%?!JlKoQDpMHdrG9Bcwo?moyx?37vVxEv*D| zCmvh@J8#8W5Z%8?4Y1(N&*095&y2z9(>aaIpKk`NVbRkqG{t5i%pSK4Goz=TAY-h{ z4!UJ~;lvA=dh)oPkV?;Qlp=5Hc~dE6{~dzcigyQ&+ePj+BF<_BUthel<|Gw~3u#&_ z>cyFWt`)xN(#BIVJ8j72m!PSZNtj(}ZwLgy1`xZ4}w@=6mwhsOT2cp_~N_yHk)bzYw9c3^Z32$IyE*I zRG^*&yNUm$slNxGIzmfDA((fR2HI3+h39I*?luP)|NqSYyr+KKVkWZV5rKfc~sSCOC;8W;? zEIjQLeG7vnSk&I4!h$Nw!oc`o^Kdku5;@XNQw;2rQE?hWmCuL=okels$604pFh6$| zJr0a`j$U_%zSjAq)oIJlA@S+p6d!BpR*j=EY@hjlB8VRZ3HuNIWxEvS+ z*ap>$7Ws1JMWpCDuDK|?OkVsoHpmYe{;a7uKhLW|Nc!=;DoV$Tx2owIH1d&G=@aWXV7|4* z^1KEIvM}1kZsvk()I?U-aLsfKSJ7<@G|?c)hi;M_=1c!V#P_k~UI$|XS5aBvYfioe zOmc&=PO`Vj%Yws*@B_#Y-;pEl_zwPL;cMT~rx0%A_iBRF|3D_pDf#>ziia^@sX?gd zxLh{a7iv&5_{rml!)@VRwGH_7F4cb_(n{=A=b7e zk@RLWw){xro@rH3&+L}@x@2ay&jVEI8_rl86NZ0o48p|S#ucQsV}|1K!`p`C&ca_ncXbpN}!eGu=$ zi|rL;H{M~dB!QwG8EQVcdolNidkoD@{2 z=7!G7HijC&>@{3}0w13x4G^j=Ly?#U0@*{3U%GqP+>=kKg%&B+Je3bTL&Hfv2NkYrStoLD0}*>jQk=JzY_aWg7xKDSj? zgi&Gofp|vA=`Yta>P&ujf@kvkT%OIIr%F^9=WX8i?jjrGyy*{98D%%G{ko1B%ARc7 z6FdDt9i#SiyGBO#=~{`5{F7fKKHmK6*L#p9=e8zp_ZDR=@nTfi4m7%n1*i>dMFAfp z=j7;Q*~tN(eA_3aFsAFkEZ5*?l%4*;h>>r*%|ynlvW)X4Cr%QX{O2U+cK!8?IeZX% zr6=o8R++xQfst>rj)cJWpL-cgMJDGvDua9}4)UdEN^v1W9mDz(1`cpA#Al{V*2>bD PF0h4BX8ZC>jFSZbNt}Yv delta 258 zcmaESoPF0Z_6jJqcK-?v+-CW0vLcLf(*+Y4C8t|$VAPpxf0AeN`&^#sr|&Z=PA)jv zv)TRhab?CioB7{gWCKzUzn*4hoHP01*Q(9xzl5cM2Fh+3hPL`b<;K?`LLY@w>mHdpdlN+Y+ZSS4PcvY5bP6-2BUTJPp zYSHw@8H}crH(ckLUT^_uf;AKOWG5Nb?a$UTvhe}UzIYC35%)gEEh5{+FEgGI006{d BVO#(J From 74c30a47177edaf97f7283f773be25288d6a476b Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 21 Nov 2024 08:22:09 +0100 Subject: [PATCH 09/14] refactor/code clean --- obp-api/src/main/scala/code/api/util/ApiRole.scala | 9 --------- 1 file changed, 9 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 137b2712a..99b157654 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -103,12 +103,6 @@ object ApiRole extends MdcLoggable{ case class CanCreateCustomer(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateCustomer = CanCreateCustomer() - - case class CanCreateAgent(requiresBankId: Boolean = true) extends ApiRole - lazy val canCreateAgent = CanCreateAgent() - - case class CanGetAgent(requiresBankId: Boolean = true) extends ApiRole - lazy val canGetAgent = CanGetAgent() case class CanUpdateAgentStatusAtAnyBank(requiresBankId: Boolean = false) extends ApiRole lazy val canUpdateAgentStatusAtAnyBank = CanUpdateAgentStatusAtAnyBank() @@ -146,9 +140,6 @@ object ApiRole extends MdcLoggable{ case class CanCreateCustomerAtAnyBank(requiresBankId: Boolean = false) extends ApiRole lazy val canCreateCustomerAtAnyBank = CanCreateCustomerAtAnyBank() - case class CanCreateAgentAtAnyBank(requiresBankId: Boolean = false) extends ApiRole - lazy val canCreateAgentAtAnyBank = CanCreateAgentAtAnyBank() - case class CanGetCorrelatedUsersInfo(requiresBankId: Boolean = true) extends ApiRole lazy val canGetCorrelatedUsersInfo = CanGetCorrelatedUsersInfo() From 730c3443e12ec89201728b94ced1460e3df1e833 Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 21 Nov 2024 08:35:29 +0100 Subject: [PATCH 10/14] refactor/tweaked the props in scalacode --- .../api/v4_0_0/TransactionRequestsTest.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala index b566a6270..3b365b514 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala @@ -1250,11 +1250,15 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { feature(s"we can create transaction requests -- $AGENT_CASH_WITHDRAWAL") { + if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) { ignore("No challenge, No FX ", ApiEndpoint11) {} } else { scenario("No challenge, No FX ", ApiEndpoint11) { + setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD,AGENT_CASH_WITHDRAWAL") + setPropsValues("AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") + When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) @@ -1285,6 +1289,8 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { } else { scenario("No challenge, With FX ", ApiEndpoint11) { + setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD,AGENT_CASH_WITHDRAWAL") + setPropsValues("AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) @@ -1324,6 +1330,10 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { ignore("With challenge, No FX ", ApiEndpoint11) {} } else { scenario("With challenge, No FX ", ApiEndpoint11) { + + setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD,AGENT_CASH_WITHDRAWAL") + setPropsValues("AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") + When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) And("We set the special conditions for different currencies") @@ -1370,6 +1380,10 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { ignore("With challenge, With FX", ApiEndpoint11) {} } else { scenario("With challenge, With FX", ApiEndpoint11) { + + setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD,AGENT_CASH_WITHDRAWAL") + setPropsValues("AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") + When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) @@ -1420,6 +1434,10 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { ignore("With N challenges, With FX", ApiEndpoint11) {} } else { scenario("With N challenges, With FX", ApiEndpoint1) { + + setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD,AGENT_CASH_WITHDRAWAL") + setPropsValues("AGENT_CASH_WITHDRAWAL_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY") + When("we prepare all the conditions for a normal success -- V400 Create Transaction Request") val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString) From d1d4af497e5b15e6948c7a4fd0231bfcbfb5e109 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 22 Nov 2024 09:33:22 +0100 Subject: [PATCH 11/14] refactor/AgentMinimalsJson --> MinimalAgentsJson --- .../ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 6 +++--- .../main/scala/code/api/v5_1_0/APIMethods510.scala | 4 ++-- .../scala/code/api/v5_1_0/JSONFactory5.1.0.scala | 12 ++++++------ .../src/test/scala/code/api/v5_1_0/AgentTest.scala | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index ada743b45..3b415f904 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5548,13 +5548,13 @@ object SwaggerDefinitionsJSON { is_pending_agent = true ) - val agentMinimalJsonV510 = AgentMinimalJsonV510( + val minimalAgentJsonV510 = MinimalAgentJsonV510( agent_id = agentIdExample.value, legal_name = legalNameExample.value, ) - val agentMinimalsJsonV510 = AgentMinimalsJsonV510( - agents = List(agentMinimalJsonV510) + val minimalAgentsJsonV510 = MinimalAgentsJsonV510( + agents = List(minimalAgentJsonV510) ) //The common error or success format. diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 8be53ade5..ee0fff03a 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -963,7 +963,7 @@ trait APIMethods510 { |${urlParametersDocument(true, true)} |""".stripMargin, EmptyBody, - agentMinimalsJsonV510, + minimalAgentsJsonV510, List( $BankNotFound, UnknownError @@ -978,7 +978,7 @@ trait APIMethods510 { (requestParams, callContext) <- extractQueryParams(cc.url, List("limit","offset","sort_direction"), cc.callContext) (agents, callContext) <- NewStyle.function.getAgents(bankId.value, requestParams, callContext) } yield { - (JSONFactory510.createAgentMinimalsJson(agents), HttpCode.`200`(callContext)) + (JSONFactory510.createMinimalAgentsJson(agents), HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 4c38bab18..f8e43bf58 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -341,12 +341,12 @@ case class AgentJsonV510( is_pending_agent: Boolean ) -case class AgentMinimalJsonV510( +case class MinimalAgentJsonV510( agent_id: String, legal_name: String, ) -case class AgentMinimalsJsonV510( - agents: List[AgentMinimalJsonV510] +case class MinimalAgentsJsonV510( + agents: List[MinimalAgentJsonV510] ) case class CustomersIdsJsonV510(customers: List[CustomerIdJson]) @@ -955,11 +955,11 @@ object JSONFactory510 extends CustomJsonFormats { is_pending_agent = agent.isPendingAgent ) } - def createAgentMinimalsJson(agents: List[Agent]): AgentMinimalsJsonV510 = { - AgentMinimalsJsonV510( + def createMinimalAgentsJson(agents: List[Agent]): MinimalAgentsJsonV510 = { + MinimalAgentsJsonV510( agents .filter(_.isConfirmedAgent == true) - .map(agent => AgentMinimalJsonV510( + .map(agent => MinimalAgentJsonV510( agent_id = agent.agentId, legal_name = agent.legalName ))) diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala index ec23c9d1b..3073dfdc5 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala @@ -143,7 +143,7 @@ class AgentTest extends V510ServerSetup { val request = (v5_1_0_Request / "banks" / bankId / "agents").GET val response = makeGetRequest(request) response.code should equal(200) - response.body.extract[AgentMinimalsJsonV510].agents.length shouldBe(0) + response.body.extract[MinimalAgentsJsonV510].agents.length shouldBe(0) } { @@ -160,7 +160,7 @@ class AgentTest extends V510ServerSetup { val request = (v5_1_0_Request / "banks" / bankId / "agents").GET val response = makeGetRequest(request) response.code should equal(200) - response.body.extract[AgentMinimalsJsonV510].agents.length shouldBe(1) + response.body.extract[MinimalAgentsJsonV510].agents.length shouldBe(1) } From ff68d7b7e6c5bc04ec7672a8c076a6ad209668a6 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 22 Nov 2024 14:16:14 +0100 Subject: [PATCH 12/14] refactor/use AgentAccountLink method --- .../scala/code/api/util/ErrorMessages.scala | 2 + .../main/scala/code/api/util/NewStyle.scala | 19 +++++-- .../scala/code/api/v5_1_0/APIMethods510.scala | 52 +++++++++---------- .../scala/code/bankconnectors/Connector.scala | 10 ++++ .../bankconnectors/LocalMappedConnector.scala | 29 +++++++++++ .../customer/MappedCustomerProvider.scala | 1 + .../MappedCustomerAccountLink.scala | 9 +++- .../commons/model/CommonModel.scala | 9 ++++ .../commons/model/CommonModelTrait.scala | 7 +++ 9 files changed, 106 insertions(+), 32 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 41ca284f0..0b3cb7873 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -529,6 +529,8 @@ object ErrorMessages { val AgentAccountLinkNotFound = "OBP-30325: Agent Account Link not found." val AgentsNotFound = "OBP-30326: Agents not found." val CreateAgentAccountLinkError = "OBP-30327: Could not create the agent account link." + val AgentNumberAlreadyExists = "OBP-30328: Agent Number already exists. Please specify a different value for BANK_ID or AGENT_NUMBER." + val GetAgentAccountLinksError = "OBP-30226: Could not get the agent account links." // Branch related messages val BranchesNotFoundLicense = "OBP-32001: No branches available. License may not be set." diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index ae008ba89..3e727c382 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -77,7 +77,7 @@ import code.counterpartylimit.{CounterpartyLimit} import com.openbankproject.commons.model.CounterpartyLimitTrait import code.crm.CrmEvent import code.crm.CrmEvent.CrmEvent -import com.openbankproject.commons.model.CustomerAccountLinkTrait +import com.openbankproject.commons.model.{CustomerAccountLinkTrait, AgentAccountLinkTrait} import code.dynamicMessageDoc.{DynamicMessageDocProvider, JsonDynamicMessageDoc} import code.dynamicResourceDoc.{DynamicResourceDocProvider, JsonDynamicResourceDoc} import code.endpointMapping.{EndpointMappingProvider, EndpointMappingT} @@ -754,13 +754,19 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse", 400), i._2) } } + + def checkAgentNumberAvailable(bankId: BankId, agentNumber: String, callContext: Option[CallContext]): OBPReturnType[Boolean] = { + Connector.connector.vend.checkAgentNumberAvailable(bankId: BankId, agentNumber: String, callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse", 400), i._2) + } + } + def getCustomerByCustomerNumber(customerNumber : String, bankId : BankId, callContext: Option[CallContext]): OBPReturnType[Customer] = { Connector.connector.vend.getCustomerByCustomerNumber(customerNumber, bankId, callContext) map { unboxFullOrFail(_, callContext, CustomerNotFound, 404) } } - def getCustomerAddress(customerId : String, callContext: Option[CallContext]): OBPReturnType[List[CustomerAddress]] = { Connector.connector.vend.getCustomerAddress(customerId, callContext) map { i => (connectorEmptyResponse(i._1, callContext), i._2) @@ -4128,8 +4134,8 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, CreateCustomerAccountLinkError), i._2) } - def createAgentAccountLink(agentId: String, bankId: String, accountId: String, relationshipType: String, callContext: Option[CallContext]): OBPReturnType[CustomerAccountLinkTrait] = - Connector.connector.vend.createCustomerAccountLink(agentId: String, bankId, accountId: String, relationshipType: String, callContext: Option[CallContext]) map { + def createAgentAccountLink(agentId: String, bankId: String, accountId: String, callContext: Option[CallContext]): OBPReturnType[AgentAccountLinkTrait] = + Connector.connector.vend.createAgentAccountLink(agentId: String, bankId, accountId: String, callContext: Option[CallContext]) map { i => (unboxFullOrFail(i._1, callContext, CreateAgentAccountLinkError), i._2) } @@ -4138,6 +4144,11 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, GetCustomerAccountLinksError), i._2) } + def getAgentAccountLinksByAgentId(agentId: String, callContext: Option[CallContext]): OBPReturnType[List[CustomerAccountLinkTrait]] = + Connector.connector.vend.getAgentAccountLinksByAgentId(agentId: String, callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, GetAgentAccountLinksError), i._2) + } + def getCustomerAccountLinksByBankIdAccountId(bankId: String, accountId: String, callContext: Option[CallContext]): OBPReturnType[List[CustomerAccountLinkTrait]] = Connector.connector.vend.getCustomerAccountLinksByBankIdAccountId(bankId, accountId: String, callContext: Option[CallContext]) map { i => (unboxFullOrFail(i._1, callContext, GetCustomerAccountLinksError), i._2) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index ee0fff03a..46fa5d219 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -361,10 +361,8 @@ trait APIMethods510 { $UserNotLoggedIn, $BankNotFound, InvalidJsonFormat, - CustomerNumberAlreadyExists, - UserNotFoundById, - CustomerAlreadyExistsForUser, - CreateConsumerError, + AgentNumberAlreadyExists, + CreateAgentError, UnknownError ), List(apiTagCustomer, apiTagPerson) @@ -374,15 +372,16 @@ trait APIMethods510 { case "banks" :: BankId(bankId) :: "agents" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { - postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostAgentJsonV510 ", 400, cc.callContext) { + putData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostAgentJsonV510 ", 400, cc.callContext) { json.extract[PostAgentJsonV510] } - (_, callContext) <- NewStyle.function.checkCustomerNumberAvailable(bankId, postedData.agent_number, cc.callContext) + (agentNumberIsAvailable, callContext) <- NewStyle.function.checkAgentNumberAvailable(bankId, putData.agent_number, cc.callContext) + _ <- Helper.booleanToFuture(failMsg= s"$AgentNumberAlreadyExists Current agent_number(${putData.agent_number}) and Current bank_id(${bankId.value})", cc=callContext) {agentNumberIsAvailable} (agent, callContext) <- NewStyle.function.createAgent( bankId = bankId.value, - legalName = postedData.legal_name, - mobileNumber = postedData.mobile_phone_number, - number = postedData.agent_number, + legalName = putData.legal_name, + mobileNumber = putData.mobile_phone_number, + number = putData.agent_number, callContext, ) (bankAccount, callContext) <- NewStyle.function.createBankAccount( @@ -390,14 +389,15 @@ trait APIMethods510 { AccountId(APIUtil.generateUUID()), "AGENT", "AGENT", - postedData.currency, + putData.currency, 0, - postedData.legal_name, + putData.legal_name, null, Nil, callContext ) - (_, callContext) <- NewStyle.function.createCustomerAccountLink(agent.agentId, bankAccount.bankId.value, bankAccount.accountId.value, "Owner", callContext) + (_, callContext) <- NewStyle.function.createAgentAccountLink(agent.agentId, bankAccount.bankId.value, bankAccount.accountId.value, callContext) + } yield { (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`201`(callContext)) } @@ -420,10 +420,8 @@ trait APIMethods510 { $UserNotLoggedIn, $BankNotFound, InvalidJsonFormat, - CustomerNumberAlreadyExists, - UserNotFoundById, - CustomerAlreadyExistsForUser, - CreateConsumerError, + AgentNotFound, + AgentAccountLinkNotFound, UnknownError ), List(apiTagCustomer, apiTagPerson), @@ -438,11 +436,11 @@ trait APIMethods510 { json.extract[PutAgentJsonV510] } (agent, callContext) <- NewStyle.function.getAgentByAgentId(agentId, cc.callContext) - (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agentId, callContext) - customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { - customerAccountLinks.head + (agentAccountLinks, callContext) <- NewStyle.function.getAgentAccountLinksByAgentId(agentId, callContext) + agentAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { + agentAccountLinks.head } - (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) + (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(agentAccountLink.bankId), AccountId(agentAccountLink.accountId), callContext) (agent, callContext) <- NewStyle.function.updateAgentStatus( agentId, postedData.is_pending_agent, @@ -470,6 +468,8 @@ trait APIMethods510 { List( $UserNotLoggedIn, $BankNotFound, + AgentNotFound, + AgentAccountLinkNotFound, UnknownError ), List(apiTagAccount) @@ -479,13 +479,12 @@ trait APIMethods510 { case "banks" :: BankId(bankId) :: "agents" :: agentId :: Nil JsonGet _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { - (Full(u), callContext) <- SS.user - (agent, callContext) <- NewStyle.function.getAgentByAgentId(agentId, callContext) - (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agentId, callContext) - customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { - customerAccountLinks.head + (agent, callContext) <- NewStyle.function.getAgentByAgentId(agentId, cc.callContext) + (agentAccountLinks, callContext) <- NewStyle.function.getAgentAccountLinksByAgentId(agentId, callContext) + agentAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { + agentAccountLinks.head } - (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) + (bankAccount, callContext) <- NewStyle.function.getBankAccount(BankId(agentAccountLink.bankId), AccountId(agentAccountLink.accountId), callContext) } yield { (JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`200`(callContext)) } @@ -966,6 +965,7 @@ trait APIMethods510 { minimalAgentsJsonV510, List( $BankNotFound, + AgentsNotFound, UnknownError ), List(apiTagAccount) diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index 57a28978c..a0614e1d7 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -1059,6 +1059,12 @@ trait Connector extends MdcLoggable { callContext: Option[CallContext] ): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError(nameOf(checkCustomerNumberAvailable _))), callContext)} + def checkAgentNumberAvailable( + bankId: BankId, + agentNumber: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError(nameOf(checkAgentNumberAvailable _))), callContext)} + def createCustomer( bankId: BankId, legalName: String, @@ -1793,6 +1799,8 @@ trait Connector extends MdcLoggable { def getCustomerAccountLinksByCustomerId(customerId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[CustomerAccountLinkTrait]]] = Future{(Failure(setUnimplementedError(nameOf(getCustomerAccountLinksByCustomerId _))), callContext)} + def getAgentAccountLinksByAgentId(agnetId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[CustomerAccountLinkTrait]]] = Future{(Failure(setUnimplementedError(nameOf(getCustomerAccountLinksByCustomerId _))), callContext)} + def getCustomerAccountLinksByBankIdAccountId(bankId: String, accountId: String, callContext: Option[CallContext]): OBPReturnType[Box[List[CustomerAccountLinkTrait]]] = Future{(Failure(setUnimplementedError(nameOf(getCustomerAccountLinksByBankIdAccountId _))), callContext)} def getCustomerAccountLinkById(customerAccountLinkId: String, callContext: Option[CallContext]): OBPReturnType[Box[CustomerAccountLinkTrait]] = Future{(Failure(setUnimplementedError(nameOf(getCustomerAccountLinkById _))), callContext)} @@ -1801,6 +1809,8 @@ trait Connector extends MdcLoggable { def createCustomerAccountLink(customerId: String, bankId: String, accountId: String, relationshipType: String, callContext: Option[CallContext]): OBPReturnType[Box[CustomerAccountLinkTrait]] = Future{(Failure(setUnimplementedError(nameOf(createCustomerAccountLink _))), callContext)} + def createAgentAccountLink(agentId: String, bankId: String, accountId: String, callContext: Option[CallContext]): OBPReturnType[Box[AgentAccountLinkTrait]] = Future{(Failure(setUnimplementedError(nameOf(createAgentAccountLink _))), callContext)} + def updateCustomerAccountLinkById(customerAccountLinkId: String, relationshipType: String, callContext: Option[CallContext]): OBPReturnType[Box[CustomerAccountLinkTrait]] = Future{(Failure(setUnimplementedError(nameOf(updateCustomerAccountLinkById _))), callContext)} def getConsentImplicitSCA(user: User, callContext: Option[CallContext]): OBPReturnType[Box[ConsentImplicitSCAT]] = Future{(Failure(setUnimplementedError(nameOf(getConsentImplicitSCA _))), callContext)} diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index c1715d4a9..602086fef 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -3074,6 +3074,17 @@ object LocalMappedConnector extends Connector with MdcLoggable { CustomerX.customerProvider.vend.checkCustomerNumberAvailable(bankId, customerNumber) }, callContext) } + + override def checkAgentNumberAvailable( + bankId: BankId, + agentNumber: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = Future { + //in OBP, customer and agent share the same customer model. the CustomerAccountLink and AgentAccountLink also share the same model + (tryo { + CustomerX.customerProvider.vend.checkCustomerNumberAvailable(bankId, agentNumber) + }, callContext) + } override def createCustomer( @@ -5059,6 +5070,11 @@ object LocalMappedConnector extends Connector with MdcLoggable { (CustomerAccountLinkX.customerAccountLink.vend.getCustomerAccountLinksByCustomerId(customerId),callContext) } + override def getAgentAccountLinksByAgentId(agentId: String, callContext: Option[CallContext]) = Future{ + //in OBP, customer and agent share the same customer model. the CustomerAccountLink and AgentAccountLink also share the same model + (CustomerAccountLinkX.customerAccountLink.vend.getCustomerAccountLinksByCustomerId(agentId),callContext) + } + override def getCustomerAccountLinkById(customerAccountLinkId: String, callContext: Option[CallContext]) = Future{ (CustomerAccountLinkX.customerAccountLink.vend.getCustomerAccountLinkById(customerAccountLinkId),callContext) } @@ -5078,6 +5094,19 @@ object LocalMappedConnector extends Connector with MdcLoggable { CustomerAccountLinkX.customerAccountLink.vend.createCustomerAccountLink(customerId: String, bankId, accountId: String, relationshipType: String) map { ( _, callContext) } } + override def createAgentAccountLink(agentId: String, bankId: String, accountId: String, callContext: Option[CallContext]): OBPReturnType[Box[AgentAccountLinkTrait]] = Future{ + //in OBP, customer and agent share the same customer model. the CustomerAccountLink and AgentAccountLink also share the same model + CustomerAccountLinkX.customerAccountLink.vend.createCustomerAccountLink(agentId: String, bankId, accountId: String, "Owner") map { customer => ( + AgentAccountLinkTraitCommons( + agentAccountLinkId = customer.customerAccountLinkId, + agentId = customer.customerId, + bankId = customer.bankId, + accountId = customer.accountId, + ), + callContext) + } + } + override def getConsentImplicitSCA(user: User, callContext: Option[CallContext]): OBPReturnType[Box[ConsentImplicitSCAT]] = Future { //find the email from the user, and the OBP Implicit SCA is email (Full(ConsentImplicitSCA( diff --git a/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala b/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala index 5ce838796..9ef7a8994 100644 --- a/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala +++ b/obp-api/src/main/scala/code/customer/MappedCustomerProvider.scala @@ -333,6 +333,7 @@ object MappedCustomerProvider extends CustomerProvider with MdcLoggable { } +//in OBP, customer and agent share the same customer model. the CustomerAccountLink and AgentAccountLink also share the same model class MappedCustomer extends Customer with Agent with LongKeyedMapper[MappedCustomer] with IdPK with CreatedUpdated { def getSingleton = MappedCustomer diff --git a/obp-api/src/main/scala/code/customeraccountlinks/MappedCustomerAccountLink.scala b/obp-api/src/main/scala/code/customeraccountlinks/MappedCustomerAccountLink.scala index 9734b87a5..2a3d216a6 100644 --- a/obp-api/src/main/scala/code/customeraccountlinks/MappedCustomerAccountLink.scala +++ b/obp-api/src/main/scala/code/customeraccountlinks/MappedCustomerAccountLink.scala @@ -8,7 +8,7 @@ import net.liftweb.mapper._ import scala.concurrent.Future import com.openbankproject.commons.ExecutionContext.Implicits.global import net.liftweb.util.Helpers.tryo -import com.openbankproject.commons.model.CustomerAccountLinkTrait +import com.openbankproject.commons.model.{CustomerAccountLinkTrait,AgentAccountLinkTrait} object MappedCustomerAccountLinkProvider extends CustomerAccountLinkProvider { override def createCustomerAccountLink(customerId: String, bankId: String, accountId: String, relationshipType: String): Box[CustomerAccountLinkTrait] = { @@ -103,7 +103,8 @@ object MappedCustomerAccountLinkProvider extends CustomerAccountLinkProvider { } } -class CustomerAccountLink extends CustomerAccountLinkTrait with LongKeyedMapper[CustomerAccountLink] with IdPK with CreatedUpdated { +//in OBP, customer and agent share the same customer model. the CustomerAccountLink and AgentAccountLink also share the same model +class CustomerAccountLink extends CustomerAccountLinkTrait with AgentAccountLinkTrait with LongKeyedMapper[CustomerAccountLink] with IdPK with CreatedUpdated { def getSingleton = CustomerAccountLink @@ -118,6 +119,10 @@ class CustomerAccountLink extends CustomerAccountLinkTrait with LongKeyedMapper[ override def bankId: String = BankId.get // id.toString override def accountId: String = AccountId.get override def relationshipType: String = RelationshipType.get + + override def agentId: String = CustomerId.get + override def agentAccountLinkId: String = CustomerAccountLinkId.get + } object CustomerAccountLink extends CustomerAccountLink with LongKeyedMetaMapper[CustomerAccountLink] { diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala index 14d785f23..4da9c0c0b 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala @@ -604,6 +604,15 @@ case class CustomerAccountLinkTraitCommons( object CustomerAccountLinkTraitCommons extends Converter[CustomerAccountLinkTrait, CustomerAccountLinkTraitCommons] +case class AgentAccountLinkTraitCommons( + agentAccountLinkId: String, + agentId: String, + bankId: String, + accountId: String +) extends AgentAccountLinkTrait + +object AgentAccountLinkTraitCommons extends Converter[AgentAccountLinkTrait, AgentAccountLinkTraitCommons] + case class CounterpartyLimitTraitCommons( counterpartyLimitId: String, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala index 3d07072cb..b1e939464 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala @@ -648,6 +648,13 @@ trait CustomerAccountLinkTrait { def relationshipType: String } +trait AgentAccountLinkTrait { + def agentAccountLinkId: String + def agentId: String + def bankId: String + def accountId: String +} + trait CounterpartyLimitTrait extends JsonAble{ def counterpartyLimitId: String def bankId: String From 82e5ea6a848a3f78df7e3006d39921cb8b6eae1c Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 22 Nov 2024 14:31:07 +0100 Subject: [PATCH 13/14] refactor/added agent_number to MinimalAgentsJsonV510 --- .../code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala | 1 + obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala | 4 +++- obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 3b415f904..af99ce073 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5551,6 +5551,7 @@ object SwaggerDefinitionsJSON { val minimalAgentJsonV510 = MinimalAgentJsonV510( agent_id = agentIdExample.value, legal_name = legalNameExample.value, + agent_number = agentNumberExample.value ) val minimalAgentsJsonV510 = MinimalAgentsJsonV510( diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index f8e43bf58..61104f986 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -344,6 +344,7 @@ case class AgentJsonV510( case class MinimalAgentJsonV510( agent_id: String, legal_name: String, + agent_number: String, ) case class MinimalAgentsJsonV510( agents: List[MinimalAgentJsonV510] @@ -961,7 +962,8 @@ object JSONFactory510 extends CustomJsonFormats { .filter(_.isConfirmedAgent == true) .map(agent => MinimalAgentJsonV510( agent_id = agent.agentId, - legal_name = agent.legalName + legal_name = agent.legalName, + agent_number = agent.number ))) } diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala index 3073dfdc5..e990216c6 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AgentTest.scala @@ -161,6 +161,8 @@ class AgentTest extends V510ServerSetup { val response = makeGetRequest(request) response.code should equal(200) response.body.extract[MinimalAgentsJsonV510].agents.length shouldBe(1) + response.body.extract[MinimalAgentsJsonV510].agents.head.agent_number should equal(postAgentJsonV510.agent_number) + response.body.extract[MinimalAgentsJsonV510].agents.head.legal_name should equal(postAgentJsonV510.legal_name) } From f8aff9262261b609a228a7644606221f1c6939e7 Mon Sep 17 00:00:00 2001 From: hongwei Date: Fri, 22 Nov 2024 17:08:26 +0100 Subject: [PATCH 14/14] refactor/changed the AGENT_CASH_WITHDRAWAL request json --- .../SwaggerDefinitionsJSON.scala | 6 ++++- .../main/scala/code/api/util/NewStyle.scala | 6 +++++ .../scala/code/api/v4_0_0/APIMethods400.scala | 5 ++--- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 7 +++--- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 2 ++ .../scala/code/bankconnectors/Connector.scala | 7 ++++++ .../bankconnectors/LocalMappedConnector.scala | 22 +++++++++++++------ .../code/customer/agent/AgentProvider.scala | 4 ++-- .../customer/agent/MappedAgentProvider.scala | 16 +++++++------- .../MappedTransactionRequestProvider.scala | 19 +++++++++++----- .../api/v4_0_0/TransactionRequestsTest.scala | 2 +- .../commons/model/CommonModel.scala | 6 ++--- 12 files changed, 69 insertions(+), 33 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index af99ce073..4cdf441bd 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5516,7 +5516,10 @@ object SwaggerDefinitionsJSON { List(consumerJsonV510) ) - val agentIdJson = AgentIdJson(agentIdExample.value) + val agentIdJson = AgentCashWithdrawalJson( + bankIdExample.value, + agentNumberExample.value + ) val transactionRequestBodyAgentJsonV400 = TransactionRequestBodyAgentJsonV400( to = agentIdJson, @@ -5540,6 +5543,7 @@ object SwaggerDefinitionsJSON { val agentJsonV510 = AgentJsonV510( agent_id = agentIdExample.value, + bank_id = bankIdExample.value, legal_name = legalNameExample.value, mobile_phone_number = mobilePhoneNumberExample.value, agent_number = agentNumberExample.value, diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 3e727c382..1ef1fe12c 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -2868,6 +2868,12 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, s"$AgentNotFound. Current AGENT_ID($agentId)"), i._2) } } + + def getAgentByAgentNumber(bankId: BankId, agentNumber : String, callContext: Option[CallContext]): OBPReturnType[Agent] = { + Connector.connector.vend.getAgentByAgentNumber(bankId: BankId, agentNumber : String, callContext: Option[CallContext]) map { + i => (unboxFullOrFail(i._1, callContext, s"$AgentNotFound. Current BANK_ID(${bankId.value}) and AGENT_NUMBER($agentNumber)"), i._2) + } + } def updateAgentStatus( agentId: String, diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 5924f92bf..19ee121b2 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -12631,9 +12631,8 @@ object APIMethods400 extends RestHelper with APIMethods400 { transactionRequestBodyAgent <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $AGENT_CASH_WITHDRAWAL json format", 400, callContext) { json.extract[TransactionRequestBodyAgentJsonV400] } - toAgentId = transactionRequestBodyAgent.to.agent_id - (agent, callContext) <- NewStyle.function.getAgentByAgentId(toAgentId, callContext) - (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(toAgentId, callContext) + (agent, callContext) <- NewStyle.function.getAgentByAgentNumber(BankId(transactionRequestBodyAgent.to.bank_id),transactionRequestBodyAgent.to.agent_number, callContext) + (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agent.agentId, callContext) customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { customerAccountLinks.head } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index fec521ac8..00bf4c370 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -391,12 +391,13 @@ case class ConsentInfoJsonV400(consent_id: String, api_version: String) case class ConsentInfosJsonV400(consents: List[ConsentInfoJsonV400]) -case class AgentIdJson( - agent_id: String +case class AgentCashWithdrawalJson( + bank_id: String, + agent_number: String ) case class TransactionRequestBodyAgentJsonV400( - to: AgentIdJson, + to: AgentCashWithdrawalJson, value: AmountOfMoneyJsonV121, description: String, charge_policy: String, diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 61104f986..4da50262a 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -333,6 +333,7 @@ case class PutAgentJsonV510( case class AgentJsonV510( agent_id: String, + bank_id: String, legal_name: String, mobile_phone_number: String, agent_number: String, @@ -948,6 +949,7 @@ object JSONFactory510 extends CustomJsonFormats { def createAgentJson(agent: Agent, bankAccount: BankAccount): AgentJsonV510 = { AgentJsonV510( agent_id = agent.agentId, + bank_id = agent.bankId, legal_name = agent.legalName, mobile_phone_number = agent.mobileNumber, agent_number = agent.number, diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index a0614e1d7..ed98ede63 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -1124,6 +1124,13 @@ trait Connector extends MdcLoggable { callContext: Option[CallContext] ): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(getAgentByAgentId _))), callContext)} + + def getAgentByAgentNumber( + bankId: BankId, + agentNumber: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(getAgentByAgentNumber _))), callContext)} + def getAgents( bankId : String, diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 602086fef..56eb8ed43 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -10,12 +10,12 @@ import code.api.Constant._ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.attributedefinition.{AttributeDefinition, AttributeDefinitionDI} import code.api.cache.Caching -import code.api.util.APIUtil.{OBPReturnType, _} +import code.api.util.APIUtil._ import code.api.util.ErrorMessages._ import code.api.util._ import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140 import code.api.v2_1_0._ -import code.api.v4_0_0.{AgentIdJson, PostSimpleCounterpartyJson400, TransactionRequestBodyAgentJsonV400, TransactionRequestBodySimpleJsonV400} +import code.api.v4_0_0.{AgentCashWithdrawalJson, PostSimpleCounterpartyJson400, TransactionRequestBodyAgentJsonV400, TransactionRequestBodySimpleJsonV400} import code.atmattribute.{AtmAttribute, AtmAttributeX} import code.atms.{Atms, MappedAtm} import code.bankattribute.{BankAttribute, BankAttributeX} @@ -1664,6 +1664,16 @@ object LocalMappedConnector extends Connector with MdcLoggable { ).map((_, callContext)) } + override def getAgentByAgentNumber( + bankId : BankId, + agentNumber : String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Agent]] = { + AgentX.agentProvider.vend.getAgentByAgentNumberFuture( + bankId, agentNumber: String + ).map((_, callContext)) + } + override def getAgents( bankId : String, queryParams: List[OBPQueryParam], @@ -4746,17 +4756,15 @@ object LocalMappedConnector extends Connector with MdcLoggable { bodyToAgent <- NewStyle.function.tryons(s"$TransactionRequestDetailsExtractException It can not extract to $TransactionRequestBodyAgentJsonV400", 400, callContext) { body.to_agent.get } - - toAgentId = bodyToAgent.agent_id - (agent, callContext) <- NewStyle.function.getAgentByAgentId(toAgentId, callContext) - (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(toAgentId, callContext) + (agent, callContext) <- NewStyle.function.getAgentByAgentNumber(BankId(bodyToAgent.bank_id), bodyToAgent.agent_number, callContext) + (customerAccountLinks, callContext) <- NewStyle.function.getCustomerAccountLinksByCustomerId(agent.agentId, callContext) customerAccountLink <- NewStyle.function.tryons(AgentAccountLinkNotFound, 400, callContext) { customerAccountLinks.head } (toAccount, callContext) <- NewStyle.function.getBankAccount(BankId(customerAccountLink.bankId), AccountId(customerAccountLink.accountId), callContext) agentRequestJsonBody = TransactionRequestBodyAgentJsonV400( - to = AgentIdJson(toAgentId), + to = AgentCashWithdrawalJson(bodyToAgent.bank_id, bodyToAgent.agent_number), value = AmountOfMoneyJsonV121(body.value.currency, body.value.amount), description = body.description, charge_policy = transactionRequest.charge_policy, diff --git a/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala b/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala index 8bfb02ae8..b4737d51d 100644 --- a/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala +++ b/obp-api/src/main/scala/code/customer/agent/AgentProvider.scala @@ -31,9 +31,9 @@ trait AgentProvider { def getBankIdByAgentId(agentId: String): Box[String] - def getAgentByAgentNumber(agentNumber: String, bankId: BankId): Box[Agent] + def getAgentByAgentNumber(bankId: BankId, agentNumber: String): Box[Agent] - def getAgentByAgentNumberFuture(agentNumber: String, bankId: BankId): Future[Box[Agent]] + def getAgentByAgentNumberFuture(bankId: BankId, agentNumber: String): Future[Box[Agent]] def checkAgentNumberAvailable(bankId: BankId, agentNumber: String): Boolean diff --git a/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala b/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala index bded8cb9d..c69b8afae 100644 --- a/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala +++ b/obp-api/src/main/scala/code/customer/agent/MappedAgentProvider.scala @@ -56,30 +56,30 @@ object MappedAgentProvider extends AgentProvider with MdcLoggable { available } - override def getAgentByAgentId(customerId: String): Box[Agent] = { + override def getAgentByAgentId(agentId: String): Box[Agent] = { MappedCustomer.find( - By(MappedCustomer.mCustomerId, customerId) + By(MappedCustomer.mCustomerId, agentId) ) } - override def getBankIdByAgentId(customerId: String): Box[String] = { + override def getBankIdByAgentId(agentId: String): Box[String] = { val customer: Box[MappedCustomer] = MappedCustomer.find( - By(MappedCustomer.mCustomerId, customerId) + By(MappedCustomer.mCustomerId, agentId) ) for (c <- customer) yield { c.mBank.get } } - override def getAgentByAgentNumber(customerNumber: String, bankId: BankId): Box[Agent] = { + override def getAgentByAgentNumber(bankId: BankId, agentNumber: String): Box[Agent] = { MappedCustomer.find( - By(MappedCustomer.mNumber, customerNumber), + By(MappedCustomer.mNumber, agentNumber), By(MappedCustomer.mBank, bankId.value) ) } - override def getAgentByAgentNumberFuture(customerNumber: String, bankId: BankId): Future[Box[Agent]] = { - Future(getAgentByAgentNumber(customerNumber, bankId)) + override def getAgentByAgentNumberFuture(bankId: BankId, agentNumber: String): Future[Box[Agent]] = { + Future(getAgentByAgentNumber(bankId: BankId, agentNumber: String)) } diff --git a/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala b/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala index aaf9f0b50..9838c6903 100644 --- a/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala +++ b/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala @@ -368,13 +368,22 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest] None val t_to_agent = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.AGENT_CASH_WITHDRAWAL && details.nonEmpty) { - val agentIdList: List[String] = for { + val agentNumberList: List[String] = for { JObject(child) <- parsedDetails - JField("agent_id", JString(agentId)) <- child + JField("agent_number", JString(agentNumber)) <- child } yield - agentId - val agentIdValue = if (agentIdList.isEmpty) "" else agentIdList.head - Some(TransactionRequestAgentId(agent_id = agentIdValue)) + agentNumber + val bankIdList: List[String] = for { + JObject(child) <- parsedDetails + JField("bank_id", JString(agentNumber)) <- child + } yield + agentNumber + val agentNumberValue = if (agentNumberList.isEmpty) "" else agentNumberList.head + val bankIdValue = if (bankIdList.isEmpty) "" else bankIdList.head + Some(transactionRequestAgentCashWithdrawal( + bank_id = bankIdValue, + agent_number = agentNumberValue + )) } else None diff --git a/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala index 3b365b514..77f8890c5 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/TransactionRequestsTest.scala @@ -137,7 +137,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers { var transactionRequestBodySEPA = TransactionRequestBodySEPAJSON(bodyValue, IbanJson(counterpartySEPA.otherAccountSecondaryRoutingAddress), description, sharedChargePolicy) - var transactionRequestBodyAgentCashWithdrawal = TransactionRequestBodyAgentJsonV400(AgentIdJson(agentCashWithdrawalAgent.agent_id), bodyValue, description, sharedChargePolicy) + var transactionRequestBodyAgentCashWithdrawal = TransactionRequestBodyAgentJsonV400(AgentCashWithdrawalJson(agentCashWithdrawalAgent.bank_id,agentCashWithdrawalAgent.agent_number), bodyValue, description, sharedChargePolicy) var transactionRequestBodyCounterparty = TransactionRequestBodyCounterpartyJSON(CounterpartyIdJson(counterpartyCounterparty.counterpartyId), bodyValue, description, sharedChargePolicy) var transactionRequestBodySimple = TransactionRequestBodySimpleJsonV400(SwaggerDefinitionsJSON.postSimpleCounterpartyJson400.copy( other_account_routing_address = counterpartyCounterparty.otherAccountRoutingAddress, diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala index 4da9c0c0b..23248529c 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala @@ -859,8 +859,8 @@ case class TransactionRequestTransferToAtm( //For COUNTERPARTY, it needs the counterparty_id to find the toCounterparty--> toBankAccount case class TransactionRequestCounterpartyId (counterparty_id : String) -//For AGENT_CASH_WITHDRAWAL, it needs the agent_id to find the toAgent--> toBankAccount -case class TransactionRequestAgentId (agent_id : String) +//For AGENT_CASH_WITHDRAWAL, it needs the agent_number to find the toAgent--> toBankAccount +case class transactionRequestAgentCashWithdrawal (bank_id: String , agent_number : String) case class TransactionRequestSimple ( otherBankRoutingScheme: String, @@ -974,7 +974,7 @@ case class TransactionRequestBodyAllTypes ( @optional to_sepa_credit_transfers: Option[SepaCreditTransfers]= None,//TODO not stable, from berlin Group @optional - to_agent: Option[TransactionRequestAgentId]= None, + to_agent: Option[transactionRequestAgentCashWithdrawal]= None, value: AmountOfMoney, description: String