Skip to content

Instantly share code, notes, and snippets.

@btoconnor
Created August 13, 2025 16:00
Show Gist options
  • Save btoconnor/b53925500068b3ded8ab02fd06bbc5c5 to your computer and use it in GitHub Desktop.
Save btoconnor/b53925500068b3ded8ab02fd06bbc5c5 to your computer and use it in GitHub Desktop.
diff --git a/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/clients/minos/FakeMinosClient.kt b/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/clients/minos/FakeMinosClient.kt
index e4e4efd18fe9..403b63c5d726 100644
--- a/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/clients/minos/FakeMinosClient.kt
+++ b/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/clients/minos/FakeMinosClient.kt
@@ -11,31 +11,34 @@ import com.squareup.protos.cash.minos.actions.v1.SubmitOTPThreeDSecureAuthentica
import com.squareup.protos.cash.minos.actions.v1.SubmitOTPThreeDSecureAuthenticationResultResponse
import jakarta.inject.Inject
import jakarta.inject.Singleton
@Singleton
open class FakeMinosClient @Inject constructor() : MinosClient {
override fun getAvailableThreeDSecureAuthenticationChannel(
request: GetAvailableThreeDSecureAuthenticationChannelRequest
): GetAvailableThreeDSecureAuthenticationChannelResponse {
if (request.customer_token == "C_user_token") {
return GetAvailableThreeDSecureAuthenticationChannelResponse.Builder()
.channel(Channel.Builder().type(ChannelType.SMS).identifier("1234567890").build())
.build()
} else if (request.customer_token == "C_no_available_channel") {
return GetAvailableThreeDSecureAuthenticationChannelResponse.Builder().build()
+ } else if (request.customer_token.equals("C_service_unavailable")) {
+ throw ServiceUnavailableException("Failed to get authentication channel")
}
+
return GetAvailableThreeDSecureAuthenticationChannelResponse.Builder()
.channel(Channel.Builder().type(ChannelType.EMAIL).identifier("[email protected]").build())
.build()
}
override fun sendOTPToCustomerForThreeDSecureAuthentication(
request: SendOTPToCustomerForThreeDSecureAuthenticationRequest
): SendOTPToCustomerForThreeDSecureAuthenticationResponse {
if (request.request_token.equals("failed-response-expected")) {
throw ServiceUnavailableException("Failed to send OTP")
}
return SendOTPToCustomerForThreeDSecureAuthenticationResponse.Builder().build()
}
override fun submitOTPThreeDSecureAuthenticationResult(
diff --git a/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/handlers/actions/entersekt/GetCardDetailsWebAction.kt b/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/handlers/actions/entersekt/GetCardDetailsWebAction.kt
index fae2b7b1ddd6..1ae6cadf773a 100644
--- a/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/handlers/actions/entersekt/GetCardDetailsWebAction.kt
+++ b/issuing-acs-service/service/src/main/kotlin/com/squareup/cash/issuingacsservice/handlers/actions/entersekt/GetCardDetailsWebAction.kt
@@ -58,31 +58,47 @@ constructor(
issuingAcsServiceMetrics.incrementGetCardDetailsRequestedCount()
LogHelper.log(
logger,
LogLevel.INFO,
"Received getCardDetails request",
requestToken = requestToken,
messageId = getCardDetailsRequest.messageID,
)
val card = cardController.getCardDetails(pan, expiryMonth, expiryYear, requestToken)
// Retrieve the authentication channel only for active cards, as authentication with inactive cards will be
// declined before risk evaluation
val channel =
card?.let {
- if (it.state == CardState.ACTIVE) cardController.getAuthenticationChannel(it, requestToken) else null
+ if (it.state == CardState.ACTIVE) {
+ try {
+ cardController.getAuthenticationChannel(it, requestToken)
+ } catch (e: Exception) {
+ LogHelper.log(
+ logger,
+ LogLevel.ERROR,
+ "Failed to get authentication channel for card",
+ e = e,
+ requestToken = requestToken,
+ cardToken = it.cardToken,
+ )
+ null
+ }
+ } else {
+ null
+ }
}
cardController.saveAuthenticationNotificationChannel(UUID.fromString(requestToken), card, channel)
issuingAcsServiceMetrics.incrementGetCardDetailsProcessedCount()
issuingAcsServiceMetrics.emitRequestProcessLatency(
requestName = javaClass.simpleName,
latency = Duration.between(receivedTime, Instant.now()).toMillis().toDouble(),
)
return buildCardDetailsResponse(getCardDetailsRequest, card, channel)
} catch (e: JOSEException) {
LogHelper.log(logger, LogLevel.ERROR, "Failed to decrypt JWE token", e = e)
throw DecryptionFailedException(e.message, e)
} catch (e: Exception) {
diff --git a/issuing-acs-service/service/src/test/kotlin/com/squareup/cash/issuingacsservice/actions/entersekt/GetCardDetailsWebActionTest.kt b/issuing-acs-service/service/src/test/kotlin/com/squareup/cash/issuingacsservice/actions/entersekt/GetCardDetailsWebActionTest.kt
index bdea6847e7b9..1e7980aa501b 100644
--- a/issuing-acs-service/service/src/test/kotlin/com/squareup/cash/issuingacsservice/actions/entersekt/GetCardDetailsWebActionTest.kt
+++ b/issuing-acs-service/service/src/test/kotlin/com/squareup/cash/issuingacsservice/actions/entersekt/GetCardDetailsWebActionTest.kt
@@ -486,30 +486,57 @@ internal class GetCardDetailsWebActionTest : IssuingAcsServiceTest() {
assertEquals(response.card!!.status.toString(), "ACTIVE")
assertEquals(response.card!!.expiryMonth, "06")
assertEquals(response.card!!.expiryYear, "2099")
assertNotNull(response.customer!!.person?.name?.firstName, "first-name")
assertNotNull(response.customer!!.person?.name?.lastName, "last-name")
assertEquals(
response.customer!!.customerDigital!!.authenticationCommsOptions,
listOf(EntersektCustomerDigital.AuthenticationCommsOption.SMS),
)
assertEquals(response.customer!!.customerDigital!!.mobileNumber, TEST_MOBILE_NUMBER)
assertNotNull(response.result)
assertEquals(response.result!!.action, EntersektResult.EntersektAction.APPROVED)
}
+ @Test
+ fun `Handle error if minos unavailable`() {
+ fakeFeatureFlags.overrideKey(
+ IssuingAcsServiceFeatureFlags.ENABLE_AUTHENTICATION_NOTIFICATION_CHANNEL,
+ "C_user_token",
+ true,
+ )
+ val request = EncryptedEntersektGetCardDetailsRequest.Builder().encryptedToken(ENTERSEKT_TEST_SUCCESS_JWE_TOKEN)
+ .build()
+ val response =
+ action.GetCardDetails(request)
+
+ assertEquals(response.messageID, "messageID")
+ assertNotNull(response.card)
+ assertNull(response.card!!.pan)
+ assertEquals(response.card!!.panReferenceID, "test-token")
+ assertEquals(response.card!!.status.toString(), "ACTIVE")
+ assertNotNull(response.customer!!.person?.name?.firstName, "first-name")
+ assertNotNull(response.customer!!.person?.name?.lastName, "last-name")
+ assertEquals(
+ response.customer!!.customerDigital!!.authenticationCommsOptions,
+ listOf(EntersektCustomerDigital.AuthenticationCommsOption.SMS),
+ )
+ assertEquals(response.customer!!.customerDigital!!.mobileNumber, TEST_MOBILE_NUMBER)
+ assertEquals(response.customer!!.customerBanking!!.bankingCustomerID, "test-token")
+ }
+
private fun assertEntersektCustomer(
response: EntersektGetCardDetailsResponse,
customerToken: String? = "C_usertoken",
cardToken: String,
cardState: EntersektCard.Status? = EntersektCard.Status.ACTIVE,
) {
assertEquals(response.customer!!.customerBanking!!.bankingCustomerID, cardToken)
assertEquals(response.customer!!.customerDigital!!.digitalCustomerID, customerToken)
if (cardState == EntersektCard.Status.ACTIVE) {
assertEquals(
response.customer!!.customerDigital!!.authenticationCommsOptions,
listOf(EntersektCustomerDigital.AuthenticationCommsOption.SMS),
)
assertEquals(response.customer!!.customerDigital!!.mobileNumber, Card.MOBILE_NUMBER_PLACE_HOLDER)
} else {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment