Skip to content

Implementation

Complete guide to integrating Checkout Drop-in into your Android application.

Overview

Checkout Drop-in provides a complete, pre-built payment interface that automatically handles multiple payment methods. It follows a simple three-step lifecycle:

  1. Initialise: Configure Drop-in with your session and transaction data.
  2. Create: Generate the composable component from a coroutine.
  3. Handle callbacks: Respond to payment success, errors, and other events.

Drop-in automatically detects available payment methods, renders the UI, and handles all payment flows.

Backend verification is mandatory. Always verify payments on your backend before fulfilling orders. Frontend callbacks can be manipulated by malicious users.

Before you start

Make sure you've activated the Checkout Drop-in service in the Unity Portal.

Step 1: Add the SDK dependency

Add the latest version of the Android SDK to your project's build.gradle file:

dependencies {
    implementation("io.pxp.android:checkout-sdk:1.0.0")
}

Step 2: Get your API credentials

In order to initialise Checkout Drop-in, you'll need to send authenticated requests to the PXP API.

To get your credentials:

  1. In the Unity Portal, go to Merchant setup > Merchant groups.
  2. Select a merchant group.
  3. Click the Inbound calls tab.
  4. Copy the Client ID in the top-right corner.
  5. Click New token.
  6. Choose a number of days before token expiry. For example, 30.
  7. Click Save to confirm. Your token is now created.
  8. Copy the token ID and token value. Make sure to keep these confidential to protect the integrity of your authentication process.

As best practice, we recommend regularly generating and implementing new tokens.

Step 3: Create a session on your backend

Checkout Drop-in requires a session from the PXP Sessions API. This must be done on your backend using HMAC authentication to keep your credentials secure.

Understanding HMAC authentication

Our platform uses HMAC (Hash-based Message Authentication Code) with SHA256 for authentication to ensure secure communication and data integrity. This method involves creating a signature by hashing your request data with a secret key, which must then be included in the HTTP headers of your API request.

Create an HMAC signature

To create the HMAC signature, you need to prepare a string that includes five parts separated by colons:

  • Token ID: Your API token identifier (e.g., 9aac6071-38d0-4545-9d2f-15b936af6d7f)
  • Timestamp: The current time in Unix milliseconds (e.g., 1754701373)
  • Request ID: A unique GUID for this request (e.g., ce244054-b372-42c2-9102-f0d976db69f6)
  • Request path: The API endpoint path: api/v1/sessions
  • Request body: The complete JSON request body as a minified string

Example request body to minify:

