Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Marko Milić 2024-11-23 11:56:26 +01:00
commit b4f3404482
33 changed files with 1722 additions and 613 deletions

View File

@ -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.

View File

@ -5515,6 +5515,53 @@ object SwaggerDefinitionsJSON {
val consumersJsonV510 = ConsumersJsonV510(
List(consumerJsonV510)
)
val agentIdJson = AgentCashWithdrawalJson(
bankIdExample.value,
agentNumberExample.value
)
val transactionRequestBodyAgentJsonV400 = TransactionRequestBodyAgentJsonV400(
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 putAgentJsonV510 = PutAgentJsonV510(
is_pending_agent = false,
is_confirmed_agent = true
)
val agentJsonV510 = AgentJsonV510(
agent_id = agentIdExample.value,
bank_id = bankIdExample.value,
legal_name = legalNameExample.value,
mobile_phone_number = mobilePhoneNumberExample.value,
agent_number = agentNumberExample.value,
currency = currencyExample.value,
is_confirmed_agent = false,
is_pending_agent = true
)
val minimalAgentJsonV510 = MinimalAgentJsonV510(
agent_id = agentIdExample.value,
legal_name = legalNameExample.value,
agent_number = agentNumberExample.value
)
val minimalAgentsJsonV510 = MinimalAgentsJsonV510(
agents = List(minimalAgentJsonV510)
)
//The common error or success format.
//Just some helper format to use in Json
case class NotSupportedYet()

View File

@ -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)
}
}

View File

