> ## 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.

# Handle Errors & Edge Cases

> Gracefully handle payment failures, cancellations, and edge cases

## User Closes the Modal

When a customer closes the payment modal without completing, the SDK throws a `CubePayClosedError`. This is not a failure — it means the user chose to cancel.

```typescript theme={null}
try {
  const result = await cubePayService.executePayment({
    amount: 50.0,
    currency: "USD",
  });
} catch (error) {
  if (error.name === "CubePayClosedError") {
    // User closed the modal — not an error
    console.log("Payment flow cancelled by user");
    return;
  }
  // Actual error
  console.error("Payment failed:", error.message);
}
```

<Info>
  Always check for `CubePayClosedError` before treating a thrown error as a failure. This is the most common "error" you will encounter.
</Info>

***

## Client-Side Errors

| Error                      | Cause                                                          | Resolution                                                |
| -------------------------- | -------------------------------------------------------------- | --------------------------------------------------------- |
| `CubePayClosedError`       | User closed the payment modal                                  | Let the user retry when ready                             |
| Missing merchant ID        | `NEXT_PUBLIC_CUBEPAY_MERCHANT_ID` not set                      | Add the environment variable                              |
| Missing container          | `#cubepay-sdk-root` element not in DOM                         | Add the mount point to your layout                        |
| Script load failure        | SDK bundle not found at `/vendor/cubist-pay-client-sdk.umd.js` | Verify the file exists in your `public/vendor/` directory |
| SDK initialization failure | `window.CubePaySDK` not available after script load            | Check for script loading errors in the browser console    |

***

## Server-Side Errors

### Session Creation Errors

| HTTP Status | Cause                                              | Resolution                                                          |
| ----------- | -------------------------------------------------- | ------------------------------------------------------------------- |
| `400`       | Invalid request body or missing fields             | Check that `amount` is a positive number and `currency` is provided |
| `400`       | Amount is not a valid number, is zero, or negative | Validate the amount before sending                                  |
| `401`       | Invalid or missing API key                         | Verify `CUBEPAY_API_KEY` in your environment                        |
| `500`       | Internal server error                              | Retry with exponential backoff                                      |
| `502`       | Upstream API unavailable                           | The payment service is temporarily unreachable — retry shortly      |

### Handling Server Errors

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

  // Validate before calling the API
  const numericAmount = parseFloat(amount);
  if (isNaN(numericAmount) || !isFinite(numericAmount) || numericAmount <= 0) {
    return NextResponse.json(
      { error: "Amount must be a positive number" },
      { status: 400 }
    );
  }

  try {
    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(numericAmount), currency }),
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => null);
      return NextResponse.json(
        { error: errorData?.message || "Failed to create session" },
        { status: response.status }
      );
    }

    return NextResponse.json(await response.json());
  } catch (error) {
    return NextResponse.json(
      { error: "Payment service unavailable" },
      { status: 502 }
    );
  }
}
```

***

## Webhook Error Handling

Design your webhook handler to be **idempotent** and always return `200`:

```typescript theme={null}
export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { method, params } = body;

    if (method === "cubepay_paymentStatusUpdate") {
      await processStatusUpdate(params[0]);
    }
  } catch (error) {
    // Log the error but still return 200
    console.error("Webhook processing error:", error);
  }

  return NextResponse.json({ jsonrpc: "2.0", result: "ok" });
}
```

<Warning>
  If your webhook handler returns a non-2xx status code, the payment service will retry delivery. Return `200` even when your internal processing fails to avoid duplicate deliveries.
</Warning>

***

## Next Steps

Set up your [Sandbox Environment](/payment-sdk/testing/test-in-sandbox) to test the full payment flow with test networks.
