# How to Integrate Square with V0

- Tool: V0
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: March 2026

## TL;DR

To integrate Square payments with V0 by Vercel, use V0 to generate your checkout UI, then create a Next.js API route that processes payments server-side using the Square Node.js SDK. Store your SQUARE_ACCESS_TOKEN as a Vercel environment variable — never expose it to the browser. Square's sandbox environment lets you test end-to-end without real charges.

## Accept Square Payments in Your V0-Generated Next.js App

Square is the go-to payment processor for businesses that also operate in person — it bridges online and offline sales in a unified dashboard. If you are building a V0-powered app for a restaurant, retail store, service business, or any merchant already using Square hardware, integrating Square payments in your Next.js app is the right move. It keeps all transactions, inventory, and customer data in one ecosystem.

Square's integration model requires two layers: a client-side layer that tokenizes card data using the Square Web Payments SDK, and a server-side layer that uses the token to create actual payments. This separation is a PCI compliance requirement — your server never sees raw card numbers, only the tokenized reference (called a nonce). Your Next.js API route handles everything server-side using the official square npm package, and your SQUARE_ACCESS_TOKEN stays safely in Vercel's encrypted environment variable storage.

V0 makes the UI generation fast. You describe your checkout form, product display, or order summary and V0 produces the Tailwind-styled React components. You then wire those components to Square's payment flow. This guide walks you through the complete integration from V0 UI generation to first successful sandbox payment.

## Before you start

- A Square developer account at developer.squareup.com — free to create, provides sandbox credentials immediately
- Your Square Application ID and Sandbox Access Token from the Square Developer Dashboard
- Your Square Location ID from the Square Developer Dashboard (found under Locations in your app settings)
- A V0 project with Next.js App Router structure
- Node.js package management access to install the square npm package in your project

## Step-by-step guide

### 1. Get Your Square API Credentials and Install the SDK

Log in to the Square Developer Dashboard at developer.squareup.com and either create a new application or select an existing one. You need three values: your Application ID (starts with sq0idp-), your Sandbox Access Token (starts with EAAAl or similar — found under Credentials tab → Sandbox), and your Location ID (found under Locations in the left sidebar — click the location to see its ID). Square provides completely separate sandbox and production credentials, which is excellent for testing. The sandbox is a full simulation of the Square API with test card numbers that trigger different responses. Save these three values securely — you will put them in Vercel environment variables, never in code. Next, install the Square Node.js SDK in your project. Open your project in your local editor or use V0's built-in GitHub integration to work with the code. In your project's package.json, you need to add the square package. The official package name is simply square and it is published by Square Inc on npm. Also note the Square Web Payments SDK script URL: https://sandbox.web.squarecdn.com/v1/square.js for sandbox and https://web.squarecdn.com/v1/square.js for production — you will load this via a script tag in your page to initialize the card payment form on the frontend.

```
// Install the Square SDK
// Run in your project terminal:
// npm install square

// package.json dependency (add to dependencies):
// "square": "^40.0.0"
```

**Expected result:** The square package is listed in your package.json dependencies and installed in node_modules. You have your Application ID, Sandbox Access Token, and Location ID ready.

### 2. Generate the Checkout UI with V0

Open V0 and describe your checkout page in detail. Square's Web Payments SDK requires a div element with a specific ID (conventionally 'card-container') where it will inject the hosted card input fields. Ask V0 to create this container in the appropriate place in the form. The Square SDK injects an iframe into this container, so you cannot style the inner card fields directly — but you can style the container div and surrounding layout. Ask V0to create the order summary section alongside the payment form so users can see what they are paying for. V0 should generate a React component for the checkout page. After generating, you will add the Square SDK initialization logic to this component — V0 will not know the Square-specific JavaScript API calls, so you will need to add those manually or in a follow-up V0 prompt. The key pattern is: on component mount, load the Square Web Payments SDK script, call window.Square.payments(appId, locationId) to initialize, then call payments.card() to create a card payment instance, then card.attach('#card-container') to render the input fields into your div. When the user submits the form, call card.tokenize() to get the payment nonce, then send that nonce to your Next.js API route. Keep this flow in mind as you review V0's generated component code.

