{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-guides/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["sub-heading","admonition","br","details","endpoint","required"]},"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":["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":["Recurring payments enable secure subscriptions and scheduled billing with Google Pay. By configuring the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," object in your transaction data during the initial payment, you can obtain a reusable payment token for future transactions without requiring the customer to re-authenticate each time."]},{"$$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 via the Transactions API."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Recurring payments are ideal 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":["Recurring payments are enabled through the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," configuration in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionData"]},". This is separate from the consent component, which is used to collect customer permission to store payment methods. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/components/web/google-pay/configuration#consent-component-configuration"},"children":["Configuration"]}," for details on the consent component."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-recurring-payments-work","__idx":2},"children":["How recurring payments work"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The recurring payment flow involves two main phases:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"phase-1-initial-payment","__idx":3},"children":["Phase 1: Initial payment"]},{"$$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":["The SDK processes the payment with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," configuration in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionData"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The backend receives and stores the payment token for future use."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The customer receives confirmation of their payment and the recurring setup."]}]},{"$$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":["You initiate a recurring payment using the stored token via backend API."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The payment processes without customer interaction."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The customer receives a notification of charge. The token remains valid until the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["frequencyExpiration"]}," date."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"implementing-recurring-payments","__idx":5},"children":["Implementing recurring 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":["Optionally, collect customer consent using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["google-pay-consent"]}," component or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetConsent"]}," callback (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/components/web/google-pay/configuration#consent-component-configuration"},"children":["Configuration"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Provide a shopper ID via ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onGetShopper"]}," for 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":"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."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"const pxpSdk = PxpCheckout.initialize({\n  environment: 'test',\n  session: sessionData,\n  ownerId: 'your-owner-id',\n  ownerType: 'MerchantGroup',\n  kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection\n  transactionData: {\n    currency: 'GBP',\n    amount: 9.99,\n    merchantTransactionId: crypto.randomUUID(),\n    merchantTransactionDate: () => new Date().toISOString(),\n    entryType: 'Ecom',\n    intent: {\n      card: 'Authorisation'\n    },\n    // Configure recurring payment schedule\n    recurring: {\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: async () => {\n    return { id: 'customer-123' };\n  }\n});\n","lang":"typescript"},"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":{"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":["number"]}]},{"$$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":["The date and time when the recurring token should expire, in ISO 8601 format."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"creating-the-google-pay-button","__idx":7},"children":["Creating the Google Pay button"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Configure the Google Pay button with your payment parameters:"]},{"$$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":"typescript","header":{"controls":{"copy":{}}},"source":"const googlePayButton = pxpSdk.create('google-pay-button', {\n  paymentDataRequest: {\n    allowedPaymentMethods: [{\n      type: 'CARD',\n      parameters: {\n        allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],\n        allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']\n      }\n    }],\n    transactionInfo: {\n      currencyCode: 'GBP',\n      totalPriceStatus: 'FINAL',\n      totalPrice: '9.99'\n    }\n  },\n  \n  // Handle payment authorisation\n  onPostAuthorisation: async (result, paymentData) => {\n    if (result && 'merchantTransactionId' in result) {\n      // Success - MerchantSubmitResult\n      console.log('Payment authorised');\n      console.log('Transaction ID:', result.merchantTransactionId);\n      console.log('System Transaction ID:', result.systemTransactionId);\n      \n      // Store the transaction for subscription tracking\n      await storeSubscriptionTransaction({\n        customerId: getCurrentCustomerId(),\n        transactionId: result.merchantTransactionId,\n        systemTransactionId: result.systemTransactionId\n      });\n      \n      console.log('Subscription transaction recorded');\n      window.location.href = '/subscription-confirmed';\n    }\n  }\n});\n\ngooglePayButton.mount('google-pay-container');\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"collecting-consent-optional","__idx":8},"children":["Collecting consent (optional)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To collect explicit customer consent for storing payment information, use the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["google-pay-consent"]}," component. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/components/web/google-pay/configuration#consent-component-configuration"},"children":["Configuration"]}," for full documentation."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Create consent component\nconst consentComponent = pxpSdk.create('google-pay-consent', {\n  label: 'I agree to save my payment method for recurring payments',\n  checked: false\n});\n\n// Link consent to Google Pay button\nconst googlePayButton = pxpSdk.create('google-pay-button', {\n  paymentDataRequest: {\n    // ... payment configuration\n  },\n  googlePayConsentComponent: consentComponent,  // Link the consent\n  onPostAuthorisation: async (result, paymentData) => {\n    // Handle payment\n  }\n});\n\n// Mount both components\nconsentComponent.mount('consent-container');\ngooglePayButton.mount('google-pay-container');\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"complete-implementation-example","__idx":9},"children":["Complete implementation example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here's a full example for a subscription service with recurring payments:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { useEffect, useState } from 'react';\nimport { PxpCheckout } from '@pxpio/web-components-sdk';\n\nfunction SubscriptionCheckout() {\n  const [googlePayButton, setGooglePayButton] = useState(null);\n  const [subscriptionPlan, setSubscriptionPlan] = useState('monthly');\n  \n  // Get subscription details\n  const planDetails = {\n    monthly: { amount: '9.99', frequency: 'Monthly', frequencyDays: 30 },\n    annual: { amount: '99.99', frequency: 'Annually', frequencyDays: 365 }\n  };\n  \n  const plan = planDetails[subscriptionPlan];\n  \n  useEffect(() => {\n    // Initialise PXP SDK with recurring configuration\n    const pxpSdk = PxpCheckout.initialize({\n      environment: 'test',\n      session: sessionData, // Get from your backend\n      ownerId: 'your-owner-id',\n      ownerType: 'MerchantGroup',\n      kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection\n      transactionData: {\n        currency: 'GBP',\n        amount: parseFloat(plan.amount),\n        merchantTransactionId: crypto.randomUUID(),\n        merchantTransactionDate: () => new Date().toISOString(),\n        entryType: 'Ecom',\n        intent: {\n          card: 'Authorisation'\n        },\n        // Configure recurring payment schedule\n        recurring: {\n          frequencyInDays: plan.frequencyDays,\n          frequencyExpiration: getSubscriptionExpiry(plan.frequency)\n        }\n      },\n      onGetShopper: async () => {\n        // Return customer ID for tracking\n        return { id: getCurrentCustomerId() };\n      }\n    });\n    \n    // Create Google Pay button\n    const button = pxpSdk.create('google-pay-button', {\n      paymentDataRequest: {\n        allowedPaymentMethods: [{\n          type: 'CARD',\n          parameters: {\n            allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],\n            allowedAuthMethods: ['CRYPTOGRAM_3DS'] // Prefer 3DS for subscriptions\n          }\n        }],\n        transactionInfo: {\n          currencyCode: 'GBP',\n          countryCode: 'GB',\n          totalPriceStatus: 'FINAL',\n          totalPrice: plan.amount,\n          totalPriceLabel: `${plan.frequency} Subscription`,\n          displayItems: [\n            {\n              label: `Premium ${plan.frequency} Plan`,\n              type: 'LINE_ITEM',\n              price: plan.amount,\n              status: 'FINAL'\n            }\n          ]\n        }\n      },\n      \n      // Handle initial payment authorisation\n      onPostAuthorisation: async (result, paymentData) => {\n        console.log('Payment authorisation result:', result);\n        \n        if (result && 'merchantTransactionId' in result) {\n          // Success - MerchantSubmitResult\n          console.log('✅ Initial subscription payment authorised');\n          console.log('Transaction ID:', result.merchantTransactionId);\n          console.log('System Transaction ID:', result.systemTransactionId);\n          \n          try {\n            // Create subscription record on your backend\n            await fetch('/api/subscriptions/create', {\n              method: 'POST',\n              headers: { 'Content-Type': 'application/json' },\n              body: JSON.stringify({\n                customerId: getCurrentCustomerId(),\n                transactionId: result.merchantTransactionId,\n                systemTransactionId: result.systemTransactionId,\n                plan: subscriptionPlan,\n                amount: plan.amount,\n                frequency: plan.frequency,\n                nextBillingDate: getNextBillingDate(plan.frequency),\n                startDate: new Date().toISOString()\n              })\n            });\n            \n            console.log('✅ Subscription created successfully');\n            \n            // Redirect to success page\n            window.location.href = '/subscription/success?plan=' + subscriptionPlan;\n          } catch (error) {\n            console.error('❌ Error creating subscription:', error);\n            showError('An error occurred. Please contact support.');\n          }\n        } else if (result && 'errorCode' in result) {\n          // Failure - FailedSubmitResult\n          console.error('❌ Payment declined:', result.errorReason);\n          showError('Payment declined. Please try another payment method.');\n        }\n      },\n      \n      // Handle errors\n      onError: (error) => {\n        console.error('Payment error:', error);\n        showError('An error occurred during payment. Please try again.');\n      },\n      \n      // Handle cancellation\n      onCancel: () => {\n        console.log('Payment cancelled by user');\n        showMessage('Subscription setup cancelled');\n      }\n    });\n    \n    // Mount button\n    button.mount('google-pay-button-container');\n    setGooglePayButton(button);\n    \n    // Cleanup on unmount\n    return () => {\n      if (button) button.unmount();\n    };\n  }, [subscriptionPlan]);\n  \n  return (\n    <div className=\"subscription-checkout\">\n      <h1>Start your subscription</h1>\n      \n      <div className=\"plan-selector\">\n        <label>\n          <input\n            type=\"radio\"\n            name=\"plan\"\n            value=\"monthly\"\n            checked={subscriptionPlan === 'monthly'}\n            onChange={(e) => setSubscriptionPlan(e.target.value)}\n          />\n          Monthly - £9.99/month\n        </label>\n        <label>\n          <input\n            type=\"radio\"\n            name=\"plan\"\n            value=\"annual\"\n            checked={subscriptionPlan === 'annual'}\n            onChange={(e) => setSubscriptionPlan(e.target.value)}\n          />\n          Annual - £99.99/year (Save 17%)\n        </label>\n      </div>\n      \n      <div id=\"google-pay-button-container\"></div>\n      \n      <p className=\"subscription-terms\">\n        By subscribing, you authorise us to charge your payment method {\n          subscriptionPlan === 'monthly' ? 'monthly' : 'annually'\n        } until you cancel. See our <a href=\"/terms\">Terms of Service</a>.\n      </p>\n    </div>\n  );\n}\n\n// Helper functions\nfunction getCurrentCustomerId() {\n  // Get current customer ID from your auth system\n  return localStorage.getItem('customerId') || 'guest-user';\n}\n\nfunction getNextBillingDate(frequency) {\n  const nextDate = new Date();\n  if (frequency === 'Monthly') {\n    nextDate.setMonth(nextDate.getMonth() + 1);\n  } else if (frequency === 'Annually') {\n    nextDate.setFullYear(nextDate.getFullYear() + 1);\n  }\n  return nextDate.toISOString();\n}\n\nfunction getSubscriptionExpiry(frequency) {\n  // Set token expiry (e.g., 2 years from now)\n  const expiry = new Date();\n  expiry.setFullYear(expiry.getFullYear() + 2);\n  return expiry.toISOString();\n}\n\nfunction showError(message) {\n  alert(message); // Replace with your error handling UI\n}\n\nfunction showMessage(message) {\n  console.log(message); // Replace with your notification system\n}\n\nexport default SubscriptionCheckout;\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For collecting explicit customer consent, add the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["google-pay-consent"]}," component. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/checkout/components/web/google-pay/configuration#consent-component-configuration"},"children":["Configuration"]}," for implementation details."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"processing-recurring-payments","__idx":10},"children":["Processing recurring payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After the initial payment, you must initiate subsequent recurring charges from your backend using the Transactions API. PXP doesn't automatically charge customers based on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["frequencyInDays"]},", so you need to implement your own scheduler to trigger these charges."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"charging-subsequent-recurring-payments","__idx":11},"children":["Charging subsequent recurring payments"]},{"$$mdtype":"Tag","name":"Endpoint","attributes":{"method":"POST"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["/v1/transactions"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use the following request to charge a customer for a subsequent recurring payment. You'll need the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gatewayTokenId"]}," from the initial transaction stored in your system."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"merchant\": \"MERCHANT-1\",\n  \"site\": \"SITE-1\",\n  \"merchantTransactionId\": \"sub-charge-456\",\n  \"merchantTransactionDate\": \"2025-02-27T08:51:02.826Z\",\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":{"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":["merchant"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string (≤ 20 characters)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your unique merchant identifier, as assigned by PXP."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["site"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string (≤ 20 characters)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your unique site identifier, as assigned by PXP."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionId"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string (≤ 50 characters)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A unique identifier for this transaction."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["merchantTransactionDate"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["date-time"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The date and time when the transaction happened, in ISO 8601 format."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionMethod"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Details about the transaction method."]}]},{"$$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 (enum)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The payment intent. For recurring charges, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Purchase"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorisation"]},".",{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},"Possible values:",{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorisation"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Purchase"]}]}]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionMethod.entryType"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The entry type. For e-commerce transactions, this is always ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Ecom"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["transactionMethod.fundingType"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The funding type. For card transactions, this is always ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Card"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fundingData"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Details about the payment method used for the transaction."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fundingData.card"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Details about the card."]}]},{"$$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 (UUID)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The gateway token ID from the initial transaction. This replaces the need to provide full card details."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amounts"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Details about the transaction amount."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amounts.transaction"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["number"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The transaction amount."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["amounts.currencyCode"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["string (3 characters)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The currency code in ISO 4217 format."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Details","attributes":{},"children":["object"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Details about the recurring payment."]}]},{"$$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 (enum)"]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Required","attributes":{},"children":[]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The processing model for the recurring payment. Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedSubsequentRecurring"]}," for standard subscription charges.",{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},"Possible values:",{"$$mdtype":"Tag","name":"Break","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedSubsequentRecurring"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedReAuthorisation"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedResubmission"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedDelayedCharge"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MerchantInitiatedNoShow"]}]}]}]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":4,"id":"response-example","__idx":12},"children":["Response example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If your request is successful, you'll receive a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," response with the transaction state. You'll also receive a webhook notification."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"state\": \"Captured\",\n  \"stateData\": {},\n  \"approvalCode\": \"210693\",\n  \"merchantTransactionId\": \"sub-charge-456\",\n  \"systemTransactionId\": \"635b1b51-4a27-4d23-8c9f-8150ff7eb9dd\",\n  \"merchantTransactionDate\": \"2025-02-27T08:51:02.826Z\",\n  \"fundingData\": {\n    \"cardScheme\": \"Visa\",\n    \"maskedPrimaryAccountNumber\": \"411111******1111\",\n    \"expiryMonth\": \"09\",\n    \"expiryYear\": \"2025\",\n    \"gatewayTokenId\": \"5fbd77ce-02c1-40ed-94bc-1016660b7512\",\n    \"providerResponse\": {\n      \"provider\": \"pxpfinancial\",\n      \"code\": \"00\",\n      \"paymentAccountReference\": \"637607302178175469\",\n      \"authorisedAmount\": 9.99\n    }\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/transactions/initiate-transactions"},"children":["Learn more about initiating transactions via API"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"backend-implementation-example","__idx":13},"children":["Backend implementation example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Once you have stored the consent token, use it to process recurring payments:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Backend API endpoint for processing recurring payment\napp.post('/api/subscriptions/charge', async (req, res) => {\n  const { customerId, subscriptionId } = req.body;\n  \n  try {\n    // Retrieve subscription details\n    const subscription = await getSubscription(subscriptionId);\n    \n    // Process recurring payment through backend API\n    // Note: Recurring payment implementation depends on your payment processor\n    const response = await pxpBackendSDK.payments.create({\n      amount: subscription.billingAmount,\n      currency: subscription.billingCurrency,\n      customerId: customerId,\n      subscriptionId: subscriptionId,\n      metadata: {\n        billingPeriod: getCurrentBillingPeriod(),\n        paymentType: 'recurring'\n      }\n    });\n    \n    if (response.status === 'Authorised') {\n      // Update subscription record\n      await updateSubscriptionLastCharge(subscriptionId, {\n        transactionId: response.transactionId,\n        amount: response.amount,\n        date: new Date().toISOString(),\n        status: 'success'\n      });\n      \n      // Send receipt to customer\n      await sendPaymentReceipt(customerId, response);\n      \n      res.json({ success: true, transactionId: response.transactionId });\n    } else {\n      // Handle payment failure\n      await handleRecurringPaymentFailure(subscriptionId, response);\n      res.status(402).json({ success: false, error: response.errorReason });\n    }\n    \n  } catch (error) {\n    console.error('Recurring payment error:', error);\n    res.status(500).json({ success: false, error: 'Payment processing failed' });\n  }\n});\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"scheduled-billing","__idx":14},"children":["Scheduled billing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement automated recurring billing:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Cron job or scheduled task to process recurring payments\nasync function processScheduledBilling() {\n  console.log('🔄 Processing scheduled billing...');\n  \n  // Get subscriptions due for billing\n  const dueSubscriptions = await getSubscriptionsDueForBilling();\n  \n  console.log(`Found ${dueSubscriptions.length} subscriptions to process`);\n  \n  for (const subscription of dueSubscriptions) {\n    try {\n      console.log(`Processing subscription ${subscription.id}`);\n      \n      // Process payment using stored token\n      const result = await processRecurringPayment(subscription);\n      \n      if (result.success) {\n        console.log(`✅ Successfully charged subscription ${subscription.id}`);\n        \n        // Schedule next billing\n        await scheduleNextBilling(subscription);\n        \n        // Send confirmation email\n        await sendBillingConfirmation(subscription.customerId, result);\n      } else {\n        console.error(`❌ Failed to charge subscription ${subscription.id}`);\n        \n        // Handle failed payment\n        await handleBillingFailure(subscription, result);\n      }\n    } catch (error) {\n      console.error(`Error processing subscription ${subscription.id}:`, error);\n      await logBillingError(subscription.id, error);\n    }\n  }\n  \n  console.log('✅ Scheduled billing complete');\n}\n\n// Run daily at 2 AM\ncron.schedule('0 2 * * *', processScheduledBilling);\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"managing-subscriptions","__idx":15},"children":["Managing subscriptions"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"updating-payment-methods","__idx":16},"children":["Updating payment methods"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Allow customers to update their payment method for an existing subscription:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"import { PxpCheckout } from '@pxpio/web-components-sdk';\n\nfunction UpdatePaymentMethod({ subscriptionId }) {\n  const [googlePayButton, setGooglePayButton] = useState(null);\n  \n  useEffect(() => {\n    const pxpSdk = PxpCheckout.initialize({\n      environment: 'test',\n      session: sessionData, // Get from your backend\n      ownerId: 'your-owner-id',\n      ownerType: 'MerchantGroup',\n      kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection\n      transactionData: {\n        currency: 'GBP',\n        amount: 0, // No charge for update\n        merchantTransactionId: crypto.randomUUID(),\n        merchantTransactionDate: () => new Date().toISOString(),\n        entryType: 'Ecom',\n        intent: {\n          card: 'Verification' // Use Verification for payment method updates\n        },\n        recurring: {\n          frequencyInDays: 30, // Maintain existing frequency\n          frequencyExpiration: getSubscriptionExpiry()\n        }\n      },\n      onGetShopper: async () => ({ id: getCurrentCustomerId() })\n    });\n    \n    const button = pxpSdk.create('google-pay-button', {\n      paymentDataRequest: {\n        allowedPaymentMethods: [{\n          type: 'CARD',\n          parameters: {\n            allowedCardNetworks: ['VISA', 'MASTERCARD', 'AMEX'],\n            allowedAuthMethods: ['PAN_ONLY', 'CRYPTOGRAM_3DS']\n          }\n        }],\n        transactionInfo: {\n          currencyCode: 'GBP',\n          totalPriceStatus: 'FINAL',\n          totalPrice: '0.00' // No charge for update\n        }\n      },\n      \n      onPostAuthorisation: async (result, paymentData) => {\n        if (result && 'merchantTransactionId' in result) {\n          // Success - Payment method update successful\n          await updateSubscriptionPaymentMethod(subscriptionId, {\n            transactionId: result.merchantTransactionId,\n            systemTransactionId: result.systemTransactionId,\n            updatedAt: new Date().toISOString()\n          });\n          \n          alert('Payment method updated successfully');\n          window.location.href = '/account/subscription';\n        }\n      }\n    });\n    \n    button.mount('update-payment-button');\n    setGooglePayButton(button);\n    \n    return () => {\n      button.unmount();\n    };\n  }, [subscriptionId]);\n  \n  return (\n    <div>\n      <h2>Update payment method</h2>\n      <p>Add a new payment method for your subscription.</p>\n      <div id=\"update-payment-button\"></div>\n    </div>\n  );\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"cancelling-subscriptions","__idx":17},"children":["Cancelling subscriptions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Handle subscription cancellations:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function cancelSubscription(subscriptionId, reason) {\n  try {\n    // Update subscription status\n    await fetch(`/api/subscriptions/${subscriptionId}/cancel`, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        cancellationReason: reason,\n        cancelledAt: new Date().toISOString()\n      })\n    });\n    \n    // Note: Consent token should be deleted from your system\n    // but the token itself remains valid with the payment processor\n    // until its expiry date. Ensure your backend doesn't use it.\n    \n    console.log('Subscription cancelled successfully');\n    \n    // Send cancellation confirmation\n    await sendCancellationConfirmation(subscriptionId);\n    \n    return { success: true };\n  } catch (error) {\n    console.error('Error cancelling subscription:', error);\n    return { success: false, error: error.message };\n  }\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"handling-failed-recurring-payments","__idx":18},"children":["Handling failed recurring payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement retry logic and customer notifications:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"async function handleRecurringPaymentFailure(subscription, result) {\n  console.error('Recurring payment failed:', result.errorReason);\n  \n  // Track failure\n  await recordPaymentFailure(subscription.id, {\n    reason: result.errorReason,\n    attemptNumber: subscription.failedAttempts + 1,\n    date: new Date().toISOString()\n  });\n  \n  const failedAttempts = subscription.failedAttempts + 1;\n  \n  if (failedAttempts === 1) {\n    // First failure: Retry in 3 days\n    await scheduleRetry(subscription.id, 3);\n    await sendPaymentFailureEmail(subscription.customerId, 'first_failure');\n    \n  } else if (failedAttempts === 2) {\n    // Second failure: Retry in 5 days\n    await scheduleRetry(subscription.id, 5);\n    await sendPaymentFailureEmail(subscription.customerId, 'second_failure');\n    \n  } else if (failedAttempts >= 3) {\n    // Third failure: Suspend subscription\n    await suspendSubscription(subscription.id);\n    await sendPaymentFailureEmail(subscription.customerId, 'subscription_suspended');\n  }\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"best-practices","__idx":19},"children":["Best practices"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"security","__idx":20},"children":["Security"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// ✅ DO: Store subscription data securely on backend\nawait secureStorage.store({\n  customerId: customer.id,\n  transactionId: result.merchantTransactionId,\n  systemTransactionId: result.systemTransactionId,\n  createdAt: new Date()\n});\n\n// ❌ DON'T: Never store sensitive payment data in frontend/localStorage\nlocalStorage.setItem('transactionId', result.merchantTransactionId); // AVOID - use backend storage\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"customer-communication","__idx":21},"children":["Customer communication"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Always inform customers before charging\nasync function notifyUpcomingCharge(subscription) {\n  const daysBeforeCharge = 3;\n  \n  await sendEmail(subscription.customerEmail, {\n    subject: 'Upcoming subscription payment',\n    body: `Your ${subscription.planName} subscription will renew in ${daysBeforeCharge} days for £${subscription.amount}.`\n  });\n}\n\n// Send receipt after successful charge\nasync function sendChargeConfirmation(subscription, transaction) {\n  await sendEmail(subscription.customerEmail, {\n    subject: 'Payment confirmation',\n    body: `We've successfully charged £${transaction.amount} for your ${subscription.planName} subscription.`\n  });\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"compliance","__idx":22},"children":["Compliance"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Provide clear subscription terms\nfunction SubscriptionTerms({ amount, frequency }) {\n  return (\n    <div className=\"subscription-terms\">\n      <h3>Subscription agreement</h3>\n      <p>\n        By completing this purchase, you authorise {merchantName} to:\n      </p>\n      <ul>\n        <li>Charge £{amount} to your payment method {frequency.toLowerCase()}</li>\n        <li>Store your payment information securely for recurring billing</li>\n        <li>Continue billing until you cancel your subscription</li>\n      </ul>\n      <p>\n        You can cancel at any time from your account settings.\n        Cancellations take effect at the end of your current billing period.\n      </p>\n    </div>\n  );\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing-recurring-payments","__idx":23},"children":["Testing recurring payments"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"test-scenarios","__idx":24},"children":["Test scenarios"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Successful initial payment:"]}," Verify payment token is created and stored with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recurring"]}," configuration."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Failed initial payment:"]}," Ensure no token is stored"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Successful recurring payment:"]}," Process payment using stored token via backend API."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Failed recurring payment:"]}," Test retry logic and notifications."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Token update:"]}," Successfully replace existing payment method."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Subscription cancellation:"]}," Verify token is no longer used."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"test-implementation","__idx":25},"children":["Test implementation"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"typescript","header":{"controls":{"copy":{}}},"source":"// Test mode configuration\nconst testConfig = {\n  environment: 'test',\n  testMode: true,\n  testCards: {\n    success: '4111111111111111',\n    decline: '4000000000000002',\n    insufficientFunds: '4000000000009995'\n  }\n};\n\n// Verify subscription creation\nasync function testSubscriptionCreation() {\n  const result = await processInitialPayment();\n  assert(result.merchantTransactionId, 'Transaction ID should be present');\n  assert(result.systemTransactionId, 'System Transaction ID should be present');\n  \n  const subscription = await getSubscription(result.subscriptionId);\n  assert(subscription, 'Subscription should be created');\n  assert(subscription.status === 'active', 'Subscription should be active');\n  assert(subscription.recurring, 'Recurring configuration should be present');\n}\n","lang":"typescript"},"children":[]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Always test the complete subscription lifecycle including initial payment, recurring charges, failed payments, retries, and cancellations before going live."]}]}]},"headings":[{"value":"Recurring payments","id":"recurring-payments","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"How recurring payments work","id":"how-recurring-payments-work","depth":2},{"value":"Phase 1: Initial payment","id":"phase-1-initial-payment","depth":3},{"value":"Phase 2: Subsequent payments","id":"phase-2-subsequent-payments","depth":3},{"value":"Implementing recurring payments","id":"implementing-recurring-payments","depth":2},{"value":"Recurring configuration","id":"recurring-configuration","depth":3},{"value":"Creating the Google Pay button","id":"creating-the-google-pay-button","depth":3},{"value":"Collecting consent (optional)","id":"collecting-consent-optional","depth":3},{"value":"Complete implementation example","id":"complete-implementation-example","depth":2},{"value":"Processing recurring payments","id":"processing-recurring-payments","depth":2},{"value":"Charging subsequent recurring payments","id":"charging-subsequent-recurring-payments","depth":3},{"value":"Response example","id":"response-example","depth":4},{"value":"Backend implementation example","id":"backend-implementation-example","depth":3},{"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":"Best practices","id":"best-practices","depth":2},{"value":"Security","id":"security","depth":3},{"value":"Customer communication","id":"customer-communication","depth":3},{"value":"Compliance","id":"compliance","depth":3},{"value":"Testing recurring payments","id":"testing-recurring-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-06-12T11:56:36.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/checkout/components/web/google-pay/recurring-payments","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}