Skip to main content

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.
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);
}
Always check for CubePayClosedError before treating a thrown error as a failure. This is the most common “error” you will encounter.

Client-Side Errors

ErrorCauseResolution
CubePayClosedErrorUser closed the payment modalLet the user retry when ready
Missing merchant IDNEXT_PUBLIC_CUBEPAY_MERCHANT_ID not setAdd the environment variable
Missing container#cubepay-sdk-root element not in DOMAdd the mount point to your layout
Script load failureSDK bundle not found at /vendor/cubist-pay-client-sdk.umd.jsVerify the file exists in your public/vendor/ directory
SDK initialization failurewindow.CubePaySDK not available after script loadCheck for script loading errors in the browser console

Server-Side Errors

Session Creation Errors

HTTP StatusCauseResolution
400Invalid request body or missing fieldsCheck that amount is a positive number and currency is provided
400Amount is not a valid number, is zero, or negativeValidate the amount before sending
401Invalid or missing API keyVerify CUBEPAY_API_KEY in your environment
500Internal server errorRetry with exponential backoff
502Upstream API unavailableThe payment service is temporarily unreachable — retry shortly

Handling Server Errors

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:
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" });
}
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.

Next Steps

Set up your Sandbox Environment to test the full payment flow with test networks.