# Sessions Flow

The Sessions flow is the fastest way to start accepting payments. With a single API call to create a session, Plexy handles payment method rendering, 3D Secure authentication, redirects, and result processing.

### How Sessions Work

<Steps>
  <Step title="Create a Session">
    Your server makes a single API call to `/v2/sessions` with payment details.
  </Step>
  <Step title="Initialize Checkout">
    Use the session ID to mount the Plexy checkout component on your page.
  </Step>
  <Step title="Customer Completes Payment">
    The customer enters their payment details in the embedded UI. Plexy handles
    all payment method logic, 3D Secure challenges, and redirects.
  </Step>
  <Step title="Handle the Result">
    Receive the payment result via webhooks and update your order status.
  </Step>
</Steps>

### Server-Side: Create a Session

Create a payment session by calling the `/v2/sessions` endpoint with the payment amount, currency, and return URL.

```bash
curl -X POST https://api.plexypay.com/v2/sessions \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": {
      "value": 10000,
      "currency": "KZT"
    },
    "reference": "order_12345",
    "returnUrl": "https://your-site.com/checkout/complete?orderId=order_12345",
    "expiresAt": "2024-12-31T23:59:59Z",
    "shopperReference": "customer_12345",
    "shopperEmail": "customer@example.com",
    "lineItems": [
      {
        "description": "Premium Subscription",
        "quantity": 1,
        "amountIncludingTax": 10000
      }
    ],
    "metadata": {
      "orderId": "order_12345",
      "source": "web_checkout"
    }
  }'
```

:::info[APSE region]
For production, use a live APSE `x-api-key` for `/v2/sessions`. For test transactions, use the test API key from the same merchant account and keep the client SDK environment live/APSE. Do not add `live-apse` to the request body; the APSE region is selected in the client SDK configuration. The SDK `clientKey` must belong to the same merchant account and mode as the backend `x-api-key`.
:::

**Response:**
```json
{
  "id": "ses_abc123xyz",
  "sessionData": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": "2024-12-31T23:59:59Z",
  "status": "active",
  "amount": {
    "value": 10000,
    "currency": "KZT"
  },
  "reference": "order_12345"
}
```

### Client-Side: Mount the Checkout

After creating a session, use the session data to initialize the Plexy checkout component on your frontend.

<Tabs>
  <Tab title="JavaScript">
    ```javascript
    import { PlexyCheckout } from '@plexy/plexy-web/browser';

    async function initializeCheckout() {
      // Fetch session from your server
      const response = await fetch('/api/create-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          orderId: 'order_12345',
          amount: 10000,
          currency: 'KZT'
        })
      });
      const { sessionId, sessionData } = await response.json();

      // Initialize checkout
      const checkout = new PlexyCheckout({
        sessionId: sessionId,
        sessionData: sessionData,
        environment: 'live-apse',
        onPaymentCompleted: (result) => {
          console.log('Payment completed:', result);
          // Redirect or show success message
          window.location.href = `/order-confirmation?id=${result.reference}`;
        },
        onPaymentFailed: (error) => {
          console.error('Payment failed:', error);
          // Show error message to customer
          showErrorMessage(error.message);
        },
        onError: (error) => {
          console.error('Checkout error:', error);
          // Handle initialization or network errors
        }
      });

      // Mount the checkout component
      checkout.mount('#checkout-container');
    }

    initializeCheckout();
    ```

  </Tab>
  <Tab title="React">
    ```js
    import { useEffect, useState } from 'react';
    import { PlexyCheckout } from '@plexy/plexy-web/react';

    interface CheckoutSession {
      sessionId: string;
      sessionData: string;
    }

    export function CheckoutPage({ orderId, amount, currency }: {
      orderId: string;
      amount: number;
      currency: string;
    }) {
      const [session, setSession] = useState<CheckoutSession | null>(null);
      const [error, setError] = useState<string | null>(null);

      useEffect(() => {
        async function createSession() {
          try {
            const response = await fetch('/api/create-session', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ orderId, amount, currency })
            });
            const data = await response.json();
            setSession(data);
          } catch (err) {
            setError('Failed to initialize checkout');
          }
        }
        createSession();
      }, [orderId, amount, currency]);

      const handlePaymentCompleted = (result: any) => {
        console.log('Payment completed:', result);
        window.location.href = `/order-confirmation?id=${result.reference}`;
      };

      const handlePaymentFailed = (error: any) => {
        console.error('Payment failed:', error);
        setError(error.message);
      };

      if (error) {
        return <div className="error">{error}</div>;
      }

      if (!session) {
        return <div className="loading">Loading checkout...</div>;
      }

      return (
        <PlexyCheckout
          sessionId={session.sessionId}
          sessionData={session.sessionData}
          environment="live-apse"
          onPaymentCompleted={handlePaymentCompleted}
          onPaymentFailed={handlePaymentFailed}
        />
      );
    }
    ```

  </Tab>
  <Tab title="HTML">
    ```html
    <!DOCTYPE html>
    <html>
    <head>
      <title>Checkout</title>
      <script src="https://checkout.plexy.money/sdk.js"></script>
    </head>
    <body>
      <div id="checkout-container"></div>

      <script>
        // Session data passed from your server
        const sessionId = '{{ sessionId }}';
        const sessionData = '{{ sessionData }}';

        const checkout = new PlexyCheckout({
          sessionId: sessionId,
          sessionData: sessionData,
          environment: 'live-apse',
          onPaymentCompleted: function(result) {
            window.location.href = '/order-confirmation?id=' + result.reference;
          },
          onPaymentFailed: function(error) {
            alert('Payment failed: ' + error.message);
          }
        });

        checkout.mount('#checkout-container');
      </script>
    </body>
    </html>
    ```

  </Tab>
