## Information

### Android specs:

    compile_sdk           : 31
    min_sdk               : 22
    target_sdk            : 30

Content copied to clipboard

# Guide

## 1. Setup SDK

ClintFactory used to create Client interface instance with specified parameters

    import pl.novelpay.retailer.factory.ClientFactory
    import pl.novelpay.retailer.factory.RetailerConfiguration

    ClientFactory.bindConfiguration(
        SDKConfiguration.defaultConfiguration(
            clientApplicationInfo = PackageInfo(
                applicationName = applicationInfo.name,
                applicationPackage = packageName
            )
        )
    ).bindContext(
        context = applicationContext
    ).bindProtocolConfiguration(
        protocolConfiguration = RetailerProtocolFactory.createProtocol(
            RetailerConfiguration(
                saleID = "SALE_ID",
                operatorId = "OPERATOR_IDENTIFIER",
                softwareComponent = SoftwareComponent(
                    providerCompanyIdentifier = "YourCompany",
                    name = "YourAppName",
                    softwareVersion = "1.0.0",
                    description = "Your App Description"
                )
            )
        )
    ).bindInterceptor(
        interceptor = { message: DomainMessage ->
            when (message) {
                // Check message type
                is SuccessRetailerLoginResponse,
                is ErrorRetailerLoginResponse,
                is SuccessRetailerLogoutResponse -> {
                    //Handle message
                }
            }
        }
    ).bindEventObserver { event: ObservableEvent ->
        when (event) {
            is ObservableEvent.ServerEvent -> Logger.d("New server event")
            is ObservableEvent.TransactionStateChanged ->
                when (event.state) {
                    is IdleTransactionState -> bringToForeground(MainActivity::class)
                    else -> {
                        // Ignore
                    }
                }
        }
    }.build().provideCommunicationInterface(RetailerCommunicationInterface::class)

Content copied to clipboard

#### 1.1 SDKConfiguration stands here for interprocess communication parameters.

This method allows to setup parameters of inter-process communication with Payment application

`clientApplicationInfo` parameter stands for "Your" application package info. It's being shared with PaymentApplication but doesn't affects communication itself.

`paymentApplicationPackage` is used to set up a package info of Payment application package. It does affect a communication process. If your environment is PAX device and PayOnSite application, don't provide explicitly this parameter.

    bindConfiguration(
        SDKConfiguration.defaultConfiguration(
            clientApplicationInfo = PackageInfo(
                applicationName = applicationInfo.name,
                applicationPackage = packageName
            )
        )
    )

Content copied to clipboard

#### 1.2 ProtocolConfiguration stands for specific protocol parameters.

Our case is Nexo Retailer Protocol v3. Your application's parameters as `ECR` might be specified here. It's important to provide information about your application's software component. This is done by creating a `SoftwareComponent` object and passing it to the `RetailerConfiguration`.

See the ../../protocol/protocol-retailer/src/main/java/pl/novelpay/retailer/configuration/SoftwareComponent.kt description

    import pl.novelpay.retailer.configuration.SoftwareComponent

    bindProtocolConfiguration(
        protocolConfiguration = RetailerProtocolFactory.createProtocol(
            RetailerConfiguration(
                saleID = "SALE_ID",
                operatorId = "OPERATOR_IDENTIFIER",
                softwareComponent = SoftwareComponent(
                    providerCompanyIdentifier = "YourCompany",
                    name = "YourAppName",
                    softwareVersion = "1.0.0",
                    description = "Your App Description"
                )
            )
        )
    )

Content copied to clipboard

#### 1.3 Interceptor class which will handle all of the responses from Payment Application.

../../protocol/protocol-retailer/src/main/java/pl/novelpay/retailer/RetailerProtocol.kt class contains a `RetailerResponse` interface, all the Retailer messages are inherit from it.

    bindInterceptor(
        interceptor = { message: DomainMessage ->
            when (message) {
                // Check message type
                is SuccessRetailerLoginResponse,
                is ErrorRetailerLoginResponse,
                is SuccessRetailerLogoutResponse -> {
                    //Handle message
                }
            }
        }
    )

