---
title: QR Code Integration
description: Generate and customize QR codes for mobile wallet payments
---

QR codes are the most common way to bridge the gap between web applications and
mobile wallets. Solana Pay includes a built-in `createQR` function powered by
`@solana/qr-code-styling` — no additional QR code packages needed.

## Basic QR Code Generation

### Generate and Display a QR Code

```typescript
import { address } from "@solana/kit";
import { encodeURL, createQR } from "@solana/pay";

// Create payment URL
const url = encodeURL({
  recipient: address("FvJ8k8HhXp4a3zQyFMZd4FvEqcYdYE7gSZWxrEBRfBsB"),
  amount: 0.05,
  label: "Coffee Shop",
  message: "Grande Americano"
});

// Generate QR code and append to DOM
const qr = createQR(url);
qr.append(document.getElementById("payment-qr"));
```

### Customizing QR Codes

The `createQR` function accepts optional parameters for size, background color,
and foreground color:

```typescript
// createQR(url, size?, background?, color?)
const qr = createQR(url, 400, "#F5F5F5", "#512DA8");
qr.append(document.getElementById("payment-qr"));
```

### Getting QR Options for Advanced Use

Use `createQROptions` to get the raw configuration object for more control:

```typescript
import { createQROptions } from "@solana/pay";

const options = createQROptions(url, 300, "#ffffff", "#000000");
// Pass to @solana/qr-code-styling or customize further
```

## Using the Merchant Client

The merchant client also exposes QR code methods:

```typescript
import { address } from "@solana/kit";
import { createMerchantClient } from "@solana/pay";

const merchant = createMerchantClient({
  rpcUrl: "https://api.mainnet-beta.solana.com"
});

const recipient = address("FvJ8k8HhXp4a3zQyFMZd4FvEqcYdYE7gSZWxrEBRfBsB");
const url = merchant.pay.encodeURL({ recipient, amount: 1.5 });

// Generate QR code
const qr = merchant.pay.createQR(url);
qr.append(document.getElementById("qr-code"));
```

## React Integration

### Basic React Component

```tsx
import { useEffect, useRef } from "react";
import { address } from "@solana/kit";
import { encodeURL, createQR } from "@solana/pay";

function PaymentQR({ recipient, amount, label, message }) {
  const qrRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const url = encodeURL({
      recipient: address(recipient),
      amount,
      label,
      message
    });

    const qr = createQR(url, 300);

    // Clear previous QR code and append new one
    if (qrRef.current) {
      qrRef.current.innerHTML = "";
      qr.append(qrRef.current);
    }
  }, [recipient, amount, label, message]);

  return (
    <div>
      <div ref={qrRef} />
      <p className="text-center mt-2 text-sm text-gray-600">
        Scan with your Solana wallet
      </p>
    </div>
  );
}

// Usage
<PaymentQR
  recipient="FvJ8k8HhXp4a3zQyFMZd4FvEqcYdYE7gSZWxrEBRfBsB"
  amount={1.5}
  label="Online Store"
  message="Order #12345"
/>;
```

### Payment QR with Status Updates

```tsx
import { useEffect, useRef, useState } from "react";
import type { Address } from "@solana/kit";
import { address, generateKeyPair, getAddressFromPublicKey } from "@solana/kit";
import { encodeURL, createQR, createMerchantClient } from "@solana/pay";

function PaymentQRWithStatus({ recipient, amount, label }) {
  const qrRef = useRef<HTMLDivElement>(null);
  const [status, setStatus] = useState<"pending" | "confirmed" | "timeout">(
    "pending"
  );
  const [reference, setReference] = useState<Address | null>(null);

  useEffect(() => {
    let interval: ReturnType<typeof setInterval>;
    let timeout: ReturnType<typeof setTimeout>;
    let cancelled = false;

    async function setup() {
      // Generate unique reference
      const keypair = await generateKeyPair();
      const ref = await getAddressFromPublicKey(keypair.publicKey);
      setReference(ref);

      const url = encodeURL({
        recipient: address(recipient),
        amount,
        reference: ref,
        label
      });

      const qr = createQR(url, 300);
      if (qrRef.current) {
        qrRef.current.innerHTML = "";
        qr.append(qrRef.current);
      }

      // Start monitoring
      const merchant = createMerchantClient({
        rpcUrl: "https://api.mainnet-beta.solana.com"
      });

      interval = setInterval(async () => {
        try {
          const found = await merchant.pay.findReference(ref);
          await merchant.pay.validateTransfer(found.signature, {
            recipient: address(recipient),
            amount,
            reference: ref
          });
          if (!cancelled) {
            setStatus("confirmed");
            clearInterval(interval);
            clearTimeout(timeout);
          }
        } catch {
          // Not found yet
        }
      }, 2000);

      // Timeout after 5 minutes
      timeout = setTimeout(
        () => {
          clearInterval(interval);
          if (!cancelled) setStatus("timeout");
        },
        5 * 60 * 1000
      );
    }

    setup();

    return () => {
      cancelled = true;
      clearInterval(interval);
      clearTimeout(timeout);
    };
  }, [recipient, amount, label]);

  return (
    <div className="relative">
      <div ref={qrRef} />

      {status === "confirmed" && (
        <div className="absolute inset-0 flex items-center justify-center bg-green-500 bg-opacity-90 rounded-lg">
          <div className="text-white text-center">
            <p className="font-semibold">Payment Confirmed!</p>
          </div>
        </div>
      )}

      {status === "timeout" && (
        <p className="text-center mt-2 text-sm text-red-600">
          Payment timed out. Please try again.
        </p>
      )}
    </div>
  );
}
```

