App-naar-App integratie

1. Inleiding & Overzicht

1.1 Wat is App-naar-App integratie?

App-naar-App integratie is de oplossing waarmee uw applicatie (de Elektronische Kassa of POS-systeem) veilig kan communiceren met de Market Pay Betaalapplicatie op hetzelfde toestel.

Dit wordt bereikt via Inter-Process Communication (IPC). In de Android-omgeving maakt IPC communicatie mogelijk tussen twee aparte applicaties die gelijktijdig op hetzelfde toestel draaien.

1.2 Waarom de Market Pay SDK gebruiken?

Het integreren van de Market Pay SDK is de eenvoudigste manier om App-naar-App communicatie tot stand te brengen. Het primaire doel van de SDK is om de complexe, laag-niveau details van Android IPC, zoals AIDL (Android Interface Definition Language) en Binder-connectiviteit, te abstraheren.

In plaats van te werken met ruwe data marshalling en procesbinding, biedt de SDK een duidelijke, hoog-niveau Client interface (een Plain Old Java Object of POJO). Deze interface laat uw ontwikkelaars acties zoals betalingen en inloggen starten met eenvoudige, intuïtieve methode-aanroepen, zoals sendPaymentRequest().

De SDK bevat ook robuuste functies voor berichtafhandeling en statusbeheer:

  • Interprotocol Conversie: Converteert automatisch berichten tussen verschillende protocolstandaarden (bv. Nexo Retailer Protocol) indien nodig.
  • Connectiviteit Beheer: Beheert het verbinden, verbreken en opnieuw verbinden met de Betaalapplicatie-service.
  • Asynchrone Feedback: Gebruikt een Interceptor om transactieresponsen te leveren en een EventObserver om uw applicatie te informeren over statuswijzigingen (bv. IdleTransactionState, PaymentTransactionState).

1.3 Voorwaarden Checklist

Zorg ervoor dat aan de volgende vereisten is voldaan voordat u met de SDK-installatie begint:

VereisteWaarde / ActieOpmerkingen
Android Min SDK22Vereist voor de applicatie die de SDK-integratie bouwt.
Android Target SDK30Vereist voor de applicatie die de SDK-integratie bouwt.
SDK Dependency FileGeleverd .aar-bestand (bv. client-clientRelease-X.X.X.aar)Moet geplaatst worden in de libs-map van uw module.

2. Setup: Installeren en Initialiseren van de SDK

U vindt een gedetailleerde beschrijving en een gebruiksvoorbeeld in: pl.novelpay.client.sdk voor een voorbeeld van SDK-setup.

2.1 Stap 1: Voeg de SDK-afhankelijkheid toe

  1. Plaats het geleverde .aar bestand (bv. client-clientRelease-0.0.8_cc9d51b.aar) in de libs map van uw module.
  2. Voeg de implementatieregel toe aan uw module-level build.gradle bestand:
dependencies {
    // ... other dependencies
    implementation files('libs/client-clientRelease-0.X.X.aar')
}

2.2 Stap 2: Configureer en Bouw de Client

De ClientFactory wordt één keer gebruikt, meestal tijdens het opstarten van uw applicatie (bv. in uw Application class of hoofd-Activity), om de verbinding tot stand te brengen en handlers te configureren.

Dit is het belangrijkste codeblok. Breek het op in duidelijke, becommentarieerde stappen:

  1. Start de configuratieketen
  2. Configureer de bestemmingsapp (SDK-configuratie)
  3. Configureer protocol (in ons geval Nexo Retailer Protocol v3 voorbeeld)
  4. Stel message response handler in
  5. Stel application event handler in
  6. Finaliseer en verbind

 

