> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paynext.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handle Expired Sessions

> Manage client session expiration and create new sessions when needed.

Sessions have a limited lifetime and will eventually expire. How you handle session expiration significantly impacts application performance and user experience. Below are the different approaches, organized from best to worst practice.

## Recommended: Stateful with Proactive Validation

**This is the best approach for production applications.**

Store the session (in `localStorage`, cookies, or memory) and proactively validate its expiration before displaying the checkout form.

### Implementation:

1. When creating a session on the server, store both the session token and its expiration time
2. Before displaying the checkout form, check if the stored session is still valid
3. If the session has expired, create a new session first
4. Only then display the checkout form with the valid session

### Example:

<CodeGroup>
  ```tsx Next.js (Client Component) theme={"system"}
  'use client'

  import { useEffect, useState } from 'react'
  import { PayNextCheckout } from '@paynext/sdk'
  import '@paynext/sdk/styles'

  interface StoredSession {
    id: string
    expiryDate: string
  }

  const CheckoutForm: React.FC = () => {
    const [clientToken, setClientToken] = useState<string>('')

    useEffect(() => {
      let checkout: PayNextCheckout | undefined

      const mountCheckout = async (token: string) => {
        if (!token) return

        checkout = new PayNextCheckout()

        await checkout.mount('paynext-checkout', {
          clientToken: token,
          environment: 'sandbox',
          // ... other checkout config options
        })
      }

      const validateAndMountCheckout = async () => {
        // Get stored session from localStorage
        const storedSessionData = localStorage.getItem('paynext_session')
        let storedSession: StoredSession | null = null

        if (storedSessionData) {
          try {
            storedSession = JSON.parse(storedSessionData)
          } catch (e) {
            console.error('Failed to parse stored session', e)
          }
        }

        // Validate session expiration
        if (storedSession && new Date(storedSession.expiryDate) > new Date()) {
          // Session is still valid - use it
          console.info('Using existing valid session')
          setClientToken(storedSession.id)
        } else {
          // Session expired or doesn't exist - create new one
          console.info('Session expired or missing, creating new session')
          
          try {
            const response = await fetch('/api/client-session', { method: 'POST', body: JSON.stringify(payload) })
            const data = await response.json()

            // Store new session with expiry date
            localStorage.setItem('paynext_session', JSON.stringify({
              id: data.id,
              expiryDate: data.expiry_date,
            }))

            setClientToken(data.id)
          } catch (error) {
            console.error('Failed to create session', error)
          }
        }
      }

      if (!clientToken) {
        validateAndMountCheckout()
      } else {
        mountCheckout(clientToken)
      }

      return () => {
        checkout?.unmount()
      }
    }, [clientToken])

    return <div id='paynext-checkout' />
  }

  export default CheckoutForm
  ```

  ```typescript Vanilla TypeScript theme={"system"}
  import { PayNextCheckout } from '@paynext/sdk'
  import '@paynext/sdk/styles'

  interface StoredSession {
    id: string
    expiryDate: string
  }

  async function validateAndMountCheckout() {
    // Get stored session from localStorage
    const storedSessionData = localStorage.getItem('paynext_session')
    let storedSession: StoredSession | null = null

    if (storedSessionData) {
      try {
        storedSession = JSON.parse(storedSessionData)
      } catch (e) {
        console.error('Failed to parse stored session', e)
      }
    }

    let clientToken: string

    // Validate session expiration
    if (storedSession && new Date(storedSession.expiryDate) > new Date()) {
      // Session is still valid - use it
      console.info('Using existing valid session')
      clientToken = storedSession.id
    } else {
      // Session expired or doesn't exist - create new one
      console.info('Session expired or missing, creating new session')
      
      const response = await fetch('/api/client-session', { method: 'POST', body: JSON.stringify(payload) })
      const data = await response.json()

      // Store new session with expiry date
      localStorage.setItem('paynext_session', JSON.stringify({
        id: data.id,
        expiryDate: data.expiry_date,
      }))

      clientToken = data.id
    }

    // Mount checkout with valid session
    const checkout = new PayNextCheckout()
    await checkout.mount('paynext-checkout', {
      clientToken,
      environment: 'sandbox',
      // ... other checkout config options
    })
  }

  validateAndMountCheckout()
  ```
