Merge branch 'OpenBankProject:develop' into develop

This commit is contained in:
Marko Milić 2022-09-29 12:29:46 +02:00
commit ee9765509a
28 changed files with 1179 additions and 91 deletions

View File

@ -16,7 +16,7 @@ import code.api.v3_0_0.JSONFactory300.createBranchJsonV300
import code.api.v3_0_0.custom.JSONFactoryCustom300
import code.api.v3_0_0.{LobbyJsonV330, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, CustomerWithAttributesJsonV310, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.api.v4_0_0.{AccountMinimalJson400, BankAttributeBankResponseJsonV400, CustomerMinimalJsonV400, FastFirehoseAccountsJsonV400, PostHistoricalTransactionAtBankJson, _}
import code.api.v4_0_0.{AccountMinimalJson400, BankAttributeBankResponseJsonV400, CardJsonV400, CustomerMinimalJsonV400, FastFirehoseAccountsJsonV400, PostHistoricalTransactionAtBankJson, _}
import code.api.v3_1_0.{AccountBalanceV310, AccountsBalancesV310Json, BadLoginStatusJson, ContactDetailsJson, InviteeJson, ObpApiLoopbackJson, PhysicalCardWithAttributesJsonV310, PutUpdateCustomerEmailJsonV310, _}
import code.api.v5_0_0._
import code.branches.Branches.{Branch, DriveUpString, LobbyString}
@ -4196,6 +4196,22 @@ object SwaggerDefinitionsJSON {
refund = RefundJson(transactionIdExample.value, transactionRequestRefundReasonCodeExample.value)
)
val cardJsonV400 = CardJsonV400(
card_type = cardTypeExample.value,
brand = brandExample.value,
cvv = cvvExample.value,
card_number = cardNumberExample.value,
name_on_card = nameOnCardExample.value,
expiry_year = expiryYearExample.value,
expiry_month = expiryMonthExample.value,
)
val transactionRequestBodyCardJsonV400 = TransactionRequestBodyCardJsonV400(
card = cardJsonV400,
to = counterpartyIdJson,
value = amountOfMoneyJsonV121,
description = "A card payment description. "
)
val customerAttributesResponseJson = CustomerAttributesResponseJson (
customer_attributes = List(customerAttributeResponseJson)
)
@ -4743,7 +4759,79 @@ object SwaggerDefinitionsJSON {
valid_from = Some(new Date()),
time_to_live = Some(3600)
)
val createPhysicalCardJsonV500 = CreatePhysicalCardJsonV500(
card_number = bankCardNumberExample.value,
card_type = cardTypeExample.value,
name_on_card = nameOnCardExample.value,
issue_number = issueNumberExample.value,
serial_number = serialNumberExample.value,
valid_from_date = DateWithDayExampleObject,
expires_date = DateWithDayExampleObject,
enabled = true,
technology = technologyExample.value,
networks = networksExample.value.split("[,;]").toList,
allows = List(CardAction.CREDIT.toString.toLowerCase, CardAction.DEBIT.toString.toLowerCase),
account_id =accountIdExample.value,
replacement = Some(replacementJSON),
pin_reset = List(pinResetJSON, pinResetJSON1),
collected = Some(DateWithDayExampleObject),
posted = Some(DateWithDayExampleObject),
customer_id = customerIdExample.value,
brand = brandExample.value
)
val physicalCardJsonV500 = PhysicalCardJsonV500(
card_id = cardIdExample.value,
bank_id = bankIdExample.value,
card_number = bankCardNumberExample.value,
card_type = cardTypeExample.value,
name_on_card = nameOnCardExample.value,
issue_number = issueNumberExample.value,
serial_number = serialNumberExample.value,
valid_from_date = DateWithDayExampleObject,
expires_date = DateWithDayExampleObject,
enabled = true,
cancelled = true,
on_hot_list = true,
technology = technologyExample.value,
networks = networksExample.value.split("[,;]").toList,
allows = List(CardAction.CREDIT.toString.toLowerCase, CardAction.DEBIT.toString.toLowerCase),
account = accountJSON,
replacement = replacementJSON,
pin_reset = List(pinResetJSON),
collected = DateWithDayExampleObject,
posted = DateWithDayExampleObject,
customer_id = customerIdExample.value,
cvv = cvvExample.value,
brand = brandExample.value
)
val physicalCardWithAttributesJsonV500 = PhysicalCardWithAttributesJsonV500(
card_id = cardIdExample.value,
bank_id = bankIdExample.value,
card_number = bankCardNumberExample.value,
card_type = cardTypeExample.value,
name_on_card = nameOnCardExample.value,
issue_number = issueNumberExample.value,
serial_number = serialNumberExample.value,
valid_from_date = DateWithDayExampleObject,
expires_date = DateWithDayExampleObject,
enabled = true,
cancelled = true,
on_hot_list = true,
technology = technologyExample.value,
networks = networksExample.value.split("[,;]").toList,
allows = List(CardAction.CREDIT.toString.toLowerCase, CardAction.DEBIT.toString.toLowerCase),
account = accountBasicV310,
replacement = replacementJSON,
pin_reset = List(pinResetJSON),
collected = DateWithDayExampleObject,
posted = DateWithDayExampleObject,
customer_id = customerIdExample.value,
card_attributes = List(cardAttributeCommons),
brand = brandExample.value
)
//The common error or success format.
//Just some helper format to use in Json
case class NotSupportedYet()

View File

@ -118,6 +118,8 @@ import scala.xml.{Elem, XML}
object APIUtil extends MdcLoggable with CustomJsonFormats{
val DateWithYear = "yyyy"
val DateWithMonth = "yyyy-MM"
val DateWithDay = "yyyy-MM-dd"
val DateWithDay2 = "yyyyMMdd"
val DateWithDay3 = "dd/MM/yyyy"
@ -126,11 +128,15 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
val DateWithMs = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
val DateWithMsRollback = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" //?? what does this `Rollback` mean ??
val DateWithYearFormat = new SimpleDateFormat(DateWithYear)
val DateWithMonthFormat = new SimpleDateFormat(DateWithMonth)
val DateWithDayFormat = new SimpleDateFormat(DateWithDay)
val DateWithSecondsFormat = new SimpleDateFormat(DateWithSeconds)
val DateWithMsFormat = new SimpleDateFormat(DateWithMs)
val DateWithMsRollbackFormat = new SimpleDateFormat(DateWithMsRollback)
val DateWithYearExampleString: String = "1100"
val DateWithMonthExampleString: String = "1100-01"
val DateWithDayExampleString: String = "1100-01-01"
val DateWithSecondsExampleString: String = "1100-01-01T01:01:01Z"
val DateWithMsExampleString: String = "1100-01-01T01:01:01.000Z"

View File

@ -398,6 +398,8 @@ object ErrorMessages {
val ProductFeeNotFoundById = "OBP-30117: Product Fee not found. Please specify a valid value for PRODUCT_FEE_ID."
val CreateProductFeeError = "OBP-30118: Could not insert the Product Fee."
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 EntitlementIsBankRole = "OBP-30205: This entitlement is a Bank Role. Please set bank_id to a valid bank id."

View File

@ -372,7 +372,19 @@ object ExampleValue {
glossaryItems += makeGlossaryItem("Adapter.card_id", cardIdExample)
lazy val nameOnCardExample = ConnectorField(owner1Example.value, s"The name on the physical card")
glossaryItems += makeGlossaryItem("Adapter.name_on_card", nameOnCardExample)
glossaryItems += makeGlossaryItem("Adapter.name_on_card", nameOnCardExample)
lazy val cvvExample = ConnectorField("123", s"Card Verification Value")
glossaryItems += makeGlossaryItem("Adapter.cvv", nameOnCardExample)
lazy val brandExample = ConnectorField("Visa", s"The brand of the card, eg: Visa, Mastercard")
glossaryItems += makeGlossaryItem("Adapter.brand", nameOnCardExample)
lazy val expiryYearExample = ConnectorField("2023", s"The expiry year of the card")
glossaryItems += makeGlossaryItem("Adapter.expiry_year", expiryYearExample)
lazy val expiryMonthExample = ConnectorField("01", s"The expiry month of the card")
glossaryItems += makeGlossaryItem("Adapter.expiry_month", expiryMonthExample)
lazy val issueNumberExample = ConnectorField("1", s"The issue number of the physical card, eg 1,2,3,4 ....")
glossaryItems += makeGlossaryItem("Adapter.issue_number", issueNumberExample)

View File

@ -2591,6 +2591,8 @@ object NewStyle extends MdcLoggable{
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]
): OBPReturnType[PhysicalCard] = {
validateBankId(bankId, callContext)
@ -2616,6 +2618,8 @@ object NewStyle extends MdcLoggable{
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]
) map {
i => (unboxFullOrFail(i._1, callContext, s"$CreateCardError"), i._2)
@ -2681,6 +2685,12 @@ object NewStyle extends MdcLoggable{
i => (unboxFullOrFail(i._1, callContext, CardNotFound), i._2)
}
def getPhysicalCardByCardNumber(bankCardNumber: String, callContext:Option[CallContext]) : OBPReturnType[PhysicalCardTrait] = {
Connector.connector.vend.getPhysicalCardByCardNumber(bankCardNumber: String, callContext:Option[CallContext]) map {
i => (unboxFullOrFail(i._1, callContext, InvalidCardNumber), i._2)
}
}
def getPhysicalCardForBank(bankId: BankId, cardId:String ,callContext:Option[CallContext]) : OBPReturnType[PhysicalCardTrait] =
Connector.connector.vend.getPhysicalCardForBank(bankId: BankId, cardId: String, callContext:Option[CallContext]) map {
i => (unboxFullOrFail(i._1, callContext, s"$CardNotFound Current CardId($cardId)"), i._2)

View File

@ -1001,6 +1001,8 @@ trait APIMethods210 {
collected= Option(CardCollectionInfo(postJson.collected)),
posted= Option(CardPostedInfo(postJson.posted)),
customerId = "",// this field is introduced from V310
cvv = "",// this field is introduced from V500
brand = "",// this field is introduced from V500
callContext
)

View File

@ -4814,6 +4814,8 @@ trait APIMethods310 {
collected = collected,
posted = posted,
customerId = postJson.customer_id,
cvv = "",//added from v500
brand = "",//added from v500
callContext
)
} yield {
@ -4841,7 +4843,7 @@ trait APIMethods310 {
UnknownError
),
List(apiTagCard, apiTagNewStyle),
Some(List(canCreateCardsForBank)))
Some(List(canUpdateCardsForBank)))
lazy val updatedCardForBank: OBPEndpoint = {
case "management" :: "banks" :: BankId(bankId) :: "cards" :: cardId :: Nil JsonPut json -> _ => {
cc =>
@ -4957,7 +4959,8 @@ trait APIMethods310 {
emptyObjectJson,
physicalCardWithAttributesJsonV310,
List(UserNotLoggedIn,BankNotFound, UnknownError),
List(apiTagCard, apiTagNewStyle))
List(apiTagCard, apiTagNewStyle),
Some(List(canGetCardsForBank)))
lazy val getCardForBank : OBPEndpoint = {
case "management" :: "banks" :: BankId(bankId) :: "cards" :: cardId :: Nil JsonGet _ => {
cc => {

View File

@ -914,26 +914,77 @@ trait APIMethods400 {
def createTransactionRequest(bankId: BankId, accountId: AccountId, viewId: ViewId, transactionRequestType: TransactionRequestType, json: JValue): Future[(TransactionRequestWithChargeJSON400, Option[CallContext])] = {
for {
(Full(u), fromAccount, callContext) <- SS.userAccount
_ <- NewStyle.function.isEnabledTransactionRequests(callContext)
_ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext) {
isValidID(accountId.value)
}
_ <- Helper.booleanToFuture(InvalidBankIdFormat, cc=callContext) {
isValidID(bankId.value)
}
(Full(u), callContext) <- SS.user
account = BankIdAccountId(bankId, accountId)
_ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, callContext)
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)
}
transactionRequestTypeValue <- NewStyle.function.tryons(s"$InvalidTransactionRequestType: '${transactionRequestType.value}'. OBP does not support it.", 400, callContext) {
TransactionRequestTypes.withName(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]
@ -1152,6 +1203,38 @@ trait APIMethods400 {
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,
None,
callContext)
} yield (createdTransactionRequest, callContext)
}
case SIMPLE => {
@ -1330,6 +1413,53 @@ trait APIMethods400 {
}
staticResourceDocs += ResourceDoc(
createTransactionRequestCard,
implementedInApiVersion,
nameOf(createTransactionRequestCard),
"POST",
"/transaction-request-types/CARD/transaction-requests",
"Create Transaction Request (CARD)",
s"""
|
|When using CARD, the payee is set in the request body .
|
|Money goes into the Counterparty in the request body.
|
|$transactionRequestGeneralText
|
""".stripMargin,
transactionRequestBodyCardJsonV400,
transactionRequestWithChargeJSON400,
List(
$UserNotLoggedIn,
InvalidBankIdFormat,
InvalidAccountIdFormat,
InvalidJsonFormat,
$BankNotFound,
AccountNotFound,
$BankAccountNotFound,
InsufficientAuthorisationToCreateTransactionRequest,
InvalidTransactionRequestType,
InvalidJsonFormat,
InvalidNumber,
NotPositiveAmount,
InvalidTransactionRequestCurrency,
TransactionDisabled,
UnknownError
),
List(apiTagTransactionRequest, apiTagPSD2PIS, apiTagPsd2, apiTagNewStyle)
)
lazy val createTransactionRequestCard: OBPEndpoint = {
case "transaction-request-types" :: "CARD" :: "transaction-requests" :: Nil JsonPost json -> _ =>
cc =>
val transactionRequestType = TransactionRequestType("CARD")
createTransactionRequest(BankId(""), AccountId(""), ViewId("owner"), transactionRequestType, json)
}
staticResourceDocs += ResourceDoc(
answerTransactionRequestChallenge,
implementedInApiVersion,

View File

@ -37,7 +37,7 @@ import code.api.v1_2_1.{BankRoutingJsonV121, JSONFactory, UserJSONV121, ViewJSON
import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, TransactionRequestAccountJsonV140, transformToLocationFromV140, transformToMetaFromV140}
import code.api.v2_0_0.JSONFactory200.UserJsonV200
import code.api.v2_0_0.{CreateEntitlementJSON, EntitlementJSONs, JSONFactory200, TransactionRequestChargeJsonV200}
import code.api.v2_1_0.{IbanJson, JSONFactory210, PostCounterpartyBespokeJson, ResourceUserJSON, TransactionRequestBodyCounterpartyJSON}
import code.api.v2_1_0.{CounterpartyIdJson, IbanJson, JSONFactory210, PostCounterpartyBespokeJson, ResourceUserJSON, TransactionRequestBodyCounterpartyJSON}
import code.api.v2_2_0.CounterpartyMetadataJson
import code.api.v3_0_0.JSONFactory300._
import code.api.v3_0_0._
@ -54,7 +54,6 @@ import code.model.dataAccess.ResourceUser
import code.model.{Consumer, ModeratedBankAccount, ModeratedBankAccountCore}
import code.ratelimiting.RateLimiting
import code.standingorders.StandingOrderTrait
import code.userlocks.UserLocks
import code.users.{UserAgreement, UserAttribute, UserInvitation}
import code.views.system.AccountAccess
@ -411,6 +410,23 @@ case class TransactionRequestBodySimpleJsonV400(
future_date: Option[String] = None
) extends TransactionRequestCommonBodyJSON
case class CardJsonV400(
card_type: String,
brand: String,
cvv: String,
card_number: String,
name_on_card: String,
expiry_year: String,
expiry_month: String,
)
case class TransactionRequestBodyCardJsonV400(
card: CardJsonV400,
to: CounterpartyIdJson,
value: AmountOfMoneyJsonV121,
description: String,
) extends TransactionRequestCommonBodyJSON
case class TransactionRequestReasonJsonV400(
code: String,
document_number: Option[String],

View File

@ -13,6 +13,7 @@ import code.api.v3_0_0.JSONFactory300
import code.api.v3_1_0._
import code.api.v4_0_0.JSONFactory400.createCustomersMinimalJson
import code.api.v4_0_0.{JSONFactory400, PutProductJsonV400}
import code.api.v5_0_0.JSONFactory500.createPhysicalCardJson
import code.bankconnectors.Connector
import code.consent.{ConsentRequests, Consents}
import code.entitlement.Entitlement
@ -23,7 +24,7 @@ import code.views.Views
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication
import com.openbankproject.commons.model.{AccountId, AccountRouting, BankId, CreditLimit, CreditRating, CustomerFaceImage, CustomerId, ProductCode, UserAuthContextUpdateStatus}
import com.openbankproject.commons.model.{AccountId, AccountRouting, BankId, BankIdAccountId, CardAction, CardAttributeCommons, CardCollectionInfo, CardPostedInfo, CardReplacementInfo, CardReplacementReason, CreditLimit, CreditRating, CustomerFaceImage, CustomerId, PinResetInfo, PinResetReason, ProductCode, TransactionRequestType, UserAuthContextUpdateStatus, View, ViewId}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.{Empty, Full}
import net.liftweb.http.Req
@ -32,6 +33,7 @@ import net.liftweb.json
import net.liftweb.json.{Extraction, compactRender, prettyRender}
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.Props
import java.util.concurrent.ThreadLocalRandom
import scala.collection.immutable.{List, Nil}
import scala.collection.mutable.ArrayBuffer
@ -1290,6 +1292,104 @@ trait APIMethods500 {
staticResourceDocs += ResourceDoc(
addCardForBank,
implementedInApiVersion,
nameOf(addCardForBank),
"POST",
"/management/banks/BANK_ID/cards",
"Create Card",
s"""Create Card at bank specified by BANK_ID .
|
|${authenticationRequiredMessage(true)}
|""",
createPhysicalCardJsonV500,
physicalCardJsonV500,
List(
$UserNotLoggedIn,
$BankNotFound,
UserHasMissingRoles,
AllowedValuesAre,
UnknownError
),
List(apiTagCard, apiTagNewStyle),
Some(List(canCreateCardsForBank)))
lazy val addCardForBank: OBPEndpoint = {
case "management" :: "banks" :: BankId(bankId) :: "cards" :: Nil JsonPost json -> _ => {
cc =>
for {
(Full(u), _,callContext) <- SS.userBank
failMsg = s"$InvalidJsonFormat The Json body should be the $CreatePhysicalCardJsonV500 "
postJson <- NewStyle.function.tryons(failMsg, 400, callContext) {json.extract[CreatePhysicalCardJsonV500]}
_ <- postJson.allows match {
case List() => Future {true}
case _ => Helper.booleanToFuture(AllowedValuesAre + CardAction.availableValues.mkString(", "), cc=callContext)(postJson.allows.forall(a => CardAction.availableValues.contains(a)))
}
failMsg = AllowedValuesAre + CardReplacementReason.availableValues.mkString(", ")
cardReplacementReason <- NewStyle.function.tryons(failMsg, 400, callContext) {
postJson.replacement match {
case Some(value) => CardReplacementReason.valueOf(value.reason_requested)
case None => CardReplacementReason.valueOf(CardReplacementReason.FIRST.toString)
}
}
_<-Helper.booleanToFuture(s"${maximumLimitExceeded.replace("10000", "10")} Current issue_number is ${postJson.issue_number}", cc=callContext)(postJson.issue_number.length<= 10)
(_, callContext)<- NewStyle.function.getBankAccount(bankId, AccountId(postJson.account_id), callContext)
(_, callContext)<- NewStyle.function.getCustomerByCustomerId(postJson.customer_id, callContext)
replacement = postJson.replacement match {
case Some(replacement) =>
Some(CardReplacementInfo(requestedDate = replacement.requested_date, cardReplacementReason))
case None => None
}
collected = postJson.collected match {
case Some(collected) => Some(CardCollectionInfo(collected))
case None => None
}
posted = postJson.posted match {
case Some(posted) => Option(CardPostedInfo(posted))
case None => None
}
cvv = ThreadLocalRandom.current().nextLong(100, 999)
(card, callContext) <- NewStyle.function.createPhysicalCard(
bankCardNumber=postJson.card_number,
nameOnCard=postJson.name_on_card,
cardType = postJson.card_type,
issueNumber=postJson.issue_number,
serialNumber=postJson.serial_number,
validFrom=postJson.valid_from_date,
expires=postJson.expires_date,
enabled=postJson.enabled,
cancelled=false,
onHotList=false,
technology=postJson.technology,
networks= postJson.networks,
allows= postJson.allows,
accountId= postJson.account_id,
bankId=bankId.value,
replacement = replacement,
pinResets= postJson.pin_reset.map(e => PinResetInfo(e.requested_date, PinResetReason.valueOf(e.reason_requested.toUpperCase))),
collected = collected,
posted = posted,
customerId = postJson.customer_id,
cvv = cvv.toString,
brand = postJson.brand,
callContext
)
} yield {
//NOTE: OBP do not store the 3 digits cvv, only the hash, so we copy it here.
(createPhysicalCardJson(card, u).copy(cvv=cvv.toString), HttpCode.`201`(callContext))
}
}
}
}
}

View File

@ -27,15 +27,17 @@
package code.api.v5_0_0
import java.util.Date
import code.api.util.APIUtil.stringOrNull
import code.api.util.APIUtil.{stringOptionOrNull, stringOrNull}
import code.api.v1_2_1.BankRoutingJsonV121
import code.api.v1_4_0.JSONFactory1_4_0.{CustomerFaceImageJson, MetaJsonV140}
import code.api.v1_3_0.JSONFactory1_3_0.{cardActionsToString, createAccountJson, createPinResetJson, createReplacementJson}
import code.api.v1_3_0.{PinResetJSON, ReplacementJSON}
import code.api.v1_4_0.JSONFactory1_4_0.CustomerFaceImageJson
import code.api.v2_1_0.CustomerCreditRatingJSON
import code.api.v3_1_0.PostConsentEntitlementJsonV310
import code.api.v3_1_0.{AccountBasicV310, PhysicalCardWithAttributesJsonV310, PostConsentEntitlementJsonV310}
import code.api.v4_0_0.BankAttributeBankResponseJsonV400
import code.bankattribute.BankAttribute
import com.openbankproject.commons.model.{AccountRoutingJsonV121, AmountOfMoneyJsonV121, Bank, UserAuthContext, UserAuthContextUpdate}
import com.openbankproject.commons.model.{AccountRoutingJsonV121, AmountOfMoneyJsonV121, Bank, CardAttribute, PhysicalCardTrait, User, UserAuthContext, UserAuthContextUpdate, View, ViewBasic}
import net.liftweb.json.JsonAST.JValue
import scala.collection.immutable.List
@ -149,6 +151,125 @@ case class PostConsentRequestJsonV500(
)
case class ConsentJsonV500(consent_id: String, jwt: String, status: String, consent_request_id: Option[String])
case class CreatePhysicalCardJsonV500(
card_number: String,
card_type: String,
name_on_card: String,
issue_number: String,
serial_number: String,
valid_from_date: Date,
expires_date: Date,
enabled: Boolean,
technology: String,
networks: List[String],
allows: List[String],
account_id: String,
replacement: Option[ReplacementJSON],
pin_reset: List[PinResetJSON],
collected: Option[Date],
posted: Option[Date],
customer_id: String,
brand: String
)
case class PhysicalCardJsonV500(
card_id: String,
bank_id: String,
card_number: String,
card_type: String,
name_on_card: String,
issue_number: String,
serial_number: String,
valid_from_date: Date,
expires_date: Date,
enabled: Boolean,
cancelled: Boolean,
on_hot_list: Boolean,
technology: String,
networks: List[String],
allows: List[String],
account: code.api.v1_2_1.AccountJSON,
replacement: ReplacementJSON,
pin_reset: List[PinResetJSON],
collected: Date,
posted: Date,
customer_id: String,
cvv: String,
brand: String
)
case class UpdatedPhysicalCardJsonV500(
card_id: String,
bank_id: String,
card_number: String,
card_type: String,
name_on_card: String,
issue_number: String,
serial_number: String,
valid_from_date: Date,
expires_date: Date,
enabled: Boolean,
cancelled: Boolean,
on_hot_list: Boolean,
technology: String,
networks: List[String],
allows: List[String],
account: code.api.v1_2_1.AccountJSON,
replacement: ReplacementJSON,
pin_reset: List[PinResetJSON],
collected: Date,
posted: Date,
customer_id: String,
brand: String
)
case class PhysicalCardWithAttributesJsonV500(
card_id: String,
bank_id: String,
card_number: String,
card_type: String,
name_on_card: String,
issue_number: String,
serial_number: String,
valid_from_date: Date,
expires_date: Date,
enabled: Boolean,
cancelled: Boolean,
on_hot_list: Boolean,
technology: String,
networks: List[String],
allows: List[String],
account: AccountBasicV310,
replacement: ReplacementJSON,
pin_reset: List[PinResetJSON],
collected: Date,
posted: Date,
customer_id: String,
card_attributes: List[CardAttribute],
brand: String
)
case class UpdatePhysicalCardJsonV500(
card_type: String,
name_on_card: String,
issue_number: String,
serial_number: String,
valid_from_date: Date,
expires_date: Date,
enabled: Boolean,
technology: String,
networks: List[String],
allows: List[String],
account_id: String,
replacement: ReplacementJSON,
pin_reset: List[PinResetJSON],
collected: Date,
posted: Date,
customer_id: String,
brand: String
)
object JSONFactory500 {
def createUserAuthContextJson(userAuthContext: UserAuthContext): UserAuthContextJsonV500 = {
@ -200,6 +321,64 @@ object JSONFactory500 {
)
)
}
def createPhysicalCardWithAttributesJson(card: PhysicalCardTrait, cardAttributes: List[CardAttribute],user : User, views: List[View]): PhysicalCardWithAttributesJsonV500 = {
PhysicalCardWithAttributesJsonV500(
card_id = stringOrNull(card.cardId),
bank_id = stringOrNull(card.bankId),
card_number = stringOrNull(card.bankCardNumber),
card_type = stringOrNull(card.cardType),
name_on_card = stringOrNull(card.nameOnCard),
issue_number = stringOrNull(card.issueNumber),
serial_number = stringOrNull(card.serialNumber),
valid_from_date = card.validFrom,
expires_date = card.expires,
enabled = card.enabled,
cancelled = card.cancelled,
on_hot_list = card.onHotList,
technology = stringOrNull(card.technology),
networks = card.networks,
allows = card.allows.map(cardActionsToString).toList,
account = AccountBasicV310(
card.account.accountId.value,
card.account.label,
views.map(view => ViewBasic(view.viewId.value, view.name, view.description)),
card.account.bankId.value),
replacement = card.replacement.map(createReplacementJson).getOrElse(null),
pin_reset = card.pinResets.map(createPinResetJson),
collected = card.collected.map(_.date).getOrElse(null),
posted = card.posted.map(_.date).getOrElse(null),
customer_id = stringOrNull(card.customerId),
card_attributes = cardAttributes,
brand = stringOptionOrNull(card.brand),
)
}
def createPhysicalCardJson(card: PhysicalCardTrait, user : User): PhysicalCardJsonV500 = {
PhysicalCardJsonV500(
card_id = stringOrNull(card.cardId),
bank_id = stringOrNull(card.bankId),
card_number = stringOrNull(card.bankCardNumber),
card_type = stringOrNull(card.cardType),
name_on_card = stringOrNull(card.nameOnCard),
issue_number = stringOrNull(card.issueNumber),
serial_number = stringOrNull(card.serialNumber),
valid_from_date = card.validFrom,
expires_date = card.expires,
enabled = card.enabled,
cancelled = card.cancelled,
on_hot_list = card.onHotList,
technology = stringOrNull(card.technology),
networks = card.networks,
allows = card.allows.map(cardActionsToString).toList,
account = createAccountJson(card.account, user),
replacement = card.replacement.map(createReplacementJson).getOrElse(null),
pin_reset = card.pinResets.map(createPinResetJson),
collected = card.collected.map(_.date).getOrElse(null),
posted = card.posted.map(_.date).getOrElse(null),
customer_id = stringOrNull(card.customerId),
cvv = stringOptionOrNull(card.cvv),
brand = stringOptionOrNull(card.brand)
)
}
}

View File

@ -628,6 +628,9 @@ trait Connector extends MdcLoggable {
def getPhysicalCardsForUser(user : User, callContext: Option[CallContext] = None) : OBPReturnType[Box[List[PhysicalCard]]] = Future{(Failure(setUnimplementedError), callContext)}
def getPhysicalCardForBank(bankId: BankId, cardId: String, callContext:Option[CallContext]) : OBPReturnType[Box[PhysicalCardTrait]] = Future{(Failure(setUnimplementedError), callContext)}
def getPhysicalCardByCardNumber(bankCardNumber: String, callContext:Option[CallContext]) : OBPReturnType[Box[PhysicalCardTrait]] = Future{(Failure(setUnimplementedError), callContext)}
def deletePhysicalCardForBank(bankId: BankId, cardId: String, callContext:Option[CallContext]) : OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)}
def getPhysicalCardsForBankLegacy(bank: Bank, user : User, queryParams: List[OBPQueryParam]) : Box[List[PhysicalCard]] = Failure(setUnimplementedError)
@ -654,6 +657,8 @@ trait Connector extends MdcLoggable {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]
): Box[PhysicalCard] = Failure(setUnimplementedError)
@ -678,6 +683,8 @@ trait Connector extends MdcLoggable {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]
): OBPReturnType[Box[PhysicalCard]] = Future{(Failure{setUnimplementedError}, callContext)}
@ -1415,7 +1422,7 @@ trait Connector extends MdcLoggable {
}yield{
(createdTransactionId,callContext)
}
case transactionRequestType => Future((throw new Exception(s"${InvalidTransactionRequestType}: '${transactionRequestType}'. Not supported in this version.")), callContext)
case _ => Future((throw new Exception(s"${InvalidTransactionRequestType}: '${transactionRequestType}'. Not completed in this version.")), callContext)
}
didSaveTransId <- Future{saveTransactionRequestTransaction(transactionRequestId, transactionId).openOrThrowException(attemptedToOpenAnEmptyBox)}

View File

@ -1204,7 +1204,9 @@ object LocalMappedConnector extends Connector with MdcLoggable {
pinResets = l.pinResets,
collected = l.collected,
posted = l.posted,
customerId = l.customerId
customerId = l.customerId,
cvv = l.cvv,
brand = l.brand
)
(Full(cardList), callContext)
}
@ -1216,6 +1218,13 @@ object LocalMappedConnector extends Connector with MdcLoggable {
)
}
override def getPhysicalCardByCardNumber(bankCardNumber: String, callContext:Option[CallContext]) : OBPReturnType[Box[PhysicalCardTrait]] = Future {
(
code.cards.PhysicalCard.physicalCardProvider.vend.getPhysicalCardByCardNumber(bankCardNumber: String, callContext: Option[CallContext]),
callContext
)
}
override def getPhysicalCardsForBankLegacy(bank: Bank, user: User, queryParams: List[OBPQueryParam]): Box[List[PhysicalCard]] = {
val list = code.cards.PhysicalCard.physicalCardProvider.vend.getPhysicalCardsForBank(bank, user, queryParams)
val cardList = for (l <- list) yield
@ -1240,7 +1249,9 @@ object LocalMappedConnector extends Connector with MdcLoggable {
pinResets = l.pinResets,
collected = l.collected,
posted = l.posted,
customerId = l.customerId
customerId = l.customerId,
cvv = l.cvv,
brand = l.brand
)
Full(cardList)
}
@ -1276,6 +1287,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = Future {
(createPhysicalCardLegacy(
bankCardNumber: String,
@ -1298,6 +1311,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]),
callContext)
}
@ -1324,6 +1339,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]): Box[PhysicalCard] = {
val physicalCardBox: Box[MappedPhysicalCard] = code.cards.PhysicalCard.physicalCardProvider.vend.createPhysicalCard(
bankCardNumber: String,
@ -1346,6 +1363,8 @@ object LocalMappedConnector extends Connector with MdcLoggable {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext])
for (l <- physicalCardBox) yield
@ -1370,7 +1389,9 @@ object LocalMappedConnector extends Connector with MdcLoggable {
pinResets = l.pinResets,
collected = l.collected,
posted = l.posted,
customerId = l.customerId
customerId = l.customerId,
cvv = l.cvv,
brand = l.brand,
)
}
@ -5088,7 +5109,7 @@ object LocalMappedConnector extends Connector with MdcLoggable {
} yield {
(transactionId, callContext)
}
case COUNTERPARTY =>
case COUNTERPARTY | CARD =>
for {
bodyToCounterparty <- NewStyle.function.tryons(s"$TransactionRequestDetailsExtractException It can not extract to $TransactionRequestBodyCounterpartyJSON", 400, callContext) {
body.to_counterparty.get

View File

@ -1430,7 +1430,8 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)))
customerId=customerIdExample.value
)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1496,7 +1497,8 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value))
customerId=customerIdExample.value
))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1652,7 +1654,9 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)
customerId=customerIdExample.value,
cvv = cvvExample.value,
brand = brandExample.value)
),
exampleInboundMessage = (
InBoundCreatePhysicalCard(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
@ -1701,9 +1705,14 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit {
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
override def createPhysicalCard(bankCardNumber: String, nameOnCard: String, cardType: String, issueNumber: String, serialNumber: String, validFrom: Date, expires: Date, enabled: Boolean, cancelled: Boolean, onHotList: Boolean, technology: String, networks: List[String], allows: List[String], accountId: String, bankId: String, replacement: Option[CardReplacementInfo], pinResets: List[PinResetInfo], collected: Option[CardCollectionInfo], posted: Option[CardPostedInfo], customerId: String, callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = {
override def createPhysicalCard(bankCardNumber: String, nameOnCard: String, cardType: String, issueNumber: String,
serialNumber: String, validFrom: Date, expires: Date, enabled: Boolean, cancelled: Boolean, onHotList: Boolean,
technology: String, networks: List[String], allows: List[String], accountId: String, bankId: String,
replacement: Option[CardReplacementInfo], pinResets: List[PinResetInfo], collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo], customerId: String, cvv: String, brand: String,
callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = {
import com.openbankproject.commons.dto.{InBoundCreatePhysicalCard => InBound, OutBoundCreatePhysicalCard => OutBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankCardNumber, nameOnCard, cardType, issueNumber, serialNumber, validFrom, expires, enabled, cancelled, onHotList, technology, networks, allows, accountId, bankId, replacement, pinResets, collected, posted, customerId)
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankCardNumber, nameOnCard, cardType, issueNumber, serialNumber, validFrom, expires, enabled, cancelled, onHotList, technology, networks, allows, accountId, bankId, replacement, pinResets, collected, posted, customerId, cvv, brand)
val response: Future[Box[InBound]] = (southSideActor ? req).mapTo[InBound].recoverWith(recoverFunction).map(Box !! _)
response.map(convertToTuple[PhysicalCard](callContext))
}

View File

@ -1570,7 +1570,8 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)))
customerId=customerIdExample.value
)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1750,8 +1751,7 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)))
),
customerId=customerIdExample.value)))),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1792,8 +1792,9 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)
),
customerId=customerIdExample.value,
cvv = cvvExample.value,
brand = brandExample.value)),
exampleInboundMessage = (
InBoundCreatePhysicalCard(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
status=MessageDocsSwaggerDefinitions.inboundStatus,
@ -1836,14 +1837,18 @@ trait RestConnector_vMar2019 extends Connector with KafkaHelper with MdcLoggable
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value))
),
customerId=customerIdExample.value,
cvv = Some(cvvExample.value),
brand = Some(brandExample.value)))),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
override def createPhysicalCard(bankCardNumber: String, nameOnCard: String, cardType: String, issueNumber: String, serialNumber: String, validFrom: Date, expires: Date, enabled: Boolean, cancelled: Boolean, onHotList: Boolean, technology: String, networks: List[String], allows: List[String], accountId: String, bankId: String, replacement: Option[CardReplacementInfo], pinResets: List[PinResetInfo], collected: Option[CardCollectionInfo], posted: Option[CardPostedInfo], customerId: String, callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = {
import com.openbankproject.commons.dto.{InBoundCreatePhysicalCard => InBound, OutBoundCreatePhysicalCard => OutBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankCardNumber, nameOnCard, cardType, issueNumber, serialNumber, validFrom, expires, enabled, cancelled, onHotList, technology, networks, allows, accountId, bankId, replacement, pinResets, collected, posted, customerId)
override def createPhysicalCard(bankCardNumber: String, nameOnCard: String, cardType: String, issueNumber: String, serialNumber: String, validFrom: Date, expires: Date, enabled: Boolean,
cancelled: Boolean, onHotList: Boolean, technology: String, networks: List[String], allows: List[String], accountId: String, bankId: String, replacement: Option[CardReplacementInfo],
pinResets: List[PinResetInfo], collected: Option[CardCollectionInfo], posted: Option[CardPostedInfo], customerId: String, cvv: String,
brand: String,callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = {
import com.openbankproject.commons.dto.{InBoundCreatePhysicalCard => InBound, OutBoundCreatePhysicalCard => OutBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankCardNumber, nameOnCard, cardType, issueNumber, serialNumber, validFrom, expires, enabled, cancelled, onHotList, technology, networks, allows, accountId, bankId, replacement, pinResets, collected, posted, customerId,cvv, brand)
val response: Future[Box[InBound]] = sendRequest[InBound](getUrl(callContext, "createPhysicalCard"), HttpMethods.POST, req, callContext)
response.map(convertToTuple[PhysicalCard](callContext))
}

View File

@ -1615,7 +1615,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value))
customerId=customerIdExample.value
))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1729,7 +1730,8 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)))
customerId=customerIdExample.value
)))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
@ -1771,7 +1773,9 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value)
customerId=customerIdExample.value,
cvv = cvvExample.value,
brand = brandExample.value)
),
exampleInboundMessage = (
InBoundCreatePhysicalCard(inboundAdapterCallContext=MessageDocsSwaggerDefinitions.inboundAdapterCallContext,
@ -1815,14 +1819,20 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable {
reasonRequested=com.openbankproject.commons.model.PinResetReason.FORGOT)),
collected=Some(CardCollectionInfo(toDate(collectedExample))),
posted=Some(CardPostedInfo(toDate(postedExample))),
customerId=customerIdExample.value))
customerId=customerIdExample.value
))
),
adapterImplementation = Some(AdapterImplementation("- Core", 1))
)
override def createPhysicalCard(bankCardNumber: String, nameOnCard: String, cardType: String, issueNumber: String, serialNumber: String, validFrom: Date, expires: Date, enabled: Boolean, cancelled: Boolean, onHotList: Boolean, technology: String, networks: List[String], allows: List[String], accountId: String, bankId: String, replacement: Option[CardReplacementInfo], pinResets: List[PinResetInfo], collected: Option[CardCollectionInfo], posted: Option[CardPostedInfo], customerId: String, callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = {
import com.openbankproject.commons.dto.{InBoundCreatePhysicalCard => InBound, OutBoundCreatePhysicalCard => OutBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankCardNumber, nameOnCard, cardType, issueNumber, serialNumber, validFrom, expires, enabled, cancelled, onHotList, technology, networks, allows, accountId, bankId, replacement, pinResets, collected, posted, customerId)
override def createPhysicalCard(bankCardNumber: String, nameOnCard: String, cardType: String, issueNumber: String, serialNumber: String,
validFrom: Date, expires: Date, enabled: Boolean, cancelled: Boolean, onHotList: Boolean, technology: String, networks: List[String],
allows: List[String], accountId: String, bankId: String, replacement: Option[CardReplacementInfo], pinResets: List[PinResetInfo],
collected: Option[CardCollectionInfo], posted: Option[CardPostedInfo], customerId: String, cvv: String,
brand: String, callContext: Option[CallContext]): OBPReturnType[Box[PhysicalCard]] = {
import com.openbankproject.commons.dto.{InBoundCreatePhysicalCard => InBound, OutBoundCreatePhysicalCard => OutBound}
val req = OutBound(callContext.map(_.toOutboundAdapterCallContext).orNull, bankCardNumber, nameOnCard, cardType, issueNumber,
serialNumber, validFrom, expires, enabled, cancelled, onHotList, technology, networks, allows, accountId, bankId, replacement, pinResets, collected, posted, customerId, cvv, brand)
val response: Future[Box[InBound]] = sendRequest[InBound]("obp_create_physical_card", req, callContext)
response.map(convertToTuple[PhysicalCard](callContext))
}

View File

@ -139,6 +139,8 @@ object MappedPhysicalCardProvider extends PhysicalCardProvider {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]
): Box[MappedPhysicalCard] = {
@ -196,6 +198,8 @@ object MappedPhysicalCardProvider extends PhysicalCardProvider {
.mPosted(p.date)
.mAccount(mappedBankAccountPrimaryKey) // Card <-MappedLongForeignKey-> BankAccount, so need the primary key here.
.mCustomerId(customerId)
.mBrand(brand)
.mCVV(HashUtil.Sha256Hash(cvv))
.saveMe()
} ?~! ErrorMessages.CreateCardError
}
@ -231,6 +235,12 @@ object MappedPhysicalCardProvider extends PhysicalCardProvider {
}
cards
}
override def getPhysicalCardByCardNumber(bankCardNumber: String, callContext:Option[CallContext]) : Box[PhysicalCardTrait] = {
MappedPhysicalCard.find(
By(MappedPhysicalCard.mBankCardNumber, bankCardNumber),
)
}
def getPhysicalCardsForBank(bank: Bank, user: User, queryParams: List[OBPQueryParam]) = {
val customerId: Option[Cmp[MappedPhysicalCard, String]] = queryParams.collect { case OBPCustomerId(value) =>
@ -312,6 +322,9 @@ class MappedPhysicalCard extends PhysicalCardTrait with LongKeyedMapper[MappedPh
//Maybe this will be first uesd for the initialization. and then we can add more `allows` for this card.
object mCardType extends MappedString(this, 255)
object mCustomerId extends MappedString(this, 255)
object mBrand extends MappedString(this, 255)
object mCVV extends MappedString(this, 255)
def bankId: String = mBankId.get
def bankCardNumber: String = mBankCardNumber.get
@ -353,6 +366,8 @@ class MappedPhysicalCard extends PhysicalCardTrait with LongKeyedMapper[MappedPh
def cardType: String = mCardType.get
def cardId: String = mCardId.get
def customerId: String = mCustomerId.get
override def cvv: Option[String] = Some(mCVV.get)
override def brand: Option[String] = Some(mBrand.get)
}
object MappedPhysicalCard extends MappedPhysicalCard with LongKeyedMetaMapper[MappedPhysicalCard] {

View File

@ -41,6 +41,8 @@ trait PhysicalCardProvider {
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String,
cvv: String,
brand: String,
callContext: Option[CallContext]
): Box[MappedPhysicalCard]
@ -76,6 +78,8 @@ trait PhysicalCardProvider {
def getPhysicalCardForBank(bankId: BankId, cardId: String, callContext:Option[CallContext]) : Box[PhysicalCardTrait]
def deletePhysicalCardForBank(bankId: BankId, cardId: String, callContext:Option[CallContext]) : Box[Boolean]
def getPhysicalCardByCardNumber(bankCardNumber: String, callContext:Option[CallContext]) : Box[PhysicalCardTrait]
}

View File

@ -273,7 +273,8 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest]
else
None
val t_to_counterparty = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.COUNTERPARTY){
val t_to_counterparty = if (TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.COUNTERPARTY ||
TransactionRequestTypes.withName(transactionType) == TransactionRequestTypes.CARD){
val counterpartyIdList: List[String] = for {
JObject(child) <- parsedDetails
JField("counterparty_id", JString(counterpartyId)) <- child

View File

@ -17,7 +17,7 @@ object TransactionRequests extends SimpleInjector {
object TransactionRequestTypes extends Enumeration {
type TransactionRequestTypes = Value
val SANDBOX_TAN, ACCOUNT, ACCOUNT_OTP, COUNTERPARTY, SEPA, FREE_FORM, SIMPLE,
val SANDBOX_TAN, ACCOUNT, ACCOUNT_OTP, COUNTERPARTY, SEPA, FREE_FORM, SIMPLE, CARD,
TRANSFER_TO_PHONE, TRANSFER_TO_ATM, TRANSFER_TO_ACCOUNT, TRANSFER_TO_REFERENCE_ACCOUNT,
//The following are BerlinGroup Standard
SEPA_CREDIT_TRANSFERS, INSTANT_SEPA_CREDIT_TRANSFERS, TARGET_2_PAYMENTS, CROSS_BORDER_CREDIT_TRANSFERS, REFUND = Value

View File

@ -43,7 +43,7 @@ class PhysicalCardsTest extends ServerSetup with DefaultUsers with DefaultConne
pinResets = Nil,
collected = None,
posted = None,
customerId = ""
customerId = "",
)
val user1CardAtBank1 = createCard("1")

View File

@ -1,19 +1,22 @@
package code.api.v4_0_0
import java.util.UUID
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.util.APIUtil.OAuth._
import code.api.util.APIUtil.extractErrorMessageCode
import code.api.util.ApiRole.CanCreateAnyTransactionRequest
import code.api.util.ApiRole.{CanCreateAnyTransactionRequest, CanCreateCardsForBank}
import code.api.util.ErrorMessages._
import code.api.util.{APIUtil, ErrorMessages}
import code.api.v1_4_0.JSONFactory1_4_0.{ChallengeAnswerJSON, TransactionRequestAccountJsonV140}
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.bankconnectors.Connector
import code.entitlement.Entitlement
import code.fx.fx
import code.model.BankAccountX
import code.setup.{APIResponse, DefaultUsers}
@ -46,6 +49,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
object ApiEndpoint7 extends Tag(nameOf(Implementations4_0_0.createTransactionRequestFreeForm))
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))
def transactionCount(accounts: BankAccount*): Int = {
accounts.foldLeft(0)((accumulator, account) => {
@ -133,6 +137,33 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
other_account_secondary_routing_address= counterpartyCounterparty.otherAccountSecondaryRoutingAddress
), bodyValue, description, sharedChargePolicy)
val customerId = createAndGetCustomerIdViaEndpoint(bankId.value, user1)
val requestWithAuthUser = (v5_0_0_Request / "management" /"banks" / bankId.value / "cards" ).POST <@ (user1)
val properCardJson = createPhysicalCardJsonV500.copy(
account_id = fromAccount.accountId.value,
issue_number = "123",
customer_id = customerId,
expires_date = org.apache.commons.lang3.time.DateUtils.addMonths(new Date(), 3)
)
Entitlement.entitlement.vend.addEntitlement(bankId.value, resourceUser1.userId, CanCreateCardsForBank.toString)
val responseProper = makePostRequest(requestWithAuthUser, write(properCardJson))
val cardJsonV500 = responseProper.body.extract[PhysicalCardJsonV500]
var transactionRequestBodyCard = TransactionRequestBodyCardJsonV400(
card = CardJsonV400(
card_type = cardJsonV500.card_type,
brand = cardJsonV500.brand,
cvv = cardJsonV500.cvv,
card_number = cardJsonV500.card_number,
name_on_card = cardJsonV500.name_on_card,
expiry_year = (cardJsonV500.expires_date.getYear+1900).toString,
expiry_month = (cardJsonV500.expires_date.getMonth+1).toString
),
CounterpartyIdJson(counterpartyCounterparty.counterpartyId),
bodyValue,
description
)
def setAnswerTransactionRequest(challengeId: String = this.challengeId, transRequestId: String = this.transRequestId, consumerAndToken: Option[(Consumer, Token)] = user1 , challengeAnswer:String = "123") = {
this.challengeId = challengeId
this.transRequestId = transRequestId
@ -154,11 +185,14 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
*/
var createTransReqRequest = (v4_0_0_Request / "banks" / testBank.bankId.value / "accounts" / fromAccount.accountId.value /
CUSTOM_OWNER_VIEW_ID / "transaction-request-types" / transactionRequestType / "transaction-requests").POST <@ (user1)
var createTransReqRequestCard = (v4_0_0_Request / "transaction-request-types" / "CARD" / "transaction-requests").POST <@ (user1)
def makeCreateTransReqRequest: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBody))
def makeCreateTransReqRequestSEPA: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodySEPA))
def makeCreateTransReqRequestCounterparty: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodyCounterparty))
def makeCreateTransReqRequestSimple: APIResponse = makePostRequest(createTransReqRequest, write(transactionRequestBodySimple))
def makeCreateTransReqRequestCard: APIResponse = makePostRequest(createTransReqRequestCard, write(transactionRequestBodyCard))
def checkAllCreateTransReqResBodyField(createTransactionRequestResponse: APIResponse, withChallenge: Boolean): Unit = {
Then("we should get a 201 created code")
@ -397,9 +431,9 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
Then("we should get a 400 created code")
response.code should equal(400)
response.body.extract[ErrorMessage].message should startWith(ErrorMessages.InvalidTransactionRequestType)
}
}
@ -521,7 +555,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
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()
@ -539,7 +573,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
And("We checked all the fields of createTransactionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest(challengeAnswer ="1234")
@ -594,7 +628,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
@ -602,7 +636,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
@ -619,7 +653,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
When("we prepare all the conditions for a normal success -- V400 Create Transaction Request")
val helper = defaultSetup(FREE_FORM.toString)
addEntitlement(helper.bankId.value, resourceUser1.userId, CanCreateAnyTransactionRequest.toString)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequest
@ -767,7 +801,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
@ -775,7 +809,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
@ -936,7 +970,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
@ -944,7 +978,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
@ -1105,7 +1139,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
@ -1113,7 +1147,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
@ -1152,12 +1186,12 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
user1,
PostViewJsonV400(view_id = "owner", is_system = true)
)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestCounterparty
val createTransactionRequestJsonResponse = createTransactionRequestResponse.body.extract[TransactionRequestWithChargeJSON400]
createTransactionRequestJsonResponse.status should equal(TransactionRequestStatus.INITIATED.toString)
val challengeOfUser1: Option[ChallengeJsonV400] = createTransactionRequestJsonResponse.challenges.find(_.user_id == resourceUser1.userId)
val challengeOfUser2: Option[ChallengeJsonV400] = createTransactionRequestJsonResponse.challenges.find(_.user_id == resourceUser2.userId)
@ -1199,7 +1233,6 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
}
feature("we can create transaction requests -- SIMPLE") {
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
@ -1354,7 +1387,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
@ -1362,7 +1395,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
@ -1401,15 +1434,15 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
user1,
PostViewJsonV400(view_id = "owner", is_system = true)
)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestSimple
val createTransactionRequestJsonResponse = createTransactionRequestResponse.body.extract[TransactionRequestWithChargeJSON400]
createTransactionRequestJsonResponse.status should equal(TransactionRequestStatus.INITIATED.toString)
val challengeOfUser1: Option[ChallengeJsonV400] = createTransactionRequestJsonResponse.challenges.find(_.user_id == resourceUser1.userId)
val challengeOfUser2: Option[ChallengeJsonV400] = createTransactionRequestJsonResponse.challenges.find(_.user_id == resourceUser2.userId)
Then("We checked all the fields of createTransactionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true)
@ -1433,7 +1466,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
And("we call the endpoint")
val ansReqResponseUser1 = helper.makeAnswerRequest
ansReqResponseUser1.body.extract[ErrorMessage].message contains extractErrorMessageCode(NextChallengePending) should be (true)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest(
@ -1445,7 +1478,272 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
ansReqResponseUser2.body.extract[TransactionRequestWithChargeJSON400].status should equal(TransactionRequestStatus.COMPLETED.toString)
}
}
}
feature("we can create transaction requests -- CARD") {
setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD")
setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("No challenge, No FX ", ApiEndpoint10) {}
} else {
scenario("No challenge, No FX ", ApiEndpoint10) {
setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD")
setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
When("we prepare all the conditions for a normal success -- V400 Create Transaction Request")
val helper = defaultSetup(CARD.toString)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestCard
Then("We checked all the fields of createTransact dionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, false)
When("we need check the 'Get all Transaction Requests. - V400' to double check it in database")
val getTransReqResponse = helper.makeGetTransReqRequest
Then("We checked all the fields of getTransReqResponse body")
helper.checkAllGetTransReqResBodyField(getTransReqResponse, false)
When("we need to check the 'Get Transactions for Account (Full) -V400' to check the transaction info ")
val getTransResponse = helper.makeGetTransRequest
Then("We checked all the fields of getTransResponse body")
helper.checkAllGetTransResBodyField(getTransResponse, false)
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(true)
}
}
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("No challenge, With FX ", ApiEndpoint10) {}
} else {
scenario("No challenge, With FX ", ApiEndpoint10) {
setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD")
setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
When("we prepare all the conditions for a normal success -- V400 Create Transaction Request")
val helper = defaultSetup(CARD.toString)
Then("we call the 'V400 Create Transaction Request' endpoint")
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.transactionRequestBodyCard = helper.transactionRequestBodyCard.copy(value=helper.bodyValue)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestCard
Then("We checked all the fields of createTransactionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, false)
When("we need check the 'Get all Transaction Requests. - V400' to double check it in database")
val getTransReqResponse = helper.makeGetTransReqRequest
Then("We checked all the fields of getTransReqResponse body")
helper.checkAllGetTransReqResBodyField(getTransReqResponse, false)
When("we need to check the 'Get Transactions for Account (Full) -V400' to check the transaction info ")
val getTransResponse = helper.makeGetTransRequest
Then("We checked all the fields of getTransResponse body")
helper.checkAllGetTransResBodyField(getTransResponse, false)
When("We checked all the data in database, we need check the account amout info")
helper.checkBankAccountBalance(true)
}
}
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("With challenge, No FX ", ApiEndpoint10) {}
} else {
scenario("With challenge, No FX ", ApiEndpoint10) {
setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD")
setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
When("we prepare all the conditions for a normal success -- V400 Create Transaction Request")
val helper = defaultSetup(CARD.toString)
And("We set the special conditions for different currencies")
val fromCurrency = "AED"
val toCurrency = "AED"
val amt = "50000.00"
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.transactionRequestBodyCard = helper.transactionRequestBodyCard.copy(value=helper.bodyValue)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestCard
And("We checked all the fields of createTransactionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true)
Then("we need check the 'Get all Transaction Requests. - V400' to double check it in database")
val getTransReqResponse = helper.makeGetTransReqRequest
And("We checked all the fields of getTransReqResponse body")
helper.checkAllGetTransReqResBodyField(getTransReqResponse, true)
Then("we need to check the 'Get Transactions for Account (Full) -V400' to check the transaction info ")
val getTransResponse = helper.makeGetTransRequest
And("We checked all the fields of getTransResponse body")
helper.checkAllGetTransResBodyField(getTransResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
And("we call the endpoint")
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
}
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("With challenge, With FX", ApiEndpoint10) {}
} else {
scenario("With challenge, With FX", ApiEndpoint10) {
setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD")
setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
When("we prepare all the conditions for a normal success -- V400 Create Transaction Request")
val helper = defaultSetup(CARD.toString)
And("We set the special conditions for different currencies")
val fromCurrency = "AED"
val toCurrency = "INR"
val amt = "50000.00"
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.transactionRequestBodyCard = helper.transactionRequestBodyCard.copy(value=helper.bodyValue)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestCard
Then("We checked all the fields of createTransactionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true)
When("we need check the 'Get all Transaction Requests. - V400' to double check it in database")
val getTransReqResponse = helper.makeGetTransReqRequest
Then("We checked all the fields of getTransReqResponse body")
helper.checkAllGetTransReqResBodyField(getTransReqResponse, true)
When("we need to check the 'Get Transactions for Account (Full) -V400' to check the transaction info ")
val getTransResponse = helper.makeGetTransRequest
Then("We checked all the fields of getTransResponse body")
helper.checkAllGetTransResBodyField(getTransResponse, true)
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest()
And("we call the endpoint")
val ansReqResponse = helper.makeAnswerRequest
And("We check the all the fields of getAnsReqResponse body ")
helper.checkAllAnsTransReqBodyFields(ansReqResponse, true)
Then("we need check the account amount info")
helper.checkBankAccountBalance(true)
}
}
if (APIUtil.getPropsAsBoolValue("transactionRequests_enabled", false) == false) {
ignore("With N challenges, With FX", ApiEndpoint10) {}
} else {
scenario("With N challenges, With FX", ApiEndpoint10) {
setPropsValues("transactionRequests_supported_types" -> "SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,ACCOUNT_OTP,SIMPLE,CARD")
setPropsValues("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
When("we prepare all the conditions for a normal success -- V400 Create Transaction Request")
val helper = defaultSetup(CARD.toString)
And("We set the special conditions for different currencies")
val fromCurrency = "AED"
val toCurrency = "INR"
val amt = "50000.00"
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.transactionRequestBodyCard = helper.transactionRequestBodyCard.copy(value=helper.bodyValue)
createAccountAttributeViaEndpoint(
helper.bankId.value,
helper.accountId1.value,
"REQUIRED_CHALLENGE_ANSWERS",
"2",
"INTEGER",
Some("LKJL98769F")
)
val grantedView = grantUserAccessToViewViaEndpoint(
helper.bankId.value,
helper.accountId1.value,
resourceUser2.userId,
user1,
PostViewJsonV400(view_id = "owner", is_system = true)
)
Then("we call the 'V400 Create Transaction Request' endpoint")
val createTransactionRequestResponse = helper.makeCreateTransReqRequestCard
val createTransactionRequestJsonResponse = createTransactionRequestResponse.body.extract[TransactionRequestWithChargeJSON400]
createTransactionRequestJsonResponse.status should equal(TransactionRequestStatus.INITIATED.toString)
val challengeOfUser1: Option[ChallengeJsonV400] = createTransactionRequestJsonResponse.challenges.find(_.user_id == resourceUser1.userId)
val challengeOfUser2: Option[ChallengeJsonV400] = createTransactionRequestJsonResponse.challenges.find(_.user_id == resourceUser2.userId)
Then("We checked all the fields of createTransactionRequestResponse body ")
helper.checkAllCreateTransReqResBodyField(createTransactionRequestResponse, true)
When("we need check the 'Get all Transaction Requests. - V400' to double check it in database")
val getTransReqResponse = helper.makeGetTransReqRequest
Then("We checked all the fields of getTransReqResponse body")
helper.checkAllGetTransReqResBodyField(getTransReqResponse, true)
When("we need to check the 'Get Transactions for Account (Full) -V400' to check the transaction info ")
val getTransResponse = helper.makeGetTransRequest
Then("We checked all the fields of getTransResponse body")
helper.checkAllGetTransResBodyField(getTransResponse, true)
When("We checked all the data in database, we need check the account amount info")
helper.checkBankAccountBalance(false)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest(challengeId = challengeOfUser1.map(_.id).getOrElse(""))
And("we call the endpoint")
val ansReqResponseUser1 = helper.makeAnswerRequest
ansReqResponseUser1.body.extract[ErrorMessage].message contains extractErrorMessageCode(NextChallengePending) should be (true)
Then("We call 'Answer Transaction Request Challenge - V400' to finish the request")
And("we prepare the parameters for it")
helper.setAnswerTransactionRequest(
challengeId = challengeOfUser2.map(_.id).getOrElse(""),
consumerAndToken = user2
)
And("we call the endpoint")
val ansReqResponseUser2 = helper.makeAnswerRequest
ansReqResponseUser2.body.extract[TransactionRequestWithChargeJSON400].status should equal(TransactionRequestStatus.COMPLETED.toString)
}
}
}
feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") {
@ -1453,11 +1751,11 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
lazy val bankId = testBankId1.value
lazy val accountId = testAccountId1.value
lazy val view = "owner"
scenario("We will call the endpoint WITHOUT user credentials", ApiEndpoint1, VersionOfApi) {
val transactionRequestId = randomTransactionRequestViaEndpoint(bankId, accountId, view, user1).id
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "banks" / bankId / "accounts"/ accountId / view / "transaction-requests" / transactionRequestId).GET
val response400 = makeGetRequest(request400)
@ -1467,9 +1765,9 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
}
scenario("We will call the endpoint WITH user credentials", ApiEndpoint1, VersionOfApi) {
val transactionRequestId = randomTransactionRequestViaEndpoint(bankId, accountId, view, user1).id
When("We make a request v4.0.0")
val request400 = (v4_0_0_Request / "banks" / bankId / "accounts"/ accountId / view / "transaction-requests" / transactionRequestId).GET <@ (user1)
val response400 = makeGetRequest(request400)
@ -1477,7 +1775,7 @@ class TransactionRequestsTest extends V400ServerSetup with DefaultUsers {
response400.code should equal(200)
response400.body.extract[TransactionRequestWithChargeJSON210].id should equal(transactionRequestId)
}
}
}

View File

@ -36,6 +36,7 @@ import scala.util.Random.nextInt
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 dynamicEndpoint_Request: Req = baseRequest / "obp" / ApiShortVersions.`dynamic-endpoint`.toString
def dynamicEntity_Request: Req = baseRequest / "obp" / ApiShortVersions.`dynamic-entity`.toString

View File

@ -0,0 +1,158 @@
package code.api.v5_0_0
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.{createPhysicalCardJsonV500}
import code.api.util.ApiRole
import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole.CanCreateCustomer
import code.api.util.ErrorMessages._
import code.api.v1_3_0.ReplacementJSON
import code.api.v3_1_0.CustomerJsonV310
import code.api.v5_0_0.APIMethods500.Implementations5_0_0
import code.entitlement.Entitlement
import code.setup.DefaultUsers
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.{CardAction, CardReplacementReason}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
import java.util.Date
class CardTest extends V500ServerSetupAsync with DefaultUsers {
object VersionOfApi extends Tag(ApiVersion.v5_0_0.toString)
object ApiEndpointAddCardForBank extends Tag(nameOf(Implementations5_0_0.addCardForBank))
feature("test Card APIs") {
scenario("We will create Card with many error cases",
ApiEndpointAddCardForBank,
VersionOfApi
) {
Given("The test bank and test account")
val testBank = testBankId1
val testAccount = testAccountId1
val dummyCard = createPhysicalCardJsonV500
And("We need to prepare the Customer Info")
Then("We prepare the Customer data")
val request310 = (v5_0_0_Request / "banks" / testBankId1.value / "customers").POST <@(user1)
val postCustomerJson = SwaggerDefinitionsJSON.postCustomerJsonV310
Entitlement.entitlement.vend.addEntitlement(testBank.value, resourceUser1.userId, CanCreateCustomer.toString)
val responseCustomer310 = makePostRequest(request310, write(postCustomerJson))
val customerId = responseCustomer310.body.extract[CustomerJsonV310].customer_id
val properCardJson = dummyCard.copy(account_id = testAccount.value, issue_number = "123", customer_id = customerId)
val requestAnonymous = (v5_0_0_Request / "management"/"banks" / testBank.value / "cards" ).POST
val requestWithAuthUser = (v5_0_0_Request / "management" /"banks" / testBank.value / "cards" ).POST <@ (user1)
Then(s"We test with anonymous user.")
val responseAnonymous = makePostRequest(requestAnonymous, write(properCardJson))
And(s"We should get 401 and get the authentication error")
responseAnonymous.code should equal(401)
responseAnonymous.body.toString contains(s"$UserNotLoggedIn") should be (true)
Then(s"We call the authentication user, but without proper role: ${ApiRole.canCreateCardsForBank}")
val responseUserButNoRole = makePostRequest(requestWithAuthUser, write(properCardJson))
And(s"We should get 403 and the error message missing can ${ApiRole.canCreateCardsForBank} role")
responseUserButNoRole.code should equal(403)
responseUserButNoRole.body.toString contains(s"${ApiRole.canCreateCardsForBank}") should be (true)
Then(s"We grant the user ${ApiRole.canCreateCardsForBank} role, but with the wrong AccountId")
Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, ApiRole.canCreateCardsForBank.toString)
val wrongAccountCardJson = dummyCard
val responseHasRoleWrongAccountId = makePostRequest(requestWithAuthUser, write(wrongAccountCardJson))
And(s"We should get 404 and get the error message: $BankAccountNotFound.")
responseHasRoleWrongAccountId.code should equal(404)
responseHasRoleWrongAccountId.body.toString contains(s"$BankAccountNotFound")
Then(s"We call the authentication user, but totally wrong Json format.")
val wrongPostJsonFormat = testBankId1
val responseWrongJsonFormat = makePostRequest(requestWithAuthUser, write(wrongPostJsonFormat))
And(s"We should get 400 and get the error message: $InvalidJsonFormat.")
responseWrongJsonFormat.code should equal(400)
responseWrongJsonFormat.body.toString contains(s"$InvalidJsonFormat")
Then(s"We call the authentication user, but wrong card.allows value")
val withWrongVlaueForAllows = properCardJson.copy(allows = List("123"))
val responseWithWrongVlaueForAllows = makePostRequest(requestWithAuthUser, write(withWrongVlaueForAllows))
And(s"We should get 400 and get the error message")
responseWithWrongVlaueForAllows.code should equal(400)
responseWithWrongVlaueForAllows.body.toString contains(AllowedValuesAre++ CardAction.availableValues.mkString(", "))
Then(s"We call the authentication user, but wrong card.replacement value")
val wrongCardReplacementReasonJson = dummyCard.copy(replacement = Some(ReplacementJSON(new Date(),"Wrong"))) // The replacement must be Enum of `CardReplacementReason`
val responseWrongCardReplacementReasonJson = makePostRequest(requestWithAuthUser, write(wrongCardReplacementReasonJson))
And(s"We should get 400 and get the error message")
responseWrongCardReplacementReasonJson.code should equal(400)
responseWrongCardReplacementReasonJson.body.toString contains(AllowedValuesAre + CardReplacementReason.availableValues.mkString(", "))
Then(s"We call the authentication user, but too long issue number.")
val properCardJsonTooLongIssueNumber = dummyCard.copy(account_id = testAccount.value, issue_number = "01234567891")
val responseUserWrongIssueNumber = makePostRequest(requestWithAuthUser, write(properCardJsonTooLongIssueNumber))
And(s"We should get 400 and the error message missing can ${ApiRole.canCreateCardsForBank} role")
responseUserWrongIssueNumber.code should equal(400)
responseUserWrongIssueNumber.body.toString contains(s"${maximumLimitExceeded.replace("10000", "10")}") should be (true)
Then(s"We grant the user ${ApiRole.canCreateCardsForBank} role, but wrong customerId")
val wrongCustomerCardJson = properCardJson.copy(customer_id = "wrongId")
val responsewrongCustomerCardJson = makePostRequest(requestWithAuthUser, write(wrongCustomerCardJson))
And(s"We should get 400 and get the error message: $CustomerNotFoundByCustomerId.")
responsewrongCustomerCardJson.code should equal(404)
responsewrongCustomerCardJson.body.toString contains(s"$CustomerNotFoundByCustomerId") should be (true)
Then(s"We test the success case, prepare all stuff.")
val responseProper = makePostRequest(requestWithAuthUser, write(properCardJson))
And("We should get 400 and get the error message: Not Found the BankAccount.")
responseProper.code should equal(201)
val cardJsonV500 = responseProper.body.extract[PhysicalCardJsonV500]
cardJsonV500.card_number should be (properCardJson.card_number)
cardJsonV500.card_type should be (properCardJson.card_type)
cardJsonV500.name_on_card should be (properCardJson.name_on_card)
cardJsonV500.issue_number should be (properCardJson.issue_number)
cardJsonV500.serial_number should be (properCardJson.serial_number )
cardJsonV500.valid_from_date should be (properCardJson.valid_from_date )
cardJsonV500.expires_date should be (properCardJson.expires_date )
cardJsonV500.enabled should be (properCardJson.enabled )
cardJsonV500.cancelled should be (false)
cardJsonV500.on_hot_list should be (false)
cardJsonV500.technology should be (properCardJson.technology )
cardJsonV500.networks should be (properCardJson.networks )
cardJsonV500.allows should be (properCardJson.allows )
cardJsonV500.account.id should be (properCardJson.account_id )
cardJsonV500.replacement should be {
properCardJson.replacement match {
case Some(x) => x
case None => null
}
}
cardJsonV500.pin_reset.toString() should be(properCardJson.pin_reset.toString())
cardJsonV500.collected should be {
properCardJson.collected match {
case Some(x) => x
case None => null
}
}
cardJsonV500.posted should be {
properCardJson.posted match {
case Some(x) => x
case None => null
}
}
cardJsonV500.cvv.length equals (3) should be(true)
cardJsonV500.brand equals ("Visa") should be(true)
Then(s"We create the card with same bankId, cardNumber and issueNumber")
val responseDeplicated = makePostRequest(requestWithAuthUser, write(properCardJson))
And("We should get 400 and get the error message: the card already existing .")
responseDeplicated.code should equal(400)
responseDeplicated.body.toString contains(s"$CardAlreadyExists") should be (true)
}
}
}