// 1. Start the configuration chain
val clientInstance = ClientFactory
    .bindConfiguration( 
        // 2. CONFIGURE DESTINATION APP (The Payment Terminal App)
        SDKConfiguration.defaultConfiguration(
            clientApplicationInfo = PackageInfo(
                applicationName = applicationInfo.name, // Your app's name 
                applicationPackage = packageName // Your app's package name
            )
        )
    )
    .bindContext(context = applicationContext)
    .bindProtocolConfiguration(
        // 3. CONFIGURE PROTOCOL (Nexo Retailer Protocol v3 example. 
        // Your application's parameters as POS might be specified here. 
        RetailerConfiguration(saleID = "Communicator_SALE")
    )
    .bindInterceptor { message: DomainMessage -> 
        // 4. SET MESSAGE RESPONSE HANDLER (See Section 3.2)
        // This is where all transaction responses arrive (Success, Error, etc.)
        when (message) {
        
            is SuccessRetailerLoginResponse -> { /* Handle login success */ }
            is ErrorRetailerLoginResponse -> { /* Handle login failure */ }
            // ... all other responses
        }
    }
    .bindEventObserver { event: ObservableEvent -> 
        // 5. SET APPLICATION EVENT HANDLER (See Section 3.3)
        // Handles state changes (Idle, PaymentProcessing) and foregrounding.
        when (event) {
            is ObservableEvent.TransactionStateChanged -> { 
                // Handle state changes and bring your app to foreground
            }
            is ObservableEvent.ServerEvent -> { 
                // Handle general server events
            }
        }
    }
    .build() // 6. Finalize and connect

3. Kerngebruik: Verzoeken versturen en antwoorden verwerken

3.1 Nexo Retailer protocol specificaties

Elke communicatiesessie moet beginnen met een RetailerLoginRequest. Dit moet getriggerd worden in de volgende gevallen:

  • Update van de Betaalapplicatie
  • Herstart van de Betaalapplicatie
  • Herstart van de client (uw) applicatie
  • De verbinding tussen applicaties is verloren
  • RetailerLogoutRequest werd verzonden

Een nieuwe loginprocedure moet worden gestart om de sessie te initialiseren. Anders worden verzoeken van uw applicatie als niet-geautoriseerd beschouwd en genegeerd door de Betaalapplicatie.

3.1 Verzoeken versturen (Client Interface)

Het Client interface-instantie geretourneerd door ClientFactory::build() is uw primaire communicatietool. Alle methodes zijn suspended functies en moeten aangeroepen worden vanuit een coroutine scope.

ActieMethodeOpmerkingen
Sessie startensuspend fun sendLoginRequest()Moet als eerste worden aangeroepen. Start een nieuwe sessie na elke onderbreking (herstarts, updates, verlies van verbinding).
Betalingsuspend fun sendPaymentRequest(...)Wordt gebruikt voor verkopen, terugbetalingen, pre-autorisaties en afrondingen.
Annulatiesuspend fun sendReversalRequest(...)Gebruikt voor het annuleren van transacties.
Statuscontrolesuspend fun sendTransactionStatusRequest(...)Haalt de status op van de laatst voltooide transactie.

Login Request

Elke integratie moet beginnen met het opzetten van een veilige sessie. Het login-verzoek moet worden aangeroepen bij het opstarten van uw applicatie en opnieuw worden getriggerd als de verbinding verloren is of de terminal-app opnieuw wordt opgestart.

// Establishing a secure communication session
fun performLogin() {
    viewModelScope.launch {
        try {
            client.sendLoginRequest()
            // Success is handled via your registered Interceptor
        } catch (e: Exception) {
            Logger.e("Login failed to initiate: ${e.message}")
        }
    }
}

Een betaling starten

Dit is de kernfunctie voor het verwerken van financiële transacties. Er is een unieke transactie-ID vereist voor tracking en de specifieke bedragen die moeten worden aangerekend.