@ -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"""

View File

@ -103,6 +103,12 @@ object ApiRole extends MdcLoggable{
case class CanCreateCustomer(requiresBankId: Boolean = true) extends ApiRole
lazy val canCreateCustomer = CanCreateCustomer()
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()

View File

@ -452,6 +452,9 @@ 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 CreateAgentError = "OBP-30202: Could not create Agent."
val UpdateAgentError = "OBP-30203: Could not update Agent."
val CustomerAccountLinkNotFound = "OBP-30204: Customer Account Link not found"
@ -523,6 +526,11 @@ 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."
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."

View File

@ -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)

View File

@ -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}
@ -748,18 +748,25 @@ object NewStyle extends MdcLoggable{
unboxFullOrFail(_, callContext, s"$CustomerNotFoundByCustomerId. Current CustomerId($customerId)", 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)
}
}
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)
@ -2832,6 +2839,56 @@ object NewStyle extends MdcLoggable{
callContext) map {
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 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,
isPendingAgent: Boolean,
isConfirmedAgent: Boolean,
callContext: Option[CallContext]): OBPReturnType[Agent] =
Connector.connector.vend.updateAgentStatus(
agentId: String,
isPendingAgent: Boolean,
isConfirmedAgent: Boolean,
callContext: Option[CallContext]
) map {
i => (unboxFullOrFail(i._1, callContext, UpdateAgentError), i._2)
}
def updateCustomerCreditData(customerId: String,
creditRating: Option[String],
creditSource: Option[String],
@ -4083,11 +4140,21 @@ object NewStyle extends MdcLoggable{
i => (unboxFullOrFail(i._1, callContext, CreateCustomerAccountLinkError), i._2)
}
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)
}
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)
}
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)

File diff suppressed because it is too large Load Diff

View File

@ -391,6 +391,19 @@ case class ConsentInfoJsonV400(consent_id: String,
api_version: String)
case class ConsentInfosJsonV400(consents: List[ConsentInfoJsonV400])
case class AgentCashWithdrawalJson(
bank_id: String,
agent_number: String
)
case class TransactionRequestBodyAgentJsonV400(
to: AgentCashWithdrawalJson,
value: AmountOfMoneyJsonV121,
description: String,
charge_policy: String,
future_date: Option[String] = None
) extends TransactionRequestCommonBodyJSON
case class TransactionRequestBodySEPAJsonV400(
value: AmountOfMoneyJsonV121,
to: IbanJson,

View File

@ -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) {

View File

@ -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
@ -18,15 +19,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 +344,153 @@ trait APIMethods510 {
}
}
}
staticResourceDocs += ResourceDoc(
createAgent,
implementedInApiVersion,
nameOf(createAgent),
"POST",
"/banks/BANK_ID/agents",
"Create Agent",
s"""
|${authenticationRequiredMessage(true)}
|""",
postAgentJsonV510,
agentJsonV510,
List(
$UserNotLoggedIn,
$BankNotFound,
InvalidJsonFormat,
AgentNumberAlreadyExists,
CreateAgentError,
UnknownError
),
List(apiTagCustomer, apiTagPerson)
)
lazy val createAgent : OBPEndpoint = {
case "banks" :: BankId(bankId) :: "agents" :: Nil JsonPost json -> _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
putData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $PostAgentJsonV510 ", 400, cc.callContext) {
json.extract[PostAgentJsonV510]
}
(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 = putData.legal_name,
mobileNumber = putData.mobile_phone_number,
number = putData.agent_number,
callContext,
)
(bankAccount, callContext) <- NewStyle.function.createBankAccount(
bankId,
AccountId(APIUtil.generateUUID()),
"AGENT",
"AGENT",
putData.currency,
0,
putData.legal_name,
null,
Nil,
callContext
)
(_, callContext) <- NewStyle.function.createAgentAccountLink(agent.agentId, bankAccount.bankId.value, bankAccount.accountId.value, callContext)
} yield {
(JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`201`(callContext))
}
}
}
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,
AgentNotFound,
AgentAccountLinkNotFound,
UnknownError
),
List(apiTagCustomer, apiTagPerson),
Some(canUpdateAgentStatusAtAnyBank :: canUpdateAgentStatusAtOneBank :: Nil)
)
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) {
json.extract[PutAgentJsonV510]
}
(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(agentAccountLink.bankId), AccountId(agentAccountLink.accountId), callContext)
(agent, callContext) <- NewStyle.function.updateAgentStatus(
agentId,
postedData.is_pending_agent,
postedData.is_confirmed_agent,
callContext)
} yield {
(JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`200`(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,
AgentNotFound,
AgentAccountLinkNotFound,
UnknownError
),
List(apiTagAccount)
)
lazy val getAgent: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "agents" :: agentId :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(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(agentAccountLink.bankId), AccountId(agentAccountLink.accountId), callContext)
} yield {
(JSONFactory510.createAgentJson(agent, bankAccount), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
createNonPersonalUserAttribute,
implementedInApiVersion,
@ -802,6 +948,40 @@ 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,
minimalAgentsJsonV510,
List(
$BankNotFound,
AgentsNotFound,
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)
(agents, callContext) <- NewStyle.function.getAgents(bankId.value, requestParams, callContext)
} yield {
(JSONFactory510.createMinimalAgentsJson(agents), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getAtmAttributes,

View File

@ -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, AtmId, AtmT, BankId, BankIdAccountId, BranchRoutingJsonV141, CreateViewJson, Customer, Location, Meta, RegulatedEntityTrait, 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
@ -317,6 +314,43 @@ case class UserAttributesResponseJsonV510(
)
case class CustomerIdJson(id: String)
case class AgentJson(
id: String,
name:String
)
case class PostAgentJsonV510(
legal_name: String,
mobile_phone_number: String,
agent_number: String,
currency: String
)
case class PutAgentJsonV510(
is_pending_agent: Boolean,
is_confirmed_agent: Boolean
)
case class AgentJsonV510(
agent_id: String,
bank_id: String,
legal_name: String,
mobile_phone_number: String,
agent_number: String,
currency: String,
is_confirmed_agent: Boolean,
is_pending_agent: Boolean
)
case class MinimalAgentJsonV510(
agent_id: String,
legal_name: String,
agent_number: String,
)
case class MinimalAgentsJsonV510(
agents: List[MinimalAgentJsonV510]
)
case class CustomersIdsJsonV510(customers: List[CustomerIdJson])
case class PostCustomerLegalNameJsonV510(legal_name: String)
@ -912,6 +946,28 @@ object JSONFactory510 extends CustomJsonFormats {
ConsumersJsonV510(consumers.map(createConsumerJSON(_,None)))
}
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,
currency = bankAccount.currency,
is_confirmed_agent = agent.isConfirmedAgent,
is_pending_agent = agent.isPendingAgent
)
}
def createMinimalAgentsJson(agents: List[Agent]): MinimalAgentsJsonV510 = {
MinimalAgentsJsonV510(
agents
.filter(_.isConfirmedAgent == true)
.map(agent => MinimalAgentJsonV510(
agent_id = agent.agentId,
legal_name = agent.legalName,
agent_number = agent.number
)))
}
}

View File

@ -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,
@ -1112,6 +1118,41 @@ trait Connector extends MdcLoggable {
customerNumber: Option[String],
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 getAgentByAgentNumber(
bankId: BankId,
agentNumber: String,
callContext: Option[CallContext]
): OBPReturnType[Box[Agent]] = Future{(Failure(setUnimplementedError(nameOf(getAgentByAgentNumber _))), 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,
isPendingAgent: Boolean,
isConfirmedAgent: Boolean,
callContext: Option[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],
@ -1765,6 +1806,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)}
@ -1773,6 +1816,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)}

View File

@ -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.{PostSimpleCounterpartyJson400, 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}
@ -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,67 @@ 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 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],
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]]] = {
@ -3023,6 +3084,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(
@ -4679,6 +4751,40 @@ 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
}
(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 = AgentCashWithdrawalJson(bodyToAgent.bank_id, bodyToAgent.agent_number),
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 +4950,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 {
@ -4972,6 +5078,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)
}
@ -4991,6 +5102,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(

View File

@ -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)

View File

@ -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()

View File

@ -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 = ""
)
}

View File

@ -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
@ -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.
@ -331,7 +333,8 @@ object MappedCustomerProvider extends CustomerProvider with MdcLoggable {
}
class MappedCustomer extends Customer with LongKeyedMapper[MappedCustomer] with IdPK with CreatedUpdated {
//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
@ -361,7 +364,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 +403,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: 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] {

View File

@ -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(bankId: BankId, agentNumber: String): Box[Agent]
def getAgentByAgentNumberFuture(bankId: BankId, agentNumber: String): 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]]
}

View File

@ -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(agentId: String): Box[Agent] = {
MappedCustomer.find(
By(MappedCustomer.mCustomerId, agentId)
)
}
override def getBankIdByAgentId(agentId: String): Box[String] = {
val customer: Box[MappedCustomer] = MappedCustomer.find(
By(MappedCustomer.mCustomerId, agentId)
)
for (c <- customer) yield {
c.mBank.get
}
}
override def getAgentByAgentNumber(bankId: BankId, agentNumber: String): Box[Agent] = {
MappedCustomer.find(
By(MappedCustomer.mNumber, agentNumber),
By(MappedCustomer.mBank, bankId.value)
)
}
override def getAgentByAgentNumberFuture(bankId: BankId, agentNumber: String): Future[Box[Agent]] = {
Future(getAgentByAgentNumber(bankId: BankId, agentNumber: String))
}
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)
}
}

View File

@ -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] {

View File

@ -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,29 @@ 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 agentNumberList: List[String] = for {
JObject(child) <- parsedDetails
JField("agent_number", JString(agentNumber)) <- child
} yield
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
//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 +404,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
)

View File

@ -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(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,
@ -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,24 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
}
}
feature(s"we can create transaction requests -- $AGENT_CASH_WITHDRAWAL") {
feature("we can create transaction requests -- SIMPLE") {
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) {
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(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 +1285,26 @@ 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) {
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(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 +1327,15 @@ 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) {
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(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 +1343,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 +1377,15 @@ 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) {
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(SIMPLE.toString)
val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString)
And("We set the special conditions for different currencies")
val fromCurrency = "AED"
@ -1368,10 +1395,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 +1431,15 @@ 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) {
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(SIMPLE.toString)
val helper = defaultSetup(AGENT_CASH_WITHDRAWAL.toString)
And("We set the special conditions for different currencies")
val fromCurrency = "AED"
@ -1418,7 +1449,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 +1469,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)

View File

@ -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

View File

@ -0,0 +1,172 @@
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[MinimalAgentsJsonV510].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[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)
}
}
}
}

View File

@ -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")

View File

@ -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,
@ -594,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,
@ -840,6 +859,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_number to find the toAgent--> toBankAccount
case class transactionRequestAgentCashWithdrawal (bank_id: String , agent_number : String)
case class TransactionRequestSimple (
otherBankRoutingScheme: String,
otherBankRoutingAddress: String,
@ -951,6 +973,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[transactionRequestAgentCashWithdrawal]= None,
value: AmountOfMoney,
description: String
@ -1161,29 +1185,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,

View File

@ -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

View File

@ -55,6 +55,16 @@ trait Customer {
def nameSuffix: String
}
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 {
def url : String
def date : Date

View File

@ -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