{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "sessionTimeout": 120,
  "merchantTransactionId": "0ce72cfd-014d-4256-a006-a56601b2ffc4",
  "transactionMethod": {
    "intent": {
      "card": "Authorisation",
      "paypal": "Purchase"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 25.00
  },
  "allowTransaction": true,
  "serviceType": "CheckoutDropIn"
}

When creating the HMAC signature, the request body must be minified (no whitespace or formatting). The formatted JSON above is for readability only.

Session request parameters

The session request accepts the following parameters:

ParameterDescription
merchant
string (≤ 20 characters)
required
Your unique merchant identifier, as assigned by PXP. You can find it in the Unity Portal, by going to Merchant setup > Merchants and checking the Merchant ID column.
site
string (≤ 20 characters)
required
Your unique site identifier, as assigned by PXP. You can find it in the Unity Portal, by going to Merchant setup > Sites and checking the Site ID column.
merchantTransactionId
string (≤ 50 characters)
required
A unique identifier of your choice that represents this transaction.
sessionTimeout
number
required
The duration of the session, in minutes.
transactionMethod
object
required
Details about the transaction method, including the intent for each payment type.
transactionMethod.intent
object
required
The transaction intent for each payment method.
transactionMethod.intent.card
string
The intent for card, Apple Pay, or Google Pay transactions.

Possible values:
  • Authorisation
  • Purchase
  • Verification
  • EstimatedAuthorisation
transactionMethod.intent.paypal
string
The intent for PayPal transactions.

Possible values:
  • Authorisation
  • Purchase
amounts
object
required
Details about the transaction amount.
amounts.currencyCode
string (3 characters)
required
The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies.
amounts.transactionValue
number
required
The transaction amount. The numbers after the decimal will be zero padded if they are less than the expected currencyCode exponent. For example, GBP 1.1 = GBP 1.10, USD 1 = USD 1.00, or BHD 1.3 = 1.300. The transaction will be rejected if numbers after the decimal are greater than the expected currencyCode exponent (e.g., GBP 1.234), or if a decimal is supplied when the currencyCode of the exponent does not require it (e.g., JPY 1.0).
allowTransaction
boolean
Whether or not to proceed with the transaction.

Session response

If your request is successful, you'll receive a 200 response containing the session data:

{
  "sessionId": "c5f0799b-0839-43ce-abc5-5b462a98f250",
  "hmacKey": "904bc42395d4af634e2fd48ee8c2c7f52955a1da97a3aa3d82957ff12980a7bb",
  "encryptionKey": "20d175a669ad3f8c195c9c283fc86155",
  "sessionExpiry": "2025-05-19T13:39:20.3843454Z",
  "allowedFundingTypes": {
    "cardSchemes": [
      "Visa",
      "Diners",
      "Mastercard",
      "AmericanExpress"
    ],
    "cards": [],
    "wallets": {
      "paypal": {
        "allowedFundingOptions": [
          "venmo", 
          "paylater", 
          "paypal"
        ],
        "merchantId": "paypal-merchant-123"
      },
      "googlePay": {
        "merchantId": "BCR2DN4TWWPKJ45P",
        "merchantName": "Your Store Name",
        "gatewayMerchantId": "gateway-merchant-id"
      }
    }
  }
}

Checkout Drop-in automatically detects available payment methods from the allowedFundingTypes in your session data. You don't need to manually configure which payment methods to show!

Step 4: Initialise Drop-in in your app

Import the necessary types and initialise Drop-in with your configuration.

import com.pxp.checkout.checkoutdropin.CheckoutDropIn
import com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig
import com.pxp.checkout.models.DropInTransactionData
import com.pxp.checkout.models.DropInTransactionIntentData
import com.pxp.checkout.models.Environment
import com.pxp.checkout.models.EntryType
import com.pxp.checkout.models.IntentType
import com.pxp.checkout.checkoutdropin.types.DropInSubmitResult
import com.pxp.checkout.exceptions.BaseSdkException
import java.time.Instant
import java.util.UUID

// Fetch session data from your backend
val sessionData = fetchSessionFromBackend()

// Initialise Checkout Drop-in
val checkoutDropIn = CheckoutDropIn.initialize(
    context = context,
    config = CheckoutDropInConfig(
        environment = Environment.TEST,
        session = sessionData,
        ownerType = "MerchantGroup",
        ownerId = "MERCHANT-1",
        transactionData = DropInTransactionData(
            amount = 25.0,
            currency = "USD",
            entryType = EntryType.Ecom,
            intent = DropInTransactionIntentData(
                card = IntentType.Purchase,
                paypalDropInIntent = DropInPayPalIntentType.Authorisation
            ),
            merchant = "MERCHANT-1",
            merchantTransactionId = UUID.randomUUID().toString(),
            merchantTransactionDate = { Instant.now().toString() }
        ),
        kountDisabled = false, // OPTIONAL: Set to true to disable Kount fraud detection
        onGetShopper = {
            Shopper(id = "shopper-123")
        },
        onBeforeSubmit = { paymentMethod ->
            // Validate merchant form
            validateMerchantForm()
        },
        onSuccess = { result ->
            // CRITICAL: Verify on backend before fulfilling order
            verifyPaymentOnBackend(result)
        },
        onError = { error ->
            // Show error message
            showPaymentError(error.message)
        }
    )
)

Configuration parameters

The CheckoutDropInConfig class accepts the following configuration parameters:

ParameterDescription
environment
Environment
required
The environment type.

Possible values:
  • Environment.TEST: For development and testing.
  • Environment.LIVE: For live transactions.
session
SessionConfig
required
Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, and allowedFundingTypes.
ownerType
String
required
Always set to "MerchantGroup" for Drop-in.
ownerId
String (≤ 20 characters)
required
Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal.
transactionData
DropInTransactionData
required
Details about the transaction.
clientId
String
Client identifier used by the SDK configuration. Defaults to empty string.
merchantId
String
Optional merchant identifier when required by the integration. Defaults to empty string.
kountDisabled
Boolean
Set to true to disable Kount fraud detection. Defaults to false.
restrictions
Restrictions?
Optional card restrictions supplied at SDK level.
paypalConfig
PaypalConfig?
Optional PayPal configuration used by SDK-level PayPal flows.
onGetShippingAddress
() -> ShippingAddress?
Function to supply shipping address when Drop-in needs it.
onGetShopper
() -> Shopper?
required
Function to retrieve shopper information. Required for Card-on-File functionality. Returns a Shopper object containing id.
locale
String
Locale used by the checkout experience. Defaults to "en-US".
localisations
Map<String, LocalisationConfig>
Optional localised labels and messages by locale code. Defaults to empty map.
analyticsEvent
(BaseAnalyticsEvent) -> Unit
Receives SDK analytics events so merchants can forward them to their analytics platform.
methodConfig
DropInMethodConfig?
Optional payment-method-specific configuration for global, card, PayPal, and Google Pay behaviour.
onBeforeSubmit
suspend (PaymentMethod) -> Boolean
Callback fired when a payment method is selected and the user is about to submit payment. Receives payment method. Return true to proceed or false to cancel.
onSubmit
(PaymentMethod) -> Unit
Callback fired when payment processing begins. Use this to show loading indicators.
onSuccess
(DropInSubmitResult) -> Unit
required
Callback fired when payment succeeds.
onError
(BaseSdkException) -> Unit
required
Callback fired when payment fails. Receives error details including code and message.

Transaction data parameters

The DropInTransactionData class contains transaction details:

ParameterDescription
amount
Double
required
The transaction amount. Must match the currency's expected decimal places.
currency
String (3 characters)
required
The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies.
entryType
EntryType?
The entry type.

Possible values:
  • EntryType.Ecom: E-commerce transactions
  • EntryType.Moto: Mail order/telephone order transactions
intent
DropInTransactionIntentData
required
The transaction intents for each payment method. See supported transaction intents for details.
merchant
String
required
Your merchant name or identifier.
merchantTransactionId
String (≤ 50 characters)
required
A unique identifier for this transaction, as assigned by you.
merchantTransactionDate
() -> String
required
A function that returns the current date and time in ISO 8601 format.
shopper
Shopper?
Optional shopper data attached to the transaction.
recurring
RecurringData?
Optional recurring payment configuration for subscriptions.
cardAcceptorName
String?
Optional card acceptor name shown on card statements.

Transaction intent parameters

The DropInTransactionIntentData class specifies payment intents:

ParameterDescription
card
IntentType?
The intent for card or Google Pay transactions.

Possible values:
  • IntentType.Authorisation
  • IntentType.EstimatedAuthorisation
  • IntentType.Purchase
  • IntentType.Verification
paypalDropInIntent
DropInPayPalIntentType?
The intent for PayPal transactions.

Possible values:
  • DropInPayPalIntentType.Authorisation
  • DropInPayPalIntentType.Purchase

Step 5: Create and render Drop-in

Create the Drop-in component from a coroutine, then render it in your Compose UI:

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent

@Composable
fun CheckoutScreen() {
    var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }
    
    // Create the Drop-in component
    LaunchedEffect(Unit) {
        checkoutDropInComponent = checkoutDropIn.create()
    }
    
    // Render the Drop-in content
    checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())
}

