Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,14 @@ There are the following request types on this access path:
val basket = SigningBasketX.signingBasketProvider.vend.getSigningBasketByBasketId(basketId)
val existAll: Box[Boolean] =
basket.flatMap(_.payments.map(_.forall(i => Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext).isDefined)))
if (existAll.getOrElse(false)) {
val alreadyCompleted: List[String] =
basket.flatMap(_.payments).getOrElse(Nil).filter { i =>
Connector.connector.vend.getTransactionRequestImpl(TransactionRequestId(i), callContext)
.exists(_._1.status == COMPLETED.toString)
}
if (alreadyCompleted.nonEmpty) {
unboxFullOrFail(Empty, callContext, s"$InvalidConnectorResponse Some of paymentIds [${alreadyCompleted.mkString(",")}] are already completed")
} else if (existAll.getOrElse(false)) {
basket.map { i =>
i.payments.map(_.map { i =>
NewStyle.function.saveTransactionRequestStatusImpl(TransactionRequestId(i), COMPLETED.toString, callContext)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package code.api.berlin.group.v1_3

import code.api.Constant.SYSTEM_INITIATE_PAYMENTS_BERLIN_GROUP_VIEW_ID
import code.api.berlin.group.ConstantsBG
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{AuthorisationJsonV13, ErrorMessagesBG, PostSigningBasketJsonV13, ScaStatusJsonV13, SigningBasketGetResponseJson, SigningBasketResponseJson, StartPaymentAuthorisationJson}
import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{AuthorisationJsonV13, ErrorMessagesBG, InitiatePaymentResponseJson, PostSigningBasketJsonV13, ScaStatusJsonV13, SigningBasketGetResponseJson, SigningBasketResponseJson, StartPaymentAuthorisationJson}
import code.api.berlin.group.v1_3.model.TransactionStatus
import code.api.builder.SigningBasketsApi.APIMethods_SigningBasketsApi
import code.api.util.APIUtil.OAuth._
import code.api.util.ErrorMessages._
import code.model.dataAccess.BankAccountRouting
import code.setup.{APIResponse, DefaultUsers}
import code.views.Views
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus
import com.openbankproject.commons.model.ViewId
import com.openbankproject.commons.model.enums.{AccountRoutingScheme, PaymentServiceTypes, StrongCustomerAuthenticationStatus, TransactionRequestTypes}
import net.liftweb.mapper.By
import org.scalatest.Tag

class SigningBasketServiceSBSApiTest extends BerlinGroupServerSetupV1_3 with DefaultUsers {
Expand All @@ -21,6 +27,29 @@ class SigningBasketServiceSBSApiTest extends BerlinGroupServerSetupV1_3 with Def
object getSigningBasketAuthorisation extends Tag(nameOf(APIMethods_SigningBasketsApi.getSigningBasketAuthorisation))
object updateSigningBasketPsuData extends Tag(nameOf(APIMethods_SigningBasketsApi.updateSigningBasketPsuData))

// Helper: create a real SEPA payment via BG PIS API and return its paymentId
private def createRealPaymentId(): String = {
val accountsRoutingIban = BankAccountRouting.findAll(By(BankAccountRouting.AccountRoutingScheme, AccountRoutingScheme.IBAN.toString))
val ibanFrom = accountsRoutingIban.head
val ibanTo = accountsRoutingIban.last
Views.views.vend.systemView(ViewId(SYSTEM_INITIATE_PAYMENTS_BERLIN_GROUP_VIEW_ID)).foreach(view =>
Views.views.vend.grantAccessToSystemView(ibanFrom.bankId, ibanFrom.accountId, view, resourceUser1)
)
val initiatePaymentJson =
s"""{
| "debtorAccount": { "iban": "${ibanFrom.accountRouting.address}" },
| "instructedAmount": { "currency": "EUR", "amount": "10" },
| "creditorAccount": { "iban": "${ibanTo.accountRouting.address}" },
| "creditorName": "TestCreditor"
|}""".stripMargin
val requestPost = (V1_3_BG / PaymentServiceTypes.payments.toString / TransactionRequestTypes.SEPA_CREDIT_TRANSFERS.toString).POST <@ (user1)
val response: APIResponse = makePostRequest(requestPost, initiatePaymentJson)
response.code should equal(201)
val payment = response.body.extract[InitiatePaymentResponseJson]
payment.transactionStatus should be(TransactionStatus.ACCP.code)
payment.paymentId
}

feature(s"test the BG v1.3 - ${createSigningBasket.name}") {
scenario("Failed Case - Unauthenticated Access", BerlinGroupV1_3, SBS, createSigningBasket) {
val postJson =
Expand Down Expand Up @@ -61,21 +90,26 @@ class SigningBasketServiceSBSApiTest extends BerlinGroupServerSetupV1_3 with Def
}
}

// TODO Add check that paymentId is an existing transaction request
feature(s"test the BG v1.3 -${createSigningBasket.name}") {
scenario("Failed Case - Successful", BerlinGroupV1_3, SBS, createSigningBasket) {
scenario("Success Case - 201 with real paymentId and response body validation", BerlinGroupV1_3, SBS, createSigningBasket) {
val realPaymentId = createRealPaymentId()
val postJson =
s"""{
| "paymentIds": [
| "123qwert456789",
| "12345qwert7899"
| "${realPaymentId}"
| ]
|}""".stripMargin

val requestPost = (V1_3_BG / "signing-baskets").POST <@ (user1)
val response: APIResponse = makePostRequest(requestPost, postJson)
Then("We should get a 201 ")
Then("We should get a 201")
response.code should equal(201)
val createdBasket = response.body.extract[SigningBasketResponseJson]
createdBasket.basketId should not be empty
createdBasket.transactionStatus should be(ConstantsBG.SigningBasketsStatus.RCVD.toString.toLowerCase())
createdBasket._links.self.href should not be empty
createdBasket._links.status.href should not be empty
createdBasket._links.startAuthorisation.href should not be empty
}
}

Expand All @@ -90,6 +124,43 @@ class SigningBasketServiceSBSApiTest extends BerlinGroupServerSetupV1_3 with Def
And("error should be " + error)
responseGet.body.extract[ErrorMessagesBG].tppMessages.head.text should startWith(error)
}
scenario("Success Case - 200 with multiple real paymentIds and payment status validation", BerlinGroupV1_3, SBS, getSigningBasket) {
// Create two real payments, then create a basket referencing both
val paymentId1 = createRealPaymentId()
val paymentId2 = createRealPaymentId()
val postJson =
s"""{
| "paymentIds": [
| "${paymentId1}",
| "${paymentId2}"
| ]
|}""".stripMargin
val requestPost = (V1_3_BG / "signing-baskets").POST <@ (user1)
val responsePost: APIResponse = makePostRequest(requestPost, postJson)
responsePost.code should equal(201)
val basketId = responsePost.body.extract[SigningBasketResponseJson].basketId

// Verify basket GET returns correct data including both real paymentIds
val requestGet = (V1_3_BG / "signing-baskets" / basketId).GET <@ (user1)
val responseGet = makeGetRequest(requestGet)
Then("We should get a 200")
responseGet.code should be(200)
val basket = responseGet.body.extract[SigningBasketGetResponseJson]
basket.transactionStatus should be(ConstantsBG.SigningBasketsStatus.RCVD.toString.toLowerCase())
basket.payments.isDefined should be(true)
basket.payments.get should contain(paymentId1)
basket.payments.get should contain(paymentId2)

// Verify each paymentId in the basket has ACCP status
Then("Each payment in the basket should return ACCP status")
basket.payments.get.foreach { pid =>
val requestPaymentStatus = (V1_3_BG / PaymentServiceTypes.payments.toString / TransactionRequestTypes.SEPA_CREDIT_TRANSFERS.toString / pid / "status").GET <@ (user1)
val responsePaymentStatus = makeGetRequest(requestPaymentStatus)
responsePaymentStatus.code should be(200)
val txStatus = (responsePaymentStatus.body \ "transactionStatus").extract[String]
txStatus should be(TransactionStatus.ACCP.code)
}
}
}

feature(s"test the BG v1.3 - ${getSigningBasketStatus.name}") {
Expand Down
11 changes: 10 additions & 1 deletion obp-api/src/test/scala/code/setup/ServerSetup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,18 @@ trait ServerSetup extends FeatureSpec with SendServerRequests
setPropsValues("starConnector_supported_types" -> "mapped,internal,cardano_vJun2025")
setPropsValues("connector" -> "star")

// Berlin Group
// Berlin Group - set in trait body for initial setup
setPropsValues("berlin_group_mandatory_headers" -> "")
setPropsValues("berlin_group_mandatory_header_consent" -> "")

override def beforeEach(): Unit = {
super.beforeEach()
// Re-apply Berlin Group props after each PropsReset.afterEach() restores lockedProviders
setPropsValues(
"berlin_group_mandatory_headers" -> "",
"berlin_group_mandatory_header_consent" -> ""
)
}

// Set system properties to force Pekko to use random available ports
// This prevents conflicts when both RunWebApp and tests are running
Expand Down
Loading