**Expected result:** V0 generates a checkout page with an order summary section, a cardholder name input, a div#card-container placeholder, and a payment button with a success state component.

### 3. Create the Square Payment API Route

Create the server-side API route that processes Square payments using the Square Node.js SDK. This route receives a payment nonce (sourceId) from the frontend after the card is tokenized, along with the payment amount in cents. It creates a Square Payment using your access token and location ID, which are stored as server-only environment variables. The Square SDK uses the Client class from the square package to initialize a client with your access token and the environment (SANDBOX or PRODUCTION). The PaymentsApi on the client has a createPayment method that accepts the source ID, amount (in cents as a BigInt), currency, idempotency key, and location ID. The idempotency key is crucial — it prevents duplicate charges if the request is retried. Use a UUID or a combination of the nonce and timestamp. The route returns the payment ID on success so the frontend can display a confirmation. Always validate the request body on the server side before calling Square — check that the amount is a positive number and the nonce is a non-empty string. Return appropriate HTTP status codes so the frontend can handle errors gracefully.

```
import { NextRequest, NextResponse } from 'next/server';
import { Client, Environment } from 'square';

const client = new Client({
  accessToken: process.env.SQUARE_ACCESS_TOKEN,
  environment:
    process.env.SQUARE_ENVIRONMENT === 'production'
      ? Environment.Production
      : Environment.Sandbox,
});

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { sourceId, amountCents, currency = 'USD', note = '' } = body;

    if (!sourceId || typeof sourceId !== 'string') {
      return NextResponse.json(
        { error: 'Invalid payment source' },
        { status: 400 }
      );
    }

    if (!amountCents || amountCents < 100) {
      return NextResponse.json(
        { error: 'Amount must be at least $1.00' },
        { status: 400 }
      );
    }

    const { result, statusCode } = await client.paymentsApi.createPayment({
      sourceId,
      idempotencyKey: crypto.randomUUID(),
      amountMoney: {
        amount: BigInt(amountCents),
        currency,
      },
      locationId: process.env.SQUARE_LOCATION_ID,
      note,
    });

    if (statusCode !== 200 || !result.payment) {
      return NextResponse.json(
        { error: 'Payment creation failed' },
        { status: 500 }
      );
    }

    return NextResponse.json({
      paymentId: result.payment.id,
      status: result.payment.status,
      receiptUrl: result.payment.receiptUrl,
    });
  } catch (error: unknown) {
    console.error('Square payment error:', error);
    const message =
      error instanceof Error ? error.message : 'Payment processing failed';
    return NextResponse.json({ error: message }, { status: 500 });
  }
}
```

**Expected result:** Posting to /api/square/payment with a valid sourceId and amountCents returns a JSON object with paymentId, status 'COMPLETED', and a receipt URL.

### 4. Add Frontend Square SDK Integration

Now add the Square Web Payments SDK initialization to your checkout page component. The Square Web Payments SDK is loaded via a script tag and provides the window.Square global. You need to load this script before the component tries to initialize Square. In Next.js, use the next/script component with strategy='beforeInteractive' or load it via a useEffect. The initialization sequence is critical: first call Square.payments(applicationId, locationId) with your public-facing Application ID (this is safe to expose in the browser — it is not a secret key) and Location ID. Then call payments.card() to create a card payment form object. Then call card.attach('#card-container') to render the hosted card fields into your div. Wrap this in a useEffect that runs once on mount. The card.attach() call is asynchronous, so await it. In the payment submit handler, call card.tokenize() to get back a result object — if result.status is 'OK', extract result.token (the nonce/sourceId) and send it to your API route via fetch POST. The NEXT_PUBLIC_ prefix variables (Application ID and Location ID) are safe to use on the client side because they are public-facing identifiers, not secrets. Only the access token must stay server-side.

