Skip to content

[Bug]: Crash when calling terminalCloudApi.sync(request) #1671

@DearDhruv

Description

@DearDhruv

Description

We are using this library for our Ticket Vending Machine where we make use of Adyen UX300 terminals with ANDROID app.
From past few weeks we are getting a crash within the library when making a call to POST /sync API call public TerminalAPIResponse sync(TerminalAPIRequest terminalAPIRequest).
We have been using this library for more than 2 years and atleast 1.5 years in production. Over the months we are making updates/upgrade to library versions.
Current version is 40.1.0 We are recently getting the crash out of the blue, we tried restoring the app to the previous version incase the something might have gone side ways. But, that did not help. We tried downgrading the library to 38.x still no success.
Kindly help.

Steps to reproduce

  1. Have UX300 LIVE terminal
  2. setup a payment and store related info on the cloud and dashboard.
  3. make the terminalCloudApi.sync(request) using the ip address of the terminal.

Actual behavior

When calling sync it crashes the library internally(and leads to Android app crash)
I have attached the crash logs, request logs at the bottom.

Here is the top stack trace:

java.lang.NoSuchMethodError java.lang.NoSuchMethodError: No static method supportedOptions(Ljava/lang/Class;)Ljava/util/Set; in class Ljdk/net/Sockets; or its super classes (declaration of 'jdk.net.Sockets' appears in /apex/com.android.art/javalib/core-oj.jar)
0	org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator.<clinit>(DefaultHttpClientConnectionOperator.java:87)
1	org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.createConnectionOperator(PoolingHttpClientConnectionManagerBuilder.java:325)
2	org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder.build(PoolingHttpClientConnectionManagerBuilder.java:344)
3	com.adyen.httpclient.AdyenHttpClient.createHttpClientWithSocketFactory(AdyenHttpClient.java:268)
4	com.adyen.httpclient.AdyenHttpClient.createCloseableHttpClient(AdyenHttpClient.java:258)
5	com.adyen.httpclient.AdyenHttpClient.request(AdyenHttpClient.java:127)
6	com.adyen.service.resource.Resource.request(Resource.java:120)
7	com.adyen.service.resource.Resource.request(Resource.java:94)
8	com.adyen.service.resource.Resource.request(Resource.java:70)
9	com.adyen.service.TerminalCloudAPI.sync(TerminalCloudAPI.java:75)
10	se.flygbussarna.adyen.AdyenImpl.executeTransactionSync(AdyenImpl.kt:206)
11	se.flygbussarna.blue.usecase.payment.AdyenPaymentUseCase$initiatePayment$1.invokeSuspend(AdyenPaymentUseCase.kt:122)

Expected behavior

It should open/activate the transaction on the UX300 terminal for user to make the payment.

Code snippet or screenshots (if applicable)

Crash happens when we call the terminalCloudApi.sync(request)

    override fun executeTransactionSync(request: TerminalAPIRequest): TerminalAPIResponse? =
        terminalCloudApi.sync(request)

Full class implementation

class AdyenImpl : AdyenAPI {

    private val clientProvider: ClientProvider = ClientProviderImpl()

    private val terminalCloudApi = TerminalCloudAPI(clientProvider.getClient())

    private fun getMessageHeader(
        serviceId: String,
        saleId: String,
        poiId: String,
    ) = MessageHeader().apply {
        messageClass = MessageClassType.SERVICE
        messageType = MessageType.REQUEST
        protocolVersion = "3.0"
        serviceID = serviceId
        saleID = saleId
        poiid = poiId
    }

    private fun getPaymentMessageHeader(
        serviceId: String,
        saleId: String,
        poiId: String,
    ) = getMessageHeader(
        serviceId = serviceId,
        saleId = saleId,
        poiId = poiId
    ).apply {
        messageCategory = MessageCategoryType.PAYMENT
    }

    private fun getAbortPaymentMessageHeader(
        serviceId: String,
        saleId: String,
        poiId: String,
    ) = getMessageHeader(
        serviceId = serviceId,
        saleId = saleId,
        poiId = poiId
    ).apply {
        messageCategory = MessageCategoryType.ABORT
    }

    private fun getDiagnosisMessageHeader(
        serviceId: String,
        saleId: String,
        poiId: String,
    ) = getMessageHeader(
        serviceId = serviceId,
        saleId = saleId,
        poiId = poiId
    ).apply {
        messageCategory = MessageCategoryType.DIAGNOSIS
    }


