Complete guide to integrating Checkout Drop-in into your Android application.
Checkout Drop-in provides a complete, pre-built payment interface that automatically handles multiple payment methods. It follows a simple three-step lifecycle:
- Initialise: Configure Drop-in with your session and transaction data.
- Create: Generate the composable component from a coroutine.
- 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.
Make sure you've activated the Checkout Drop-in service in the Unity Portal.
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")
}In order to initialise Checkout Drop-in, you'll need to send authenticated requests to the PXP API.
To get your credentials:
- In the Unity Portal, go to Merchant setup > Merchant groups.
- Select a merchant group.
- Click the Inbound calls tab.
- Copy the Client ID in the top-right corner.
- Click New token.
- Choose a number of days before token expiry. For example,
30. - Click Save to confirm. Your token is now created.
- 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.
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.
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.
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.
The session request accepts the following parameters:
| Parameter | Description |
|---|---|
merchantstring (≤ 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. |
sitestring (≤ 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. |
merchantTransactionIdstring (≤ 50 characters) required | A unique identifier of your choice that represents this transaction. |
sessionTimeoutnumber required | The duration of the session, in minutes. |
transactionMethodobject required | Details about the transaction method, including the intent for each payment type. |
transactionMethod.intentobject required | The transaction intent for each payment method. |
transactionMethod.intent.cardstring | The intent for card, Apple Pay, or Google Pay transactions. Possible values:
|
transactionMethod.intent.paypalstring | The intent for PayPal transactions. Possible values:
|
amountsobject required | Details about the transaction amount. |
amounts.currencyCodestring (3 characters) required | The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies. |
amounts.transactionValuenumber 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). |
allowTransactionboolean | Whether or not to proceed with the transaction. |
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!
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)
}
)
)The CheckoutDropInConfig class accepts the following configuration parameters:
| Parameter | Description |
|---|---|
environmentEnvironment required | The environment type. Possible values:
|
sessionSessionConfig required | Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, and allowedFundingTypes. |
ownerTypeString required | Always set to "MerchantGroup" for Drop-in. |
ownerIdString (≤ 20 characters) required | Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal. |
transactionDataDropInTransactionData required | Details about the transaction. |
clientIdString | Client identifier used by the SDK configuration. Defaults to empty string. |
merchantIdString | Optional merchant identifier when required by the integration. Defaults to empty string. |
kountDisabledBoolean | Set to true to disable Kount fraud detection. Defaults to false. |
restrictionsRestrictions? | Optional card restrictions supplied at SDK level. |
paypalConfigPaypalConfig? | 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. |
localeString | Locale used by the checkout experience. Defaults to "en-US". |
localisationsMap<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. |
methodConfigDropInMethodConfig? | Optional payment-method-specific configuration for global, card, PayPal, and Google Pay behaviour. |
onBeforeSubmitsuspend (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. |
The DropInTransactionData class contains transaction details:
| Parameter | Description |
|---|---|
amountDouble required | The transaction amount. Must match the currency's expected decimal places. |
currencyString (3 characters) required | The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies. |
entryTypeEntryType? | The entry type. Possible values:
|
intentDropInTransactionIntentData required | The transaction intents for each payment method. See supported transaction intents for details. |
merchantString required | Your merchant name or identifier. |
merchantTransactionIdString (≤ 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. |
shopperShopper? | Optional shopper data attached to the transaction. |
recurringRecurringData? | Optional recurring payment configuration for subscriptions. |
cardAcceptorNameString? | Optional card acceptor name shown on card statements. |
The DropInTransactionIntentData class specifies payment intents:
| Parameter | Description |
|---|---|
cardIntentType? | The intent for card or Google Pay transactions. Possible values:
|
paypalDropInIntentDropInPayPalIntentType? | The intent for PayPal transactions. Possible values:
|
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.
Drop-in requires callbacks to handle payment lifecycle events.
Returns shopper information for Card-on-File functionality.
onGetShopper = {
Shopper(id = "shopper-123")
}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")
}
}
}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.
Frontend callbacks can be manipulated by malicious users. You must verify all payments on your backend before fulfilling orders.
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
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
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
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
)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.