View File

@ -51,6 +51,8 @@ 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("CARD_OTP_INSTRUCTION_TRANSPORT" -> "DUMMY")
val server = TestServer
def baseRequest = host(server.host, server.port)

View File

@ -691,7 +691,8 @@ case class OutBoundUpdatePhysicalCard(outboundAdapterCallContext: OutboundAdapte
pinResets: List[PinResetInfo],
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String) extends TopicTrait
customerId: String
) extends TopicTrait
case class InBoundUpdatePhysicalCard(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: PhysicalCard) extends InBoundTrait[PhysicalCard]
case class OutBoundDeletePhysicalCardForBank(outboundAdapterCallContext: OutboundAdapterCallContext,
@ -734,7 +735,9 @@ case class OutBoundCreatePhysicalCard(outboundAdapterCallContext: OutboundAdapte
pinResets: List[PinResetInfo],
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String
customerId: String,
cvv: String,
brand: String
) extends TopicTrait
case class InBoundCreatePhysicalCard(inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: PhysicalCard) extends InBoundTrait[PhysicalCard]
@ -947,7 +950,9 @@ case class OutBoundCreatePhysicalCardLegacy (outboundAdapterCallContext: Outboun
pinResets: List[PinResetInfo],
collected: Option[CardCollectionInfo],
posted: Option[CardPostedInfo],
customerId: String) extends TopicTrait
customerId: String,
cvv: String = "",
brand: String = "") extends TopicTrait
case class InBoundCreatePhysicalCardLegacy (inboundAdapterCallContext: InboundAdapterCallContext, status: Status, data: PhysicalCard) extends InBoundTrait[PhysicalCard]

View File

@ -61,6 +61,8 @@ trait PhysicalCardTrait {
def collected: Option[CardCollectionInfo]
def posted: Option[CardPostedInfo]
def customerId: String
def cvv: Option[String] = None //added from V500
def brand: Option[String] = None //added from V500
}
case class PhysicalCard (
@ -84,7 +86,9 @@ case class PhysicalCard (
val pinResets : List[PinResetInfo],
val collected : Option[CardCollectionInfo],
val posted : Option[CardPostedInfo],
val customerId: String
val customerId: String,
override val cvv: Option[String] = None,
override val brand: Option[String] = None
) extends PhysicalCardTrait