At this point, Drop-in will:

  • Detect available payment methods from your session.
  • Render a vertical accordion with all available payment methods.
  • Apply branding from the Unity Portal.
  • Show a "Secured by PXP" branded footer.

Step 6: Handle callbacks

Drop-in requires callbacks to handle payment lifecycle events.

onGetShopper callback

Returns shopper information for Card-on-File functionality.

onGetShopper = {
    Shopper(id = "shopper-123")
}

onSuccess callback

Fires when payment succeeds.

Always verify on your backend before fulfilling orders.

onSuccess = { result ->
    // Verify payment on backend
    lifecycleScope.launch {
        val verified = verifyPaymentOnBackend(
            systemTransactionId = result.systemTransactionId,
            merchantTransactionId = result.merchantTransactionId
        )
        
        if (verified.success) {
            navigateToSuccess(verified.orderId)
        } else {
            showError("Payment verification failed")
        }
    }
}

onError callback

Fires when payment fails. Display appropriate error messages.

onError = { error ->
    Log.e("CheckoutDropIn", "Payment failed: ${error.code} - ${error.message}")
    showError(error.message ?: "Payment failed. Please try again.")
}

Never trust frontend callbacks for order fulfilment. Always verify payments on your backend using webhooks or the Query Transaction API before fulfilling orders.