```
'use client';

import { useEffect, useRef, useState } from 'react';

declare global {
  interface Window {
    Square: {
      payments: (appId: string, locationId: string) => Promise<{
        card: () => Promise<{
          attach: (selector: string) => Promise<void>;
          tokenize: () => Promise<{ status: string; token?: string; errors?: Array<{ message: string }> }>;
        }>;
      }>;
    };
  }
}

export default function CheckoutPage() {
  const cardRef = useRef<Awaited<ReturnType<Awaited<ReturnType<typeof window.Square.payments>>['card']>> | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [success, setSuccess] = useState<{ paymentId: string; receiptUrl: string } | null>(null);

  useEffect(() => {
    const initSquare = async () => {
      if (!window.Square) return;
      try {
        const payments = await window.Square.payments(
          process.env.NEXT_PUBLIC_SQUARE_APP_ID!,
          process.env.NEXT_PUBLIC_SQUARE_LOCATION_ID!
        );
        const card = await payments.card();
        await card.attach('#card-container');
        cardRef.current = card;
      } catch (err) {
        console.error('Square SDK init error:', err);
        setError('Failed to load payment form. Please refresh.');
      }
    };

    if (window.Square) {
      initSquare();
    } else {
      const script = document.createElement('script');
      script.src = 'https://sandbox.web.squarecdn.com/v1/square.js';
      script.onload = initSquare;
      document.head.appendChild(script);
    }
  }, []);

  const handlePayment = async () => {
    if (!cardRef.current) return;
    setLoading(true);
    setError('');

    try {
      const result = await cardRef.current.tokenize();

      if (result.status !== 'OK' || !result.token) {
        const msg = result.errors?.map((e) => e.message).join(', ') || 'Card tokenization failed';
        setError(msg);
        return;
      }

      const res = await fetch('/api/square/payment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ sourceId: result.token, amountCents: 2999 }),
      });

      const data = await res.json();

      if (!res.ok) {
        setError(data.error || 'Payment failed');
        return;
      }

      setSuccess({ paymentId: data.paymentId, receiptUrl: data.receiptUrl });
    } catch (err) {
      setError('An unexpected error occurred. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  if (success) {
    return (
      <div className="max-w-md mx-auto p-8 text-center">
        <h2 className="text-2xl font-bold text-green-600 mb-2">Payment Successful!</h2>
        <p className="text-gray-600 mb-4">Order ID: {success.paymentId}</p>
        {success.receiptUrl && (
          <a href={success.receiptUrl} target="_blank" rel="noopener noreferrer"
            className="text-blue-600 underline">View Receipt</a>
        )}
      </div>
    );
  }

  return (
    <div className="max-w-md mx-auto p-8">
      <h1 className="text-2xl font-bold mb-6">Checkout</h1>
      <div id="card-container" className="min-h-[89px] mb-4 border rounded-lg p-2" />
      {error && <p className="text-red-600 text-sm mb-4">{error}</p>}
      <button
        onClick={handlePayment}
        disabled={loading}
        className="w-full bg-black text-white py-3 rounded-lg font-medium disabled:opacity-50"
      >
        {loading ? 'Processing...' : 'Pay Now'}
      </button>
    </div>
  );
}
```

**Expected result:** The checkout page loads the Square card input fields inside the card-container div, accepts the test card number 4111 1111 1111 1111, and on submit displays a payment success confirmation.

### 5. Configure Vercel Environment Variables and Deploy

Add all five Square environment variables to your Vercel project. Go to vercel.com, open your project, click Settings in the top navigation, and select Environment Variables from the left sidebar. Add the following variables: SQUARE_ACCESS_TOKEN set to your Sandbox Access Token (starts with EAAAl — server-only, no NEXT_PUBLIC_ prefix), SQUARE_LOCATION_ID set to your Location ID (server-only, no NEXT_PUBLIC_ prefix), SQUARE_ENVIRONMENT set to sandbox (server-only), NEXT_PUBLIC_SQUARE_APP_ID set to your Application ID (safe for client-side, uses NEXT_PUBLIC_ prefix), and NEXT_PUBLIC_SQUARE_LOCATION_ID set to your Location ID again (this one is public-facing and needed by the browser SDK). Yes, you need the Location ID in both a server-only and a public variable — the server route uses it via process.env.SQUARE_LOCATION_ID, and the browser SDK initialization needs it via process.env.NEXT_PUBLIC_SQUARE_LOCATION_ID. After adding all variables, redeploy your project from the Deployments tab. Test the integration using Square's sandbox test card numbers: 4111 1111 1111 1111 with any future expiry date, any 3-digit CVV, and any zip code. This test card always succeeds. For a declined payment test, use 4000 0000 0000 0002. Check your Square Developer Dashboard under Sandbox → Transactions to verify payments are being recorded.

