Skip to main content

When to Use Integration Patterns

Use these patterns when you need to:
  • Combine multiple customization approaches: Integrate functional behavior with visual styling
  • Handle complex payment flows: Manage subscriptions, one-time payments, and multiple payment methods
  • Optimize for specific markets: Configure card networks and locales for different regions
  • Implement enterprise-grade experiences: Build robust, scalable checkout implementations
These examples demonstrate how to effectively combine the PayNext SDK configuration options to create sophisticated checkout form experiences.

Prerequisites

Before using these advanced patterns, ensure you have:
  • Basic PayNext SDK knowledge: Familiarity with Getting Started
  • React/TypeScript experience: Understanding of React components and TypeScript interfaces
  • PayNext dashboard access: Ability to configure payment methods and environments
  • Understanding of basic customization: Knowledge of Behavior and Appearance customization
The examples below show configuration fragments. Instantiate with new PayNextCheckout() and pass the element ID as the first parameter and the combined config object as the second parameter into checkout.mount('element-id', { ...config }) as in Mount the Checkout, adding the illustrated properties before calling mount.

Quick Start

Here’s a minimal example combining functional and visual customization:
quick-start.tsx
import { PayNextCheckout, type PayNextConfig, type StylesConfig } from '@paynext/sdk'

const config: PayNextConfig = {
  clientToken: process.env.NEXT_PUBLIC_PAYNEXT_TOKEN!,
  environment: 'sandbox',
  apiVersion: '1.0.0',
  variant: 'compact',
  onCheckoutLoaded: (result) => {
    if (!result.success) {
      console.error('Checkout loading failed:', result.error)
      return
    }
    console.info('Checkout loaded successfully')
  },
  onCheckoutComplete: (result) => {
    console.log('Payment completed:', result.id)
    window.location.href = '/success'
  }
}

const styles: StylesConfig = {
  SubmitButton: {
    className: 'bg-blue-600 text-white font-semibold py-3 rounded-lg hover:bg-blue-700'
  }
}

export async function mountQuickStart(containerId: string) {
  const checkout = new PayNextCheckout()
  await checkout.mount(containerId, {
    ...config,
    styles,
  })
  return checkout
}
This example combines basic functional configuration (environment, callbacks) with simple visual styling. Use this as a foundation for more complex patterns.

Basic Implementation Patterns

advanced/transactions.tsx
import { PayNextCheckout, type PayNextConfig, type PaymentResult } from '@paynext/sdk'

export async function mountTransactionsExample(containerId: string) {
  const handleComplete = (result: PaymentResult) => {
    if (result.subscription) {
      // Subscription flow
      console.log('Subscription created:', result.subscription.id)
      window.location.href = '/subscription-success'
      return
    }
    // One-time payment flow
    console.log('Payment completed:', result.id)
    window.location.href = '/payment-success'
  }

  const config: PayNextConfig = {
    clientToken: process.env.NEXT_PUBLIC_PAYNEXT_TOKEN!,
    environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
    apiVersion: '1.0.0',
    variant: 'compact',
    onCheckoutLoaded: (result) => {
      if (!result.success) {
        console.error('Checkout loading failed:', result.error)
        return
      }
      console.info('Checkout loaded successfully')
    },
    onCheckoutComplete: handleComplete,
  }

  const checkout = new PayNextCheckout()
  await checkout.mount(containerId, config)
  return checkout
}
Branch on result.subscription to reuse the same checkout for both flows.

Visual Patterns

advanced/theme.tsx
import { type StylesConfig } from '@paynext/sdk'