Content copied to clipboard

Incoming parameter has generic type DomainMessage all of the messages in app2app communication are subtypes of this class. to specify handling strategy for each response typecheck is recommended. In this example, reaction on SuccessRetailerLoginResponse, ErrorRetailerLoginResponse, SuccessRetailerLogoutResponse is illustrated.

Each operation as Payment/Reversal/Refund etc has it's own message type. It could be found manually through documentation package retailer-protocol.

#### 1.3.1 Here is map of requests and responses for main operations:

##### Login:

To send request use - Client.sendLoginRequest()

Request | Response | Response Subtypes
--- | --- | ---
RetailerLoginRequest | RetailerLoginResponse | SuccessRetailerLoginResponse, FailedRetailerLoginResponse

##### Payment:

To send request use - Client.sendPaymentRequest()

Request | Response | Response Subtypes
--- | --- | ---
RetailerPaymentRequest | RetailerPaymentResponse, RetailerDisplayRequest | SuccessRetailerPaymentResponse, ErrorRetailerPaymentResponse

Payment request is used for Refunds, Pre-Authorisations and Pre-Authorisation Completions as well. There is no separate subtypes for those operations. DisplayRequest will notify during processing which operation is being executed.

##### CardAcquisition:

To send request use - Client.sendCardAcquisitionRequest()

Request | Response | Response Subtypes
--- | --- | ---
RetailerCardAcquisitionRequest | RetailerCardAcquisitionResponse, RetailerDisplayRequest | SuccessRetailerAcquisitionResponse, ErrorRetailerAcquisitionResponse

CardAcquisition request used for Dual Transaction and Acquisition Operation (reading card data).

> Notice!

> If you are using it in Dual Transaction mode, Payment application will notify you with processing state as it will expect Dual Transaction "Completion" with PaymentOperation referring this CardAcquisitionTransactionResponse

##### Abort:

To send request use - Client.sendAbortRequest()

Request | Response | Response Subtypes
--- | --- | ---
RetailerAbortRequest | RetailerPaymentResponse, | ErrorRetailerPaymentResponse

If AbortRequest was sent after payment app sent authorisation request to host, it will be ignored.

##### Reversal:

To send request use - Client.sendReversalRequest()

Request | Response | Response Subtypes
--- | --- | ---
RetailerReversalRequest | RetailerReversalResponse, RetailerDisplayRequest | SuccessRetailerReversalResponse, ErrorRetailerReversalResponse

##### Transaction Status:

To send request use - Client.sendTransactionStatusRequest()

Request | Response | Response Subtypes
--- | --- | ---
RetailerTransactionStatusRequest | RetailerTransactionStatusResponse, RetailerDisplayRequest | SuccessRetailerTransactionStatusResponse, ErrorRetailerTransactionStatusResponse

#### 1.4 EventObserver class which will handle all of PaymentApplication states changes and events.

Observer receives ObservableEvent instances. There are two subtypes of it:

1. ObservableEvent.ServerEvent - stands for some events happend in Payment Application

2. TransactionStateChanged - stands for state changes. For example, when Payment Application starts PaymentProcessing, EventObserver will receive PaymentTransactionState instance. When PaymentProcessing considered finished by Payment Application, EventObserver will receive IdleTransactionState instance. Transitions between states might be recognized through saving previously notified state's instances.

    bindEventObserver { event: ObservableEvent ->
        when (event) {
            is ObservableEvent.ServerEvent -> Logger.d("New server event")
            is ObservableEvent.TransactionStateChanged ->
                when (event.state) {
                    is IdleTransactionState -> bringToForeground(MainActivity::class)
                    else -> {
                        // Ignore
                    }
                }
        }
    }

Content copied to clipboard