Step 7: Backend verification (CRITICAL)

Frontend callbacks can be manipulated by malicious users. You must verify all payments on your backend before fulfilling orders.

Primary method: Webhooks

Unity sends real-time webhook notifications to your backend when transactions complete. Configure your webhook endpoint in the Unity Portal.

Learn more about webhook configuration and implementation

Optional fallback: Query the Transactions API

If you need instant feedback before the webhook arrives, you can query PXP's Transactions API. This is typically used as a fallback to provide immediate UI feedback while webhooks handle the authoritative verification.

Learn more about querying transactions via API

Error handling

All errors return a consistent structure with code and message properties. Common scenarios include card declined, insufficient funds, expired card, CVV failure, and 3DS authentication failure. Display the error message to users and allow them to retry or use a different payment method.

Learn more about error handling

Complete example

Here's a complete example showing Drop-in integration in a Jetpack Compose activity:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import com.pxp.checkout.checkoutdropin.CheckoutDropIn
import com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig
import com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent
import com.pxp.checkout.models.DropInTransactionData
import com.pxp.checkout.models.DropInTransactionIntentData
import com.pxp.checkout.models.*
import com.pxp.checkout.checkoutdropin.types.*
import com.pxp.checkout.checkoutdropin.types.DropInSubmitResult
import com.pxp.checkout.exceptions.BaseSdkException
import kotlinx.coroutines.launch
import java.time.Instant
import java.util.UUID

class CheckoutActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setContent {
            MaterialTheme {
                CheckoutScreen()
            }
        }
    }
    
    @Composable
    fun CheckoutScreen() {
        var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }
        var isLoading by remember { mutableStateOf(false) }
        var errorMessage by remember { mutableStateOf<String?>(null) }
        
        LaunchedEffect(Unit) {
            try {
                // 1. Get session from backend
                val sessionData = fetchSessionFromBackend()
                
                // 2. Initialise Drop-in
                val checkoutDropIn = CheckoutDropIn.initialize(
                    context = this@CheckoutActivity,
                    config = CheckoutDropInConfig(
                        environment = Environment.TEST,
                        session = sessionData,
                        ownerType = "MerchantGroup",
                        ownerId = "MERCHANT-1",
                        transactionData = DropInTransactionData(
                            amount = 25.0,
                            currency = "USD",
                            entryType = EntryType.Ecom,
                            intent = DropInTransactionIntentData(
                                card = IntentType.Purchase,
                                paypalDropInIntent = DropInPayPalIntentType.Authorisation
                            ),
                            merchant = "MERCHANT-1",
                            merchantTransactionId = UUID.randomUUID().toString(),
                            merchantTransactionDate = { Instant.now().toString() }
                        ),
                        kountDisabled = false, // OPTIONAL: Set to true to disable Kount fraud detection
                        onGetShopper = {
                            Shopper(id = "shopper-123")
                        },
                        onBeforeSubmit = { paymentMethod ->
                            // Validate merchant form
                            validateMerchantForm()
                        },
                        onSubmit = { paymentMethod ->
                            isLoading = true
                            errorMessage = null
                        },
                        onSuccess = { result ->
                            isLoading = false
                            
                            // CRITICAL: Verify on backend
                            lifecycleScope.launch {
                                val verified = verifyPaymentOnBackend(
                                    systemTransactionId = result.systemTransactionId,
                                    merchantTransactionId = result.merchantTransactionId
                                )
                                
                                if (verified.success) {
                                    navigateToSuccess(verified.orderId)
                                } else {
                                    errorMessage = "Payment verification failed"
                                }
                            }
                        },
                        onError = { error ->
                            isLoading = false
                            errorMessage = error.message ?: "Payment failed"
                        }
                    )
                )
                
                // 3. Create the component
                checkoutDropInComponent = checkoutDropIn.create()
                
            } catch (e: Exception) {
                errorMessage = "Failed to initialise checkout: ${e.message}"
            }
        }
        
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp)
            ) {
                Text(
                    text = "Complete Your Purchase",
                    style = MaterialTheme.typography.headlineMedium,
                    modifier = Modifier.padding(bottom = 16.dp)
                )
                
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(bottom = 16.dp)
                ) {
                    Column(modifier = Modifier.padding(16.dp)) {
                        Text(
                            text = "Order Summary",
                            style = MaterialTheme.typography.titleMedium
                        )
                        Spacer(modifier = Modifier.height(8.dp))
                        Text(text = "Product: Premium Subscription")
                        Text(text = "Amount: $25.00 USD")
                    }
                }
                
                if (errorMessage != null) {
                    Card(
                        colors = CardDefaults.cardColors(
                            containerColor = MaterialTheme.colorScheme.errorContainer
                        ),
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(bottom = 16.dp)
                    ) {
                        Text(
                            text = errorMessage!!,
                            color = MaterialTheme.colorScheme.onErrorContainer,
                            modifier = Modifier.padding(16.dp)
                        )
                    }
                }
                
                if (isLoading) {
                    Box(
                        modifier = Modifier.fillMaxWidth(),
                        contentAlignment = Alignment.Center
                    ) {
                        CircularProgressIndicator()
                    }
                }
                
                checkoutDropInComponent?.Content(
                    modifier = Modifier.fillMaxWidth()
                )
                
                Text(
                    text = "Your payment information is securely processed by PXP. We never store your full card details.",
                    style = MaterialTheme.typography.bodySmall,
                    modifier = Modifier.padding(top = 16.dp)
                )
            }
        }
    }
    
    private suspend fun fetchSessionFromBackend(): SessionConfig {
        // Call your backend to create a session
        // Return SessionConfig object
        TODO("Implement session fetching")
    }
    
    private fun validateMerchantForm(): Boolean {
        // Validate merchant-owned form fields
        return true
    }
    
    private suspend fun verifyPaymentOnBackend(
        systemTransactionId: String,
        merchantTransactionId: String
    ): VerificationResult {
        // Call your backend to verify the payment
        TODO("Implement payment verification")
    }
    
    private fun navigateToSuccess(orderId: String) {
        // Navigate to success screen
        TODO("Implement navigation")
    }
}

data class VerificationResult(
    val success: Boolean,
    val orderId: String? = null
)

What's next?

Now that you've integrated Checkout Drop-in, here are some recommended next steps:

  • Configuration: Customise payment method behaviour and callbacks.
  • Events: Learn about all available callbacks and event handling.
  • Testing: Use test cards and sandbox environment to test your integration.
  • Configure webhooks: Set up server-side webhook handling for reliable payment verification.