const theme: StylesConfig = {
  Input: {
    field: {
      styles: {
        backgroundColor: '#0b1020',
        color: '#e6edf3',
        border: '1px solid #2c3654'
      },
      className: 'focus:ring-2 focus:ring-[#2563eb]'
    },
    label: { styles: { color: '#a8b3cf' } },
    error: { styles: { color: '#ef4444' } },
  },
  SubmitButton: {
    styles: {
      background: '#2563eb',
      color: '#fff',
      borderRadius: 10,
      padding: '14px 18px'
    },
    iconSvg: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
      <path d="M6 12L10 16L18 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
    </svg>`,
    className: 'hover:bg-[#1d4ed8] active:scale-95'
  }
}

// When mounting (see Getting Started "Mount the Checkout" example):
const checkout = new PayNextCheckout()
await checkout.mount('checkout-container', {
  ...config,
  styles: theme,
})
Prefer transforms (scale, translate) for smooth animations.
The submit icon automatically hides while the spinner is shown during a payment attempt, then reappears once processing finishes.

Advanced Patterns

Complete Configuration Example

Comprehensive example combining multiple customization layers:
advanced/comprehensive.tsx
import {
  PayNextCheckout,
  type PayNextConfig,
  type StylesConfig,
  type PaymentResult,
  type LoadedResult,
  type CheckoutError,
} from '@paynext/sdk'

export async function mountComprehensiveCheckout(containerId: string) {
  const handleLoaded = (result: LoadedResult) => {
    if (!result.success) {
      console.error('Checkout loading failed:', result.error)
      return
    }

    console.info('Checkout loaded successfully')
  }

  const handleComplete = (result: PaymentResult) => {
    // Log payment completion for analytics
    console.log('Payment completed:', result.id)
    
    // Handle subscription vs one-time payment
    if (result.subscription) {
      window.location.href = '/subscription-success'
    } else {
      window.location.href = '/payment-success'
    }
  }

  const handleError = (error: CheckoutError) => {
    const message = error.status_reason?.message ?? 'Payment failed. Please try again.'

    console.error('Payment error:', error.status, message)
    
    // PayNext automatically displays user-friendly error messages
    // Only add custom logic if needed for analytics or special handling
    alert(message)
  }

  const config: PayNextConfig = {
    clientToken: process.env.NEXT_PUBLIC_PAYNEXT_TOKEN!,
    environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox',
    apiVersion: '1.0.0',
    variant: 'compact',
    locale: 'en',
    onCheckoutLoaded: handleLoaded,
    onCheckoutComplete: handleComplete,
    onCheckoutFail: handleError,
  }

  const styles: StylesConfig = {
    Input: {
      field: {
        styles: {
          backgroundColor: '#ffffff',
          border: '2px solid #e5e7eb',
          borderRadius: '8px',
          padding: '12px 16px',
          fontSize: '16px',
          fontFamily: 'system-ui, sans-serif',
          transition: 'border-color 0.2s ease'
        },
        className: 'focus:border-blue-500 focus:outline-none'
      },
      label: {
        styles: {
          color: '#374151',
          fontSize: '14px',
          fontWeight: '600',
          marginBottom: '6px',
          display: 'block'
        }
      },
      error: {
        styles: {
          color: '#ef4444',
          fontSize: '13px',
          marginTop: '4px'
        },
        className: 'flex items-center gap-1'
      },
      container: {
        styles: { marginBottom: '16px' },
        focus: 'ring-2 ring-blue-500 ring-opacity-20'
      }
    },
    SubmitButton: {
      styles: {
        backgroundColor: '#3b82f6',
        color: 'white',
        border: 'none',
        borderRadius: '8px',
        padding: '16px 24px',
        fontSize: '16px',
        fontWeight: '600',
        width: '100%',
        cursor: 'pointer',
        transition: 'all 0.2s ease'
      },
      className: 'hover:bg-blue-700 active:scale-98 disabled:opacity-50'
    },
    ApplePayButton: {
      styles: {
        type: 'buy',
        style: 'black',
        borderRadius: 8,
        size: 'medium'
      }
    },
    PayPalButton: {
      styles: {
        layout: 'horizontal',
        color: 'gold',
        shape: 'rect',
        label: 'checkout',
        height: 48
      }
    },
    GooglePayButton: {
      styles: {
        type: 'buy',
        color: 'default',
        borderRadius: 8,
        size: 'medium'
      }
    }
  }

  const checkout = new PayNextCheckout()
  await checkout.mount(containerId, {
    ...config,
    styles,
  })
  return checkout
}

Best Practices

Configuration Management

Keep configuration objects immutable to prevent unnecessary re-renders:
  • Define configuration objects outside component scope when possible
  • Use useMemo for dynamic configurations
  • Separate styling from functional configuration
  • Test configurations across different environments

Error Handling

Implement comprehensive error handling:
  • Provide user-friendly error messages
  • Log detailed errors for debugging
  • Handle different error types appropriately
  • Test error scenarios thoroughly

Performance Considerations

Optimize checkout performance:
  • Minimize runtime style calculations
  • Use CSS classes over CSS-in-JS for static styles
  • Cache configuration objects
  • Test on various devices and connection speeds

Testing Approach

Validate integration patterns:
  • Test all payment methods in the sandbox
  • Verify styling across browsers and devices
  • Test error scenarios and edge cases
  • Validate callback integrations

Common Pitfalls

Avoid these integration mistakes:
  • Configuration conflicts between different customization layers
  • Missing error handling for payment failures
  • Performance issues from unstable configuration objects
  • Inconsistent styling across different states
  • Browser compatibility issues with advanced features
Start with minimal customization and add complexity incrementally. Test each customization layer thoroughly before combining multiple patterns.