</Tabs>

### Customizing the Checkout Appearance

You can customize the checkout appearance using style options:

:::warning[Shopper locale]
`locale` is the checkout interface language and regional formatting, for example `ru-KZ`, `en-US`, or `kk-KZ`. Do not use it as a currency field. Currency belongs only in `amount.currency`, for example `KZT`.
:::

```javascript
const checkout = new PlexyCheckout({
  sessionId: sessionId,
  sessionData: sessionData,
  environment: 'live-apse',
  style: {
    theme: 'light', // 'light' or 'dark'
    primaryColor: '#6366f1',
    borderRadius: '8px',
    fontFamily: 'Inter, system-ui, sans-serif',
  },
  locale: 'ru-KZ', // Checkout language
  // ... callbacks
});
```

### Webhook Integration

:::warning[Warning]
Always verify payment status via webhooks. Do not rely solely on client-side
  callbacks, as they can be manipulated.
:::

See the Webhooks documentation for full setup, signature verification, and event reference.

### Handling the Return URL

When the customer completes the payment, they are redirected to your `returnUrl`. The URL includes query parameters with the payment result.

```javascript
// pages/checkout/complete.js (Next.js example)
import { useSearchParams } from 'next/navigation';

export default function CheckoutComplete() {
  const searchParams = useSearchParams();

  const sessionId = searchParams.get('sessionId');
  const resultCode = searchParams.get('resultCode');
  const orderId = searchParams.get('orderId');

  // Always verify the result with your server
  useEffect(() => {
    async function verifyPayment() {
      const response = await fetch(`/api/verify-payment?orderId=${orderId}`);
      const { status } = await response.json();

      if (status === 'paid') {
        // Show success message
      } else {
        // Show appropriate message based on status
      }
    }
    verifyPayment();
  }, [orderId]);

  return (
    <div>
      {resultCode === 'Authorised' ? (
        <h1>Thank you for your order!</h1>
      ) : (
        <h1>Payment was not completed</h1>
      )}
    </div>
  );
}
```

:::info[Info]
The `resultCode` in the return URL is for display purposes only. Always verify
  the payment status via webhooks or by calling the API from your server.
:::

### Session States

| State       | Description                             |
| ----------- | --------------------------------------- |
| `active`    | Session is valid and ready for payment  |
| `completed` | Payment was successfully processed      |
| `expired`   | Session has passed its `expiresAt` time |
| `cancelled` | Customer cancelled the payment          |

### Error Handling

Handle errors gracefully in your checkout implementation:

```javascript
const checkout = new PlexyCheckout({
  // ... configuration
  onError: (error) => {
    switch (error.code) {
      case 'SESSION_EXPIRED':
        // Create a new session
        window.location.reload();
        break;
      case 'NETWORK_ERROR':
        // Show retry option
        showRetryButton();
        break;
      case 'INVALID_SESSION':
        // Redirect to cart
        window.location.href = '/cart';
        break;
      default:
        // Show generic error
        showErrorMessage('Something went wrong. Please try again.');
    }
  },
});
```

### Next Steps

- Configure checkout settings for your account
- Understand result codes to handle all payment outcomes
- Set up webhooks for production