`bringToForeground(MainActivity::class)` - is function, responsible for "dragging" to foreground your application after state changes in Payment Application. `MainActivity:class` is placeholder for your currently running application's activity. If your application is Single-Activity you can don't worry about activity transitions so just provide your Main Activity class as in example.

For example: You'd like show advertisement in your application or custom transaction details during payment processing instead of Payment Application's spinner. All you have to do to achive it - specify path for PaymentTransactionState like here:

    bindEventObserver { event: ObservableEvent ->
        when (event) {
            is ObservableEvent.ServerEvent -> Logger.d("New server event")
            is ObservableEvent.TransactionStateChanged ->
                when (event.state) {
                    is PaymentTransactionState -> bringToForeground(MainActivity::class)
                    else -> {
                        // Ignore
                    }
                }
        }
    }

Content copied to clipboard

now, when Payment Processing started in Payment Application your will be brought to foreground.

If you also would like to transit to foreground your application after Payment Applications finish transaction, add coresponding path for IdleTransactionState:

    bindEventObserver { event: ObservableEvent ->
        when (event) {
            is ObservableEvent.ServerEvent -> Logger.d("New server event")
            is ObservableEvent.TransactionStateChanged ->
                when (event.state) {
                    is IdleTransactionState -> bringToForeground(MainActivity::class)
                    is PaymentTransactionState -> bringToForeground(MainActivity::class)
                    else -> {
                        // Ignore
                    }
                }
        }
    }

Content copied to clipboard

## 2. SDK Usage

Once you've done with SetUp, you can use instance returned by ClientFactory::build function to initiate communication with Payment Application.

SDK Lifecycle ideally should be linked to your application. However, if you'd like to recreate SDK instance, you might use specific functions `new()` and `create()`.

Unlike `build()` function `create()` insures a new instance creation, while `build()` might return a previous instance.

Function `new()` ensures clear of previously bound resources.

    ClientFactory.new().bindProtocolConfiguration(
                RetailerProtocolFactory.createProtocol(
                    RetailerConfiguration(
                        saleID = "Your_Sale_Identification",
                        poiID = "Your_Identification_Of_POI",
                        operatorLanguage = "EN",
                        softwareComponent = SoftwareComponent(
                            softwareVersion = "1.1",
                            name = "SDKTests",
                            providerCompanyIdentifier = "Novelpay",
                            description = "Test environment"
                        ),
                        operatorId = "TestOperator"
                    )
                )
            ).create()

Content copied to clipboard

### 2.1 Client interface:

../../protocol/protocol-retailer/src/main/java/pl/novelpay/retailer/RetailerProtocol.kt contains also declaration of `CommunicationInterface`

It used to send requests and response for those requests will be delivered to previously registered in 1.3 Interceptor.

### 2.2 Nexo Retailer Protocol sepcific:

Every communication session should start with RetailerLoginRequest - Client::sendLoginRequest. If session was interrupted due to:

- Payment Application update

- Payment Application restart

- Client's (your) application restart

- Connection between applications was lost

- RetailerLogoutRequest was sent

New Login procedure should be triggered to instantiate the session. Otherwise requests from your application will be considered unauthorised and will be ignored by Payment Application.

### 2.3 Common flows

The ../../protocol/protocol-retailer/src/main/java/pl/novelpay/retailer/client/RetailerCommunicationInterfaceImpl.kt is the API available to the client application. It provides methods to send requests to the payment application, such as `sendLoginRequest` and `sendPaymentRequest`.

The ../../client/src/main/java/pl/novelpay/client/sdk/interceptor/ClientInterceptor.kt allows you to observe incoming and outgoing messages. You can use it to handle responses from the payment application.

Here are some examples of common flows:

#### Login flow

To initiate a session with the payment application, you need to send a login request. The `sendLoginRequest` method from the `RetailerCommunicationInterface` is used for this purpose.

Here is an example of how to send a login request and handle the response:

    // Send the login request
    clientSDK.sendLoginRequest()

Content copied to clipboard

