protocol-retailer
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.
Overview
pl.novelpay.retailer
The initialization and orchestration root for the protocol.
src/main/java/pl/novelpay/retailer/RetailerProtocol.kt: The concrete implementation of
pl.novelpay.protocol.Protocol.src/main/java/pl/novelpay/retailer/RetailerProtocolFactory.kt: The singleton factory that assembles the complex dependency graph of the Retailer module.
pl.novelpay.retailer.configuration
Static environment and identity definitions.
src/main/java/pl/novelpay/retailer/configuration/RetailerConfiguration.kt: Defines crucial terminal identification attributes such as
poiID,saleID, andSoftwareComponentmetadata.
pl.novelpay.retailer.client
The primary developer surface for interacting with the Retailer protocol.
src/main/java/pl/novelpay/retailer/RetailerProtocol.kt: An exhaustive API contract defining business capabilities (
sendPaymentRequest,sendReversalRequest, etc.).src/main/java/pl/novelpay/retailer/client/RetailerCommunicationInterfaceImpl.kt: The concrete router that binds high-level SDK method calls to underlying domain message generation.
pl.novelpay.retailer.message
The domain model declarations, strictly categorized by intent.
request.*: Initiators of operations (e.g., src/main/java/pl/novelpay/retailer/message/request/RetailerPaymentRequest.kt, src/main/java/pl/novelpay/retailer/message/request/RetailerLoginRequest.kt).response.*: Results of operations (e.g., src/main/java/pl/novelpay/retailer/message/response/RetailerPaymentResponse.kt).src/main/java/pl/novelpay/retailer/message/RetailerRawMessageType.kt: The low-level envelope representing the raw XML/JSON string before domain parsing.
pl.novelpay.retailer.conversion
The transformation engine powering the protocol.
src/main/java/pl/novelpay/retailer/conversion/RetailerRawDomainConverterImpl.kt: Uses strategy patterns (src/main/java/pl/novelpay/retailer/converter/set/RetailerConvertersSet.kt) to map parsed XML/JSON into concrete Kotlin data classes.
src/main/java/pl/novelpay/retailer/conversion/RetailerRawTypeConverterImpl.kt: The
RawReaderimplementation that performs the initial text analysis to determine message type.
pl.novelpay.retailer.runtime
Session and state persistence.
src/main/java/pl/novelpay/retailer/runtime/RetailerProtocolRuntimeStorage.kt: Maintains dynamic configuration like the active
MessageFormat(XML vs JSON) and session IDs.
Standard Usage Example
// Obtain the communication interface interface
val retailerApi = sdk.provideCommunicationInterface(RetailerCommunicationInterface::class)
// Configure protocol optimizations dynamically
retailerApi.switchMessageFormat(MessageFormat.JSON)
// Dispatch a domain operation
retailerApi.sendPaymentRequest(
PaymentRequestMessageArguments.RegularPaymentRequestMessageArguments(
amount = 125.50,
currency = "PLN"
)
)Protocol Versioning and Message Migration
To ensure backward compatibility between the SDK and the payment application, the SDK implements a message migration mechanism. This system allows the SDK to modify outgoing domain messages on-the-fly to conform to the specific protocol version supported by the connected payment application.
The Migration Process
Version Detection: When the SDK establishes a connection, it queries the payment application to get the specific version of the
Retailerprotocol it is using. This is handled byProtocolVersionMigrationEnvironmentImpl, which makes an IPC call to the server.Migration Execution: Before a
DomainMessageis sent, it passes through theMigrateSendingDomainMessageprocessor. This processor applies a list of registered migrations to the message. Each migration is a class that extendsMessageMigration.Conditional Logic: Inside each migration class, the
ServerClientMigrationEnvironmentprovides helper functions likeforVersions(...)andnotForVersions(...)to execute logic conditionally based on the detected protocol version.
Example: DonationsMigration
A practical example is the DonationsMigration. If a new donations field was added in version 1.3 of the protocol, older versions of the payment application would not understand it. This migration ensures that for any protocol version that is not 1.3, the donations field is removed from the RetailerPaymentRequest before it is sent.
// DonationsMigration.kt
class DonationsMigration : MessageMigration() {
override fun migrate(message: DomainMessage): DomainMessage =
// Only run this logic for versions that are NOT "1.3"
serverClientMigrationEnvironment.notForVersions("1.3") { version ->
when (message) {
is RetailerPaymentRequest -> message.copy(
// Remove the unsupported field
marketpayPaymentExtensions = extensions.copy(additionalAmount = null)
)
else -> message
}
} ?: message // If the version is 1.3, return the original message
}Important: Maintaining Migrations
It is crucial to maintain migration classes as new protocol versions are released. For example, the
DonationsMigrationusesnotForVersions("1.3"). If a new version1.4is released (which presumably supports donations), this logic would incorrectly remove the donations field when communicating with a1.4server.To prevent this, migrations must be updated to be explicit about the versions they target. The logic should be changed to either:
Specify incompatible versions:
forVersions("1.0", "1.1", "1.2") { ... }Explicitly exclude all newer versions:
notForVersions("1.3", "1.4") { ... }Forgetting to update these classes will lead to incorrect message formatting and potential communication failures with new server versions.