{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["accordion","img","admonition","code-walkthrough","step"]},"type":"markdown"},"seo":{"title":"Quickstart","description":"Transform your commerce with PXP's unified platform—seamless payments, real-time insights, and global growth in one powerful integration.","lang":"en-UK","siteUrl":"https://developer.pxp.io","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"quickstart","__idx":0},"children":["Quickstart"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"span","attributes":{"style":{"display":"inline-block","fontSize":"20px","color":"#3A3D40","borderRadius":"0","fontWeight":"500","verticalAlign":"middle","lineHeight":"1.5","margin":"0 auto 20px auto!important"}},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Follow our walkthrough to get Checkout Drop-in running in minutes."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"pre-requisites","__idx":1},"children":["Pre-requisites"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Before you start, make sure you have:"]},{"$$mdtype":"Tag","name":"ul","attributes":{"className":"code-walkthrough-list"},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Android Studio installed on your computer"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Android SDK 24 or higher"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Kotlin 2.2.10 or higher"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your API credentials from the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://portal.pxp.io","target":"_blank"},"children":["Unity Portal"]}]}]},{"$$mdtype":"Tag","name":"Accordion","attributes":{"title":"Where to find your credentials","defaultOpen":false},"children":[{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["In the ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://portal.pxp.io","target":"_blank"},"children":["Unity Portal"]},", go to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Merchant setup > Merchant groups"]}," and select a merchant group."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Click the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Inbound calls"]}," tab. Your client ID is in the top right:",{"$$mdtype":"Tag","name":"Image","attributes":{"src":"/assets/copy-client-id.0a23af7974c95fd4eb1889f740594750a69954f5f6ea5dbc5f3acf5a56d25922.fa48401d.png","alt":"","withLightbox":true,"className":"screenshot","width":"","height":""},"children":[]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Click ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["+ New token"]}," to create a token, then copy both the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ID"]}," and the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Value"]},".",{"$$mdtype":"Tag","name":"Image","attributes":{"src":"/assets/copy-token-value.51ecc9c77324b3383f3b7837f1e085d7bf7959bfe13773ed49fcfea8f9a78f0d.fa48401d.png","alt":"","withLightbox":true,"className":"screenshot","width":"","height":""},"children":[]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"add-the-sdk-dependency","__idx":2},"children":["Add the SDK dependency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To get started, add the latest version of the Android SDK to your project's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["build.gradle"]}," file."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"dependencies {\n    implementation(\"com.pxpfinancial:android-components-sdk:1.0.0\")\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK automatically includes the required Android permissions in its manifest (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["INTERNET"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ACCESS_NETWORK_STATE"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SYSTEM_ALERT_WINDOW"]},"). These permissions are necessary for payment processing, network connectivity checks, and 3DS authentication challenges."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"create-a-session-on-your-backend","__idx":3},"children":["Create a session on your backend"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Drop-in needs a session from the PXP API. This must happen on your backend using HMAC authentication."]},{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":1,"filters":{"Backend":{"items":[{"label":"Kotlin","value":"kotlin"},{"label":".NET","value":"dotnet"}],"default":"kotlin"}},"filesets":[{"files":[{"path":"guides/checkout/drop-in/android/_filesets/installation/create-session.kt","content":["import io.ktor.client.*\r","import io.ktor.client.request.*\r","import io.ktor.client.statement.*\r","import io.ktor.http.*\r","import kotlinx.serialization.Serializable\r","import kotlinx.serialization.encodeToString\r","import kotlinx.serialization.json.Json\r","import java.security.MessageDigest\r","import java.util.*\r","import javax.crypto.Mac\r","import javax.crypto.spec.SecretKeySpec\r","\r","// {% step id=\"credentials-setup\" %}\r","// Store your credentials securely\r","val CLIENT_ID = System.getenv(\"PXP_CLIENT_ID\") ?: throw Exception(\"PXP_CLIENT_ID not set\")\r","val TOKEN_ID = System.getenv(\"PXP_TOKEN_ID\") ?: throw Exception(\"PXP_TOKEN_ID not set\")\r","val TOKEN_VALUE = System.getenv(\"PXP_TOKEN_VALUE\") ?: throw Exception(\"PXP_TOKEN_VALUE not set\")\r","// {% /step %}\r","\r","// {% step id=\"create-signature-function\" %}\r","fun createHmacSignature(\r","    timestamp: Long,\r","    requestId: String,\r","    requestPath: String,\r","    requestBody: String,\r","    tokenValue: String\r","): String {\r","    val message = \"$timestamp$requestId$requestPath$requestBody\"\r","    val mac = Mac.getInstance(\"HmacSHA256\")\r","    val secretKey = SecretKeySpec(tokenValue.toByteArray(), \"HmacSHA256\")\r","    mac.init(secretKey)\r","    val hash = mac.doFinal(message.toByteArray())\r","    return hash.joinToString(\"\") { \"%02x\".format(it) }\r","}\r","// {% /step %}\r","\r","@Serializable\r","data class SessionRequest(\r","    val merchant: String,\r","    val site: String,\r","    val sessionTimeout: Int,\r","    val merchantTransactionId: String,\r","    val transactionMethod: TransactionMethod,\r","    val amounts: Amounts,\r","    val allowTransaction: Boolean = true,\r","    val serviceType: String = \"CheckoutDropIn\"\r",")\r","\r","@Serializable\r","data class TransactionMethod(\r","    val intent: Intent\r",")\r","\r","@Serializable\r","data class Intent(\r","    val card: String,\r","    val paypal: String\r",")\r","\r","@Serializable\r","data class Amounts(\r","    val currencyCode: String,\r","    val transactionValue: Double\r",")\r","\r","@Serializable\r","data class SessionResponse(\r","    val sessionId: String,\r","    val hmacKey: String,\r","    val data: String,\r","    val encryptionKey: String,\r","    val locale: String,\r","    val allowedFundingTypes: Map<String, Any>\r",")\r","\r","suspend fun createSession(): SessionResponse {\r","    val client = HttpClient()\r","    \r","    // {% step id=\"build-request-body\" %}\r","    val sessionRequest = SessionRequest(\r","        merchant = \"MERCHANT-1\",\r","        site = \"SITE-1\",\r","        sessionTimeout = 120,\r","        merchantTransactionId = UUID.randomUUID().toString(),\r","        transactionMethod = TransactionMethod(\r","            intent = Intent(\r","                card = \"Authorisation\",\r","                paypal = \"Purchase\"\r","            )\r","        ),\r","        amounts = Amounts(\r","            currencyCode = \"USD\",\r","            transactionValue = 25.00\r","        )\r","    )\r","    \r","    // Minify the JSON (no whitespace)\r","    val requestBody = Json.encodeToString(sessionRequest)\r","    // {% /step %}\r","    \r","    // {% step id=\"send-session-request\" %}\r","    val timestamp = System.currentTimeMillis()\r","    val requestId = UUID.randomUUID().toString()\r","    val requestPath = \"api/v1/sessions\"\r","    \r","    val hmac = createHmacSignature(\r","        timestamp = timestamp,\r","        requestId = requestId,\r","        requestPath = requestPath,\r","        requestBody = requestBody,\r","        tokenValue = TOKEN_VALUE\r","    )\r","    \r","    val authHeader = \"PXP-UST1 $TOKEN_ID:$timestamp:$hmac\"\r","    \r","    val response: HttpResponse = client.post(\"https://api-services.pxp.io/api/v1/sessions\") {\r","        contentType(ContentType.Application.Json)\r","        headers {\r","            append(\"X-Client-Id\", CLIENT_ID)\r","            append(\"X-Request-Id\", requestId)\r","            append(\"Authorization\", authHeader)\r","        }\r","        setBody(requestBody)\r","    }\r","    // {% /step %}\r","    \r","    // {% step id=\"handle-response\" %}\r","    val sessionData = Json.decodeFromString<SessionResponse>(response.bodyAsText())\r","    return sessionData\r","    // {% /step %}\r","}"],"metadata":{"steps":[]},"basename":"create-session.kt","language":"kotlin"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/android/_filesets/installation/create-session.kt","content":["import io.ktor.client.*\r","import io.ktor.client.request.*\r","import io.ktor.client.statement.*\r","import io.ktor.http.*\r","import kotlinx.serialization.Serializable\r","import kotlinx.serialization.encodeToString\r","import kotlinx.serialization.json.Json\r","import java.security.MessageDigest\r","import java.util.*\r","import javax.crypto.Mac\r","import javax.crypto.spec.SecretKeySpec\r","\r","// {% step id=\"credentials-setup\" %}\r","// Store your credentials securely\r","val CLIENT_ID = System.getenv(\"PXP_CLIENT_ID\") ?: throw Exception(\"PXP_CLIENT_ID not set\")\r","val TOKEN_ID = System.getenv(\"PXP_TOKEN_ID\") ?: throw Exception(\"PXP_TOKEN_ID not set\")\r","val TOKEN_VALUE = System.getenv(\"PXP_TOKEN_VALUE\") ?: throw Exception(\"PXP_TOKEN_VALUE not set\")\r","// {% /step %}\r","\r","// {% step id=\"create-signature-function\" %}\r","fun createHmacSignature(\r","    timestamp: Long,\r","    requestId: String,\r","    requestPath: String,\r","    requestBody: String,\r","    tokenValue: String\r","): String {\r","    val message = \"$timestamp$requestId$requestPath$requestBody\"\r","    val mac = Mac.getInstance(\"HmacSHA256\")\r","    val secretKey = SecretKeySpec(tokenValue.toByteArray(), \"HmacSHA256\")\r","    mac.init(secretKey)\r","    val hash = mac.doFinal(message.toByteArray())\r","    return hash.joinToString(\"\") { \"%02x\".format(it) }\r","}\r","// {% /step %}\r","\r","@Serializable\r","data class SessionRequest(\r","    val merchant: String,\r","    val site: String,\r","    val sessionTimeout: Int,\r","    val merchantTransactionId: String,\r","    val transactionMethod: TransactionMethod,\r","    val amounts: Amounts,\r","    val allowTransaction: Boolean = true,\r","    val serviceType: String = \"CheckoutDropIn\"\r",")\r","\r","@Serializable\r","data class TransactionMethod(\r","    val intent: Intent\r",")\r","\r","@Serializable\r","data class Intent(\r","    val card: String,\r","    val paypal: String\r",")\r","\r","@Serializable\r","data class Amounts(\r","    val currencyCode: String,\r","    val transactionValue: Double\r",")\r","\r","@Serializable\r","data class SessionResponse(\r","    val sessionId: String,\r","    val hmacKey: String,\r","    val data: String,\r","    val encryptionKey: String,\r","    val locale: String,\r","    val allowedFundingTypes: Map<String, Any>\r",")\r","\r","suspend fun createSession(): SessionResponse {\r","    val client = HttpClient()\r","    \r","    // {% step id=\"build-request-body\" %}\r","    val sessionRequest = SessionRequest(\r","        merchant = \"MERCHANT-1\",\r","        site = \"SITE-1\",\r","        sessionTimeout = 120,\r","        merchantTransactionId = UUID.randomUUID().toString(),\r","        transactionMethod = TransactionMethod(\r","            intent = Intent(\r","                card = \"Authorisation\",\r","                paypal = \"Purchase\"\r","            )\r","        ),\r","        amounts = Amounts(\r","            currencyCode = \"USD\",\r","            transactionValue = 25.00\r","        )\r","    )\r","    \r","    // Minify the JSON (no whitespace)\r","    val requestBody = Json.encodeToString(sessionRequest)\r","    // {% /step %}\r","    \r","    // {% step id=\"send-session-request\" %}\r","    val timestamp = System.currentTimeMillis()\r","    val requestId = UUID.randomUUID().toString()\r","    val requestPath = \"api/v1/sessions\"\r","    \r","    val hmac = createHmacSignature(\r","        timestamp = timestamp,\r","        requestId = requestId,\r","        requestPath = requestPath,\r","        requestBody = requestBody,\r","        tokenValue = TOKEN_VALUE\r","    )\r","    \r","    val authHeader = \"PXP-UST1 $TOKEN_ID:$timestamp:$hmac\"\r","    \r","    val response: HttpResponse = client.post(\"https://api-services.pxp.io/api/v1/sessions\") {\r","        contentType(ContentType.Application.Json)\r","        headers {\r","            append(\"X-Client-Id\", CLIENT_ID)\r","            append(\"X-Request-Id\", requestId)\r","            append(\"Authorization\", authHeader)\r","        }\r","        setBody(requestBody)\r","    }\r","    // {% /step %}\r","    \r","    // {% step id=\"handle-response\" %}\r","    val sessionData = Json.decodeFromString<SessionResponse>(response.bodyAsText())\r","    return sessionData\r","    // {% /step %}\r","}"],"metadata":{"steps":[]},"basename":"create-session.kt","language":"kotlin"}],"when":{"Backend":"kotlin"}},{"files":[{"path":"guides/checkout/drop-in/android/_filesets/installation/create-session.cs","content":["using System;\r","using System.Net.Http;\r","using System.Security.Cryptography;\r","using System.Text;\r","using System.Text.Json;\r","using System.Text.Json.Serialization;\r","using System.Threading.Tasks;\r","\r","// Credentials setup\r","var CLIENT_ID = Environment.GetEnvironmentVariable(\"PXP_CLIENT_ID\") \r","    ?? throw new Exception(\"PXP_CLIENT_ID not set\");\r","var TOKEN_ID = Environment.GetEnvironmentVariable(\"PXP_TOKEN_ID\") \r","    ?? throw new Exception(\"PXP_TOKEN_ID not set\");\r","var TOKEN_VALUE = Environment.GetEnvironmentVariable(\"PXP_TOKEN_VALUE\") \r","    ?? throw new Exception(\"PXP_TOKEN_VALUE not set\");\r","\r","// HMAC signature creation\r","string CreateHmacSignature(\r","    string timestamp,\r","    string requestId,\r","    string requestPath,\r","    string requestBody,\r","    string tokenValue)\r","{\r","    var message = $\"{timestamp}{requestId}{requestPath}{requestBody}\";\r","    var keyBytes = Encoding.UTF8.GetBytes(tokenValue);\r","    var messageBytes = Encoding.UTF8.GetBytes(message);\r","    \r","    using var hmac = new HMACSHA256(keyBytes);\r","    var hashBytes = hmac.ComputeHash(messageBytes);\r","    return Convert.ToHexString(hashBytes); // Uppercase\r","}\r","\r","// Request/Response models\r","public record SessionRequest(\r","    [property: JsonPropertyName(\"merchant\")] string Merchant,\r","    [property: JsonPropertyName(\"site\")] string Site,\r","    [property: JsonPropertyName(\"sessionTimeout\")] int SessionTimeout,\r","    [property: JsonPropertyName(\"merchantTransactionId\")] string MerchantTransactionId,\r","    [property: JsonPropertyName(\"transactionMethod\")] TransactionMethod TransactionMethod,\r","    [property: JsonPropertyName(\"amounts\")] Amounts Amounts,\r","    [property: JsonPropertyName(\"allowTransaction\")] bool AllowTransaction = true,\r","    [property: JsonPropertyName(\"serviceType\")] string ServiceType = \"CheckoutDropIn\"\r",");\r","\r","public record TransactionMethod(\r","    [property: JsonPropertyName(\"intent\")] Intent Intent\r",");\r","\r","public record Intent(\r","    [property: JsonPropertyName(\"card\")] string Card,\r","    [property: JsonPropertyName(\"paypal\")] string Paypal\r",");\r","\r","public record Amounts(\r","    [property: JsonPropertyName(\"currencyCode\")] string CurrencyCode,\r","    [property: JsonPropertyName(\"transactionValue\")] decimal TransactionValue\r",");\r","\r","public record SessionResponse(\r","    [property: JsonPropertyName(\"sessionId\")] string SessionId,\r","    [property: JsonPropertyName(\"hmacKey\")] string HmacKey,\r","    [property: JsonPropertyName(\"encryptionKey\")] string EncryptionKey,\r","    [property: JsonPropertyName(\"sessionExpiry\")] DateTimeOffset SessionExpiry,\r","    [property: JsonPropertyName(\"allowedFundingTypes\")] JsonElement AllowedFundingTypes\r",");\r","\r","async Task<SessionResponse> CreateSession()\r","{\r","    var client = new HttpClient();\r","    \r","    // Build request body\r","    var sessionRequest = new SessionRequest(\r","        Merchant: \"MERCHANT-1\",\r","        Site: \"SITE-1\",\r","        SessionTimeout: 120,\r","        MerchantTransactionId: Guid.NewGuid().ToString(),\r","        TransactionMethod: new TransactionMethod(\r","            Intent: new Intent(\r","                Card: \"Authorisation\",\r","                Paypal: \"Purchase\"\r","            )\r","        ),\r","        Amounts: new Amounts(\r","            CurrencyCode: \"USD\",\r","            TransactionValue: 25.00m // Decimal literal\r","        )\r","    );\r","    \r","    var requestBodyString = JsonSerializer.Serialize(sessionRequest);\r","    \r","    // Create tracking values\r","    var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();\r","    var requestId = Guid.NewGuid().ToString();\r","    var requestPath = \"api/v1/sessions\";\r","    \r","    // Create signature\r","    var signature = CreateHmacSignature(\r","        timestamp: timestamp,\r","        requestId: requestId,\r","        requestPath: requestPath,\r","        requestBody: requestBodyString,\r","        tokenValue: TOKEN_VALUE\r","    );\r","    \r","    // Build Authorization header (PXP-UST1 scheme)\r","    var authorizationHeader = $\"PXP-UST1 {TOKEN_ID}:{timestamp}:{signature}\";\r","    \r","    // Send request\r","    var request = new HttpRequestMessage(HttpMethod.Post, \"https://api-services.pxp.io/api/v1/sessions\");\r","    request.Content = new StringContent(requestBodyString, Encoding.UTF8, \"application/json\");\r","    request.Headers.TryAddWithoutValidation(\"Authorization\", authorizationHeader);\r","    request.Headers.Add(\"X-Request-Id\", requestId);\r","    request.Headers.Add(\"X-Client-Id\", CLIENT_ID);\r","    \r","    var response = await client.SendAsync(request);\r","    \r","    // Handle response\r","    var responseBody = await response.Content.ReadAsStringAsync();\r","    \r","    if (!response.IsSuccessStatusCode)\r","    {\r","        throw new InvalidOperationException(\r","            $\"Session creation failed: {(int)response.StatusCode} {response.ReasonPhrase}\\n{responseBody}\");\r","    }\r","    \r","    return JsonSerializer.Deserialize<SessionResponse>(responseBody)\r","        ?? throw new InvalidOperationException(\"Session response could not be read.\");\r","}"],"metadata":{"steps":[]},"basename":"create-session.cs","language":"csharp"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/android/_filesets/installation/create-session.cs","content":["using System;\r","using System.Net.Http;\r","using System.Security.Cryptography;\r","using System.Text;\r","using System.Text.Json;\r","using System.Text.Json.Serialization;\r","using System.Threading.Tasks;\r","\r","// Credentials setup\r","var CLIENT_ID = Environment.GetEnvironmentVariable(\"PXP_CLIENT_ID\") \r","    ?? throw new Exception(\"PXP_CLIENT_ID not set\");\r","var TOKEN_ID = Environment.GetEnvironmentVariable(\"PXP_TOKEN_ID\") \r","    ?? throw new Exception(\"PXP_TOKEN_ID not set\");\r","var TOKEN_VALUE = Environment.GetEnvironmentVariable(\"PXP_TOKEN_VALUE\") \r","    ?? throw new Exception(\"PXP_TOKEN_VALUE not set\");\r","\r","// HMAC signature creation\r","string CreateHmacSignature(\r","    string timestamp,\r","    string requestId,\r","    string requestPath,\r","    string requestBody,\r","    string tokenValue)\r","{\r","    var message = $\"{timestamp}{requestId}{requestPath}{requestBody}\";\r","    var keyBytes = Encoding.UTF8.GetBytes(tokenValue);\r","    var messageBytes = Encoding.UTF8.GetBytes(message);\r","    \r","    using var hmac = new HMACSHA256(keyBytes);\r","    var hashBytes = hmac.ComputeHash(messageBytes);\r","    return Convert.ToHexString(hashBytes); // Uppercase\r","}\r","\r","// Request/Response models\r","public record SessionRequest(\r","    [property: JsonPropertyName(\"merchant\")] string Merchant,\r","    [property: JsonPropertyName(\"site\")] string Site,\r","    [property: JsonPropertyName(\"sessionTimeout\")] int SessionTimeout,\r","    [property: JsonPropertyName(\"merchantTransactionId\")] string MerchantTransactionId,\r","    [property: JsonPropertyName(\"transactionMethod\")] TransactionMethod TransactionMethod,\r","    [property: JsonPropertyName(\"amounts\")] Amounts Amounts,\r","    [property: JsonPropertyName(\"allowTransaction\")] bool AllowTransaction = true,\r","    [property: JsonPropertyName(\"serviceType\")] string ServiceType = \"CheckoutDropIn\"\r",");\r","\r","public record TransactionMethod(\r","    [property: JsonPropertyName(\"intent\")] Intent Intent\r",");\r","\r","public record Intent(\r","    [property: JsonPropertyName(\"card\")] string Card,\r","    [property: JsonPropertyName(\"paypal\")] string Paypal\r",");\r","\r","public record Amounts(\r","    [property: JsonPropertyName(\"currencyCode\")] string CurrencyCode,\r","    [property: JsonPropertyName(\"transactionValue\")] decimal TransactionValue\r",");\r","\r","public record SessionResponse(\r","    [property: JsonPropertyName(\"sessionId\")] string SessionId,\r","    [property: JsonPropertyName(\"hmacKey\")] string HmacKey,\r","    [property: JsonPropertyName(\"encryptionKey\")] string EncryptionKey,\r","    [property: JsonPropertyName(\"sessionExpiry\")] DateTimeOffset SessionExpiry,\r","    [property: JsonPropertyName(\"allowedFundingTypes\")] JsonElement AllowedFundingTypes\r",");\r","\r","async Task<SessionResponse> CreateSession()\r","{\r","    var client = new HttpClient();\r","    \r","    // Build request body\r","    var sessionRequest = new SessionRequest(\r","        Merchant: \"MERCHANT-1\",\r","        Site: \"SITE-1\",\r","        SessionTimeout: 120,\r","        MerchantTransactionId: Guid.NewGuid().ToString(),\r","        TransactionMethod: new TransactionMethod(\r","            Intent: new Intent(\r","                Card: \"Authorisation\",\r","                Paypal: \"Purchase\"\r","            )\r","        ),\r","        Amounts: new Amounts(\r","            CurrencyCode: \"USD\",\r","            TransactionValue: 25.00m // Decimal literal\r","        )\r","    );\r","    \r","    var requestBodyString = JsonSerializer.Serialize(sessionRequest);\r","    \r","    // Create tracking values\r","    var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();\r","    var requestId = Guid.NewGuid().ToString();\r","    var requestPath = \"api/v1/sessions\";\r","    \r","    // Create signature\r","    var signature = CreateHmacSignature(\r","        timestamp: timestamp,\r","        requestId: requestId,\r","        requestPath: requestPath,\r","        requestBody: requestBodyString,\r","        tokenValue: TOKEN_VALUE\r","    );\r","    \r","    // Build Authorization header (PXP-UST1 scheme)\r","    var authorizationHeader = $\"PXP-UST1 {TOKEN_ID}:{timestamp}:{signature}\";\r","    \r","    // Send request\r","    var request = new HttpRequestMessage(HttpMethod.Post, \"https://api-services.pxp.io/api/v1/sessions\");\r","    request.Content = new StringContent(requestBodyString, Encoding.UTF8, \"application/json\");\r","    request.Headers.TryAddWithoutValidation(\"Authorization\", authorizationHeader);\r","    request.Headers.Add(\"X-Request-Id\", requestId);\r","    request.Headers.Add(\"X-Client-Id\", CLIENT_ID);\r","    \r","    var response = await client.SendAsync(request);\r","    \r","    // Handle response\r","    var responseBody = await response.Content.ReadAsStringAsync();\r","    \r","    if (!response.IsSuccessStatusCode)\r","    {\r","        throw new InvalidOperationException(\r","            $\"Session creation failed: {(int)response.StatusCode} {response.ReasonPhrase}\\n{responseBody}\");\r","    }\r","    \r","    return JsonSerializer.Deserialize<SessionResponse>(responseBody)\r","        ?? throw new InvalidOperationException(\"Session response could not be read.\");\r","}"],"metadata":{"steps":[]},"basename":"create-session.cs","language":"csharp"}],"when":{"Backend":"dotnet"}}],"steps":[{"id":"credentials-setup","heading":"Store your credentials securely"},{"id":"create-signature-function","heading":"Create the HMAC signature function"},{"id":"build-request-body","heading":"Build the session request body"},{"id":"send-session-request","heading":"Send the session creation request"},{"id":"handle-response","heading":"Return the session data to your app"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"credentials-setup","heading":"Store your credentials securely"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set up your API credentials as environment variables — never hardcode them in your application."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"create-signature-function","heading":"Create the HMAC signature function"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This function generates a secure authentication hash by combining the timestamp, request ID, request path, and request body, then hashing with your token value using HMAC SHA256."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"build-request-body","heading":"Build the session request body"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a request with your merchant details and transaction information. The request body must be minified (no whitespace) for the HMAC signature."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"send-session-request","heading":"Send the session creation request"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["POST to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://api-services.pxp.io/api/v1/sessions"]}," with your authentication headers and request body."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"handle-response","heading":"Return the session data to your app"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The API returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sessionId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hmacKey"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["data"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["encryptionKey"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["locale"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedFundingTypes"]},". Pass this entire response to your Android app."]}]}]},{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":2,"filters":{},"filesets":[{"files":[{"path":"guides/checkout/drop-in/android/_filesets/installation/CheckoutActivity.kt","content":["// {% step id=\"import-dependencies\" %}\r","package com.example.checkout\r","\r","import android.os.Bundle\r","import androidx.activity.ComponentActivity\r","import androidx.activity.compose.setContent\r","import androidx.compose.foundation.layout.Box\r","import androidx.compose.foundation.layout.fillMaxSize\r","import androidx.compose.foundation.layout.fillMaxWidth\r","import androidx.compose.material3.CircularProgressIndicator\r","import androidx.compose.material3.MaterialTheme\r","import androidx.compose.material3.Surface\r","import androidx.compose.runtime.*\r","import androidx.compose.ui.Alignment\r","import androidx.compose.ui.Modifier\r","import androidx.compose.ui.platform.LocalContext\r","import com.pxp.checkout.checkoutdropin.CheckoutDropIn\r","import com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\r","import com.pxp.checkout.checkoutdropin.types.SessionConfig\r","import com.pxp.checkout.checkoutdropin.types.Shopper\r","import com.pxp.checkout.checkoutdropin.types.DropInPayPalIntentType\r","import com.pxp.checkout.models.DropInTransactionData\r","import com.pxp.checkout.models.DropInTransactionIntentData\r","import com.pxp.checkout.models.Environment\r","import com.pxp.checkout.models.EntryType\r","import com.pxp.checkout.models.IntentType\r","import com.pxp.checkout.services.models.transaction.Shopper\r","import com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent\r","import kotlinx.coroutines.launch\r","import java.time.Instant\r","import java.util.UUID\r","// {% /step %}\r","\r","// {% step id=\"setup-activity\" %}\r","class CheckoutActivity : ComponentActivity() {\r","    override fun onCreate(savedInstanceState: Bundle?) {\r","        super.onCreate(savedInstanceState)\r","        setContent {\r","            MaterialTheme {\r","                Surface(\r","                    modifier = Modifier.fillMaxSize(),\r","                    color = MaterialTheme.colorScheme.background\r","                ) {\r","                    CheckoutScreen()\r","                }\r","            }\r","        }\r","    }\r","}\r","// {% /step %}\r","\r","@Composable\r","fun CheckoutScreen() {\r","    val context = LocalContext.current\r","    val coroutineScope = rememberCoroutineScope()\r","    var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }\r","    var isLoading by remember { mutableStateOf(true) }\r","    var sessionData by remember { mutableStateOf<SessionData?>(null) }\r","\r","    // {% step id=\"fetch-session\" %}\r","    LaunchedEffect(Unit) {\r","        // Fetch session from your backend\r","        sessionData = fetchSessionFromBackend()\r","    }\r","    // {% /step %}\r","\r","    LaunchedEffect(sessionData) {\r","        sessionData?.let { data ->\r","            // {% step id=\"initialize-dropin\" %}\r","            val checkoutDropIn = CheckoutDropIn.initialize(\r","                context = context,\r","                config = CheckoutDropInConfig(\r","                    environment = Environment.TEST,\r","                    session = sessionData,\r","                    ownerType = \"MerchantGroup\",\r","                    ownerId = \"MERCHANT-1\",\r","                    // {% step id=\"configure-transaction\" %}\r","                    transactionData = DropInTransactionData(\r","                        currency = \"USD\",\r","                        amount = 25.00,\r","                        entryType = EntryType.Ecom,\r","                        intent = DropInTransactionIntentData(\r","                            card = IntentType.Authorisation,\r","                            paypalDropInIntent = DropInPayPalIntentType.Authorisation\r","                        ),\r","                        merchant = \"MERCHANT-1\",\r","                        merchantTransactionId = UUID.randomUUID().toString(),\r","                        merchantTransactionDate = { Instant.now().toString() }\r","                    ),\r","                    // {% /step %}\r","                    // {% step id=\"setup-shopper\" %}\r","                    onGetShopper = {\r","                        // Return your shopper identifier\r","                        Shopper(id = \"shopper-${System.currentTimeMillis()}\")\r","                    },\r","                    // {% /step %}\r","                    // {% step id=\"success-callback\" %}\r","                    onSuccess = { transactionResult ->\r","                        // Payment succeeded\r","                        println(\"Payment successful: ${transactionResult.systemTransactionId}\")\r","                        // IMPORTANT: Always verify on your backend before fulfilling orders\r","                    },\r","                    // {% /step %}\r","                    // {% step id=\"error-callback\" %}\r","                    onError = { error ->\r","                        // Handle payment error\r","                        println(\"Payment failed: ${error.message}\")\r","                    }\r","                    // {% /step %}\r","                )\r","            )\r","            // {% /step %}\r","\r","            // {% step id=\"create-dropin\" %}\r","            coroutineScope.launch {\r","                checkoutDropInComponent = checkoutDropIn.create()\r","                isLoading = false\r","            }\r","            // {% /step %}\r","        }\r","    }\r","\r","    // {% step id=\"render-content\" %}\r","    Box(\r","        modifier = Modifier.fillMaxSize(),\r","        contentAlignment = Alignment.Center\r","    ) {\r","        if (isLoading) {\r","            CircularProgressIndicator()\r","        } else {\r","            checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())\r","        }\r","    }\r","    // {% /step %}\r","}\r","\r","data class SessionData(\r","    val sessionId: String,\r","    val hmacKey: String,\r","    val data: String,\r","    val encryptionKey: String,\r","    val locale: String\r",")\r","\r","suspend fun fetchSessionFromBackend(): SessionData {\r","    // Call your backend endpoint that creates a session\r","    // This is a placeholder - implement your actual backend call\r","    return SessionData(\r","        sessionId = \"your-session-id\",\r","        hmacKey = \"your-hmac-key\",\r","        data = \"your-session-data\",\r","        encryptionKey = \"your-encryption-key\",\r","        locale = \"en-US\"\r","    )\r","}"],"metadata":{"steps":[]},"basename":"CheckoutActivity.kt","language":"kotlin"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/android/_filesets/installation/CheckoutActivity.kt","content":["// {% step id=\"import-dependencies\" %}\r","package com.example.checkout\r","\r","import android.os.Bundle\r","import androidx.activity.ComponentActivity\r","import androidx.activity.compose.setContent\r","import androidx.compose.foundation.layout.Box\r","import androidx.compose.foundation.layout.fillMaxSize\r","import androidx.compose.foundation.layout.fillMaxWidth\r","import androidx.compose.material3.CircularProgressIndicator\r","import androidx.compose.material3.MaterialTheme\r","import androidx.compose.material3.Surface\r","import androidx.compose.runtime.*\r","import androidx.compose.ui.Alignment\r","import androidx.compose.ui.Modifier\r","import androidx.compose.ui.platform.LocalContext\r","import com.pxp.checkout.checkoutdropin.CheckoutDropIn\r","import com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig\r","import com.pxp.checkout.checkoutdropin.types.SessionConfig\r","import com.pxp.checkout.checkoutdropin.types.Shopper\r","import com.pxp.checkout.checkoutdropin.types.DropInPayPalIntentType\r","import com.pxp.checkout.models.DropInTransactionData\r","import com.pxp.checkout.models.DropInTransactionIntentData\r","import com.pxp.checkout.models.Environment\r","import com.pxp.checkout.models.EntryType\r","import com.pxp.checkout.models.IntentType\r","import com.pxp.checkout.services.models.transaction.Shopper\r","import com.pxp.checkout.components.checkoutdropincomponent.CheckoutDropInComponent\r","import kotlinx.coroutines.launch\r","import java.time.Instant\r","import java.util.UUID\r","// {% /step %}\r","\r","// {% step id=\"setup-activity\" %}\r","class CheckoutActivity : ComponentActivity() {\r","    override fun onCreate(savedInstanceState: Bundle?) {\r","        super.onCreate(savedInstanceState)\r","        setContent {\r","            MaterialTheme {\r","                Surface(\r","                    modifier = Modifier.fillMaxSize(),\r","                    color = MaterialTheme.colorScheme.background\r","                ) {\r","                    CheckoutScreen()\r","                }\r","            }\r","        }\r","    }\r","}\r","// {% /step %}\r","\r","@Composable\r","fun CheckoutScreen() {\r","    val context = LocalContext.current\r","    val coroutineScope = rememberCoroutineScope()\r","    var checkoutDropInComponent by remember { mutableStateOf<CheckoutDropInComponent?>(null) }\r","    var isLoading by remember { mutableStateOf(true) }\r","    var sessionData by remember { mutableStateOf<SessionData?>(null) }\r","\r","    // {% step id=\"fetch-session\" %}\r","    LaunchedEffect(Unit) {\r","        // Fetch session from your backend\r","        sessionData = fetchSessionFromBackend()\r","    }\r","    // {% /step %}\r","\r","    LaunchedEffect(sessionData) {\r","        sessionData?.let { data ->\r","            // {% step id=\"initialize-dropin\" %}\r","            val checkoutDropIn = CheckoutDropIn.initialize(\r","                context = context,\r","                config = CheckoutDropInConfig(\r","                    environment = Environment.TEST,\r","                    session = sessionData,\r","                    ownerType = \"MerchantGroup\",\r","                    ownerId = \"MERCHANT-1\",\r","                    // {% step id=\"configure-transaction\" %}\r","                    transactionData = DropInTransactionData(\r","                        currency = \"USD\",\r","                        amount = 25.00,\r","                        entryType = EntryType.Ecom,\r","                        intent = DropInTransactionIntentData(\r","                            card = IntentType.Authorisation,\r","                            paypalDropInIntent = DropInPayPalIntentType.Authorisation\r","                        ),\r","                        merchant = \"MERCHANT-1\",\r","                        merchantTransactionId = UUID.randomUUID().toString(),\r","                        merchantTransactionDate = { Instant.now().toString() }\r","                    ),\r","                    // {% /step %}\r","                    // {% step id=\"setup-shopper\" %}\r","                    onGetShopper = {\r","                        // Return your shopper identifier\r","                        Shopper(id = \"shopper-${System.currentTimeMillis()}\")\r","                    },\r","                    // {% /step %}\r","                    // {% step id=\"success-callback\" %}\r","                    onSuccess = { transactionResult ->\r","                        // Payment succeeded\r","                        println(\"Payment successful: ${transactionResult.systemTransactionId}\")\r","                        // IMPORTANT: Always verify on your backend before fulfilling orders\r","                    },\r","                    // {% /step %}\r","                    // {% step id=\"error-callback\" %}\r","                    onError = { error ->\r","                        // Handle payment error\r","                        println(\"Payment failed: ${error.message}\")\r","                    }\r","                    // {% /step %}\r","                )\r","            )\r","            // {% /step %}\r","\r","            // {% step id=\"create-dropin\" %}\r","            coroutineScope.launch {\r","                checkoutDropInComponent = checkoutDropIn.create()\r","                isLoading = false\r","            }\r","            // {% /step %}\r","        }\r","    }\r","\r","    // {% step id=\"render-content\" %}\r","    Box(\r","        modifier = Modifier.fillMaxSize(),\r","        contentAlignment = Alignment.Center\r","    ) {\r","        if (isLoading) {\r","            CircularProgressIndicator()\r","        } else {\r","            checkoutDropInComponent?.Content(modifier = Modifier.fillMaxWidth())\r","        }\r","    }\r","    // {% /step %}\r","}\r","\r","data class SessionData(\r","    val sessionId: String,\r","    val hmacKey: String,\r","    val data: String,\r","    val encryptionKey: String,\r","    val locale: String\r",")\r","\r","suspend fun fetchSessionFromBackend(): SessionData {\r","    // Call your backend endpoint that creates a session\r","    // This is a placeholder - implement your actual backend call\r","    return SessionData(\r","        sessionId = \"your-session-id\",\r","        hmacKey = \"your-hmac-key\",\r","        data = \"your-session-data\",\r","        encryptionKey = \"your-encryption-key\",\r","        locale = \"en-US\"\r","    )\r","}"],"metadata":{"steps":[]},"basename":"CheckoutActivity.kt","language":"kotlin"}]}],"steps":[{"id":"import-dependencies","heading":"Import the required dependencies"},{"id":"setup-activity","heading":"Set up your Compose activity"},{"id":"fetch-session","heading":"Fetch the session data from your backend"},{"id":"initialize-dropin","heading":"Initialise Checkout Drop-in"},{"id":"configure-transaction","heading":"Configure the transaction data"},{"id":"setup-shopper","heading":"Provide a shopper identifier"},{"id":"success-callback","heading":"Handle successful payments"},{"id":"error-callback","heading":"Handle payment errors"},{"id":"create-dropin","heading":"Create the Drop-in component"},{"id":"render-content","heading":"Render the Drop-in content"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"initialise-drop-in-in-your-app","__idx":4},"children":["Initialise Drop-in in your app"]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"import-dependencies","heading":"Import the required dependencies"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Import ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropIn"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CheckoutDropInConfig"]},", and the necessary types from the Android SDK."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"setup-activity","heading":"Set up your Compose activity"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create a Jetpack Compose activity that will host the Checkout Drop-in interface."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"fetch-session","heading":"Fetch the session data from your backend"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call your backend endpoint to get the session data you created in the previous steps."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"initialize-dropin","heading":"Initialise Checkout Drop-in"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure Drop-in with your environment, session data, and transaction details."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"configure-transaction","heading":"Configure the transaction data"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Specify the currency, amount, entry type, and payment intents for each payment method."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"setup-shopper","heading":"Provide a shopper identifier"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetShopper"]}," callback to provide shopper information. This enables Card-on-File functionality for saved cards."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"success-callback","heading":"Handle successful payments"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," callback to handle successful payments. Always verify payments on your backend before fulfilling orders — frontend callbacks can be manipulated."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"error-callback","heading":"Handle payment errors"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onError"]}," callback to handle payment failures and display appropriate error messages."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"create-dropin","heading":"Create the Drop-in component"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create()"]}," method from a coroutine to generate the composable component."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"render-content","heading":"Render the Drop-in content"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Content()"]}," composable to display the payment interface in your Compose UI."]}]}]},{"$$mdtype":"Tag","name":"CodeWalkthrough","attributes":{"__idx":3,"filters":{"Backend":{"items":[{"label":"Kotlin","value":"kotlin"},{"label":".NET","value":"dotnet"}],"default":"kotlin"}},"filesets":[{"files":[{"path":"guides/checkout/drop-in/android/_filesets/installation/webhook-handler.kt","content":["import io.ktor.server.application.*\r","import io.ktor.server.request.*\r","import io.ktor.server.response.*\r","import io.ktor.server.routing.*\r","import kotlinx.serialization.Serializable\r","import kotlinx.serialization.json.Json\r","\r","// {% step id=\"webhook-endpoint\" %}\r","fun Application.configureWebhooks() {\r","    routing {\r","        post(\"/webhooks/pxp\") {\r","            val webhookPayload = call.receive<WebhookPayload>()\r","            \r","            // {% step id=\"process-events\" %}\r","            webhookPayload.events.forEach { event ->\r","                if (event.type == \"Transaction\") {\r","                    val transaction = event.data as? TransactionData ?: return@forEach\r","                    \r","                    // {% step id=\"check-state\" %}\r","                    if (transaction.state in listOf(\"Authorised\", \"Captured\")) {\r","                        // {% /step %}\r","                        \r","                        // {% step id=\"prevent-duplicates\" %}\r","                        val alreadyProcessed = checkIfProcessed(transaction.systemTransactionId)\r","                        if (alreadyProcessed) {\r","                            return@forEach\r","                        }\r","                        // {% /step %}\r","                        \r","                        // {% step id=\"verify-details\" %}\r","                        val order = getOrderByMerchantTransactionId(transaction.merchantTransactionId)\r","                        if (order != null &&\r","                            order.amount == transaction.amount &&\r","                            order.currency == transaction.currency) {\r","                            // {% /step %}\r","                            \r","                            // {% step id=\"fulfill-order\" %}\r","                            fulfillOrder(order.id)\r","                            markTransactionAsProcessed(transaction.systemTransactionId)\r","                            // {% /step %}\r","                        }\r","                    }\r","                }\r","            }\r","            // {% /step %}\r","            \r","            // {% step id=\"respond\" %}\r","            call.respond(mapOf(\"state\" to \"Success\"))\r","            // {% /step %}\r","        }\r","    }\r","}\r","// {% /step %}\r","\r","@Serializable\r","data class WebhookPayload(\r","    val events: List<WebhookEvent>\r",")\r","\r","@Serializable\r","data class WebhookEvent(\r","    val type: String,\r","    val data: TransactionData\r",")\r","\r","@Serializable\r","data class TransactionData(\r","    val systemTransactionId: String,\r","    val merchantTransactionId: String,\r","    val state: String,\r","    val amount: Double,\r","    val currency: String\r",")\r","\r","data class Order(\r","    val id: String,\r","    val merchantTransactionId: String,\r","    val amount: Double,\r","    val currency: String\r",")\r","\r","// Placeholder functions - implement these according to your database\r","fun checkIfProcessed(systemTransactionId: String): Boolean {\r","    // Check your database to see if this transaction was already processed\r","    return false\r","}\r","\r","fun getOrderByMerchantTransactionId(merchantTransactionId: String): Order? {\r","    // Retrieve order from your database\r","    return null\r","}\r","\r","fun fulfillOrder(orderId: String) {\r","    // Fulfill the order (ship product, grant access, etc.)\r","}\r","\r","fun markTransactionAsProcessed(systemTransactionId: String) {\r","    // Mark transaction as processed in your database to prevent duplicates\r","}\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.kt","language":"kotlin"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/android/_filesets/installation/webhook-handler.kt","content":["import io.ktor.server.application.*\r","import io.ktor.server.request.*\r","import io.ktor.server.response.*\r","import io.ktor.server.routing.*\r","import kotlinx.serialization.Serializable\r","import kotlinx.serialization.json.Json\r","\r","// {% step id=\"webhook-endpoint\" %}\r","fun Application.configureWebhooks() {\r","    routing {\r","        post(\"/webhooks/pxp\") {\r","            val webhookPayload = call.receive<WebhookPayload>()\r","            \r","            // {% step id=\"process-events\" %}\r","            webhookPayload.events.forEach { event ->\r","                if (event.type == \"Transaction\") {\r","                    val transaction = event.data as? TransactionData ?: return@forEach\r","                    \r","                    // {% step id=\"check-state\" %}\r","                    if (transaction.state in listOf(\"Authorised\", \"Captured\")) {\r","                        // {% /step %}\r","                        \r","                        // {% step id=\"prevent-duplicates\" %}\r","                        val alreadyProcessed = checkIfProcessed(transaction.systemTransactionId)\r","                        if (alreadyProcessed) {\r","                            return@forEach\r","                        }\r","                        // {% /step %}\r","                        \r","                        // {% step id=\"verify-details\" %}\r","                        val order = getOrderByMerchantTransactionId(transaction.merchantTransactionId)\r","                        if (order != null &&\r","                            order.amount == transaction.amount &&\r","                            order.currency == transaction.currency) {\r","                            // {% /step %}\r","                            \r","                            // {% step id=\"fulfill-order\" %}\r","                            fulfillOrder(order.id)\r","                            markTransactionAsProcessed(transaction.systemTransactionId)\r","                            // {% /step %}\r","                        }\r","                    }\r","                }\r","            }\r","            // {% /step %}\r","            \r","            // {% step id=\"respond\" %}\r","            call.respond(mapOf(\"state\" to \"Success\"))\r","            // {% /step %}\r","        }\r","    }\r","}\r","// {% /step %}\r","\r","@Serializable\r","data class WebhookPayload(\r","    val events: List<WebhookEvent>\r",")\r","\r","@Serializable\r","data class WebhookEvent(\r","    val type: String,\r","    val data: TransactionData\r",")\r","\r","@Serializable\r","data class TransactionData(\r","    val systemTransactionId: String,\r","    val merchantTransactionId: String,\r","    val state: String,\r","    val amount: Double,\r","    val currency: String\r",")\r","\r","data class Order(\r","    val id: String,\r","    val merchantTransactionId: String,\r","    val amount: Double,\r","    val currency: String\r",")\r","\r","// Placeholder functions - implement these according to your database\r","fun checkIfProcessed(systemTransactionId: String): Boolean {\r","    // Check your database to see if this transaction was already processed\r","    return false\r","}\r","\r","fun getOrderByMerchantTransactionId(merchantTransactionId: String): Order? {\r","    // Retrieve order from your database\r","    return null\r","}\r","\r","fun fulfillOrder(orderId: String) {\r","    // Fulfill the order (ship product, grant access, etc.)\r","}\r","\r","fun markTransactionAsProcessed(systemTransactionId: String) {\r","    // Mark transaction as processed in your database to prevent duplicates\r","}\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.kt","language":"kotlin"}],"when":{"Backend":"kotlin"}},{"files":[{"path":"guides/checkout/drop-in/android/_filesets/installation/webhook-handler.cs","content":["using Microsoft.AspNetCore.Mvc;\r","using System.Text.Json.Serialization;\r","\r","// {% step id=\"webhook-endpoint\" %}\r","[ApiController]\r","[Route(\"webhooks\")]\r","public class WebhookController : ControllerBase\r","{\r","    [HttpPost(\"pxp\")]\r","    public async Task<IActionResult> HandleWebhook([FromBody] WebhookPayload payload)\r","    {\r","        // {% step id=\"process-events\" %}\r","        foreach (var webhookEvent in payload.Events)\r","        {\r","            if (webhookEvent.Type == \"Transaction\")\r","            {\r","                var transaction = webhookEvent.Data;\r","                \r","                // {% step id=\"check-state\" %}\r","                if (transaction.State == \"Authorised\" || transaction.State == \"Captured\")\r","                {\r","                    // {% /step %}\r","                    \r","                    // {% step id=\"prevent-duplicates\" %}\r","                    var alreadyProcessed = await CheckIfProcessed(transaction.SystemTransactionId);\r","                    if (alreadyProcessed)\r","                    {\r","                        continue;\r","                    }\r","                    // {% /step %}\r","                    \r","                    // {% step id=\"verify-details\" %}\r","                    var order = await GetOrderByMerchantTransactionId(transaction.MerchantTransactionId);\r","                    if (order != null &&\r","                        order.Amount == transaction.Amount &&\r","                        order.Currency == transaction.Currency)\r","                    {\r","                        // {% /step %}\r","                        \r","                        // {% step id=\"fulfill-order\" %}\r","                        await FulfillOrder(order.Id);\r","                        await MarkTransactionAsProcessed(transaction.SystemTransactionId);\r","                        // {% /step %}\r","                    }\r","                }\r","            }\r","        }\r","        // {% /step %}\r","        \r","        // {% step id=\"respond\" %}\r","        return Ok(new { state = \"Success\" });\r","        // {% /step %}\r","    }\r","}\r","// {% /step %}\r","\r","public record WebhookPayload(\r","    [property: JsonPropertyName(\"events\")] List<WebhookEvent> Events\r",");\r","\r","public record WebhookEvent(\r","    [property: JsonPropertyName(\"type\")] string Type,\r","    [property: JsonPropertyName(\"data\")] TransactionData Data\r",");\r","\r","public record TransactionData(\r","    [property: JsonPropertyName(\"systemTransactionId\")] string SystemTransactionId,\r","    [property: JsonPropertyName(\"merchantTransactionId\")] string MerchantTransactionId,\r","    [property: JsonPropertyName(\"state\")] string State,\r","    [property: JsonPropertyName(\"amount\")] double Amount,\r","    [property: JsonPropertyName(\"currency\")] string Currency\r",");\r","\r","public record Order(\r","    string Id,\r","    string MerchantTransactionId,\r","    double Amount,\r","    string Currency\r",");\r","\r","// Placeholder methods - implement these according to your database\r","async Task<bool> CheckIfProcessed(string systemTransactionId)\r","{\r","    // Check your database to see if this transaction was already processed\r","    return false;\r","}\r","\r","async Task<Order?> GetOrderByMerchantTransactionId(string merchantTransactionId)\r","{\r","    // Retrieve order from your database\r","    return null;\r","}\r","\r","async Task FulfillOrder(string orderId)\r","{\r","    // Fulfill the order (ship product, grant access, etc.)\r","    await Task.CompletedTask;\r","}\r","\r","async Task MarkTransactionAsProcessed(string systemTransactionId)\r","{\r","    // Mark transaction as processed in your database to prevent duplicates\r","    await Task.CompletedTask;\r","}\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.cs","language":"csharp"}],"downloadAssociatedFiles":[{"path":"guides/checkout/drop-in/android/_filesets/installation/webhook-handler.cs","content":["using Microsoft.AspNetCore.Mvc;\r","using System.Text.Json.Serialization;\r","\r","// {% step id=\"webhook-endpoint\" %}\r","[ApiController]\r","[Route(\"webhooks\")]\r","public class WebhookController : ControllerBase\r","{\r","    [HttpPost(\"pxp\")]\r","    public async Task<IActionResult> HandleWebhook([FromBody] WebhookPayload payload)\r","    {\r","        // {% step id=\"process-events\" %}\r","        foreach (var webhookEvent in payload.Events)\r","        {\r","            if (webhookEvent.Type == \"Transaction\")\r","            {\r","                var transaction = webhookEvent.Data;\r","                \r","                // {% step id=\"check-state\" %}\r","                if (transaction.State == \"Authorised\" || transaction.State == \"Captured\")\r","                {\r","                    // {% /step %}\r","                    \r","                    // {% step id=\"prevent-duplicates\" %}\r","                    var alreadyProcessed = await CheckIfProcessed(transaction.SystemTransactionId);\r","                    if (alreadyProcessed)\r","                    {\r","                        continue;\r","                    }\r","                    // {% /step %}\r","                    \r","                    // {% step id=\"verify-details\" %}\r","                    var order = await GetOrderByMerchantTransactionId(transaction.MerchantTransactionId);\r","                    if (order != null &&\r","                        order.Amount == transaction.Amount &&\r","                        order.Currency == transaction.Currency)\r","                    {\r","                        // {% /step %}\r","                        \r","                        // {% step id=\"fulfill-order\" %}\r","                        await FulfillOrder(order.Id);\r","                        await MarkTransactionAsProcessed(transaction.SystemTransactionId);\r","                        // {% /step %}\r","                    }\r","                }\r","            }\r","        }\r","        // {% /step %}\r","        \r","        // {% step id=\"respond\" %}\r","        return Ok(new { state = \"Success\" });\r","        // {% /step %}\r","    }\r","}\r","// {% /step %}\r","\r","public record WebhookPayload(\r","    [property: JsonPropertyName(\"events\")] List<WebhookEvent> Events\r",");\r","\r","public record WebhookEvent(\r","    [property: JsonPropertyName(\"type\")] string Type,\r","    [property: JsonPropertyName(\"data\")] TransactionData Data\r",");\r","\r","public record TransactionData(\r","    [property: JsonPropertyName(\"systemTransactionId\")] string SystemTransactionId,\r","    [property: JsonPropertyName(\"merchantTransactionId\")] string MerchantTransactionId,\r","    [property: JsonPropertyName(\"state\")] string State,\r","    [property: JsonPropertyName(\"amount\")] double Amount,\r","    [property: JsonPropertyName(\"currency\")] string Currency\r",");\r","\r","public record Order(\r","    string Id,\r","    string MerchantTransactionId,\r","    double Amount,\r","    string Currency\r",");\r","\r","// Placeholder methods - implement these according to your database\r","async Task<bool> CheckIfProcessed(string systemTransactionId)\r","{\r","    // Check your database to see if this transaction was already processed\r","    return false;\r","}\r","\r","async Task<Order?> GetOrderByMerchantTransactionId(string merchantTransactionId)\r","{\r","    // Retrieve order from your database\r","    return null;\r","}\r","\r","async Task FulfillOrder(string orderId)\r","{\r","    // Fulfill the order (ship product, grant access, etc.)\r","    await Task.CompletedTask;\r","}\r","\r","async Task MarkTransactionAsProcessed(string systemTransactionId)\r","{\r","    // Mark transaction as processed in your database to prevent duplicates\r","    await Task.CompletedTask;\r","}\r",""],"metadata":{"steps":[]},"basename":"webhook-handler.cs","language":"csharp"}],"when":{"Backend":"dotnet"}}],"steps":[{"id":"webhook-endpoint","heading":"Create the webhook endpoint"},{"id":"process-events","heading":"Process webhook events"},{"id":"check-state","heading":"Check payment state"},{"id":"prevent-duplicates","heading":"Prevent duplicate processing"},{"id":"verify-details","heading":"Verify transaction details"},{"id":"fulfill-order","heading":"Fulfil the order"},{"id":"respond","heading":"Respond to webhook"}],"inputs":{},"toggles":{}},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"verify-payments","__idx":5},"children":["Verify payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a payment succeeds, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onSuccess"]}," callback fires with transaction details. However, you must ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["always verify the payment on your backend"]}," before fulfilling orders."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure webhooks in the Unity Portal to receive real-time payment notifications on your backend."]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"webhook-endpoint","heading":"Create the webhook endpoint"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set up an endpoint at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/webhooks/pxp"]}," to receive payment notifications from Unity."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"process-events","heading":"Process webhook events"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Loop through the events array and filter for Transaction events."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"check-state","heading":"Check payment state"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Verify the transaction state is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorised"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Captured"]}," before processing."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"prevent-duplicates","heading":"Prevent duplicate processing"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Check if you've already processed this transaction using ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["systemTransactionId"]},"."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"verify-details","heading":"Verify transaction details"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Match the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionId"]},", amount, and currency against your order records."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"fulfill-order","heading":"Fulfil the order"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If verification passes, fulfil the order and mark the transaction as processed."]}]},{"$$mdtype":"Tag","name":"CodeStep","attributes":{"id":"respond","heading":"Respond to webhook"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Always return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ state: 'Success' }"]}," to acknowledge receipt, even if processing failed."]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can also verify payments using the Transactions API to query transaction status directly. See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/drop-in/android/implementation#backend-verification-critical"},"children":["Implementation guide"]}," for details."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That's it! You now have a working Checkout Drop-in integration."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"whats-next","__idx":6},"children":["What's next?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Now that you have Drop-in running, here are the recommended next steps:"]},{"$$mdtype":"Tag","name":"ul","attributes":{"className":"code-walkthrough-list"},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/guides/checkout/drop-in/branding"},"children":["Customise the look and feel"]}," to match your brand (configured in Unity Portal)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/guides/checkout/drop-in/android/implementation#backend-verification-critical"},"children":["Set up backend verification"]}," to verify payments before fulfilling orders."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/guides/checkout/drop-in/android/events"},"children":["Add optional callbacks"]}," to enhance the user experience with validation and loading states."]}]}]},"headings":[{"value":"Quickstart","id":"quickstart","depth":1},{"value":"Pre-requisites","id":"pre-requisites","depth":2},{"value":"Add the SDK dependency","id":"add-the-sdk-dependency","depth":2},{"value":"Create a session on your backend","id":"create-a-session-on-your-backend","depth":2},{"value":"Initialise Drop-in in your app","id":"initialise-drop-in-in-your-app","depth":2},{"value":"Verify payments","id":"verify-payments","depth":2},{"value":"What's next?","id":"whats-next","depth":2}],"frontmatter":{"markdown":{"toc":{"hide":true}},"footer":{"hide":true},"seo":{"title":"Quickstart"}},"lastModified":"2026-05-22T10:01:08.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/drop-in/android/quickstart","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}