</CodeGroup>

### Benefits:

* **Fast render time** - form displays immediately with a valid session
* **Best user experience** - no errors or delays
* **Optimal performance** - reuses valid sessions, creates new ones only when needed

***

## Optional: Reactive Error Handling

**Use this only if you cannot implement proactive validation.**

Do not validate session lifetime upfront. Instead, handle expiration errors reactively when they occur.

### Implementation:

1. Display the checkout form with the existing session
2. When the session expires, the form will return an error
3. Catch this error, create a new session, and re-render the form

### Example:

<CodeGroup>
  ```tsx Next.js (Client Component) theme={"system"}
  'use client'

  import { useEffect, useState } from 'react'
  import { PayNextCheckout } from '@paynext/sdk'
  import '@paynext/sdk/styles'

  const CheckoutForm: React.FC = () => {
    const [clientToken, setClientToken] = useState<string>('')
    const [sessionError, setSessionError] = useState<boolean>(false)

    useEffect(() => {
      let checkout: PayNextCheckout | undefined

      const mountCheckout = async (token: string) => {
        if (!token) return
        checkout = new PayNextCheckout()

        await checkout.mount('paynext-checkout', {
          clientToken: token,
          environment: 'sandbox',
          onCheckoutLoaded: (result) => {
            if (!result.success) {
              // Check if error is due to expired session
              if (result.error?.status === 'SESSION_EXPIRED') {
                console.warn('Session expired, creating new one')
                setSessionError(true)
                return
              }
              console.error('Checkout loading failed:', result.error?.status_reason?.message)
              return
            }
            console.info('Checkout loaded successfully')
            setSessionError(false)
          },
          onCheckoutFail: (error) => {
            // Handle session expiration during payment
            if (error.status === 'SESSION_EXPIRED') {
              console.warn('Session expired during payment')
              setSessionError(true)
              return
            }
            console.error('Payment failed:', error.status, error.status_reason?.message)
          },
          // ... other checkout config options
        })
      }

      const createNewSession = async () => {
        try {
          const response = await fetch('/api/client-session', { method: 'POST', body: JSON.stringify(payload) })
          const data = await response.json()

          // Optionally store session (though not validating expiry)
          localStorage.setItem('paynext_session', JSON.stringify({
            id: data.id,
            expiryDate: data.expiry_date,
          }))

          setClientToken(data.id)
        } catch (error) {
          console.error('Failed to create session', error)
        }
      }

      if (sessionError) {
        // Session expired - create new one and remount
        checkout?.unmount()
        createNewSession()
      } else if (!clientToken) {
        // Initial load - get stored session or create new one
        const storedSessionData = localStorage.getItem('paynext_session')
        if (storedSessionData) {
          try {
            const storedSession = JSON.parse(storedSessionData)
            setClientToken(storedSession.id)
          } catch (e) {
            createNewSession()
          }
        } else {
          createNewSession()
        }
      } else {
        mountCheckout(clientToken)
      }

      return () => {
        checkout?.unmount()
      }
    }, [clientToken, sessionError])

    return <div id='paynext-checkout' />
  }

  export default CheckoutForm
  ```

  ```typescript Vanilla TypeScript theme={"system"}
  import { PayNextCheckout } from '@paynext/sdk'
  import '@paynext/sdk/styles'

  let checkout: PayNextCheckout | undefined

  async function createNewSession(): Promise<string> {
    const response = await fetch('/api/client-session', { method: 'POST', body: JSON.stringify(payload) })
    const data = await response.json()

    // Optionally store session (though not validating expiry)
    localStorage.setItem('paynext_session', JSON.stringify({
      id: data.id,
      expiryDate: data.expiry_date,
    }))

    return data.id
  }

  async function mountCheckout(clientToken: string) {
    checkout = new PayNextCheckout()

    await checkout.mount('paynext-checkout', {
      clientToken,
      environment: 'sandbox',
      onCheckoutLoaded: async (result) => {
        if (!result.success) {
          // Check if error is due to expired session
          if (result.error?.status === 'SESSION_EXPIRED') {
            console.warn('Session expired, creating new one')
            checkout?.unmount()
            
            // Create new session and remount
            const newToken = await createNewSession()
            await mountCheckout(newToken)
            return
          }
          console.error('Checkout loading failed:', result.error?.status_reason?.message)
          return
        }
        console.info('Checkout loaded successfully')
      },
      onCheckoutFail: async (error) => {
        // Handle session expiration during payment
        if (error.status === 'SESSION_EXPIRED') {
          console.warn('Session expired during payment')
          checkout?.unmount()
          
          // Create new session and remount
          const newToken = await createNewSession()
          await mountCheckout(newToken)
          return
        }
        console.error('Payment failed:', error.status, error.status_reason?.message)
      },
      // ... other checkout config options
    })
  }

  async function init() {
    // Get stored session or create new one
    const storedSessionData = localStorage.getItem('paynext_session')
    let clientToken: string

    if (storedSessionData) {
      try {
        const storedSession = JSON.parse(storedSessionData)
        clientToken = storedSession.id
      } catch (e) {
        clientToken = await createNewSession()
      }
    } else {
      clientToken = await createNewSession()
    }

    await mountCheckout(clientToken)
  }

  init()
  ```