You can then handle the login response in your `ClientInterceptor`:

    bindInterceptor(
        interceptor = { message: DomainMessage ->
            when (message) {
                is SuccessRetailerLoginResponse -> {
                    // Handle successful login
                }
                is ErrorRetailerLoginResponse -> {
                    // Handle login error
                }
            }
        }
    )

Content copied to clipboard

### Notice

1. All the messaging with Payment application should be done within session. Once you've received successful `Login` response it points that session was created. Basically there is no need to send `Logout` request (if you don't want to clear the session explicitly).

2. If Login operation wasn't successful it might be caused by several reasons:

> A. Payment Application is in initialization stage. Then you'll observe a `BUSY` error condition

> B. Login Request has incorrect data in its fields. It will be responded with `LoginResponse` containing problem description

If Login procedure was failed it's worth to perform a retry, without login, no operations would be available in Payment application.

#### Payment flow

To perform a payment, you need to send a payment request. The `sendPaymentRequest` method from the `RetailerCommunicationInterface` is used for this purpose.

Here is an example of how to send a payment request and handle the response:

    // Send the payment request
    clientSDK.sendPaymentRequest(
        RetailerMessageArguments.PaymentRequestMessageArguments.RegularPaymentRequestMessageArguments(
            saleTransactionId = "your_transaction_identification",
            paymentAmounts = PaymentAmounts(
                currency = "EUR",
                amount = BigDecimal("10.00")
            ),
            paymentType = PaymentType.NORMAL
        )
    )

Content copied to clipboard

### Notice

> Your transaction identification should be alphanumeric, and it's length should be <= 35 symbols

You can then handle the payment response in your `ClientInterceptor`:

    bindInterceptor(
        interceptor = { message: DomainMessage ->
            when (message) {
                is SuccessRetailerPaymentResponse -> {
                    // Handle successful payment
                }
                is ErrorRetailerPaymentResponse -> {
                    // Handle payment error
                }
                is RetailerDisplayRequest -> {
                    // Handle display request from the payment application
                }
            }
        }
    )

Content copied to clipboard

### Blocking communication

For cases where you need to wait for a specific response before proceeding, you can use the `BlockingMessageGateway` utility. This allows you to send a request and suspend the current coroutine until a matching response is received.

Here's how to use it:

First, create an instance of `BlockingMessageGateway`:

    val blockingMessageGateway = BlockingMessageGateway()

Content copied to clipboard

Then, you need to pass the incoming messages from your `ClientInterceptor` to the `BlockingMessageGateway`:

    bindInterceptor(
        interceptor = { message: DomainMessage ->
            // Pass the message to the gateway
            blockingMessageGateway.onNewMessage(message)

            // You can still handle messages in a non-blocking way here
            when (message) {
                is RetailerDisplayRequest -> {
                    // Handle Display Request
                }
                // ... other non-blocking handlers
            }
        }
    )

Content copied to clipboard

Now you can send requests in a blocking way using `sendBlocking`. This function will suspend until a response of the specified type is received.

    // Example of sending a login request and waiting for the response
    val loginResponse = blockingMessageGateway.sendBlocking<RetailerLoginResponse>(
        timeoutMillis = 30000L // Custom timeout of 30 seconds
    ) {
        clientSDK.sendLoginRequest()
    }

    when (loginResponse) {
        is SuccessRetailerLoginResponse -> {
            // Handle successful login
        }
        is ErrorRetailerLoginResponse -> {
            // Handle login error
        }
    }

Content copied to clipboard

The `sendBlocking` function also accepts an optional `timeoutMillis` parameter, which defaults to 60,000 milliseconds (60 seconds). You can provide a custom timeout to control how long the function should wait for a response. If the timeout is exceeded, a `kotlinx.coroutines.TimeoutCancellationException` will be thrown.

#### Choosing the right response type

When using `sendBlocking`, it's important to specify the correct super-type for the response you are expecting. This ensures that you capture all possible outcomes (like success or failure).