```
// Square test card numbers for sandbox testing:
// Success: 4111 1111 1111 1111
// Declined: 4000 0000 0000 0002
// CVV Failure: 4000 0000 0000 0010
// Expiry: any future date (MM/YY)
// CVV: any 3 digits
// Zip: any 5 digits
```

**Expected result:** Your deployed V0 app processes test payments through Square's sandbox. Test transactions appear in your Square Developer Dashboard under Sandbox Transactions.

## Best practices

- Never expose SQUARE_ACCESS_TOKEN to the browser — always use it exclusively in server-side API routes or server components with no NEXT_PUBLIC_ prefix
- Always use idempotency keys when creating Square payments to prevent duplicate charges if users click the pay button multiple times or if a network retry occurs
- Test all payment scenarios in sandbox mode before switching to production — Square provides test card numbers for success, decline, CVV failure, and expired card scenarios
- Store the Square payment ID returned from the API in your database immediately so you have a record linking orders to Square transactions for reconciliation
- Use environment-specific SDK script URLs — sandbox.web.squarecdn.com for testing and web.squarecdn.com for production — and make this configurable via an environment variable
- Implement proper error handling that distinguishes between card errors (show to user) and server errors (show generic message, log details) so you do not expose internal error details
- Consider adding Square webhooks to handle asynchronous payment status updates — some payment methods do not complete synchronously and require webhook confirmation

## Use cases

### One-Time Product Checkout

A small business sells physical products online and already uses Square POS in their store. Building a checkout page in V0 that processes payments through Square keeps all sales data in the Square dashboard for unified reporting across online and in-store channels.

Prompt example:

```
Create a Next.js checkout page with a product summary on the left showing item name, quantity, and price total, and a payment form on the right with fields for cardholder name, a Square card element container div with id 'card-container', and a pay button. Use Tailwind CSS with a clean two-column layout. The pay button should call a handlePayment async function and show a loading spinner while processing.
```

### Service Booking Payment

A service provider (salon, consultant, personal trainer) wants clients to pay a deposit when booking appointments. The V0 app shows the service details and price, collects payment via Square, and confirms the booking.

Prompt example:

```
Build a service booking confirmation page that shows the service name, date, time, provider name, and total price. Include a Square payment section with a card input area and a 'Confirm Booking and Pay' button. Add a success state that shows a confirmation message with a booking reference number after payment. Style it with Tailwind in a professional blue and white color scheme.
```

### Donation or Tip Collection

A nonprofit or creator wants to accept custom-amount donations through their V0 site. Users enter a custom amount, pay via Square, and receive a confirmation. The Square dashboard tracks all donations for reporting.

Prompt example:

```
Create a donation page with preset amount buttons ($10, $25, $50, $100) and a custom amount input field. Show a brief message about where donations go. Below the amount selector, add a Square card payment form and a 'Donate Now' button. Include a success banner with a thank you message and the donated amount after payment completes.
```

## Troubleshooting

### TypeError: Cannot read properties of undefined (reading 'payments') — Square SDK not loading

Cause: The Square Web Payments SDK script has not finished loading before your useEffect tries to call window.Square.payments(). This happens if the script is loaded asynchronously and the component initializes before the script is ready.

Solution: Add an onload callback to the script element before appending it, as shown in the checkout component code. Check that the script URL matches your environment (sandbox vs production). Also verify the Application ID format is correct — it should start with 'sq0idp-'.

```
const script = document.createElement('script');
script.src = 'https://sandbox.web.squarecdn.com/v1/square.js';
script.onload = () => initSquare(); // Only call initSquare after script loads
document.head.appendChild(script);
```