## Point of Sale Integration

### POS Terminal QR Display

```typescript
import { address, generateKeyPair, getAddressFromPublicKey } from "@solana/kit";
import { encodeURL, createQR, createMerchantClient } from "@solana/pay";

class POSTerminal {
  private merchantWallet;
  private merchant;

  constructor(merchantWallet, rpcUrl) {
    this.merchantWallet = address(merchantWallet);
    this.merchant = createMerchantClient({ rpcUrl });
  }

  async createOrder(items, customLabel) {
    const total = items.reduce((sum, item) => sum + item.price, 0);
    const keypair = await generateKeyPair();
    const reference = await getAddressFromPublicKey(keypair.publicKey);

    const orderId = Date.now().toString();

    const url = this.merchant.pay.encodeURL({
      recipient: this.merchantWallet,
      amount: total,
      reference,
      label: customLabel || "Point of Sale",
      message: `Receipt #${orderId}`,
      memo: `POS-${orderId}`
    });

    const qr = this.merchant.pay.createQR(url, 400);

    return { orderId, total, reference, qr, url };
  }

  async monitorPayment(reference, amount, callback) {
    const checkPayment = async () => {
      try {
        const found = await this.merchant.pay.findReference(reference);
        await this.merchant.pay.validateTransfer(found.signature, {
          recipient: this.merchantWallet,
          amount,
          reference
        });
        callback({ success: true, signature: found.signature });
        return true;
      } catch {
        return false;
      }
    };

    const interval = setInterval(async () => {
      const found = await checkPayment();
      if (found) clearInterval(interval);
    }, 1000);

    // Timeout after 5 minutes
    setTimeout(
      () => {
        clearInterval(interval);
        callback({ success: false, error: "Payment timeout" });
      },
      5 * 60 * 1000
    );
  }
}

// Usage
const pos = new POSTerminal(
  "FvJ8k8HhXp4a3zQyFMZd4FvEqcYdYE7gSZWxrEBRfBsB",
  "https://api.mainnet-beta.solana.com"
);

const { qr, reference, total } = await pos.createOrder(
  [
    { name: "Coffee", price: 3.5 },
    { name: "Muffin", price: 2.25 }
  ],
  "Local Coffee Shop"
);

// Display QR code
qr.append(document.getElementById("pos-display"));

// Monitor for payment
pos.monitorPayment(reference, total, (result) => {
  if (result.success) {
    console.log("Payment received!", result.signature);
  } else {
    console.log("Payment failed or timed out");
  }
});
```

### Mobile-Optimized Display

On mobile devices, consider providing a direct link alongside the QR code:

```tsx
function MobilePayment({ paymentUrl }) {
  const qrRef = useRef(null);
  const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);

  useEffect(() => {
    const qr = createQR(paymentUrl, isMobile ? 250 : 300);
    if (qrRef.current) {
      qrRef.current.innerHTML = "";
      qr.append(qrRef.current);
    }
  }, [paymentUrl, isMobile]);

  return (
    <div>
      <div ref={qrRef} />
      {isMobile && (
        <button
          onClick={() => window.open(paymentUrl.toString())}
          className="mt-4 bg-purple-600 text-white px-6 py-3 rounded-lg w-full"
        >
          Open in Wallet
        </button>
      )}
    </div>
  );
}
```

## Accessibility

```tsx
function AccessiblePaymentQR({ paymentUrl, amount, label }) {
  const qrRef = useRef(null);

  useEffect(() => {
    const qr = createQR(paymentUrl);
    if (qrRef.current) {
      qrRef.current.innerHTML = "";
      qr.append(qrRef.current);
    }
  }, [paymentUrl]);

  return (
    <div
      role="img"
      aria-label={`Payment QR code for ${amount} SOL to ${label}`}
    >
      <div ref={qrRef} />

      {/* Manual URL copy option */}
      <details className="mt-2">
        <summary className="cursor-pointer text-sm text-gray-600">
          Copy payment URL manually
        </summary>
        <input
          type="text"
          value={paymentUrl.toString()}
          readOnly
          className="w-full mt-1 p-2 border rounded text-xs"
          onClick={(e) => e.target.select()}
        />
      </details>
    </div>
  );
}
```