For example, for a login request, you should await `RetailerLoginResponse`. This is a sealed class that encompasses both `SuccessRetailerLoginResponse` and `ErrorRetailerLoginResponse`. If you were to only await `SuccessRetailerLoginResponse`, your coroutine would not resume if the login fails, and it would eventually time out. By awaiting `RetailerLoginResponse`, you can handle both success and error cases as shown in the example above.

## 3. SDK parameters mappings on Acquirer API (Communication with host)

Function name | Input parameters | Request name | Host field name | Host Field Format | Remark
--- | --- | --- | --- | --- | ---
sendPaymentRequest | saleReferenceId | AccptrAuthstnReq | SaleRefId | Max35Text max lenght 35 |
| transactionID |  | SaleRefNb | Max35Text max lenght 35 |
| paymentAmounts -> currency |  | Ccy | Code (Example EUR) | Should match with terminal currency!
| paymentAmounts -> amount |  | TotalAmount | Double: totalDigits 18; fractionDigits 5; |
| paymentAmounts -> tipAmount |  | CashBack | Double: totalDigits 18; fractionDigits 5; |
| paymentAmounts -> cashbackAmount |  | Gratuity | Double: totalDigits 18; fractionDigits 5; |
| paymentAmounts -> paidAmount |  | Not used | Not used |
| paymentType |  |  |  |
sendReversalRequest | saleReferenceId | AccptrAuthstnReq | SaleRefId | Max35Text max lenght 35 |
| poiTransactionId |  | SaleRefNb | Max35Text max lenght 35 |
| transactionData -> forecastedAmount |  | TtlAmt | Double: totalDigits 18; fractionDigits 5; | Should completely match with amount of transaction!
| transactionData -> reversedAmount |  | Not used | Not used |
| transactionData -> currency |  | Not used | Not used |

# App2AppSDK

## The process of SDK setting up:

### 1. Library import:

Add the appropriate repository inside your project's `settings.gradle`.

### From Remote Repository

    dependencyResolutionManagement {
      repositories {
    		  // here
    	  }
      }

Content copied to clipboard

> May not work with

>        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

> Content copied to clipboard

> Comment this line in case it causes problems. Notice that in this case your subprojects would have right to use third-party repositories.

    maven {
      credentials {
      username "$MAVEN_LOGIN"
      password "$MAVEN_PASSWORD"
      }
      url = "http://public.novelpay.pl:8088/repository/novelpay-android-release/"
      allowInsecureProtocol = true
    }
    maven {
        credentials {
            username "$MAVEN_LOGIN"
            password "$MAVEN_PASSWORD"
        }
        url = "http://public.novelpay.pl:8088/repository/novelpay_android_repository/"
        allowInsecureProtocol = true
    }

Content copied to clipboard

You may add credentials right in inside `maven` block or define it in `gradle.properties`

    	MAVEN_LOGIN="login"
    	MAVEN_PASSWORD="password"

Content copied to clipboard

### 2. Android specs:

    compile_sdk           : 31
    min_sdk               : 22
    target_sdk            : 30

Content copied to clipboard

### Modules description:

- Client - contains point of interaction with SDK classes

- Protocol-Retailer - contains Retailer domain message classes

- AIDL-API - is used in terms of backward compatibility and contains code responsible for communication between applications

- Util - is just a set of tools used across SDK

## All modules:

app2app-android
Link copied to clipboard
app2app-kotlin
Link copied to clipboard
client
Link copied to clipboard

There is a bunch of useful stuff for discovering integration capabilities

parser-retailer
Link copied to clipboard
protocol-retailer
Link copied to clipboard

The `protocol-retailer` module provides the definitive implementation of the Retailer (POI-PIS) protocol. It is the primary high-level communication language between Sale Systems (ECR) and Payment Terminals (POI) within the App2AppSDK, featuring robust support for both XML and JSON data encapsulations.

util
Link copied to clipboard

Utilities module describe abstractions used across the SDK