### Square API returns UNAUTHORIZED or 401 error

Cause: The SQUARE_ACCESS_TOKEN environment variable is missing, incorrect, or you are using a production token in sandbox mode (or vice versa).

Solution: Verify the SQUARE_ACCESS_TOKEN in your Vercel environment variables matches the access token shown in the Square Developer Dashboard under Credentials → Sandbox Access Token. Make sure SQUARE_ENVIRONMENT matches the credentials you are using. After updating environment variables in Vercel, redeploy for the changes to take effect.

### Payment fails with 'Amount must be greater than 0' or 'INVALID_VALUE' error from Square

Cause: The amountCents value is being passed as a string, a float, or is zero. Square's API requires a positive integer in the smallest currency unit (cents for USD).

Solution: Ensure amountCents is always converted to an integer before sending. BigInt(amountCents) will throw if the value is a float like 29.99 — you must pass 2999. Parse and round before calling BigInt.

```
// Convert price to cents correctly
const priceInDollars = 29.99;
const amountCents = Math.round(priceInDollars * 100); // 2999
// In the API route:
amountMoney: {
  amount: BigInt(amountCents), // BigInt(2999)
  currency: 'USD',
}
```

### Card tokenization succeeds but the API route returns a 500 error

Cause: The SQUARE_LOCATION_ID environment variable is missing or incorrect, or the Square Client is being initialized incorrectly with the wrong environment setting.

Solution: Double-check that SQUARE_LOCATION_ID in your Vercel environment variables matches the Location ID in Square Developer Dashboard → Locations. Verify that the Client environment is set to Environment.Sandbox (import from the square package) for testing. Check Vercel function logs under your project → Functions → Logs for the specific Square error message.

## Frequently asked questions

### Can I use Square with V0 without a physical Square card reader?

Yes. Square's online payment APIs work entirely without hardware. The Web Payments SDK handles card-not-present transactions over the internet. You only need Square hardware if you want to also accept in-person payments at a physical location.

### Is Square available internationally for V0 apps?

Square is available in the United States, Canada, United Kingdom, Australia, Japan, Ireland, France, and Spain. If your target market is primarily outside these countries, consider Stripe, which has broader international coverage. Check Square's current country list at squareup.com/global.

### What is the difference between Square Sandbox and production credentials?

Square provides completely separate API credentials for sandbox (testing) and production (real payments). Sandbox transactions never charge real cards and do not transfer money. You switch between them by changing the access token, environment setting, and Web Payments SDK script URL. Always develop and test using sandbox credentials before going live.

### Can V0 generate a Square payment component I can reuse across pages?

Yes. Prompt V0 to create a reusable SquarePayment component that accepts an amountCents prop and an onSuccess callback. The component handles Square SDK initialization, card tokenization, and API route calls internally, exposing only the amount and success callback to parent pages. This pattern keeps payment logic encapsulated and reusable.

### How do I handle Square webhooks in my Next.js app?

Create a webhook endpoint at app/api/square/webhook/route.ts. Square sends webhook events for payment updates, subscription changes, and more. Verify the webhook signature using the x-square-hmacsha256-signature header and your webhook signature key from the Square Developer Dashboard. Process the event type from request.body and return a 200 status quickly — do any heavy processing asynchronously.

### Does Square support subscriptions and recurring payments?

Yes, Square has a Subscriptions API that handles recurring billing with automatic card charges. You create a plan (billing frequency and amount), then subscribe a customer to that plan. The subscription API is separate from the one-time payment API and requires storing a customer card on file using Square's SavedCard feature.

### What currencies does Square support in the API?

The currencies supported depend on your Square account country. US accounts support USD, Canadian accounts support CAD, UK accounts support GBP, and so on. Square does not support multi-currency accounts in the same way Stripe does — your Square account is tied to a single country and currency. Always set the currency field in createPayment to match your account's currency.

---

Source: https://www.rapidevelopers.com/v0-integrations/square
© RapidDev — https://www.rapidevelopers.com/v0-integrations/square
