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

# Create a Payment Session

> Generate a payment session and launch the payment modal

## Server-Side: Create a Session

Create payment sessions from your backend using your API key. This returns a session ID and token that the client uses to open the payment modal.

```typescript theme={null}
// app/api/payment-sessions/route.ts (Next.js App Router)
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const { amount, currency, requestId } = await request.json();

  const response = await fetch(`${process.env.CUBEPAY_API_HOST}/payment-sessions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.CUBEPAY_API_KEY}`,
    },
    body: JSON.stringify({
      amount: String(amount),
      currency: currency || "USD",
      requestId,
    }),
  });

  const session = await response.json();

  return NextResponse.json({
    paymentSessionId: session.paymentSessionId,
    paymentSessionToken: session.paymentSessionToken,
  });
}
```

### Request Parameters

| Parameter   | Type     | Required | Description                                                        |
| ----------- | -------- | -------- | ------------------------------------------------------------------ |
| `amount`    | `string` | Yes      | Payment amount as a decimal string (e.g., `"50.00"`)               |
| `currency`  | `string` | Yes      | Currency code (e.g., `"USD"`)                                      |
| `requestId` | `string` | No       | Idempotency key — pass your own UUID to prevent duplicate sessions |

### Response

```json theme={null}
{
  "paymentSessionId": "ps_abc123def456",
  "paymentSessionToken": "eyJhbGciOiJIUzI1NiIs...",
  "amount": "50.00",
  "currency": "USD",
  "network": "SEPOLIA",
  "tokenSymbol": "USDC",
  "oneTimeWalletAddress": "0x1234...abcd",
  "status": "PENDING",
  "createdAt": "2026-03-20T12:00:00Z"
}
```

<Warning>
  The `paymentSessionToken` is a short-lived credential scoped to this session. Pass it to the client, but never log or store it permanently.
</Warning>

***

## Client-Side: Launch the Payment Modal

Pass the session credentials to the SDK service to open the payment modal.

```typescript theme={null}
import { cubePayService } from "@/services/cubepay-sdk";

async function handlePayment(amount: number) {
  try {
    const result = await cubePayService.executePayment({
      amount,
      currency: "USD",
    });

    // Payment succeeded
    console.log("Transaction hash:", result.transactionHash);
    console.log("Session:", result.session);
  } catch (error) {
    // Payment failed or was cancelled
    console.error("Payment error:", error);
  }
}
```

### What `executePayment` Does

The `executePayment` method handles the full payment lifecycle:

<Steps>
  <Step title="Creates the session">
    Calls your server's `POST /api/payment-sessions` endpoint with the amount and currency.
  </Step>

  <Step title="Initializes the SDK">
    Loads the SDK bundle (if not already loaded) and initializes it with your merchant configuration.
  </Step>

  <Step title="Opens the modal">
    Calls `sdk.open()` with the session ID and token. The customer sees the payment modal with wallet options.
  </Step>

  <Step title="Returns the result">
    On success, returns the transaction hash and session details. On cancellation, throws a `CubePayClosedError`.
  </Step>
</Steps>

## Using in a React Component

```tsx theme={null}
"use client";

import { useState } from "react";
import { cubePayService } from "@/services/cubepay-sdk";

export function PayButton({ amount }: { amount: number }) {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    try {
      const result = await cubePayService.executePayment({
        amount,
        currency: "USD",
      });
      // Redirect to success page or update UI
      window.location.href = `/success?tx=${result.transactionHash}`;
    } catch (error) {
      if (error.name === "CubePayClosedError") {
        // User closed the modal — not an error
        console.log("Payment cancelled by user");
      } else {
        console.error("Payment failed:", error.message);
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? "Processing..." : `Pay $${amount.toFixed(2)}`}
    </button>
  );
}
```

<Info>
  The SDK modal supports both mobile (drawer) and desktop (dialog) layouts automatically based on viewport size.
</Info>

## Next Steps

Learn how to [Handle Payment Completion](/payment-sdk/integration-guide/handle-payment-completion) and confirm payments via webhooks.