</CodeGroup>

### Drawbacks:

* **User sees an error before the form reloads**
* **Requires additional error handling logic**
* **Suboptimal user experience (error → reload flow)**

<Note>
  While this approach works, it creates unnecessary friction for users. Use proactive validation whenever possible.
</Note>

***

## Not Recommended: Stateless (No Session Storage)

**Avoid this approach in production applications.**

Never store the session anywhere. Always generate a new session every time you need to display the checkout form.

### Implementation:

<CodeGroup>
  ```tsx Next.js (Client Component) theme={"system"}
  'use client'

  import { useEffect, useState } from 'react'
  import { PayNextCheckout } from '@paynext/sdk'
  import '@paynext/sdk/styles'

  const CheckoutForm: React.FC = () => {
    const [clientToken, setClientToken] = useState<string>('')

    useEffect(() => {
      let checkout: PayNextCheckout | undefined

      const mountCheckout = async (token: string) => {
        if (!token) return
        checkout = new PayNextCheckout()
        await checkout.mount('paynext-checkout', {
          clientToken: token,
          environment: 'sandbox',
          // ... other checkout config options
        })
      }

      // ❌ BAD: Always create new session - no storage, no reuse
      const createAndMountCheckout = async () => {
        try {
          // Every component mount creates a new session
          const response = await fetch('/api/client-session', { method: 'POST', body: JSON.stringify(payload) })
          const data = await response.json()

          // Not storing the session anywhere - it's lost after unmount
          setClientToken(data.id)
        } catch (error) {
          console.error('Failed to create session', error)
        }
      }

      if (!clientToken) {
        createAndMountCheckout() // Slow network request every time
      } else {
        mountCheckout(clientToken)
      }

      return () => {
        checkout?.unmount()
        // Session is lost here - next mount will create another one
      }
    }, [clientToken])

    return <div id='paynext-checkout' />
  }

  export default CheckoutForm
  ```

  ```typescript Vanilla TypeScript theme={"system"}
  import { PayNextCheckout } from '@paynext/sdk'
  import '@paynext/sdk/styles'

  async function init() {
    // ❌ BAD: Always create new session - no storage, no reuse
    // User must wait for this network request every time
    const response = await fetch('/api/client-session', { method: 'POST', body: JSON.stringify(payload) })
    const data = await response.json()

    const clientToken = data.id
    // Not storing the session - next page load will create another one

    const checkout = new PayNextCheckout()
    await checkout.mount('paynext-checkout', {
      clientToken,
      environment: 'sandbox',
      // ... other checkout config options
    })
  }

  // Every page load creates a new session
  init()
  ```
</CodeGroup>

### Why This is Bad:

* **Slow render time** - must wait for server request before every form display
* **Increased server load** - creates unnecessary sessions
* **Poor performance** - network latency delays form appearance
* **Bad UX** - users wait longer for the form to appear

<Warning>
  **Do not use this approach.** It significantly degrades performance and user experience. Always store and reuse valid sessions.
</Warning>

***

**Best Practice:** Always implement stateful session management with proactive expiration validation for optimal performance and user experience.