// Initiating a standard payment transaction
fun startPayment(amount: Double) {
    viewModelScope.launch {
        client.sendPaymentRequest(
            transactionID = "Txn_${UUID.randomUUID()}", // Unique sale ID
            paymentAmounts = PaymentAmounts(amount, "EUR"), 
            paymentType = PaymentType.Normal
        )
    }
// Responses will be processed asynchronously in the Interceptor (Section 3.2).

TransactionID mag maximaal 35 tekens lang zijn. Indien u UUID gebruikt, verwijder het "-" (koppelteken) uit de gegenereerde UUID.

Transactie Status Verzoek

Gebruik dit om het uiteindelijke resultaat van een transactie te controleren als uw app tijdens de verwerking de verbinding verloor. Het haalt de status op van het laatst voltooide bericht.

// Checking the status of a specific transaction reference
fun checkStatus(reference: MessageReference) {
    viewModelScope.launch {
        client.sendTransactionStatusRequest(reference)
    }
}

Reversal Request

Als een transactie per ongeluk is voltooid (bv. verkeerd bedrag ingevoerd), annuleert het reversal-verzoek dat specifieke record met behulp van de originele terminal transactie-ID.

// Canceling a previously completed transaction
fun reverseTransaction(originalPoiId: String) {
    viewModelScope.launch {
        client.sendReversalRequest(
            saleReferenceId = "Sale_Ref_98765",
            poiTransactionId = originalPoiId, // The ID returned by the terminal in the original response
            transactionData = ReversalRequestMessageArguments.TransactionAmountsData(...)
        )
    }
}

Update

Deze functie is bedoeld voor administratieve terminaltaken, zoals het starten van software-updates of het downloaden van de nieuwste beschikbare parameters.

// Triggering administrative tasks like terminal updates
fun updateTerminal() {
    viewModelScope.launch {
        client.sendAdminRequest(RetailerAdminExtension.Update)
    }
}

Diagnose

Een eenvoudige "heartbeat"-controle om te verifiëren dat de link tussen uw app en de betaalterminal nog actief is zonder een volledige transactie te starten.

// Verifying the heartbeat connection to the terminal
fun testConnection() {
    viewModelScope.launch {
        client.sendTestConnectionRequest(
            DiagnosisRequestMessageArguments(messageId = "test_123")
        )
    }
}

Logout verzoek

 

// Closing the current session gracefully
fun performLogout() {
    viewModelScope.launch {
        client.sendLogoutRequest()
    }
}

 

3.2 Antwoorden ontvangen (De Interceptor)

Alle antwoorden op de via de Client interface verzonden verzoeken worden afgeleverd aan de Interceptor die u in de setup hebt gedefinieerd.

De inkomende parameter heeft het generieke type DomainMessage; alle berichten in App-naar-App communicatie zijn subtypes van deze klasse. Om de afhandelingsstrategie voor elk antwoordtype te specificeren, wordt typecheck aanbevolen. 

Elke operatie zoals (Betaling, Reversal, Refund...) heeft zijn eigen berichttype. Dit kan worden gevonden in het documentatiepakket retailer-protocol. 

Overzicht van verzoeken en antwoorden voor hoofdoperaties:

OperatieVerzoektypeAntwoordtypeVerwachte antwoordsubtypes
LoginRetailerLoginRequestRetailerLoginResponseSuccessRetailerLoginResponse, FailedRetailerLoginResponse
BetalingRetailerPaymentRequest

RetailerPaymentResponse

RetailerDisplayRequest

SuccessRetailerPaymentResponse, ErrorRetailerPaymentResponse
AnnulatieRetailerAbortRequestRetailerAbortResponseErrorRetailerPaymentResponse
ReversalRetailerReversalRequestRetailerReversalResponseSuccessRetailerReversalResponse, ErrorRetailerReversalResponse
StatusTransactionStatus

RetailerTransactionStatusResponse

RetailerDisplayRequest

SuccessRetailerTransactionStatusResponse, ErrorRetailerTransactionStatusResponse, RetailerDisplayRequest

Opmerking. Payment request wordt ook gebruikt voor terugbetalingen, pre-autorisaties en pre-autorisatie-afrondingen. Er zijn geen aparte subtypes voor deze operaties. DisplayRequest zal tijdens de verwerking aangeven welke operatie wordt uitgevoerd.

4. Status en Events Afhandelen

4.1 De EventObserver (Statuswijzigingen)

Gebruik de bindEventObserver om de operationele status van de Betaalapplicatie te monitoren en de gebruikersinterface van uw applicatie te beheren.

ObservableEvent.ServerEvent dit event signaleert dat een specifieke interne actie of melding heeft plaatsgevonden binnen de Betaalapplicatie.

Het belangrijkste event is ObservableEvent.TransactionStateChanged.

StatusBeschrijvingAanbeveling voor voorgrond
PaymentTransactionStateBetalingsverwerking is gestart.Optioneel: Breng uw applicatie naar de voorgrond (bv. om een aangepaste UI te tonen tijdens autorisatie).
IdleTransactionStateBetaalapplicatie is klaar met de transactie.Aanbevolen: Breng uw applicatie naar de voorgrond om het eindresultaat te tonen.
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
Ignor
}
}
}
}

