{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading","admonition","br","details","endpoint"]},"type":"markdown"},"seo":{"title":"Recurring payments","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":"recurring-payments","__idx":0},"children":["Recurring payments"]},{"$$mdtype":"Tag","name":"SubHeading","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Learn how to implement recurring payment tokens with Google Pay for subscriptions and recurring billing."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Consent payment tokens enable secure recurring payments with Google Pay. By obtaining customer consent during the initial payment, you can store a reusable payment token for future transactions without requiring the customer to re-authenticate each time."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Consent payment tokens are essential for:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Monthly or annual billing, such as subscription services"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Regular scheduled charges"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Automatic account top-ups"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Instalment plans"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Pay-as-you-go services"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Membership renewals"]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Consent payment tokens provide a frictionless payment experience for returning customers whilst maintaining security through tokenisation and customer consent requirements."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-consent-tokens-work","__idx":2},"children":["How consent tokens work"]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["PXP doesn't provide an automatic payment scheduler. You must implement your own scheduling system to initiate subsequent recurring charges using the PXP Transactions API."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The consent token flow involves two main phases:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"phase-1-initial-payment-with-consent","__idx":3},"children":["Phase 1: Initial payment with consent"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The customer makes their first payment via Google Pay."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["During payment, the customer grants consent for future charges."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The SDK creates and authorises the initial payment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The backend stores encrypted payment token for future use."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The customer receives confirmation of payment and consent."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"phase-2-subsequent-payments","__idx":4},"children":["Phase 2: Subsequent payments"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Your backend initiates recurring payments using the stored ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gatewayTokenId"]}," via the PXP Transactions API."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The payment processes without customer interaction."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The customer receives notification of the charge."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The token remains valid for future transactions until expiration."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"implementing-consent-payments","__idx":5},"children":["Implementing consent payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To implement recurring payments, you need to:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Configure the SDK with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," data in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionData"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Use either the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["google-pay-consent"]}," component or the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetConsent"]}," callback."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Provide a shopper ID via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetShopper"]}," for consent tracking."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"recurring-configuration","__idx":6},"children":["Recurring configuration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When initialising the SDK, include the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," configuration in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionData"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.pxp.PxpCheckout\nimport com.pxp.checkout.models.*\nimport com.pxp.checkout.services.models.transaction.Shopper\nimport java.text.SimpleDateFormat\nimport java.util.*\n\nval pxpCheckout = PxpCheckout.builder()\n    .withConfig(\n        PxpSdkConfig(\n            environment = Environment.TEST,\n            session = sessionData,\n            ownerId = \"your-owner-id\",\n            ownerType = \"MerchantGroup\",\n            transactionData = TransactionData(\n                currency = \"GBP\",\n                amount = 9.99,\n                merchantTransactionId = UUID.randomUUID().toString(),\n                merchantTransactionDate = { SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss'Z'\").format(Date()) },\n                entryType = EntryType.Ecom,\n                intent = TransactionIntentData(\n                    card = IntentType.Authorisation\n                ),\n                merchant = \"Merchant Name\",\n                // Configure recurring payment schedule\n                recurring = RecurringData(\n                    frequencyInDays = 30,  // How often the customer will be charged\n                    frequencyExpiration = \"2027-12-31T00:00:00Z\"  // When the recurring token expires\n                )\n            ),\n            // Required: Provide customer ID for consent tracking\n            onGetShopper = {\n                Shopper(id = \"customer-123\")\n            }\n        )\n    )\n    .withContext(context)\n    .build()\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When you include the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," object in your transaction data, the SDK automatically sets the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["processingModel"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedInitialRecurring"]}," when creating the transaction request. You don't need to set this manually."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"width":"50%","data-label":"Property"},"children":["Property "]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["frequencyInDays"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Int"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The billing frequency in days (e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["30"]}," for monthly, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["365"]}," for annual)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["frequencyExpiration"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["String?"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["When the recurring token should expire (ISO 8601 date string, e.g., ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"2027-12-31T00:00:00Z\""]},")."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1-configure-for-consent","__idx":7},"children":["Step 1: Configure for consent"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure the Google Pay button to request consent:"]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK automatically configures the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tokenizationSpecification"]}," with the correct gateway and merchant ID from your session. You only need to provide ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["allowedPaymentMethods"]}," with the card parameters."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.pxp.checkout.components.googlepay.*\nimport com.pxp.checkout.components.googlepay.types.*\n\nval googlePayConfig = GooglePayButtonComponentConfig().apply {\n    paymentDataRequest = PaymentDataRequest(\n        allowedPaymentMethods = listOf(\n            PaymentMethodSpecification(\n                parameters = PaymentMethodParameters(\n                    allowedCardNetworks = listOf(\n                        CardNetwork.VISA,\n                        CardNetwork.MASTERCARD\n                    ),\n                    allowedAuthMethods = listOf(\n                        CardAuthMethod.PAN_ONLY,\n                        CardAuthMethod.CRYPTOGRAM_3DS\n                    )\n                )\n            )\n        ),\n        transactionInfo = TransactionInfo(\n            currencyCode = \"GBP\",\n            totalPriceStatus = TotalPriceStatus.FINAL,\n            totalPrice = \"9.99\"\n        )\n    )\n    \n    // Option 1: Use the consent component (recommended)\n    googlePayConsentComponent = consentComponent\n    \n    // Option 2: Use a simple boolean consent callback\n    onGetConsent = {\n        true // Return true if customer has given consent\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2-using-the-consent-component","__idx":8},"children":["Step 2: Using the consent component"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For recurring payments, use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["google-pay-consent"]}," component to collect customer consent:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import com.pxp.checkout.components.googlepayconsent.*\nimport com.pxp.checkout.types.ComponentType\n\n// Create consent component\nval consentComponent = pxpCheckout.createComponent<\n    GooglePayConsentComponent,\n    GooglePayConsentConfig\n>(\n    type = ComponentType.GOOGLE_PAY_CONSENT,\n    config = GooglePayConsentConfig(\n        label = \"I agree to save my payment method for future purchases\",\n        initialChecked = false\n    )\n)\n\n// Link consent component to Google Pay button\nval googlePayConfig = GooglePayButtonComponentConfig().apply {\n    googlePayConsentComponent = consentComponent\n    // ... rest of configuration\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"consent-component-configuration","__idx":9},"children":["Consent component configuration"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["google-pay-consent"]}," component provides customisation options:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"width":"50%","data-label":"Property"},"children":["Property "]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["label"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["String"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The consent text displayed to the customer."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["initialChecked"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["Boolean"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Whether the checkbox is initially checked. Default: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["false"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["style"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["CheckboxStyle?"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Custom styling for the checkbox appearance (colors, text styles, etc.)."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"consent-component-example","__idx":10},"children":["Consent component example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import androidx.compose.runtime.*\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport com.pxp.checkout.styles.CheckboxStyle\n\n@Composable\nfun SubscriptionCheckout(pxpCheckout: PxpCheckout) {\n    // Create consent component\n    val consentComponent = remember {\n        pxpCheckout.createComponent<GooglePayConsentComponent, GooglePayConsentConfig>(\n            type = ComponentType.GOOGLE_PAY_CONSENT,\n            config = GooglePayConsentConfig(\n                label = \"Save this payment method for my monthly subscription\",\n                initialChecked = false,\n                style = CheckboxStyle(\n                    checkedColor = Color(0xFF4CAF50),\n                    checkedLabelStyle = TextStyle(\n                        fontWeight = FontWeight.Bold,\n                        color = Color(0xFF4CAF50)\n                    )\n                )\n            )\n        )\n    }\n    \n    // Create Google Pay button with consent\n    val googlePayComponent = remember {\n        pxpCheckout.createComponent<GooglePayButtonComponent, GooglePayButtonComponentConfig>(\n            type = ComponentType.GOOGLE_PAY_BUTTON,\n            config = GooglePayButtonComponentConfig().apply {\n                googlePayConsentComponent = consentComponent\n                // ... payment configuration\n            }\n        )\n    }\n    \n    // Render components\n    Column {\n        googlePayComponent.Content()\n        Spacer(modifier = Modifier.height(16.dp))\n        consentComponent.Content()\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"charging-subsequent-recurring-payments","__idx":11},"children":["Charging subsequent recurring payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After the initial setup with Google Pay, you initiate subsequent recurring charges from your backend using the PXP Transactions API with the stored ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gatewayTokenId"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"Endpoint","attributes":{"method":"POST"},"children":["/v1/transactions"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Example API request:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"merchant\": \"MERCHANT-1\",\n  \"site\": \"SITE-1\",\n  \"merchantTransactionId\": \"recurring-charge-456\",\n  \"merchantTransactionDate\": \"2025-03-15T10:30:00.000Z\",\n  \"transactionMethod\": {\n    \"intent\": \"Purchase\",\n    \"entryType\": \"Ecom\",\n    \"fundingType\": \"Card\"\n  },\n  \"fundingData\": {\n    \"card\": {\n      \"gatewayTokenId\": \"5fbd77ce-02c1-40ed-94bc-1016660b7512\"\n    }\n  },\n  \"amounts\": {\n    \"transaction\": 9.99,\n    \"currencyCode\": \"GBP\"\n  },\n  \"recurring\": {\n    \"processingModel\": \"MerchantInitiatedSubsequentRecurring\"\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"width":"30%","data-label":"Parameter"},"children":["Parameter "]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fundingData.card.gatewayTokenId"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["String"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The token ID obtained from the initial Google Pay payment (stored on your backend after the initial authorisation)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring.processingModel"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["String"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Must be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"MerchantInitiatedSubsequentRecurring\""]}," for recurring charges after the initial setup."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionMethod.intent"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["String"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"Purchase\""]}," for immediate charge or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"Authorisation\""]}," for pre-authorisation."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Example API response:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"state\": \"Captured\",\n  \"provider\": {\n    \"code\": \"00\",\n    \"message\": \"Successful\",\n    \"transactionId\": \"TXN-987654321\"\n  },\n  \"fundingData\": {\n    \"card\": {\n      \"cardScheme\": \"VISA\",\n      \"expiryMonth\": \"12\",\n      \"expiryYear\": \"2026\",\n      \"lastFour\": \"4242\",\n      \"gatewayTokenId\": \"5fbd77ce-02c1-40ed-94bc-1016660b7512\"\n    }\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The SDK automatically sets ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["processingModel"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"MerchantInitiatedInitialRecurring\""]}," for the initial Google Pay transaction. For subsequent charges, you must use the Transactions API from your backend with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"MerchantInitiatedSubsequentRecurring\""]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For more details on the Transactions API, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/transactions/initiate-transactions"},"children":["Initiate transactions"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"complete-implementation-example","__idx":12},"children":["Complete implementation example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here's a complete example of implementing recurring payments with Google Pay:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"import android.os.Bundle\nimport android.util.Log\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport com.pxp.PxpCheckout\nimport com.pxp.checkout.components.googlepay.*\nimport com.pxp.checkout.components.googlepay.types.*\nimport com.pxp.checkout.components.googlepayconsent.*\nimport com.pxp.checkout.models.*\nimport com.pxp.checkout.services.models.transaction.Shopper\nimport com.pxp.checkout.types.ComponentType\nimport java.text.SimpleDateFormat\nimport java.util.*\n\nclass SubscriptionActivity : ComponentActivity() {\n    \n    private lateinit var pxpCheckout: PxpCheckout\n    \n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        \n        // Initialise SDK\n        pxpCheckout = PxpCheckout.builder()\n            .withConfig(\n                PxpSdkConfig(\n                    environment = Environment.TEST,\n                    session = sessionData,\n                    ownerId = \"your-owner-id\",\n                    ownerType = \"MerchantGroup\",\n                    transactionData = TransactionData(\n                        currency = \"GBP\",\n                        amount = 9.99,\n                        merchantTransactionId = UUID.randomUUID().toString(),\n                        merchantTransactionDate = { SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss'Z'\").format(Date()) },\n                        entryType = EntryType.Ecom,\n                        intent = TransactionIntentData(card = IntentType.Authorisation),\n                        merchant = \"Merchant Name\",\n                        recurring = RecurringData(\n                            frequencyInDays = 30,\n                            frequencyExpiration = \"2027-12-31T00:00:00Z\"\n                        )\n                ),\n                onGetShopper = {\n                    Shopper(id = \"customer-123\")\n                }\n                )\n            )\n            .withContext(this)\n            .build()\n        \n        setContent {\n            SubscriptionScreen(pxpCheckout)\n        }\n    }\n}\n\n@Composable\nfun SubscriptionScreen(pxpCheckout: PxpCheckout) {\n    // Create consent component\n    val consentComponent = remember {\n        pxpCheckout.createComponent<GooglePayConsentComponent, GooglePayConsentConfig>(\n            type = ComponentType.GOOGLE_PAY_CONSENT,\n            config = GooglePayConsentConfig(\n                label = \"Save my payment method for monthly subscription payments\"\n            )\n        )\n    }\n    \n    // Create Google Pay button\n    val googlePayComponent = remember {\n        pxpCheckout.createComponent<GooglePayButtonComponent, GooglePayButtonComponentConfig>(\n            type = ComponentType.GOOGLE_PAY_BUTTON,\n            config = GooglePayButtonComponentConfig().apply {\n                paymentDataRequest = PaymentDataRequest(\n                    allowedPaymentMethods = listOf(\n                        PaymentMethodSpecification(\n                            parameters = PaymentMethodParameters(\n                                allowedCardNetworks = listOf(\n                                    CardNetwork.VISA,\n                                    CardNetwork.MASTERCARD\n                                ),\n                                allowedAuthMethods = listOf(\n                                    CardAuthMethod.PAN_ONLY,\n                                    CardAuthMethod.CRYPTOGRAM_3DS\n                                )\n                            )\n                        )\n                    ),\n                    transactionInfo = TransactionInfo(\n                        currencyCode = \"GBP\",\n                        totalPriceStatus = TotalPriceStatus.FINAL,\n                        totalPrice = \"9.99\"\n                    )\n                )\n                \n                googlePayConsentComponent = consentComponent\n                \n                onPostAuthorisation = { result, paymentData ->\n                    when (result) {\n                        is AuthorizedSubmitResult -> {\n                            Log.d(\"Subscription\", \"Subscription authorized: ${result.providerResponse.code}\")\n                            // Store subscription details\n                        }\n                        is CapturedSubmitResult -> {\n                            Log.d(\"Subscription\", \"Subscription captured: ${result.providerResponse.code}\")\n                            // Store subscription details\n                        }\n                        is MerchantSubmitResult -> {\n                            Log.d(\"Subscription\", \"Subscription created: ${result.merchantTransactionId}\")\n                        }\n                        is FailedSubmitResult -> {\n                            Log.e(\"Subscription\", \"Payment failed: ${result.errorReason}\")\n                        }\n                    }\n                }\n            }\n        )\n    }\n    \n    // Render UI\n    Column(\n        modifier = Modifier\n            .fillMaxSize()\n            .padding(16.dp)\n    ) {\n        Text(\"Premium Subscription\", style = MaterialTheme.typography.headlineMedium)\n        Text(\"£9.99/month\", style = MaterialTheme.typography.titleLarge)\n        Spacer(modifier = Modifier.height(24.dp))\n        googlePayComponent.Content()\n        Spacer(modifier = Modifier.height(16.dp))\n        consentComponent.Content()\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"processing-recurring-payments","__idx":13},"children":["Processing recurring payments"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"backend-implementation","__idx":14},"children":["Backend implementation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your backend must handle the initial token creation and subsequent recurring charges. Here's the flow:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"step-1-initial-payment-with-consent","__idx":15},"children":["Step 1: Initial payment with consent"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When the first payment is completed, your webhook receives:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"transactionId\": \"txn_123\",\n  \"customerId\": \"customer-123\",\n  \"amount\": 9.99,\n  \"currency\": \"GBP\",\n  \"consentToken\": \"tok_abc123\",\n  \"frequencyInDays\": 30,\n  \"frequencyExpiration\": \"2027-12-31T00:00:00Z\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Store the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["consentToken"]}," securely in your database for future use."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"step-2-recurring-charge","__idx":16},"children":["Step 2: Recurring charge"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To process subsequent payments, make a server-to-server API call to the PXP API using the stored consent token:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"// Server-side code (not SDK)\nPOST /api/v1/transactions\nAuthorization: Bearer your-api-key\n\n{\n  \"amount\": 9.99,\n  \"currency\": \"GBP\",\n  \"customerId\": \"customer-123\",\n  \"consentToken\": \"tok_abc123\",\n  \"merchantTransactionId\": \"recurring_456\",\n  \"description\": \"Monthly subscription - February 2024\"\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"scheduled-billing","__idx":17},"children":["Scheduled billing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement a scheduled job to process recurring payments:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"// Example server-side scheduler\nfun scheduleRecurringPayments() {\n    val subscriptions = getActiveSubscriptions()\n    \n    subscriptions.forEach { subscription ->\n        if (subscription.isPaymentDue()) {\n            processRecurringPayment(\n                customerId = subscription.customerId,\n                consentToken = subscription.consentToken,\n                amount = subscription.amount,\n                currency = subscription.currency\n            )\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"managing-subscriptions","__idx":18},"children":["Managing subscriptions"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"updating-payment-methods","__idx":19},"children":["Updating payment methods"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Customers can update their payment method by making a new payment with consent:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"val updatePaymentConfig = GooglePayButtonComponentConfig().apply {\n    googlePayConsentComponent = consentComponent\n    \n    onPostAuthorisation = { result, paymentData ->\n        when (result) {\n            is AuthorizedSubmitResult -> {\n                // Retrieve and update stored consent token from backend\n                val transactionDetails = fetchTransactionDetails(result.providerResponse.code)\n                \n                updateSubscriptionPaymentMethod(\n                    customerId = \"customer-123\",\n                    newConsentToken = transactionDetails.consentToken\n                )\n            }\n            is CapturedSubmitResult -> {\n                // Retrieve and update stored consent token from backend\n                val transactionDetails = fetchTransactionDetails(result.providerResponse.code)\n                \n                updateSubscriptionPaymentMethod(\n                    customerId = \"customer-123\",\n                    newConsentToken = transactionDetails.consentToken\n                )\n            }\n            is MerchantSubmitResult -> {\n                // Retrieve and update stored consent token from backend\n                val transactionDetails = fetchTransactionDetails(result.systemTransactionId)\n                \n                updateSubscriptionPaymentMethod(\n                    customerId = \"customer-123\",\n                    newConsentToken = transactionDetails.consentToken\n                )\n            }\n            is FailedSubmitResult -> {\n                showError(\"Failed to update payment method\")\n            }\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"cancelling-subscriptions","__idx":20},"children":["Cancelling subscriptions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Allow customers to cancel their subscriptions:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"fun cancelSubscription(customerId: String) {\n    // Delete or deactivate the consent token\n    deleteConsentToken(customerId)\n    \n    // Update subscription status\n    updateSubscriptionStatus(customerId, SubscriptionStatus.CANCELLED)\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"handling-failed-recurring-payments","__idx":21},"children":["Handling failed recurring payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement retry logic for failed recurring payments:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"fun handleFailedRecurringPayment(\n    subscription: Subscription,\n    failureReason: String\n) {\n    when {\n        subscription.retryCount < 3 -> {\n            // Retry with exponential backoff\n            scheduleRetry(\n                subscription = subscription,\n                delayDays = 2.pow(subscription.retryCount)\n            )\n        }\n        else -> {\n            // Suspend subscription after 3 failed attempts\n            suspendSubscription(subscription)\n            notifyCustomer(subscription, FailureReason.PAYMENT_FAILURE)\n        }\n    }\n}\n","lang":"kotlin"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing-consent-payments","__idx":22},"children":["Testing consent payments"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"test-scenarios","__idx":23},"children":["Test scenarios"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Test these key scenarios:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Initial payment with consent:"]}," Verify consent token is created and stored."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Recurring payment:"]}," Test automated recurring charges using stored token."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Failed recurring payment:"]}," Test retry logic and customer notifications."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Update payment method:"]}," Verify customers can update their payment details."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Cancel subscription:"]}," Test subscription cancellation flow."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"test-implementation","__idx":24},"children":["Test implementation"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","header":{"controls":{"copy":{}}},"source":"@Test\nfun testRecurringPaymentFlow() {\n    // Step 1: Initial payment with consent\n    val initialResult = processInitialPayment(\n        amount = 9.99,\n        withConsent = true\n    )\n    \n    assert(initialResult.consentToken != null)\n    \n    // Step 2: Process recurring payment\n    val recurringResult = processRecurringPayment(\n        consentToken = initialResult.consentToken,\n        amount = 9.99\n    )\n    \n    assert(recurringResult.isSuccess)\n}\n","lang":"kotlin"},"children":[]}]},"headings":[{"value":"Recurring payments","id":"recurring-payments","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"How consent tokens work","id":"how-consent-tokens-work","depth":2},{"value":"Phase 1: Initial payment with consent","id":"phase-1-initial-payment-with-consent","depth":3},{"value":"Phase 2: Subsequent payments","id":"phase-2-subsequent-payments","depth":3},{"value":"Implementing consent payments","id":"implementing-consent-payments","depth":2},{"value":"Recurring configuration","id":"recurring-configuration","depth":3},{"value":"Step 1: Configure for consent","id":"step-1-configure-for-consent","depth":3},{"value":"Step 2: Using the consent component","id":"step-2-using-the-consent-component","depth":3},{"value":"Consent component configuration","id":"consent-component-configuration","depth":3},{"value":"Consent component example","id":"consent-component-example","depth":3},{"value":"Charging subsequent recurring payments","id":"charging-subsequent-recurring-payments","depth":2},{"value":"Complete implementation example","id":"complete-implementation-example","depth":2},{"value":"Processing recurring payments","id":"processing-recurring-payments","depth":2},{"value":"Backend implementation","id":"backend-implementation","depth":3},{"value":"Step 1: Initial payment with consent","id":"step-1-initial-payment-with-consent","depth":4},{"value":"Step 2: Recurring charge","id":"step-2-recurring-charge","depth":4},{"value":"Scheduled billing","id":"scheduled-billing","depth":3},{"value":"Managing subscriptions","id":"managing-subscriptions","depth":2},{"value":"Updating payment methods","id":"updating-payment-methods","depth":3},{"value":"Cancelling subscriptions","id":"cancelling-subscriptions","depth":3},{"value":"Handling failed recurring payments","id":"handling-failed-recurring-payments","depth":2},{"value":"Testing consent payments","id":"testing-consent-payments","depth":2},{"value":"Test scenarios","id":"test-scenarios","depth":3},{"value":"Test implementation","id":"test-implementation","depth":3}],"frontmatter":{"seo":{"title":"Recurring payments"}},"lastModified":"2026-04-08T14:46:04.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/components/android/google-pay/recurring-payments","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}