From 65d656e2664fc9bae84c06b2cc135b8501ecc4dc Mon Sep 17 00:00:00 2001 From: gcatanese Date: Mon, 13 Apr 2026 10:06:13 +0200 Subject: [PATCH 1/8] Add docs for Cloud Device API --- README.md | 314 +++--------------- doc/CloudDeviceApi.md | 156 +++++++++ doc/TerminalApi.md | 249 ++++++++++++++ .../com/adyen/service/TerminalCloudAPI.java | 4 + 4 files changed, 450 insertions(+), 273 deletions(-) create mode 100644 doc/CloudDeviceApi.md create mode 100644 doc/TerminalApi.md diff --git a/README.md b/README.md index e2e5fc8c7..c99357adc 100644 --- a/README.md +++ b/README.md @@ -10,31 +10,33 @@ This is the officially supported Java library for using Adyen's APIs. The Library supports all APIs under the following services: -| API | Description | Service Name | Supported version | -|------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|-------------------| -| [BIN lookup API](https://docs.adyen.com/api-explorer/BinLookup/54/overview) | The BIN Lookup API provides endpoints for retrieving information based on a given BIN. | BinLookup | **v54** | -| [Checkout API](https://docs.adyen.com/api-explorer/Checkout/71/overview) | Our latest integration for accepting online payments. | Checkout | **v71** | -| [Capital API](https://docs.adyen.com/api-explorer/capital/1/overview) | Provides endpoints for embedding Adyen Capital into your marketplace or platform. | Capital | **v1** | -| [Configuration API](https://docs.adyen.com/api-explorer/balanceplatform/2/overview) | The Configuration API enables you to create a platform where you can onboard your users as account holders and create balance accounts, cards, and business accounts. | balanceplatform package subclasses | **v2** | -| [DataProtection API](https://docs.adyen.com/development-resources/data-protection-api) | Adyen Data Protection API provides a way for you to process [Subject Erasure Requests](https://gdpr-info.eu/art-17-gdpr/) as mandated in GDPR. Use our API to submit a request to delete shopper's data, including payment details and other related information (for example, delivery address or shopper email) | DataProtection | **v1** | -| [Disputes API](https://docs.adyen.com/api-explorer/Disputes/30/overview) | You can use the [Disputes API](https://docs.adyen.com/risk-management/disputes-api) to automate the dispute handling process so that you can respond to disputes and chargebacks as soon as they are initiated. The Disputes API lets you retrieve defense reasons, supply and delete defense documents, and accept or defend disputes. | DisputesApi | **v30** | -| [Legal Entity Management API](https://docs.adyen.com/api-explorer/legalentity/4/overview) | Manage legal entities that contain information required for verification. | legalentitymanagement package subclasses | **v4** | -| [Local/Cloud-based Terminal API](https://docs.adyen.com/point-of-sale/terminal-api-reference) | Our point-of-sale integration. | TerminalLocalAPI or TerminalCloudAPI | **v1** | -| [Management API](https://docs.adyen.com/api-explorer/Management/3/overview) | Configure and manage your Adyen company and merchant accounts, stores, and payment terminals. | management package subclasses | **v3** | -| [Open Banking API](https://docs.adyen.com/api-explorer/open-banking/1/overview) | The Open Banking API provides secure endpoints to share financial data and services with third parties. | openbanking package subclasses | **v1** | -| [Payments API](https://docs.adyen.com/api-explorer/Payment/68/overview) | Our classic integration for online payments. | Payment | **v68** | -| [Payments App API](https://docs.adyen.com/api-explorer/payments-app/1/overview) | The Payments App API is used to Board and manage the Adyen Payments App on your Android mobile devices. | PaymentsAppApi | **v1** | -| [Payouts API](https://docs.adyen.com/api-explorer/Payout/68/overview) | Endpoints for sending funds to your customers. | Payout | **v68** | -| [POS Mobile API](https://docs.adyen.com/api-explorer/possdk/68/overview) | The POS Mobile API is used in the mutual authentication flow between an Adyen Android or iOS [POS Mobile SDK](https://docs.adyen.com/point-of-sale/ipp-mobile/) and the Adyen payments platform. The POS Mobile SDK for Android or iOS devices enables businesses to accept in-person payments using a commercial off-the-shelf (COTS) device like a phone. For example, Tap to Pay transactions, or transactions on a mobile device in combination with a card reader | POS Mobile | **v68** | -| [POS Terminal Management API](https://docs.adyen.com/api-explorer/postfmapi/1/overview) | ~~Endpoints for managing your point-of-sale payment terminals.~~ ‼️ **Deprecated**: use instead the [Management API](https://docs.adyen.com/api-explorer/Management/latest/overview) for the management of your terminal fleet. | ~~TerminalManagement~~ | ~~**v1**~~ | -| [Recurring API](https://docs.adyen.com/api-explorer/Recurring/68/overview) | Endpoints for managing saved payment details. | Recurring | **v68** | -| [Session Authentication API](https://docs.adyen.com/api-explorer/sessionauthentication/1/overview) | Create and manage the JSON Web Tokens (JWT) required for integrating [Onboarding](https://docs.adyen.com/platforms/onboard-users/components) and [Platform Experience](https://docs.adyen.com/platforms/build-user-dashboards) components. | SessionAuthentication | **v1** | -| [Stored Value API](https://docs.adyen.com/payment-methods/gift-cards/stored-value-api) | Manage both online and point-of-sale gift cards and other stored-value cards. | StoredValue | **v46** | -| [Transfers API](https://docs.adyen.com/api-explorer/transfers/4/overview) | The Transfers API provides endpoints that can be used to get information about all your transactions, move funds within your balance platform or send funds from your balance platform to a transfer instrument. | Transfers | **v4** | -| [Classic Platforms Account API](https://docs.adyen.com/api-explorer/Account/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformAccountApi | **v6** | -| [Classic Platforms Fund API](https://docs.adyen.com/api-explorer/Fund/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformFundApi | **v6** | -| [Classic Platforms Hosted Onboarding Page API](https://docs.adyen.com/api-explorer/Hop/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformHopApi | **v6** | -| [Classic Platforms Notification Configuration API](https://docs.adyen.com/api-explorer/NotificationConfiguration/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformConfigurationApi | **v6** | +| API | Description | Service Name | Supported version | +|----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|-------------------| +| [BIN lookup API](https://docs.adyen.com/api-explorer/BinLookup/54/overview) | The BIN Lookup API provides endpoints for retrieving information based on a given BIN. | BinLookup | **v54** | +| [Checkout API](https://docs.adyen.com/api-explorer/Checkout/71/overview) | Our latest integration for accepting online payments. | Checkout | **v71** | +| [Capital API](https://docs.adyen.com/api-explorer/capital/1/overview) | Provides endpoints for embedding Adyen Capital into your marketplace or platform. | Capital | **v1** | +| [Cloud device API](https://docs.adyen.com/api-explorer/cloud-device-api/latest/overview) | Cloud device point-of-sale solution for cloud integrations. | clouddevice package subclasses | **v1** | +| [Configuration API](https://docs.adyen.com/api-explorer/balanceplatform/2/overview) | The Configuration API enables you to create a platform where you can onboard your users as account holders and create balance accounts, cards, and business accounts. | balanceplatform package subclasses | **v2** | +| [DataProtection API](https://docs.adyen.com/development-resources/data-protection-api) | Adyen Data Protection API provides a way for you to process [Subject Erasure Requests](https://gdpr-info.eu/art-17-gdpr/) as mandated in GDPR. Use our API to submit a request to delete shopper's data, including payment details and other related information (for example, delivery address or shopper email) | DataProtection | **v1** | +| [Disputes API](https://docs.adyen.com/api-explorer/Disputes/30/overview) | You can use the [Disputes API](https://docs.adyen.com/risk-management/disputes-api) to automate the dispute handling process so that you can respond to disputes and chargebacks as soon as they are initiated. The Disputes API lets you retrieve defense reasons, supply and delete defense documents, and accept or defend disputes. | DisputesApi | **v30** | +| [Legal Entity Management API](https://docs.adyen.com/api-explorer/legalentity/4/overview) | Manage legal entities that contain information required for verification. | legalentitymanagement package subclasses | **v4** | +| [Cloud Terminal API](https://docs.adyen.com/api-explorer/terminal-api/latest/overview) | Our former point-of-sale solution for cloud integrations. You should consider migrating to the [Cloud device API](https://docs.adyen.com/api-explorer/cloud-device-api/latest/overview) for building your In-Person Payments cloud integration. | TerminalCloudAPI | **v1** | +| [Local Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture) | Our point-of-sale solution for local integrations. | TerminalLocalAPI | **v1** | +| [Management API](https://docs.adyen.com/api-explorer/Management/3/overview) | Configure and manage your Adyen company and merchant accounts, stores, and payment terminals. | management package subclasses | **v3** | +| [Open Banking API](https://docs.adyen.com/api-explorer/open-banking/1/overview) | The Open Banking API provides secure endpoints to share financial data and services with third parties. | openbanking package subclasses | **v1** | +| [Payments API](https://docs.adyen.com/api-explorer/Payment/68/overview) | Our classic integration for online payments. | Payment | **v68** | +| [Payments App API](https://docs.adyen.com/api-explorer/payments-app/1/overview) | The Payments App API is used to Board and manage the Adyen Payments App on your Android mobile devices. | PaymentsAppApi | **v1** | +| [Payouts API](https://docs.adyen.com/api-explorer/Payout/68/overview) | Endpoints for sending funds to your customers. | Payout | **v68** | +| [POS Mobile API](https://docs.adyen.com/api-explorer/possdk/68/overview) | The POS Mobile API is used in the mutual authentication flow between an Adyen Android or iOS [POS Mobile SDK](https://docs.adyen.com/point-of-sale/ipp-mobile/) and the Adyen payments platform. The POS Mobile SDK for Android or iOS devices enables businesses to accept in-person payments using a commercial off-the-shelf (COTS) device like a phone. For example, Tap to Pay transactions, or transactions on a mobile device in combination with a card reader | POS Mobile | **v68** | +| [~~POS Terminal Management API~~](https://docs.adyen.com/api-explorer/postfmapi/1/overview) | ~~Endpoints for managing your point-of-sale payment terminals.~~
‼️ **Deprecated**: use instead the [Management API](https://docs.adyen.com/api-explorer/Management/latest/overview) for the management of your terminal fleet. | ~~TerminalManagement~~ | ~~**v1**~~ | +| [Recurring API](https://docs.adyen.com/api-explorer/Recurring/68/overview) | Endpoints for managing saved payment details. | Recurring | **v68** | +| [Session Authentication API](https://docs.adyen.com/api-explorer/sessionauthentication/1/overview) | Create and manage the JSON Web Tokens (JWT) required for integrating [Onboarding](https://docs.adyen.com/platforms/onboard-users/components) and [Platform Experience](https://docs.adyen.com/platforms/build-user-dashboards) components. | SessionAuthentication | **v1** | +| [Stored Value API](https://docs.adyen.com/payment-methods/gift-cards/stored-value-api) | Manage both online and point-of-sale gift cards and other stored-value cards. | StoredValue | **v46** | +| [Transfers API](https://docs.adyen.com/api-explorer/transfers/4/overview) | The Transfers API provides endpoints that can be used to get information about all your transactions, move funds within your balance platform or send funds from your balance platform to a transfer instrument. | Transfers | **v4** | +| [Classic Platforms Account API](https://docs.adyen.com/api-explorer/Account/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformAccountApi | **v6** | +| [Classic Platforms Fund API](https://docs.adyen.com/api-explorer/Fund/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformFundApi | **v6** | +| [Classic Platforms Hosted Onboarding Page API](https://docs.adyen.com/api-explorer/Hop/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformHopApi | **v6** | +| [Classic Platforms Notification Configuration API](https://docs.adyen.com/api-explorer/NotificationConfiguration/6/overview) | This API is used for the classic integration. If you are just starting your implementation, refer to our new integration guide instead. | ClassicPlatformConfigurationApi | **v6** | ## Supported Webhook versions The library supports all webhooks under the following model directories: @@ -61,11 +63,9 @@ the [API Explorer](https://docs.adyen.com/api-explorer/). ## Prerequisites -* [Adyen test account](https://docs.adyen.com/get-started-with-adyen) -* [API key](https://docs.adyen.com/development-resources/api-credentials#generate-api-key). For testing, your API - credential needs to have - the [API PCI Payments role](https://docs.adyen.com/development-resources/api-credentials#roles). -* Java 11 or higher +* An [Adyen account](https://docs.adyen.com/get-started-with-adyen) +* An [API key](https://docs.adyen.com/development-resources/api-credentials#generate-api-key). +* Build on Java 11 or higher ## Installation @@ -406,258 +406,27 @@ Client client = new Client(sslContext, apiKey); ~~~~ -## Using the Cloud Terminal API -For In-Person Payments integrations with the [Cloud Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/), you must initialise the Client **setting the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud): -``` java -// Step 1: Import the required classes -import com.adyen.Client; -import com.adyen.enums.Environment; -import com.adyen.service.TerminalCloudAPI; -import com.adyen.model.nexo.*; -import com.adyen.model.terminal.*; +## In-person payments integration -// Step 2: Initialize the client object -Config config = new Config() - .environment(Environment.LIVE) - .terminalApiRegion(Region.EU) - .apiKey(apiKey); -Client client = new Client(config); +Build a feature-rich [in-person payments](https://docs.adyen.com/point-of-sale/) integrations that accept payments around the world, with global and local payment methods and create a unique shopping experience for your customers. -// Step 3: Initialize the API object -TerminalCloudAPI terminalCloudApi = new TerminalCloudAPI(client); - -// Step 4: Create the request object -String serviceID = "123456789"; -String saleID = "POS-SystemID12345"; -String POIID = "Your Device Name(eg V400m-123456789)"; - -// Use a unique transaction for every other transaction you perform -String transactionID = "TransactionID"; -TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); -SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); - -MessageHeader messageHeader = new MessageHeader(); -messageHeader.setMessageClass(MessageClassType.SERVICE); -messageHeader.setMessageCategory(MessageCategoryType.PAYMENT); -messageHeader.setMessageType(MessageType.REQUEST); -messageHeader.setProtocolVersion("3.0"); -messageHeader.setServiceID(serviceID); -messageHeader.setSaleID(saleID); -messageHeader.setPOIID(POIID); - -saleToPOIRequest.setMessageHeader(messageHeader); - -com.adyen.model.nexo.PaymentRequest paymentRequest = new com.adyen.model.nexo.PaymentRequest(); -SaleData saleData = new SaleData(); -TransactionIdentification transactionIdentification = new TransactionIdentification(); -transactionIdentification.setTransactionID("001"); -XMLGregorianCalendar timestamp = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar()); -transactionIdentification.setTimeStamp(timestamp); -saleData.setSaleTransactionID(transactionIdentification); - -SaleToAcquirerData saleToAcquirerData = new SaleToAcquirerData(); -ApplicationInfo applicationInfo = new ApplicationInfo(); -CommonField merchantApplication = new CommonField(); -merchantApplication.setVersion("1"); -merchantApplication.setName("Test"); -applicationInfo.setMerchantApplication(merchantApplication); -saleToAcquirerData.setApplicationInfo(applicationInfo); -saleData.setSaleToAcquirerData(saleToAcquirerData); - -PaymentTransaction paymentTransaction = new PaymentTransaction(); -AmountsReq amountsReq = new AmountsReq(); -amountsReq.setCurrency("EUR"); -amountsReq.setRequestedAmount(BigDecimal.valueOf(1000)); -paymentTransaction.setAmountsReq(amountsReq); - -paymentRequest.setPaymentTransaction(paymentTransaction); -paymentRequest.setSaleData(saleData); - -saleToPOIRequest.setPaymentRequest(paymentRequest); - -terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); - -// Step 5: Make the request -TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest); -``` +### Using the Cloud Device API -### Optional: perform an abort request - -To perform an [abort request](https://docs.adyen.com/point-of-sale/basic-tapi-integration/cancel-a-transaction/) you can use the following example: -``` java -TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); -SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); - -MessageHeader messageHeader = new MessageHeader(); -messageHeader.setMessageClass(MessageClassType.SERVICE); -messageHeader.setMessageCategory(MessageCategoryType.ABORT); -messageHeader.setMessageType(MessageType.REQUEST); -messageHeader.setProtocolVersion("3.0"); -messageHeader.setServiceID("Different service ID"); -messageHeader.setSaleID(saleID); -messageHeader.setPOIID(POIID); - -AbortRequest abortRequest = new AbortRequest(); -abortRequest.setAbortReason("MerchantAbort"); -MessageReference messageReference = new MessageReference(); -messageReference.setMessageCategory(MessageCategoryType.PAYMENT); -messageReference.setSaleID(saleID); -messageReference.setPOIID(POIID); -// Service ID of the payment you're aborting -messageReference.setServiceID(serviceID); -abortRequest.setMessageReference(messageReference); - -saleToPOIRequest.setAbortRequest(abortRequest); -saleToPOIRequest.setMessageHeader(messageHeader); - -terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); - -TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest); -``` +For In-Person Payments integrations, the recommended solution is the [Cloud Device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview). -### Optional: perform a status request - -To perform a [status request](https://docs.adyen.com/point-of-sale/basic-tapi-integration/verify-transaction-status/) you can use the following example: -```java -TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); -SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); - -MessageHeader messageHeader = new MessageHeader(); -messageHeader.setMessageClass(MessageClassType.SERVICE); -messageHeader.setMessageCategory(MessageCategoryType.TRANSACTION_STATUS); -messageHeader.setMessageType(MessageType.REQUEST); -messageHeader.setProtocolVersion("3.0"); -messageHeader.setServiceID("Different service ID"); -messageHeader.setSaleID(saleID); -messageHeader.setPOIID(POIID); - -TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); -transactionStatusRequest.setReceiptReprintFlag(true); -transactionStatusRequest.getDocumentQualifier().add(DocumentQualifierType.CASHIER_RECEIPT); -transactionStatusRequest.getDocumentQualifier().add(DocumentQualifierType.CUSTOMER_RECEIPT); -MessageReference messageReference = new MessageReference(); -messageReference.setMessageCategory(MessageCategoryType.PAYMENT); -messageReference.setSaleID(saleID); -// serviceID of the transaction you want the status update from -messageReference.setServiceID(serviceID); -transactionStatusRequest.setMessageReference(messageReference); - -saleToPOIRequest.setTransactionStatusRequest(transactionStatusRequest); -saleToPOIRequest.setMessageHeader(messageHeader); - -terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); - -TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest); -``` +Check the [Cloud Device API README](doc/CloudDeviceApi.md). -### Helper classes +### Using the Terminal API -Use `PredefinedContentHelper` to parse Display notification types which you find in `PredefinedContent->ReferenceID` -```java -PredefinedContentHelper helper = new PredefinedContentHelper(predefinedContent.getReferenceID()); +With the [Terminal API](https://docs.adyen.com/api-explorer/terminal-api/1/overview) you can send and receive Terminal API messages in the following ways: -// Safely extract and use the event type with Optional -helper.getEvent().ifPresent(event -> { - System.out.println("Received event: " + event); - if (event == PredefinedContentHelper.DisplayNotificationEvent.PIN_ENTERED) { - // Handle PIN entry event - System.out.println("The user has entered their PIN."); - } -}); -``` +* Local communications: using your local network, your POS system sends the request directly to the IP address of the terminal, and receives the result synchronously. +* Cloud communications: using the internet to access the cloud `/sync` and `/async` endpoints. You should consider adopting the [Cloud Device API](doc/CloudDeviceApi.md) instead. -## Using the Local Terminal API Integration -The request and response payloads are identical to the Cloud Terminal API, however, additional encryption details are required to perform the requests. -### Local terminal API Using Keystore -~~~~ java -// Import the required classes -import com.adyen.Client; -import com.adyen.Config; -import com.adyen.enums.Environment; -import com.adyen.httpclient.TerminalLocalAPIHostnameVerifier; -import com.adyen.service.TerminalLocalAPI; -import com.adyen.model.terminal.security.*; -import com.adyen.model.terminal.*; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.security.KeyStore; -import java.security.SecureRandom; - -// Create a KeyStore for the terminal certificate -KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); -keyStore.load(null, null); -keyStore.setCertificateEntry("adyenRootCertificate", adyenRootCertificate); - -// Create a TrustManagerFactory that trusts the CAs in our KeyStore -TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); -trustManagerFactory.init(keyStore); - -// Create an SSLContext with the desired protocol that uses our TrustManagers -SSLContext sslContext = SSLContext.getInstance("SSL"); -sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); - -// Configure a client for TerminalLocalAPI -Config config = new Config(); -config.setEnvironment(environment); -config.setTerminalApiLocalEndpoint("https://" + terminalIpAddress); -config.setSSLContext(sslContext); -config.setHostnameVerifier(new TerminalLocalAPIHostnameVerifier(environment)); -Client client = new Client(config); - -// Create your SecurityKey object used for encrypting the payload (keyIdentifier/passphrase you set up beforehand in CA) -SecurityKey securityKey = new SecurityKey(); -securityKey.setKeyVersion(1); -securityKey.setAdyenCryptoVersion(1); -securityKey.setKeyIdentifier("keyIdentifier"); -securityKey.setPassphrase("passphrase"); - -// Use TerminalLocalAPI -TerminalLocalAPI terminalLocalAPI = new TerminalLocalAPI(client, securityKey); -TerminalAPIResponse terminalAPIResponse = terminalLocalAPI.request(terminalAPIRequest); -~~~~ -## Using Attachments in Document API -When providing Attachments, ensure content is provided as a byte array. It's important to convert it to a Base64-encoded string before initiating the request. - -## Using the Local Terminal API Integration without Encryption (Only on TEST) -If you wish to develop the Local Terminal API integration parallel to your encryption implementation, you can opt for the unencrypted version. Be sure to remove any encryption details from the CA terminal config page. Consider this ONLY for development and testing on localhost. -```java -// Step 1: Import the required classes -import com.adyen.service.TerminalLocalAPI; -import com.adyen.model.nexo.*; -import com.adyen.model.terminal.*; -import javax.net.ssl.SSLContext; - -// Step 2: Add your Certificate Path and Local Endpoint to the config path. -Client client = new Client(); -client.getConfig().setTerminalApiLocalEndpoint("The IP of your terminal (eg https://192.168.47.169)"); -client.getConfig().setEnvironment(Environment.TEST); -config.setSSLContext(createTrustSSLContext()); // Trust all certificates for testing only -client.setConfig(config); - -// Step 3: Create an SSL context that accepts all certificates (Use in TEST only). -SSLContext createTrustSSLContext() throws Exception { - TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } - checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - } - }; - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - return sc; -} - -// Step 4: Construct a TerminalAPIRequest object -Gson gson = new GsonBuilder().create(); -TerminalAPIRequest terminalAPIPaymentRequest = new TerminalAPIRequest(); - -// Step 5: Make the request -TerminalAPIResponse terminalAPIResponse = terminalLocalAPI.request(terminalAPIRequest); -``` +Check the [Terminal API README](doc/TerminalApi.md). -### Example integrations +## Example integrations For a closer look at how our Java library works, you can clone one of our example integrations: * [Java Spring Boot example integration](https://github.com/adyen-examples/adyen-java-spring-online-payments). * [Kotlin Spring Boot example integration](https://github.com/adyen-examples/adyen-kotlin-spring-online-payments). @@ -685,6 +454,5 @@ This repository is available under the [MIT license](https://github.com/Adyen/ad ## See also * Example integrations: * [Java Spring Boot](https://github.com/adyen-examples/adyen-java-spring-online-payments) - * [Kotlin Spring Boot](https://github.com/adyen-examples/adyen-kotlin-spring-online-payments) * [Adyen docs](https://docs.adyen.com/) * [API Explorer](https://docs.adyen.com/api-explorer/) diff --git a/doc/CloudDeviceApi.md b/doc/CloudDeviceApi.md new file mode 100644 index 000000000..adfcf5bdd --- /dev/null +++ b/doc/CloudDeviceApi.md @@ -0,0 +1,156 @@ +# Cloud Device API + +The [Cloud Device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview) is our solution to create best-in-class in-person payments integrations. + +With the Cloud device API you can: + +- send Terminal API requests to a cloud endpoint. You can use this communication method when it is not an option to send Terminal API requests over your local network directly to a payment terminal. +- check the cloud connection of a payment terminal or of a device used in a Mobile solution for in-person payments. + +## Benefits of the Cloud Device API + +The Cloud Device API offers the following benefits: +- access to API logs in the Customer Area for troubleshooting errors +- using a version strategy for the API endpoints for controlled and safer rollouts +- improved reliability and security (OAuth support) + +New features and products will be released exclusively on the Cloud Device API + +## Use the Cloud Device API + +### Setup + +First you must initialise the Client **setting the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud): +``` java +// Import the required classes +import com.adyen.Client; +import com.adyen.enums.Environment; +import com.adyen.service.checkout.PaymentsApi; +import com.adyen.model.checkout.*; + +// Setup Client and Service +Client client = new Client("Your X-API-KEY", Environment.TEST); +CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(client); + +``` + +### Send a payment SYNC request + +```java + +SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); + +MessageHeader messageHeader = new MessageHeader(); + messageHeader.setProtocolVersion("3.0"); + messageHeader.setMessageClass(MessageClassType.SERVICE); + messageHeader.setMessageCategory(MessageCategoryType.PAYMENT); + messageHeader.setMessageType(MessageType.REQUEST); + messageHeader.setSaleID("001"); + messageHeader.setServiceID("001"); + messageHeader.setPOIID("P400Plus-123456789"); + + saleToPOIRequest.setMessageHeader(messageHeader); + +PaymentRequest paymentRequest = new PaymentRequest(); + +SaleData saleData = new SaleData(); +TransactionIdentification transactionIdentification = new TransactionIdentification(); + transactionIdentification.setTransactionID("001"); +OffsetDateTime timestamp = OffsetDateTime.now(ZoneOffset.UTC); + transactionIdentification.setTimeStamp(timestamp); + saleData.setSaleTransactionID(transactionIdentification); + +PaymentTransaction paymentTransaction = new PaymentTransaction(); +AmountsReq amountsReq = new AmountsReq(); + amountsReq.setCurrency("EUR"); + amountsReq.setRequestedAmount(BigDecimal.ONE); + paymentTransaction.setAmountsReq(amountsReq); + + paymentRequest.setSaleData(saleData); + paymentRequest.setPaymentTransaction(paymentTransaction); + + saleToPOIRequest.setPaymentRequest(paymentRequest); + +CloudDeviceApiRequest cloudDeviceApiRequest = new CloudDeviceApiRequest(); + cloudDeviceApiRequest.setSaleToPOIRequest(saleToPOIRequest); + +var response = cloudDeviceApi.sendSync("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); + +``` + + +### Send a payment ASYNC request + +If you choose to receive the response asynchronously, you only need to use a different method (`sendAsync`). +Don't forget to set up [event notifications](https://docs.adyen.com/point-of-sale/design-your-integration/notifications/event-notifications/) in the CA to be able to receive the Cloud Device API responses. + +```java + +... + +// define the request (same as per sendSync) +CloudDeviceApiRequest cloudDeviceApiRequest = new CloudDeviceApiRequest(); +cloudDeviceApiRequest.setSaleToPOIRequest(saleToPOIRequest); + +CloudDeviceApiAsyncResponse response = cloudDeviceApi.sendAsync("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); + +// +if("ok".equals(response.getResult())) { + + // success +} else { + // request failed: see details in the EventNotification object + EventNotification eventNotification = response.getSaleToPOIRequest().getEventNotification(); +} +``` + +### Verify the status of the terminals + + +The Cloud Device API allows your integration to check the status of the terminals. + +```java + +// list of payment terminals or SDK mobile installation IDs +ConnectedDevicesResponse response = cloudDeviceApi.getConnectedDevices("myMerchant"); +System.out.println(response.getUniqueDeviceIds()); + +// check the payment terminal or SDK mobile installation ID +DeviceStatusResponse response = cloudDeviceApi.getDeviceStatus("myMerchant", "AMS1-000168242800763"); +System.out.println(response.getStatus()); +``` + +### Protect cloud communication + +The Adyen Java library supports encrypting request and response payloads, allowing you to secure communication between your integration and the cloud. + +```java + +// Encryption credentials from the Terminal configuration on CA +EncryptionCredentialDetails encryptionCredentialDetails = + new EncryptionCredentialDetails() + .adyenCryptoVersion(0) + .keyIdentifier("CryptoKeyIdentifier12345") + .keyVersion(0) + .passphrase("p@ssw0rd123456"); + +var response = + cloudDeviceApi.sendEncryptedSync( + "TestMerchantAccount", + "V400m-123456789", + cloudDeviceApiRequest, + encryptionCredentialDetails); + +System.out.println(response); +``` + +In case of asynchronous integration, you can decrypt the payload of the event notifications using `decryptNotification()` method. + +```java +// JSON with encrypted SaleToPOIResponse (for async responses) or SaleToPOIRequest (for event notifications) +var payload = "..."; + +var response = cloudDeviceApi.decryptNotification(payload, encryptionCredentialDetails); +System.out.println(response); + +``` \ No newline at end of file diff --git a/doc/TerminalApi.md b/doc/TerminalApi.md new file mode 100644 index 000000000..e515166af --- /dev/null +++ b/doc/TerminalApi.md @@ -0,0 +1,249 @@ +### Using the Terminal API +For In-Person Payments integrations with the [Cloud Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/), you must initialise the Client **setting the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud): +``` java +// Step 1: Import the required classes +import com.adyen.Client; +import com.adyen.enums.Environment; +import com.adyen.service.TerminalCloudAPI; +import com.adyen.model.nexo.*; +import com.adyen.model.terminal.*; + +// Step 2: Initialize the client object +Client client = new Client("Your YOUR_API_KEY", Environment.TEST); + +// for LIVE environment use +// Config config = new Config(); +// config.setEnvironment(Environment.LIVE); +// config.setTerminalApiRegion(Region.EU); +// Client client = new Client(config); + +// Step 3: Initialize the API object +TerminalCloudAPI terminalCloudApi = new TerminalCloudAPI(client); + +// Step 4: Create the request object +String serviceID = "123456789"; +String saleID = "POS-SystemID12345"; +String POIID = "Your Device Name(eg V400m-123456789)"; + +// Use a unique transaction for every other transaction you perform +String transactionID = "TransactionID"; +TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); +SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); + +MessageHeader messageHeader = new MessageHeader(); +messageHeader.setMessageClass(MessageClassType.SERVICE); +messageHeader.setMessageCategory(MessageCategoryType.PAYMENT); +messageHeader.setMessageType(MessageType.REQUEST); +messageHeader.setProtocolVersion("3.0"); +messageHeader.setServiceID(serviceID); +messageHeader.setSaleID(saleID); +messageHeader.setPOIID(POIID); + +saleToPOIRequest.setMessageHeader(messageHeader); + +com.adyen.model.nexo.PaymentRequest paymentRequest = new com.adyen.model.nexo.PaymentRequest(); +SaleData saleData = new SaleData(); +TransactionIdentification transactionIdentification = new TransactionIdentification(); +transactionIdentification.setTransactionID("001"); +XMLGregorianCalendar timestamp = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar()); +transactionIdentification.setTimeStamp(timestamp); +saleData.setSaleTransactionID(transactionIdentification); + +SaleToAcquirerData saleToAcquirerData = new SaleToAcquirerData(); +ApplicationInfo applicationInfo = new ApplicationInfo(); +CommonField merchantApplication = new CommonField(); +merchantApplication.setVersion("1"); +merchantApplication.setName("Test"); +applicationInfo.setMerchantApplication(merchantApplication); +saleToAcquirerData.setApplicationInfo(applicationInfo); +saleData.setSaleToAcquirerData(saleToAcquirerData); + +PaymentTransaction paymentTransaction = new PaymentTransaction(); +AmountsReq amountsReq = new AmountsReq(); +amountsReq.setCurrency("EUR"); +amountsReq.setRequestedAmount(BigDecimal.valueOf(1000)); +paymentTransaction.setAmountsReq(amountsReq); + +paymentRequest.setPaymentTransaction(paymentTransaction); +paymentRequest.setSaleData(saleData); + +saleToPOIRequest.setPaymentRequest(paymentRequest); + +terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); + +// Step 5: Make the request +TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest); +``` + +#### Optional: perform an abort request + +To perform an [abort request](https://docs.adyen.com/point-of-sale/basic-tapi-integration/cancel-a-transaction/) you can use the following example: +``` java +TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); +SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); + +MessageHeader messageHeader = new MessageHeader(); +messageHeader.setMessageClass(MessageClassType.SERVICE); +messageHeader.setMessageCategory(MessageCategoryType.ABORT); +messageHeader.setMessageType(MessageType.REQUEST); +messageHeader.setProtocolVersion("3.0"); +messageHeader.setServiceID("Different service ID"); +messageHeader.setSaleID(saleID); +messageHeader.setPOIID(POIID); + +AbortRequest abortRequest = new AbortRequest(); +abortRequest.setAbortReason("MerchantAbort"); +MessageReference messageReference = new MessageReference(); +messageReference.setMessageCategory(MessageCategoryType.PAYMENT); +messageReference.setSaleID(saleID); +messageReference.setPOIID(POIID); +// Service ID of the payment you're aborting +messageReference.setServiceID(serviceID); +abortRequest.setMessageReference(messageReference); + +saleToPOIRequest.setAbortRequest(abortRequest); +saleToPOIRequest.setMessageHeader(messageHeader); + +terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); + +TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest); +``` + +#### Optional: perform a status request + +To perform a [status request](https://docs.adyen.com/point-of-sale/basic-tapi-integration/verify-transaction-status/) you can use the following example: +``` java +TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); +SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); + +MessageHeader messageHeader = new MessageHeader(); +messageHeader.setMessageClass(MessageClassType.SERVICE); +messageHeader.setMessageCategory(MessageCategoryType.TRANSACTION_STATUS); +messageHeader.setMessageType(MessageType.REQUEST); +messageHeader.setProtocolVersion("3.0"); +messageHeader.setServiceID("Different service ID"); +messageHeader.setSaleID(saleID); +messageHeader.setPOIID(POIID); + +TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); +transactionStatusRequest.setReceiptReprintFlag(true); +transactionStatusRequest.getDocumentQualifier().add(DocumentQualifierType.CASHIER_RECEIPT); +transactionStatusRequest.getDocumentQualifier().add(DocumentQualifierType.CUSTOMER_RECEIPT); +MessageReference messageReference = new MessageReference(); +messageReference.setMessageCategory(MessageCategoryType.PAYMENT); +messageReference.setSaleID(saleID); +// serviceID of the transaction you want the status update from +messageReference.setServiceID(serviceID); +transactionStatusRequest.setMessageReference(messageReference); + +saleToPOIRequest.setTransactionStatusRequest(transactionStatusRequest); +saleToPOIRequest.setMessageHeader(messageHeader); + +terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); + +TerminalAPIResponse terminalAPIResponse = terminalCloudApi.sync(terminalAPIRequest); +``` +### Helper classes + +Use `PredefinedContentHelper` to parse Display notification types which you find in `PredefinedContent->ReferenceID` +```java +PredefinedContentHelper helper = new PredefinedContentHelper(predefinedContent.getReferenceID()); + +// Safely extract and use the event type with Optional +helper.getEvent().ifPresent(event -> { + System.out.println("Received event: " + event); + if (event == PredefinedContentHelper.DisplayNotificationEvent.PIN_ENTERED) { + // Handle PIN entry event + System.out.println("The user has entered their PIN."); + } +}); +``` + +### Using the Local Terminal API Integration +The procedure to send In-Person requests using [Terminal API over Local Connection](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/local/) is similar to the Cloud Terminal API one, however, additional encryption details are required to perform the requests. Make sure to [install the certificate as described here](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/local/#protect-communications) +#### Local terminal API Using Keystore +```java +// Import the required classes +import com.adyen.Client; +import com.adyen.Config; +import com.adyen.enums.Environment; +import com.adyen.httpclient.TerminalLocalAPIHostnameVerifier; +import com.adyen.service.TerminalLocalAPI; +import com.adyen.model.terminal.security.*; +import com.adyen.model.terminal.*; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.security.KeyStore; +import java.security.SecureRandom; + +// Create a KeyStore for the terminal certificate +KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); +keyStore.load(null, null); +keyStore.setCertificateEntry("adyenRootCertificate", adyenRootCertificate); + +// Create a TrustManagerFactory that trusts the CAs in our KeyStore +TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); +trustManagerFactory.init(keyStore); + +// Create an SSLContext with the desired protocol that uses our TrustManagers +SSLContext sslContext = SSLContext.getInstance("SSL"); +sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); + +// Configure a client for TerminalLocalAPI +Config config = new Config(); +config.setEnvironment(environment); +config.setTerminalApiLocalEndpoint("https://" + terminalIpAddress); +config.setSSLContext(sslContext); +config.setHostnameVerifier(new TerminalLocalAPIHostnameVerifier(environment)); +Client client = new Client(config); + +// Create your SecurityKey object used for encrypting the payload (keyIdentifier/passphrase you set up beforehand in CA) +SecurityKey securityKey = new SecurityKey(); +securityKey.setKeyVersion(1); +securityKey.setAdyenCryptoVersion(1); +securityKey.setKeyIdentifier("keyIdentifier"); +securityKey.setPassphrase("passphrase"); + +// Use TerminalLocalAPI +TerminalLocalAPI terminalLocalAPI = new TerminalLocalAPI(client, securityKey); +TerminalAPIResponse terminalAPIResponse = terminalLocalAPI.request(terminalAPIRequest); +``` + +#### Using the Local Terminal API Integration without Encryption (Only on TEST) +If you wish to develop the Local Terminal API integration parallel to your encryption implementation, you can opt for the unencrypted version. Be sure to remove any encryption details from the CA terminal config page. Consider this ONLY for development and testing on localhost. + +```java +// Step 1: Import the required classes +import com.adyen.service.TerminalLocalAPI; +import com.adyen.model.nexo.*; +import com.adyen.model.terminal.*; +import javax.net.ssl.SSLContext; + +// Step 2: Add your Certificate Path and Local Endpoint to the config path. +Client client = new Client(); +client.getConfig().setTerminalApiLocalEndpoint("The IP of your terminal (eg https://192.168.47.169)"); +client.getConfig().setEnvironment(Environment.TEST); +config.setSSLContext(createTrustSSLContext()); // Trust all certificates for testing only +client.setConfig(config); + +// Step 3: Create an SSL context that accepts all certificates (Use in TEST only). +SSLContext createTrustSSLContext() throws Exception { + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } + checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} + checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} + } + }; + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + return sc; +} + +// Step 4: Construct a TerminalAPIRequest object +Gson gson = new GsonBuilder().create(); +TerminalAPIRequest terminalAPIPaymentRequest = new TerminalAPIRequest(); + +// Step 5: Make the request +TerminalAPIResponse terminalAPIResponse = terminalLocalAPI.request(terminalAPIRequest); +``` \ No newline at end of file diff --git a/src/main/java/com/adyen/service/TerminalCloudAPI.java b/src/main/java/com/adyen/service/TerminalCloudAPI.java index 0c5ef991e..6544cc53e 100644 --- a/src/main/java/com/adyen/service/TerminalCloudAPI.java +++ b/src/main/java/com/adyen/service/TerminalCloudAPI.java @@ -32,6 +32,10 @@ import com.google.gson.reflect.TypeToken; import java.io.IOException; +/** + * Terminal API for cloud integrations. + * You should consider migrating to {@link com.adyen.service.clouddevice.CloudDeviceApi}. + */ public class TerminalCloudAPI extends ApiKeyAuthenticatedService { private final Async terminalApiAsync; From 3ac7a8dae7e304389510b58331f2266e523aab4a Mon Sep 17 00:00:00 2001 From: gcatanese Date: Mon, 13 Apr 2026 14:15:03 +0200 Subject: [PATCH 2/8] Correct snippets --- doc/CloudDeviceApi.md | 77 ++++++++++++++++++++++--------------------- doc/TerminalApi.md | 50 ++++++++++++---------------- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/doc/CloudDeviceApi.md b/doc/CloudDeviceApi.md index adfcf5bdd..c0ae1d8cb 100644 --- a/doc/CloudDeviceApi.md +++ b/doc/CloudDeviceApi.md @@ -1,22 +1,22 @@ -# Cloud Device API +# Cloud device API -The [Cloud Device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview) is our solution to create best-in-class in-person payments integrations. +The [Cloud device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview) is our solution to create best-in-class in-person payments integrations. With the Cloud device API you can: - send Terminal API requests to a cloud endpoint. You can use this communication method when it is not an option to send Terminal API requests over your local network directly to a payment terminal. - check the cloud connection of a payment terminal or of a device used in a Mobile solution for in-person payments. -## Benefits of the Cloud Device API +## Benefits of the Cloud device API -The Cloud Device API offers the following benefits: +The Cloud device API offers the following benefits: - access to API logs in the Customer Area for troubleshooting errors - using a version strategy for the API endpoints for controlled and safer rollouts - improved reliability and security (OAuth support) -New features and products will be released exclusively on the Cloud Device API +New features and products will be released exclusively on the Cloud device API -## Use the Cloud Device API +## Use the Cloud device API ### Setup @@ -25,11 +25,12 @@ First you must initialise the Client **setting the closest** [Region](https://do // Import the required classes import com.adyen.Client; import com.adyen.enums.Environment; -import com.adyen.service.checkout.PaymentsApi; -import com.adyen.model.checkout.*; +import com.adyen.service.clouddevice.CloudDeviceApi; +import com.adyen.model.clouddevice.*; +import com.adyen.model.tapi.*; // Setup Client and Service -Client client = new Client("Your X-API-KEY", Environment.TEST); +Client client = new Client("YOUR_API_KEY", Environment.TEST); CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(client); ``` @@ -42,23 +43,22 @@ SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); MessageHeader messageHeader = new MessageHeader(); messageHeader.setProtocolVersion("3.0"); - messageHeader.setMessageClass(MessageClassType.SERVICE); - messageHeader.setMessageCategory(MessageCategoryType.PAYMENT); + messageHeader.setMessageClass(MessageClass.SERVICE); + messageHeader.setMessageCategory(MessageCategory.PAYMENT); messageHeader.setMessageType(MessageType.REQUEST); messageHeader.setSaleID("001"); messageHeader.setServiceID("001"); - messageHeader.setPOIID("P400Plus-123456789"); - + // POIID is set automatically from the deviceId parameter saleToPOIRequest.setMessageHeader(messageHeader); PaymentRequest paymentRequest = new PaymentRequest(); SaleData saleData = new SaleData(); -TransactionIdentification transactionIdentification = new TransactionIdentification(); - transactionIdentification.setTransactionID("001"); +TransactionIDType transactionIDType = new TransactionIDType(); + transactionIDType.setTransactionID("001"); OffsetDateTime timestamp = OffsetDateTime.now(ZoneOffset.UTC); - transactionIdentification.setTimeStamp(timestamp); - saleData.setSaleTransactionID(transactionIdentification); + transactionIDType.setTimeStamp(timestamp); + saleData.setSaleTransactionID(transactionIDType); PaymentTransaction paymentTransaction = new PaymentTransaction(); AmountsReq amountsReq = new AmountsReq(); @@ -74,32 +74,30 @@ AmountsReq amountsReq = new AmountsReq(); CloudDeviceApiRequest cloudDeviceApiRequest = new CloudDeviceApiRequest(); cloudDeviceApiRequest.setSaleToPOIRequest(saleToPOIRequest); -var response = cloudDeviceApi.sendSync("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); +CloudDeviceApiResponse response = cloudDeviceApi.sync("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); ``` ### Send a payment ASYNC request -If you choose to receive the response asynchronously, you only need to use a different method (`sendAsync`). -Don't forget to set up [event notifications](https://docs.adyen.com/point-of-sale/design-your-integration/notifications/event-notifications/) in the CA to be able to receive the Cloud Device API responses. +If you choose to receive the response asynchronously, you only need to use a different method (`async`). +Don't forget to set up [event notifications](https://docs.adyen.com/point-of-sale/design-your-integration/notifications/event-notifications/) in the CA to be able to receive the Cloud device API responses. ```java ... -// define the request (same as per sendSync) +// define the request (same as per sync) CloudDeviceApiRequest cloudDeviceApiRequest = new CloudDeviceApiRequest(); cloudDeviceApiRequest.setSaleToPOIRequest(saleToPOIRequest); -CloudDeviceApiAsyncResponse response = cloudDeviceApi.sendAsync("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); - -// -if("ok".equals(response.getResult())) { +CloudDeviceApiAsyncResponse response = cloudDeviceApi.async("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); - // success +if ("ok".equals(response.getResult())) { + // success } else { - // request failed: see details in the EventNotification object + // request failed: see details in the EventNotification object EventNotification eventNotification = response.getSaleToPOIRequest().getEventNotification(); } ``` @@ -107,17 +105,17 @@ if("ok".equals(response.getResult())) { ### Verify the status of the terminals -The Cloud Device API allows your integration to check the status of the terminals. +The Cloud device API allows your integration to check the status of the terminals. ```java // list of payment terminals or SDK mobile installation IDs -ConnectedDevicesResponse response = cloudDeviceApi.getConnectedDevices("myMerchant"); -System.out.println(response.getUniqueDeviceIds()); +ConnectedDevicesResponse connectedDevices = cloudDeviceApi.getConnectedDevices("myMerchant"); +System.out.println(connectedDevices.getUniqueDeviceIds()); // check the payment terminal or SDK mobile installation ID -DeviceStatusResponse response = cloudDeviceApi.getDeviceStatus("myMerchant", "AMS1-000168242800763"); -System.out.println(response.getStatus()); +DeviceStatusResponse deviceStatus = cloudDeviceApi.getDeviceStatus("myMerchant", "AMS1-000168242800763"); +System.out.println(deviceStatus.getStatus()); ``` ### Protect cloud communication @@ -134,12 +132,15 @@ EncryptionCredentialDetails encryptionCredentialDetails = .keyVersion(0) .passphrase("p@ssw0rd123456"); -var response = - cloudDeviceApi.sendEncryptedSync( +// Use EncryptedCloudDeviceApi instead of CloudDeviceApi +EncryptedCloudDeviceApi encryptedCloudDeviceApi = + new EncryptedCloudDeviceApi(client, encryptionCredentialDetails); + +CloudDeviceApiResponse response = + encryptedCloudDeviceApi.sync( "TestMerchantAccount", "V400m-123456789", - cloudDeviceApiRequest, - encryptionCredentialDetails); + cloudDeviceApiRequest); System.out.println(response); ``` @@ -150,7 +151,7 @@ In case of asynchronous integration, you can decrypt the payload of the event no // JSON with encrypted SaleToPOIResponse (for async responses) or SaleToPOIRequest (for event notifications) var payload = "..."; -var response = cloudDeviceApi.decryptNotification(payload, encryptionCredentialDetails); -System.out.println(response); +var decryptedPayload = encryptedCloudDeviceApi.decryptNotification(payload); +System.out.println(decryptedPayload); ``` \ No newline at end of file diff --git a/doc/TerminalApi.md b/doc/TerminalApi.md index e515166af..38ac6f667 100644 --- a/doc/TerminalApi.md +++ b/doc/TerminalApi.md @@ -9,7 +9,7 @@ import com.adyen.model.nexo.*; import com.adyen.model.terminal.*; // Step 2: Initialize the client object -Client client = new Client("Your YOUR_API_KEY", Environment.TEST); +Client client = new Client("YOUR_API_KEY", Environment.TEST); // for LIVE environment use // Config config = new Config(); @@ -49,6 +49,7 @@ XMLGregorianCalendar timestamp = DatatypeFactory.newInstance().newXMLGregorianCa transactionIdentification.setTimeStamp(timestamp); saleData.setSaleTransactionID(transactionIdentification); +// Optional: set SaleToAcquirerData with merchant application info SaleToAcquirerData saleToAcquirerData = new SaleToAcquirerData(); ApplicationInfo applicationInfo = new ApplicationInfo(); CommonField merchantApplication = new CommonField(); @@ -61,7 +62,8 @@ saleData.setSaleToAcquirerData(saleToAcquirerData); PaymentTransaction paymentTransaction = new PaymentTransaction(); AmountsReq amountsReq = new AmountsReq(); amountsReq.setCurrency("EUR"); -amountsReq.setRequestedAmount(BigDecimal.valueOf(1000)); +// RequestedAmount is in major currency units (e.g. 10.00 EUR) +amountsReq.setRequestedAmount(BigDecimal.valueOf(10)); paymentTransaction.setAmountsReq(amountsReq); paymentRequest.setPaymentTransaction(paymentTransaction); @@ -191,10 +193,10 @@ sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom() // Configure a client for TerminalLocalAPI Config config = new Config(); -config.setEnvironment(environment); -config.setTerminalApiLocalEndpoint("https://" + terminalIpAddress); +config.setEnvironment(Environment.TEST); +config.setTerminalApiLocalEndpoint("https://" + terminalIpAddress); // IP address of your terminal config.setSSLContext(sslContext); -config.setHostnameVerifier(new TerminalLocalAPIHostnameVerifier(environment)); +config.setHostnameVerifier(new TerminalLocalAPIHostnameVerifier(Environment.TEST)); Client client = new Client(config); // Create your SecurityKey object used for encrypting the payload (keyIdentifier/passphrase you set up beforehand in CA) @@ -214,35 +216,25 @@ If you wish to develop the Local Terminal API integration parallel to your encry ```java // Step 1: Import the required classes -import com.adyen.service.TerminalLocalAPI; +import com.adyen.Client; +import com.adyen.Config; +import com.adyen.enums.Environment; +import com.adyen.service.TerminalLocalAPIUnencrypted; import com.adyen.model.nexo.*; import com.adyen.model.terminal.*; -import javax.net.ssl.SSLContext; -// Step 2: Add your Certificate Path and Local Endpoint to the config path. -Client client = new Client(); -client.getConfig().setTerminalApiLocalEndpoint("The IP of your terminal (eg https://192.168.47.169)"); -client.getConfig().setEnvironment(Environment.TEST); -config.setSSLContext(createTrustSSLContext()); // Trust all certificates for testing only -client.setConfig(config); - -// Step 3: Create an SSL context that accepts all certificates (Use in TEST only). -SSLContext createTrustSSLContext() throws Exception { - TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } - checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {} - } - }; - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - return sc; -} +// Step 2: Configure the client with the terminal endpoint +Config config = new Config(); +config.setEnvironment(Environment.TEST); +config.setTerminalApiLocalEndpoint("https://192.168.47.169"); // IP address of your terminal +Client client = new Client(config); + +// Step 3: Initialize the unencrypted local API (handles SSL context internally) +TerminalLocalAPIUnencrypted terminalLocalAPI = new TerminalLocalAPIUnencrypted(client); // Step 4: Construct a TerminalAPIRequest object -Gson gson = new GsonBuilder().create(); -TerminalAPIRequest terminalAPIPaymentRequest = new TerminalAPIRequest(); +TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); +// ... build your SaleToPOIRequest as shown above ... // Step 5: Make the request TerminalAPIResponse terminalAPIResponse = terminalLocalAPI.request(terminalAPIRequest); From 169d1317357f91be1afcc8d1752ca72c99c1dbc7 Mon Sep 17 00:00:00 2001 From: gcatanese Date: Mon, 13 Apr 2026 14:15:14 +0200 Subject: [PATCH 3/8] Add Migration Guide --- doc/MigratingToCloudDeviceApi.md | 409 +++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 doc/MigratingToCloudDeviceApi.md diff --git a/doc/MigratingToCloudDeviceApi.md b/doc/MigratingToCloudDeviceApi.md new file mode 100644 index 000000000..6c7240663 --- /dev/null +++ b/doc/MigratingToCloudDeviceApi.md @@ -0,0 +1,409 @@ +# Migrating from Terminal (Cloud) API to Cloud device API + +## Introduction + +The Adyen Java API Library now includes the [Cloud device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview), a modern replacement for the [Terminal (Cloud) API](https://docs.adyen.com/api-explorer/terminal-api/1/overview) for In-Person Payments cloud integrations. + +The Terminal (Cloud) API (`TerminalCloudAPI`) was built manually with hand-crafted models in the `com.adyen.model.nexo` package. The Cloud device API (`CloudDeviceApi`) is generated from the [Adyen OpenAPI specification](https://github.com/Adyen/adyen-openapi), just like all other services in this library. This means the models and service classes are always in sync with the API, follow the same patterns as the rest of the library (Jackson serialization, fluent setters, `fromJson()`/`toJson()` methods), and benefit from the automated update process. + +Because the Cloud device API models are generated from the spec rather than hand-crafted, there are differences in class names, enum naming conventions, field types, and accessor methods. This guide describes these differences and what to be aware of when adopting the Cloud device API. + +### Who should migrate? + +- **New integrations**: use the Cloud device API from the start. See the [Cloud device API documentation](CloudDeviceApi.md). +- **Updating your cloud integration**: you should consider migrating to the Cloud device API to benefit from the improvements listed below. +- **Not making changes**: you can continue using the Terminal (Cloud) API. It remains functional, but you will miss out on the benefits of the Cloud device API. + +## Benefits of the Cloud device API + +The Cloud device API introduces several improvements over the Terminal (Cloud) API: + +- **API logs in the Customer Area**: troubleshoot errors using the API logs available in your Adyen Customer Area. +- **Versioned endpoints**: the API uses a version strategy for controlled and safer rollouts. +- **Improved security**: supports OAuth authentication alongside API key authentication. +- **Device management endpoints**: query connected devices and check their status directly from your integration. +- **New features**: future In-Person Payments features and products will be released exclusively on the Cloud device API. +- **Generated from the OpenAPI specification**: unlike the hand-crafted Terminal (Cloud) API models, the Cloud device API is auto-generated from the [Adyen OpenAPI spec](https://github.com/Adyen/adyen-openapi). This brings consistency with every other service in the library (Checkout, Management, Transfers, etc.), ensures the models stay in sync with the API, and provides built-in `fromJson()`/`toJson()` serialization, fluent setters, and Jackson support out of the box. + +## Key differences + +The following sections describe the key differences between the Terminal (Cloud) API and the Cloud device API. + +### Service class and endpoints + +The Cloud device API uses the `CloudDeviceApi` service class instead of `TerminalCloudAPI`: + +| | Terminal (Cloud) API | Cloud device API | +|---|---|---| +| Service class | `com.adyen.service.TerminalCloudAPI` | `com.adyen.service.clouddevice.CloudDeviceApi` | +| Request wrapper | `com.adyen.model.terminal.TerminalAPIRequest` | `com.adyen.model.clouddevice.CloudDeviceApiRequest` | +| Response wrapper | `com.adyen.model.terminal.TerminalAPIResponse` | `com.adyen.model.clouddevice.CloudDeviceApiResponse` | + +The Cloud device API `sync` and `async` endpoints require `merchantAccount` and `deviceId` as path parameters, in addition to the request body. The library automatically sets the `POIID` in the `MessageHeader` to match the `deviceId`. + +**Cloud device API:** +```java +// make a sync request +CloudDeviceApiResponse response = cloudDeviceApi.sync("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); + +// get the list of connected devices for a given merchant account. +ConnectedDevicesResponse response = cloudDeviceApi.getConnectedDevices("myMerchant"); +``` + +### Model package + +The Terminal (Cloud) API uses hand-crafted models in `com.adyen.model.nexo`. The Cloud device API uses models generated from the OpenAPI specification in `com.adyen.model.tapi`. + +| Terminal (Cloud) API | Cloud device API | +|---|---| +| `com.adyen.model.nexo.SaleToPOIRequest` | `com.adyen.model.tapi.SaleToPOIRequest` | +| `com.adyen.model.nexo.MessageHeader` | `com.adyen.model.tapi.MessageHeader` | +| `com.adyen.model.nexo.MessageCategoryType` | `com.adyen.model.tapi.MessageCategory` (renamed, see below) | +| `com.adyen.model.nexo.PaymentRequest` | `com.adyen.model.tapi.PaymentRequest` | +| `com.adyen.model.nexo.AmountsReq` | `com.adyen.model.tapi.AmountsReq` | + +### Renamed classes and enums + +#### Enum classes (dropped `Type` suffix) + +Several enum classes are named differently in the Cloud device API to match the OpenAPI specification. The `Type` suffix has been dropped: + +| Old name (nexo) | New name (tapi) | +|-----------------------------|---------------------------| +| `MessageCategoryType` | `MessageCategory` | +| `MessageClassType` | `MessageClass` | +| `ErrorConditionType` | `ErrorCondition` | +| `ResultType` | `Result` | +| `EventToNotifyType` | `EventToNotify` | +| `GlobalStatusType` | `GlobalStatus` | +| `InfoQualifyType` | `InfoQualify` | +| `DeviceType` | `Device` | +| `AlignmentType` | `Alignment` | +| `CharacterHeightType` | `CharacterHeight` | +| `CharacterStyleType` | `CharacterStyle` | +| `CharacterWidthType` | `CharacterWidth` | +| `DocumentQualifierType` | `DocumentQualifier` | +| `IdentificationSupportType` | `IdentificationSupport` | +| `InputCommandType` | `InputCommand` | +| `LoyaltyHandlingType` | `LoyaltyHandling` | +| `MenuEntryTagType` | `MenuEntryTag` | +| `OutputFormatType` | `OutputFormat` | +| `PeriodUnitType` | `PeriodUnit` | +| `PINFormatType` | `PINFormat` | +| `PrinterStatusType` | `PrinterStatus` | +| `ResponseModeType` | `ResponseMode` | +| `ReversalReasonType` | `ReversalReason` | +| `SoundActionType` | `SoundAction` | +| `SoundFormatType` | `SoundFormat` | +| `TrackFormatType` | `TrackFormat` | +| `TransactionActionType` | `TransactionAction` | + +**Note**: enums that already end with `Type` in the spec (e.g. `MessageType`, `TokenRequestedType`, `AccountType`) are **not** renamed. + +#### Model classes + +Some classes are also renamed to adopt the name of the model in the OpenAPI specification. + +| Terminal (Cloud) API (nexo) | Cloud device API (tapi) | +|--------------------------------|---------------------------| +| `TransactionIdentification` | `TransactionIDType` | + +#### Setter/getter naming + +Some attribute accessors differ due to the way the code generator handles certain naming patterns (specifically attributes starting with acronyms like `POI`): + +| Class | Terminal (Cloud) API (nexo) | Cloud device API (tapi) | +|------------|-----------------------------|-----------------------------| +| `POIData` | `getPOITransactionID()` | `getPoITransactionID()` | +| `POIData` | `setPOITransactionID()` | `setPoITransactionID()` | +| `POIData` | `getPOIReconciliationID()` | `getPoIReconciliationID()` | +| `POIData` | `setPOIReconciliationID()` | `setPoIReconciliationID()` | +| `CardData` | `getMaskedPAN()` | `getMaskedPan()` | + +### Type changes + +Some field types differ in the generated models. + +#### Timestamp fields: `XMLGregorianCalendar` -> `OffsetDateTime` + +The most common change. The Cloud device API models use `java.time.OffsetDateTime` for timestamp fields, whereas the Terminal (Cloud) API models use `XMLGregorianCalendar`. + +**Terminal (Cloud) API:** +```java +import javax.xml.datatype.XMLGregorianCalendar; + +TransactionIdentification transactionIdentification = new TransactionIdentification(); +transactionIdentification.setTimeStamp(xmlGregorianCalendarInstance); +``` + +**Cloud device API:** +```java +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +TransactionIDType transactionIDType = new TransactionIDType(); +transactionIDType.setTimeStamp(OffsetDateTime.now(ZoneOffset.UTC)); +``` + +#### Date fields: `String` -> `LocalDate` + +Some date fields (e.g. `Instalment.firstPaymentDate`) use `java.time.LocalDate` in the Cloud device API instead of `String`. + +**Terminal (Cloud) API:** +```java +instalment.setFirstPaymentDate("2025-01-15"); +``` + +**Cloud device API:** +```java +import java.time.LocalDate; +instalment.setFirstPaymentDate(LocalDate.of(2025, 1, 15)); +``` + +#### Numeric fields + +- `BigInteger` fields are `Integer` in the Cloud device API +- In `CurrencyConversion`: the `rate` and `markup` fields are `String` instead of `BigDecimal` +- In `POIData`: `poIReconciliationID` is `Integer` instead of `String` + +#### Boolean helper methods + +The Terminal (Cloud) API `nexo` models include boolean helper methods with default values (e.g. `PaymentResult.isOnlineFlag()` defaulting to `true`). The Cloud device API `tapi` models do not include these convenience methods, so null checks are needed. + +**Terminal (Cloud) API:** +```java +boolean isOnline = paymentResult.isOnlineFlag(); // defaults to true when null +``` + +**Cloud device API:** +```java +Boolean onlineFlag = paymentResult.getOnlineFlag(); +boolean isOnline = (onlineFlag != null) ? onlineFlag : true; +``` + +### Serialization: Gson vs Jackson + +The Terminal (Cloud) API `nexo` models use Gson annotations (`@SerializedName`), while the Cloud device API `tapi` models use Jackson annotations (`@JsonProperty`). If you perform custom serialization/deserialization of the Terminal API models, this is an important difference to be aware of. + +The Cloud device API `tapi` models include built-in `fromJson()` and `toJson()` methods: + +```java +String json = saleToPOIRequest.toJson(); +SaleToPOIRequest request = SaleToPOIRequest.fromJson(jsonString); +``` + +### Fluent setters + +The Cloud device API `tapi` models support a fluent API for building objects, which is not available in the `nexo` models. + +**Terminal (Cloud) API:** +```java +MessageHeader messageHeader = new MessageHeader(); +messageHeader.setProtocolVersion("3.0"); +messageHeader.setMessageClass(MessageClassType.SERVICE); +messageHeader.setMessageCategory(MessageCategoryType.PAYMENT); +messageHeader.setMessageType(MessageType.REQUEST); +messageHeader.setSaleID("001"); +messageHeader.setServiceID("001"); +``` + +**Cloud device API:** +```java +MessageHeader messageHeader = new MessageHeader() + .protocolVersion("3.0") + .messageClass(MessageClass.SERVICE) + .messageCategory(MessageCategory.PAYMENT) + .messageType(MessageType.REQUEST) + .saleID("001") + .serviceID("001"); +``` + +### Async response handling + +The async response handling differs between the two APIs. The Terminal (Cloud) API `async` method returns a plain `String`. The Cloud device API wraps the response in `CloudDeviceApiAsyncResponse`. + +**Terminal (Cloud) API:** +```java +String response = terminalCloudAPI.async(terminalAPIRequest); +// response is "ok" on success or null +``` + +**Cloud device API:** +```java +CloudDeviceApiAsyncResponse response = cloudDeviceApi.async("myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); + +if ("ok".equals(response.getResult())) { + // success +} else { + // request failed: check EventNotification for error details + EventNotification eventNotification = response.getSaleToPOIRequest().getEventNotification(); +} +``` + +### Device management (new) + +The Cloud device API includes endpoints for device management that are not available in the Terminal (Cloud) API: + +```java +// List connected devices for a merchant account +ConnectedDevicesResponse devices = cloudDeviceApi.getConnectedDevices("myMerchant"); +System.out.println(devices.getUniqueDeviceIds()); + +// Check the status of a specific device +DeviceStatusResponse status = cloudDeviceApi.getDeviceStatus("myMerchant", "P400Plus-123456789"); +System.out.println(status.getStatus()); +``` + +## Side-by-side comparison + +### Terminal (Cloud) API +```java +import com.adyen.Client; +import com.adyen.enums.Environment; +import com.adyen.service.TerminalCloudAPI; +import com.adyen.model.terminal.TerminalAPIRequest; +import com.adyen.model.terminal.TerminalAPIResponse; +import com.adyen.model.nexo.*; + +Client client = new Client("Your X-API-KEY", Environment.TEST); +TerminalCloudAPI terminalCloudAPI = new TerminalCloudAPI(client); + +SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); + +MessageHeader messageHeader = new MessageHeader(); +messageHeader.setProtocolVersion("3.0"); +messageHeader.setMessageClass(MessageClassType.SERVICE); +messageHeader.setMessageCategory(MessageCategoryType.PAYMENT); +messageHeader.setMessageType(MessageType.REQUEST); +messageHeader.setSaleID("001"); +messageHeader.setServiceID("001"); +messageHeader.setPOIID("P400Plus-123456789"); +saleToPOIRequest.setMessageHeader(messageHeader); + +PaymentRequest paymentRequest = new PaymentRequest(); +SaleData saleData = new SaleData(); +TransactionIdentification transactionIdentification = new TransactionIdentification(); +transactionIdentification.setTransactionID("001"); +// Using XMLGregorianCalendar +XMLGregorianCalendar timestamp = DatatypeFactory.newInstance() + .newXMLGregorianCalendar(new GregorianCalendar()); +transactionIdentification.setTimeStamp(timestamp); +saleData.setSaleTransactionID(transactionIdentification); + +PaymentTransaction paymentTransaction = new PaymentTransaction(); +AmountsReq amountsReq = new AmountsReq(); +amountsReq.setCurrency("EUR"); +amountsReq.setRequestedAmount(BigDecimal.ONE); +paymentTransaction.setAmountsReq(amountsReq); + +paymentRequest.setSaleData(saleData); +paymentRequest.setPaymentTransaction(paymentTransaction); +saleToPOIRequest.setPaymentRequest(paymentRequest); + +TerminalAPIRequest terminalAPIRequest = new TerminalAPIRequest(); +terminalAPIRequest.setSaleToPOIRequest(saleToPOIRequest); + +TerminalAPIResponse response = terminalCloudAPI.sync(terminalAPIRequest); +``` + +### Cloud device API +```java +import com.adyen.Client; +import com.adyen.enums.Environment; +import com.adyen.service.clouddevice.CloudDeviceApi; +import com.adyen.model.clouddevice.CloudDeviceApiRequest; +import com.adyen.model.clouddevice.CloudDeviceApiResponse; +import com.adyen.model.tapi.*; + +Client client = new Client("Your X-API-KEY", Environment.TEST); +CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(client); + +SaleToPOIRequest saleToPOIRequest = new SaleToPOIRequest(); + +MessageHeader messageHeader = new MessageHeader() + .protocolVersion("3.0") + .messageClass(MessageClass.SERVICE) + .messageCategory(MessageCategory.PAYMENT) + .messageType(MessageType.REQUEST) + .saleID("001") + .serviceID("001"); +saleToPOIRequest.setMessageHeader(messageHeader); + +PaymentRequest paymentRequest = new PaymentRequest(); +SaleData saleData = new SaleData(); +TransactionIDType transactionIDType = new TransactionIDType() + .transactionID("001") + .timeStamp(OffsetDateTime.now(ZoneOffset.UTC)); +saleData.setSaleTransactionID(transactionIDType); + +PaymentTransaction paymentTransaction = new PaymentTransaction(); +AmountsReq amountsReq = new AmountsReq() + .currency("EUR") + .requestedAmount(BigDecimal.ONE); +paymentTransaction.setAmountsReq(amountsReq); + +paymentRequest.setSaleData(saleData); +paymentRequest.setPaymentTransaction(paymentTransaction); +saleToPOIRequest.setPaymentRequest(paymentRequest); + +CloudDeviceApiRequest cloudDeviceApiRequest = new CloudDeviceApiRequest(); +cloudDeviceApiRequest.setSaleToPOIRequest(saleToPOIRequest); + +// Note: POIID is set automatically from the deviceId parameter +CloudDeviceApiResponse response = cloudDeviceApi.sync( + "myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); +``` + +## Models not yet available in the Cloud device API + +Some models present in the `nexo` package are not available in the `tapi` package because they are not part of the current OpenAPI specification. These represent features that were never supported by the Terminal (Cloud) API: + +- `BatchRequest` / `BatchResponse` +- `LoyaltyRequest` +- `PINRequest` / `PINResponse` +- `CardReaderInitRequest` / `CardReaderInitResponse` +- `CardReaderPowerOffRequest` / `CardReaderPowerOffResponse` +- `SoundRequest` / `SoundResponse` +- `TransmitRequest` / `TransmitResponse` +- `ContentInformation` + +If your integration uses these models, they are not yet supported in the Cloud device API. Contact [Adyen Support](https://www.adyen.help/hc/en-us/requests/new) for guidance. + +## Testing and validation +Perform a thorough validation of the migration. + +### 1. Unit tests + +- Verify that your request objects serialize to the same JSON structure with the new models. Use `toJson()` on the `tapi` models and compare with the expected JSON payloads from your existing test data. +- Pay special attention to the renamed fields and type changes described above. + +### 2. Integration tests on TEST environment + +- Point the `CloudDeviceApi` to the test environment (`Environment.TEST`). +- Run your most common payment flows: payment, reversal, refund, and reconciliation. +- Verify that the response structure is parsed correctly, especially the fields with type changes (`OffsetDateTime`, `LocalDate`, `Integer`). +- Test your async flow to confirm event notifications are received and can be parsed. +- Test with a POS terminal. + +### 3. Validate response handling + +- Check that `PaymentResponse`, `ReversalResponse`, and other response models deserialize without errors. +- Verify that the accessor name changes (e.g. `getPoITransactionID()`) are correctly updated in your business logic. +- Confirm that boolean field handling works correctly without the old default-value helpers. + +### 4. Device management (new) + +- Use `getConnectedDevices()` and `getDeviceStatus()` to validate your terminal connectivity before running transaction tests. + +### 5. Rollout strategy + +- Consider running the Cloud device API integration in parallel with your existing Terminal (Cloud) API integration. +- Observe traffic (with the Customer Area API Logs or your own observability tools) to validate the API requests and responses. +- Compare the results of both integrations to ensure functional equivalence. + +## Further resources + +- [Cloud device API documentation](CloudDeviceApi.md) +- [Cloud device API Explorer](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview) +- [Terminal API documentation](TerminalApi.md) +- [In-Person Payments documentation](https://docs.adyen.com/point-of-sale/) From 3f718550e95e40275b33d028681e5bdea2efcadc Mon Sep 17 00:00:00 2001 From: gcatanese Date: Mon, 13 Apr 2026 14:28:22 +0200 Subject: [PATCH 4/8] Address PR review --- README.md | 9 ++++----- doc/CloudDeviceApi.md | 3 +++ doc/TerminalApi.md | 4 ++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c99357adc..5f15a4711 100644 --- a/README.md +++ b/README.md @@ -412,24 +412,23 @@ Build a feature-rich [in-person payments](https://docs.adyen.com/point-of-sale/) ### Using the Cloud Device API -For In-Person Payments integrations, the recommended solution is the [Cloud Device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview). +For In-Person Payments integrations, the recommended solution is the [Cloud device API](https://docs.adyen.com/api-explorer/cloud-device-api/1/overview). -Check the [Cloud Device API README](doc/CloudDeviceApi.md). +Check the [Cloud device API README](doc/CloudDeviceApi.md). ### Using the Terminal API With the [Terminal API](https://docs.adyen.com/api-explorer/terminal-api/1/overview) you can send and receive Terminal API messages in the following ways: * Local communications: using your local network, your POS system sends the request directly to the IP address of the terminal, and receives the result synchronously. -* Cloud communications: using the internet to access the cloud `/sync` and `/async` endpoints. You should consider adopting the [Cloud Device API](doc/CloudDeviceApi.md) instead. +* Cloud communications: using the internet to access the cloud `/sync` and `/async` endpoints. You should consider adopting the [Cloud device API](doc/CloudDeviceApi.md) instead. -Check the [Terminal API README](doc/TerminalApi.md). +Check the [Terminal (Cloud) API README](doc/TerminalApi.md). ## Example integrations For a closer look at how our Java library works, you can clone one of our example integrations: * [Java Spring Boot example integration](https://github.com/adyen-examples/adyen-java-spring-online-payments). -* [Kotlin Spring Boot example integration](https://github.com/adyen-examples/adyen-kotlin-spring-online-payments). These include commented code, highlighting key features and concepts, and examples of API calls that can be made using the library. diff --git a/doc/CloudDeviceApi.md b/doc/CloudDeviceApi.md index c0ae1d8cb..8040205ea 100644 --- a/doc/CloudDeviceApi.md +++ b/doc/CloudDeviceApi.md @@ -28,6 +28,9 @@ import com.adyen.enums.Environment; import com.adyen.service.clouddevice.CloudDeviceApi; import com.adyen.model.clouddevice.*; import com.adyen.model.tapi.*; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; // Setup Client and Service Client client = new Client("YOUR_API_KEY", Environment.TEST); diff --git a/doc/TerminalApi.md b/doc/TerminalApi.md index 38ac6f667..2ef0a53c5 100644 --- a/doc/TerminalApi.md +++ b/doc/TerminalApi.md @@ -7,6 +7,10 @@ import com.adyen.enums.Environment; import com.adyen.service.TerminalCloudAPI; import com.adyen.model.nexo.*; import com.adyen.model.terminal.*; +import java.math.BigDecimal; +import java.util.GregorianCalendar; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; // Step 2: Initialize the client object Client client = new Client("YOUR_API_KEY", Environment.TEST); From fde835506eb45304c610762bae17ce9be6d01f87 Mon Sep 17 00:00:00 2001 From: Beppe Catanese <1771700+gcatanese@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:09:48 +0200 Subject: [PATCH 5/8] Add section about Helper Classes --- doc/CloudDeviceApi.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/CloudDeviceApi.md b/doc/CloudDeviceApi.md index 8040205ea..981793c9c 100644 --- a/doc/CloudDeviceApi.md +++ b/doc/CloudDeviceApi.md @@ -120,6 +120,24 @@ System.out.println(connectedDevices.getUniqueDeviceIds()); DeviceStatusResponse deviceStatus = cloudDeviceApi.getDeviceStatus("myMerchant", "AMS1-000168242800763"); System.out.println(deviceStatus.getStatus()); ``` +### Helper classes + +You can use `PredefinedContentHelper` to parse Display notification types which you find in `PredefinedContent->ReferenceID` +```java +import com.adyen.util.tapi.PredefinedContentHelper; + +// Parse ReferenceID (i.e. key1=value1&key2=value2) +PredefinedContentHelper helper = new PredefinedContentHelper(predefinedContent.getReferenceID()); + +// Safely extract and use the event type with Optional +helper.getEvent().ifPresent(event -> { + System.out.println("Received event: " + event); + if (event == PredefinedContentHelper.DisplayNotificationEvent.PIN_ENTERED) { + // Handle PIN entry event + System.out.println("The user has entered their PIN."); + } +}); +``` ### Protect cloud communication @@ -157,4 +175,4 @@ var payload = "..."; var decryptedPayload = encryptedCloudDeviceApi.decryptNotification(payload); System.out.println(decryptedPayload); -``` \ No newline at end of file +``` From af8d1cf90fd00e8c067db32b039a479f00905b20 Mon Sep 17 00:00:00 2001 From: Beppe Catanese <1771700+gcatanese@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:17:03 +0200 Subject: [PATCH 6/8] Add Helper classes --- doc/MigratingToCloudDeviceApi.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/MigratingToCloudDeviceApi.md b/doc/MigratingToCloudDeviceApi.md index 6c7240663..0fc84487c 100644 --- a/doc/MigratingToCloudDeviceApi.md +++ b/doc/MigratingToCloudDeviceApi.md @@ -369,6 +369,26 @@ Some models present in the `nexo` package are not available in the `tapi` packag If your integration uses these models, they are not yet supported in the Cloud device API. Contact [Adyen Support](https://www.adyen.help/hc/en-us/requests/new) for guidance. +### Helper classes + +You can use `PredefinedContentHelper` to parse Display notification types which you find in `PredefinedContent->ReferenceID` +```java +import com.adyen.util.tapi.PredefinedContentHelper; + +// Parse ReferenceID (i.e. key1=value1&key2=value2) +PredefinedContentHelper helper = new PredefinedContentHelper(predefinedContent.getReferenceID()); + +// Safely extract and use the event type with Optional +helper.getEvent().ifPresent(event -> { + System.out.println("Received event: " + event); + if (event == PredefinedContentHelper.DisplayNotificationEvent.PIN_ENTERED) { + // Handle PIN entry event + System.out.println("The user has entered their PIN."); + } +}); +``` +You shouldn't use `PredefinedContentHelper` from the (legacy) `nexo` folder. + ## Testing and validation Perform a thorough validation of the migration. From e92fb1308e16f96333e63a310cda7192ad8fb047 Mon Sep 17 00:00:00 2001 From: Beppe Catanese <1771700+gcatanese@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:23:49 +0200 Subject: [PATCH 7/8] Update MigratingToCloudDeviceApi.md --- doc/MigratingToCloudDeviceApi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/MigratingToCloudDeviceApi.md b/doc/MigratingToCloudDeviceApi.md index 0fc84487c..5ab3f9b1b 100644 --- a/doc/MigratingToCloudDeviceApi.md +++ b/doc/MigratingToCloudDeviceApi.md @@ -354,7 +354,7 @@ CloudDeviceApiResponse response = cloudDeviceApi.sync( "myMerchant", "P400Plus-123456789", cloudDeviceApiRequest); ``` -## Models not yet available in the Cloud device API +## Models not available in the Cloud device API Some models present in the `nexo` package are not available in the `tapi` package because they are not part of the current OpenAPI specification. These represent features that were never supported by the Terminal (Cloud) API: @@ -367,7 +367,7 @@ Some models present in the `nexo` package are not available in the `tapi` packag - `TransmitRequest` / `TransmitResponse` - `ContentInformation` -If your integration uses these models, they are not yet supported in the Cloud device API. Contact [Adyen Support](https://www.adyen.help/hc/en-us/requests/new) for guidance. +If your integration requires these models, please [create a new issue](https://github.com/Adyen/adyen-java-api-library/issues), so we can understand your needs and provide you with the necessary help. ### Helper classes From 967585f4aafe13dd6b9db0c0cf86e161f325216b Mon Sep 17 00:00:00 2001 From: gcatanese Date: Thu, 16 Apr 2026 16:25:26 +0200 Subject: [PATCH 8/8] Address PR feedback --- doc/CloudDeviceApi.md | 30 +++++++++++++++++++++++++----- doc/MigratingToCloudDeviceApi.md | 20 ++++++++++++++------ doc/TerminalApi.md | 4 ++-- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/doc/CloudDeviceApi.md b/doc/CloudDeviceApi.md index 981793c9c..2fa51b4ae 100644 --- a/doc/CloudDeviceApi.md +++ b/doc/CloudDeviceApi.md @@ -20,7 +20,7 @@ New features and products will be released exclusively on the Cloud device API ### Setup -First you must initialise the Client **setting the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud): +First you must initialise the Client (see an example on TEST): ``` java // Import the required classes import com.adyen.Client; @@ -32,12 +32,30 @@ import java.math.BigDecimal; import java.time.OffsetDateTime; import java.time.ZoneOffset; -// Setup Client and Service -Client client = new Client("YOUR_API_KEY", Environment.TEST); +// Setup Client on TEST +Client client = new Client(new Config().apiKey("test").environment(Environment.TEST)); + CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(client); ``` +On LIVE environment you must **set the closest** [Region](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/#cloud) +``` java +// Import the required classes +import com.adyen.Client; +import com.adyen.enums.Environment; +import com.adyen.service.clouddevice.CloudDeviceApi; +import com.adyen.model.clouddevice.*; +import com.adyen.model.tapi.*; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +// Setup Client on LIVE (Region is required) +Client client = new Client(new Config().apiKey("test").environment(Environment.LIVE).terminalApiRegion(Region.US)); +CloudDeviceApi cloudDeviceApi = new CloudDeviceApi(client); + +``` ### Send a payment SYNC request ```java @@ -115,10 +133,12 @@ The Cloud device API allows your integration to check the status of the terminal // list of payment terminals or SDK mobile installation IDs ConnectedDevicesResponse connectedDevices = cloudDeviceApi.getConnectedDevices("myMerchant"); System.out.println(connectedDevices.getUniqueDeviceIds()); +// [P400Plus-123456789, AMS1-000168242800763] // check the payment terminal or SDK mobile installation ID DeviceStatusResponse deviceStatus = cloudDeviceApi.getDeviceStatus("myMerchant", "AMS1-000168242800763"); System.out.println(deviceStatus.getStatus()); +// ONLINE ``` ### Helper classes @@ -148,9 +168,9 @@ The Adyen Java library supports encrypting request and response payloads, allowi // Encryption credentials from the Terminal configuration on CA EncryptionCredentialDetails encryptionCredentialDetails = new EncryptionCredentialDetails() - .adyenCryptoVersion(0) + .adyenCryptoVersion(1) .keyIdentifier("CryptoKeyIdentifier12345") - .keyVersion(0) + .keyVersion(1) .passphrase("p@ssw0rd123456"); // Use EncryptedCloudDeviceApi instead of CloudDeviceApi diff --git a/doc/MigratingToCloudDeviceApi.md b/doc/MigratingToCloudDeviceApi.md index 5ab3f9b1b..c9b8c1ac0 100644 --- a/doc/MigratingToCloudDeviceApi.md +++ b/doc/MigratingToCloudDeviceApi.md @@ -8,12 +8,6 @@ The Terminal (Cloud) API (`TerminalCloudAPI`) was built manually with hand-craft Because the Cloud device API models are generated from the spec rather than hand-crafted, there are differences in class names, enum naming conventions, field types, and accessor methods. This guide describes these differences and what to be aware of when adopting the Cloud device API. -### Who should migrate? - -- **New integrations**: use the Cloud device API from the start. See the [Cloud device API documentation](CloudDeviceApi.md). -- **Updating your cloud integration**: you should consider migrating to the Cloud device API to benefit from the improvements listed below. -- **Not making changes**: you can continue using the Terminal (Cloud) API. It remains functional, but you will miss out on the benefits of the Cloud device API. - ## Benefits of the Cloud device API The Cloud device API introduces several improvements over the Terminal (Cloud) API: @@ -23,6 +17,13 @@ The Cloud device API introduces several improvements over the Terminal (Cloud) A - **Improved security**: supports OAuth authentication alongside API key authentication. - **Device management endpoints**: query connected devices and check their status directly from your integration. - **New features**: future In-Person Payments features and products will be released exclusively on the Cloud device API. + +### Who should migrate? + +- **New integrations**: use the Cloud device API from the start. See the [Cloud device API documentation](CloudDeviceApi.md). +- **Updating your cloud integration**: you should consider migrating to the Cloud device API to benefit from the improvements listed below. +- **Not making changes**: you can continue using the Terminal (Cloud) API. It remains functional, but you will miss out on the benefits of the Cloud device API. + - **Generated from the OpenAPI specification**: unlike the hand-crafted Terminal (Cloud) API models, the Cloud device API is auto-generated from the [Adyen OpenAPI spec](https://github.com/Adyen/adyen-openapi). This brings consistency with every other service in the library (Checkout, Management, Transfers, etc.), ensures the models stay in sync with the API, and provides built-in `fromJson()`/`toJson()` serialization, fluent setters, and Jackson support out of the box. ## Key differences @@ -128,6 +129,9 @@ Some field types differ in the generated models. The most common change. The Cloud device API models use `java.time.OffsetDateTime` for timestamp fields, whereas the Terminal (Cloud) API models use `XMLGregorianCalendar`. +The key difference is how each type handles timezone information. `XMLGregorianCalendar` allows an undefined timezone: when constructed from `new GregorianCalendar()` without an explicit timezone, it inherits the JVM default. +This means the same wall-clock time (e.g. 14:30:00) could be serialized as `14:30:00+01:00` on a server in Amsterdam or `14:30:00-05:00` on one in New York — two different points in time. `OffsetDateTime` always carries an explicit offset, so `OffsetDateTime.now(ZoneOffset.UTC)` always serializes as `2025-01-15T14:30:00Z`, unambiguously. + **Terminal (Cloud) API:** ```java import javax.xml.datatype.XMLGregorianCalendar; @@ -149,6 +153,10 @@ transactionIDType.setTimeStamp(OffsetDateTime.now(ZoneOffset.UTC)); Some date fields (e.g. `Instalment.firstPaymentDate`) use `java.time.LocalDate` in the Cloud device API instead of `String`. +`LocalDate` has no timezone or offset information — it represents a calendar date only (year, month, day). When the library serializes a `LocalDate`, it uses the date as-is without any timezone conversion. +This means the date sent to the API is whatever date your system clock shows in its local timezone. If your server runs in a timezone that is behind UTC and the transaction happens near midnight UTC, the local date may be one day behind. +**Ensure the system timezone is set correctly and consistently across all environments where the SDK runs.** + **Terminal (Cloud) API:** ```java instalment.setFirstPaymentDate("2025-01-15"); diff --git a/doc/TerminalApi.md b/doc/TerminalApi.md index 2ef0a53c5..d50ade601 100644 --- a/doc/TerminalApi.md +++ b/doc/TerminalApi.md @@ -13,9 +13,9 @@ import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; // Step 2: Initialize the client object -Client client = new Client("YOUR_API_KEY", Environment.TEST); +Client client = new Client(new Config().apiKey("test").environment(Environment.TEST)); -// for LIVE environment use +// for LIVE environment set the region // Config config = new Config(); // config.setEnvironment(Environment.LIVE); // config.setTerminalApiRegion(Region.EU);