4.2 BringToForeground

bringToForeground(MainActivity :class) - is de functie verantwoordelijk voor het "naar voren halen" van uw applicatie na statuswijzigingen in de Betaalapplicatie. MainActivity:class is een placeholder voor de momenteel actieve activity van uw applicatie. Als uw applicatie een Single-Activity is hoeft u zich geen zorgen te maken over activity-transities, geef gewoon uw Main Activity class op zoals in het voorbeeld.

  • PaymentTransactionSate -> Indien actief/aanwezig: schakel terug naar uw applicatie tijdens betalingsautorisatie.
  • IdleTransactionState -> Indien actief/aanwezig: schakel terug naar uw applicatie aan het einde van de betaling.

Bijvoorbeeld: U wilt reclame tonen in uw applicatie of aangepaste transactiegegevens tijdens de betalingsverwerking in plaats van de spinner van de Betaalapplicatie. Alles wat u hoeft te doen om dit te bereiken: specificeer het pad voor PaymentTransactionState.


illustratie:

.bindEventObserver { event: ObservableEvent -> 
    when (event) {
        is ObservableEvent.TransactionStateChanged -> when (event.state) {
            // Bring your app to the foreground when payment finishes
            is IdleTransactionState -> bringToForeground(MainActivity::class.java) 
            // Optional: Bring your app to the foreground when payment starts
            is PaymentTransactionState -> bringToForeground(MainActivity::class.java)
            else -> { /* Ignore other states */ } 
        }
        is ObservableEvent.ServerEvent -> Logger.d("New server event") 
    } 
}

Voorbeeld om uw app naar de voorgrond te brengen wanneer autorisatie bezig is:

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
Ignor
}
}
}
}

Voorbeeld om uw app naar de voorgrond te brengen wanneer de betaling voltooid is:

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
Ignor
}
}
}
}

5. Bijlage: ProGuard-regels

Als uw project obfuscatie gebruikt via R8/ProGuard, zorg er dan voor dat de volgende regels zijn toegevoegd aan uw proguard-rules.pro bestand.

 -keep class pl.novelpay.** { *; } 
 -dontwarn pl.novelpay.** 
 -keep class org.koin.** { *; } 
 -keep class androidx.lifecycle.** { *; } 
 -keep class com.jakewharton.threetenabp.** { *; } 
 -dontwarn com.google.gson.annotations.SerializedName  -dontwarn javax.xml.stream.Location 
 -dontwarn javax.xml.stream.XMLEventReader 
 -dontwarn javax.xml.stream.XMLInputFactory 
 -dontwarn javax.xml.stream.events.Attribute 
 -dontwarn javax.xml.stream.events.Characters 
 -dontwarn javax.xml.stream.events.StartElement 
 -dontwarn javax.xml.stream.events.XMLEvent 
 -dontwarn kotlinx.parcelize.Parcelize 
 -keepattributes
InnerClasses,Signature,RuntimeVisible*Annotations,EnclosingMethod  -dontwarn com.google.gson.TypeAdapter 
 -dontwarn com.google.gson.stream.JsonReader  -dontwarn com.google.gson.stream.JsonWriter  -keep class kotlin.Metadata { *; } 
 -keep class org.simpleframework.** { *; } 
 # Preserve all annotations. 
 -keepattributes Annotation 

6. Bijlagen

Gerelateerd aan