    private fun getVySaleData(bookingId: String) = SaleData().apply {
        val merchantApplication = CommonField().apply {
            version = "1.0"
            name = "Vy FlygBusSarna"
        }

        val applicationInfo = ApplicationInfo().apply {
            this.merchantApplication = merchantApplication
        }
        val saleToAcquirerData = SaleToAcquirerData().apply {
            // internal code hidden
        }
        val timestamp = DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar())
        val transactionIdentification = TransactionIdentification().apply {
            // internal code hidden
            this.timeStamp = timestamp
        }
        this.saleTransactionID = transactionIdentification
        this.saleToAcquirerData = saleToAcquirerData
        // internal code hidden
    }

    private fun getSaleToPOIRequest(
        serviceId: String,
        saleId: String,
        poiId: String,
    ) = SaleToPOIRequest().apply {
        messageHeader = getPaymentMessageHeader(
            serviceId = serviceId,
            saleId = saleId,
            poiId = poiId,
        )
    }

    private fun getSaleToPOIForAbortRequest(
        serviceId: String,
        saleId: String,
        poiId: String,
    ) = SaleToPOIRequest().apply {
        messageHeader = getAbortPaymentMessageHeader(
            serviceId = serviceId,
            saleId = saleId,
            poiId = poiId,
        )
    }


    private fun getPaymentTransaction(amount: Double, currency: String) =
        PaymentTransaction().apply {
            val amountsReq = AmountsReq().apply {
                this.currency = currency // "SEK"
                this.requestedAmount = BigDecimal.valueOf(amount)
            }
            this.amountsReq = amountsReq
        }

    private fun getAbortMessageReference(
        serviceIdForAbort: String,
        saleId: String,
        poiId: String,
    ) = MessageReference().apply {
        messageCategory = MessageCategoryType.PAYMENT
        serviceID = serviceIdForAbort
        saleID = saleId
        poiid = poiId
    }

    override fun generateTerminalRequestForPayment(
        amount: Double,
        currency: String,
        serviceId: String,
        saleId: String,
        poiId: String,
        bookingId: String,
    ): TerminalAPIRequest {
        val paymentRequest = PaymentRequest().apply {
            paymentTransaction = getPaymentTransaction(amount, currency)
            saleData = getVySaleData(bookingId)
        }
        val saleToPOIRequest = getSaleToPOIRequest(
            serviceId = serviceId,
            saleId = saleId,
            poiId = poiId,
        ).apply {
            this.paymentRequest = paymentRequest
        }
        return TerminalAPIRequest().apply {
            this.saleToPOIRequest = saleToPOIRequest
        }
    }

    override fun generateTerminalRequestForAbortingPayment(
        serviceIdForAbort: String,
        serviceId: String,
        saleId: String,
        poiId: String,
    ): TerminalAPIRequest {
        val abortMessageReference = getAbortMessageReference(
            serviceIdForAbort = serviceIdForAbort,
            saleId = saleId,
            poiId = poiId,
        )
        val abortRequest = AbortRequest().apply {
            messageReference = abortMessageReference
            abortReason = "user abort"
        }
        val saleToPOIRequest = getSaleToPOIForAbortRequest(
            serviceId = serviceId,
            saleId = saleId,
            poiId = poiId,
        ).apply {
            this.abortRequest = abortRequest
        }
        return TerminalAPIRequest().apply {
            this.saleToPOIRequest = saleToPOIRequest
        }
    }

    override fun transactionStatusRequest(requestServiceID: String) {}

    override fun executeTransactionSync(request: TerminalAPIRequest): TerminalAPIResponse? =
        terminalCloudApi.sync(request)

    override fun executeTransactionAsync(request: TerminalAPIRequest): String =
        terminalCloudApi.async(request)

    override fun diagnosisRequest(
        serviceId: String,
        saleId: String,
        poiId: String,
    ): TerminalAPIRequest {

        val saleToPOIRequest = SaleToPOIRequest()
        saleToPOIRequest.setMessageHeader(
            getDiagnosisMessageHeader(
                serviceId = serviceId,
                saleId = saleId,
                poiId = poiId,
            )
        )

        saleToPOIRequest.setDiagnosisRequest(
            DiagnosisRequest().apply {
                this.isHostDiagnosisFlag = true
            }
        )

        return TerminalAPIRequest().apply {
            this.saleToPOIRequest = saleToPOIRequest
        }
    }
}

Adyen Java API Library version

38.x, 39.x, 40.x

Java version

19 and 21

Operating System

Android

Additional context

TVM-755584-HiAz4qex0r6GP_dfNtZ-console.txt
TVM-755584-HiAz4qex0r6GP_dfNtZ-callstack.txt
request-log.txt

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions