Skip to main content

@stripe/react-stripe-js vs PayPal JS SDK vs Square Web Payments 2026

·PkgPulse Team

@stripe/react-stripe-js vs PayPal JS SDK vs Square Web Payments 2026

TL;DR

Integrating payment forms in React requires choosing between three major payment processors — each with their own SDK, UX patterns, and trade-offs. @stripe/react-stripe-js provides the most developer-friendly React experience — the Payment Element embeds a single customizable component that handles 40+ payment methods (cards, Wallets, BNPL), and the server-side Payment Intent API gives you full control over the payment lifecycle. PayPal JavaScript SDK excels at buyer trust — the PayPal button is one of the most recognized in e-commerce, and the SDK handles the entire checkout flow including PayPal balance, Venmo, Pay Later, and credit cards via hosted fields; less customizable but higher conversion for PayPal users. Square Web Payments SDK is the choice for merchants already using Square's POS system — consistent payment processing across in-person and online, with Card on File for returning customers and the Square Card component for web. For developer experience and payment method variety: Stripe. For PayPal-specific audiences and consumer trust: PayPal. For Square merchants bridging online + in-person: Square Web Payments.

Key Takeaways

  • Stripe Payment Element handles 40+ methods — cards, Apple Pay, Google Pay, Link, Afterpay, Klarna, etc.
  • PayPal Smart Buttons are embedded via @paypal/react-paypal-js — includes PayPal, Venmo, Pay Later
  • Square Card component — PCI-compliant hosted fields that match your design
  • Stripe requires server-side Payment Intent — more setup, more control over the payment flow
  • PayPal has client-side only optioncreateOrder/onApprove can be client-side for simple setups
  • All three support 3DS — Strong Customer Authentication for European transactions
  • Stripe Radar — ML-based fraud detection included with every payment

Integration Patterns

Most customizable React integration       → Stripe Payment Element
Fastest integration for digital goods     → PayPal (client-side createOrder)
Square POS + web consistency              → Square Web Payments SDK
Subscription billing                      → Stripe (SetupIntent + subscription)
BNPL (Afterpay, Klarna, Affirm)          → Stripe Payment Element
Multiple wallets in one component         → Stripe (Apple + Google Pay + Link)
Crypto payments                           → PayPal (has crypto support)
In-person + online unified               → Square

@stripe/react-stripe-js: Full React Integration

Stripe's React library provides hooks and components that integrate with Stripe's Payment Element — a single UI component supporting all payment methods.

Installation

npm install @stripe/react-stripe-js @stripe/stripe-js

Server: Create Payment Intent

// app/api/create-payment-intent/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

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

  const paymentIntent = await stripe.paymentIntents.create({
    amount,                    // Amount in cents: 2999 = $29.99
    currency,                  // "usd", "eur", "gbp", etc.
    customer: customerId,      // Optional: for Card on File
    automatic_payment_methods: {
      enabled: true,           // Enables all applicable payment methods
    },
    metadata: {
      orderId: "order_123",
    },
  });

  return Response.json({ clientSecret: paymentIntent.client_secret });
}

Client: Payment Element

// components/CheckoutForm.tsx
"use client";
import { useState } from "react";
import {
  PaymentElement,
  useStripe,
  useElements,
  Elements,
} from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

// Inner form component — must be inside <Elements>
function CheckoutForm({ onSuccess }: { onSuccess: () => void }) {
  const stripe = useStripe();
  const elements = useElements();
  const [isLoading, setIsLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    setIsLoading(true);
    setErrorMessage(null);

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url: `${window.location.origin}/order/success`,
      },
    });

    if (error) {
      setErrorMessage(error.message ?? "Payment failed");
      setIsLoading(false);
    }
    // On success, Stripe redirects to return_url
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* Renders card, Apple Pay, Google Pay, etc. based on customer eligibility */}
      <PaymentElement
        options={{
          layout: "tabs",  // "tabs" | "accordion" | "auto"
          defaultValues: {
            billingDetails: {
              name: "John Doe",
              email: "john@example.com",
            },
          },
        }}
      />
      {errorMessage && (
        <p style={{ color: "red", marginTop: 8 }}>{errorMessage}</p>
      )}
      <button
        type="submit"
        disabled={!stripe || isLoading}
        style={{ marginTop: 16, width: "100%" }}
      >
        {isLoading ? "Processing..." : "Pay Now"}
      </button>
    </form>
  );
}

// Page component: fetches client secret, passes to Elements
export function StripeCheckout({ amount }: { amount: number }) {
  const [clientSecret, setClientSecret] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/create-payment-intent", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ amount, currency: "usd" }),
    })
      .then((r) => r.json())
      .then(({ clientSecret }) => setClientSecret(clientSecret));
  }, [amount]);

  if (!clientSecret) return <div>Loading...</div>;

  return (
    <Elements
      stripe={stripePromise}
      options={{
        clientSecret,
        appearance: {
          theme: "stripe",   // "stripe" | "night" | "flat"
          variables: {
            colorPrimary: "#3b82f6",
            borderRadius: "8px",
            fontFamily: "Inter, system-ui, sans-serif",
          },
        },
      }}
    >
      <CheckoutForm onSuccess={() => console.log("Paid!")} />
    </Elements>
  );
}

Stripe Express Checkout (Apple/Google Pay Only)

import { ExpressCheckoutElement, useStripe, useElements } from "@stripe/react-stripe-js";

function ExpressCheckout({ clientSecret }: { clientSecret: string }) {
  const stripe = useStripe();
  const elements = useElements();

  const handleConfirm = async () => {
    const { error } = await stripe!.confirmPayment({
      elements: elements!,
      confirmParams: { return_url: `${origin}/success` },
    });
    if (error) console.error(error);
  };

  return (
    <ExpressCheckoutElement
      onConfirm={handleConfirm}
      options={{
        buttonType: { applePay: "buy", googlePay: "buy" },
        layout: { maxColumns: 1 },
      }}
    />
  );
}

Webhook Verification

// app/api/stripe/webhook/route.ts
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get("stripe-signature")!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }

  switch (event.type) {
    case "payment_intent.succeeded": {
      const paymentIntent = event.data.object;
      await fulfillOrder(paymentIntent.metadata.orderId);
      break;
    }
    case "payment_intent.payment_failed": {
      const paymentIntent = event.data.object;
      await notifyCustomerOfFailure(paymentIntent.receipt_email);
      break;
    }
  }

  return Response.json({ received: true });
}

PayPal JavaScript SDK: @paypal/react-paypal-js

PayPal's React library renders Smart Payment Buttons — PayPal, Venmo, Pay Later, and Card via hosted fields.

Installation

npm install @paypal/react-paypal-js

Provider Setup

// app/providers.tsx
import { PayPalScriptProvider } from "@paypal/react-paypal-js";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <PayPalScriptProvider
      options={{
        clientId: process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID!,
        currency: "USD",
        intent: "capture",
        components: "buttons,hosted-fields",
        "enable-funding": "venmo,paylater",  // Enable additional funding sources
      }}
    >
      {children}
    </PayPalScriptProvider>
  );
}

PayPal Smart Buttons

"use client";
import { PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js";

export function PayPalCheckout({ amount }: { amount: string }) {
  const [{ isPending }] = usePayPalScriptReducer();

  async function createOrder() {
    // Create order on your server (recommended) or client-side
    const response = await fetch("/api/paypal/create-order", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ amount }),
    });
    const { id } = await response.json();
    return id;  // PayPal Order ID
  }

  async function onApprove(data: { orderID: string }) {
    // Capture on your server
    const response = await fetch("/api/paypal/capture-order", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderID: data.orderID }),
    });
    const details = await response.json();

    if (details.status === "COMPLETED") {
      alert(`Transaction completed by ${details.payer.name.given_name}`);
    }
  }

  if (isPending) return <div>Loading PayPal...</div>;

  return (
    <PayPalButtons
      style={{
        layout: "vertical",  // "horizontal" | "vertical"
        color: "gold",        // "gold" | "blue" | "silver" | "white" | "black"
        shape: "rect",        // "rect" | "pill"
        label: "pay",         // "pay" | "buy" | "checkout" | "paypal" | "subscribe" | "donate"
        height: 45,
      }}
      createOrder={createOrder}
      onApprove={onApprove}
      onError={(err) => console.error("PayPal error:", err)}
      onCancel={() => console.log("Payment cancelled")}
    />
  );
}

Server: Create and Capture Orders

// app/api/paypal/create-order/route.ts
const PAYPAL_API = process.env.NODE_ENV === "production"
  ? "https://api-m.paypal.com"
  : "https://api-m.sandbox.paypal.com";

async function getPayPalAccessToken(): Promise<string> {
  const credentials = Buffer.from(
    `${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
  ).toString("base64");

  const response = await fetch(`${PAYPAL_API}/v1/oauth2/token`, {
    method: "POST",
    headers: {
      Authorization: `Basic ${credentials}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: "grant_type=client_credentials",
  });

  const data = await response.json();
  return data.access_token;
}

export async function POST(request: Request) {
  const { amount } = await request.json();
  const accessToken = await getPayPalAccessToken();

  const order = await fetch(`${PAYPAL_API}/v2/checkout/orders`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      intent: "CAPTURE",
      purchase_units: [
        {
          amount: {
            currency_code: "USD",
            value: amount,  // "29.99"
          },
          description: "Your order description",
        },
      ],
    }),
  });

  const orderData = await order.json();
  return Response.json({ id: orderData.id });
}

// app/api/paypal/capture-order/route.ts
export async function POST(request: Request) {
  const { orderID } = await request.json();
  const accessToken = await getPayPalAccessToken();

  const response = await fetch(`${PAYPAL_API}/v2/checkout/orders/${orderID}/capture`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
  });

  return Response.json(await response.json());
}

Square Web Payments SDK

Square's Web Payments SDK provides embedded payment fields that work alongside Square's POS for omnichannel merchants.

Installation

# No npm package — loaded via script tag
# Or use React wrapper:
npm install react-square-web-payments-sdk

Square Card Component (React)

"use client";
import { PaymentForm, CreditCard } from "react-square-web-payments-sdk";

export function SquarePaymentForm({ amount }: { amount: number }) {
  return (
    <PaymentForm
      applicationId={process.env.NEXT_PUBLIC_SQUARE_APP_ID!}
      locationId={process.env.NEXT_PUBLIC_SQUARE_LOCATION_ID!}
      cardTokenizeResponseReceived={async (token, verifiedBuyer) => {
        // token.token = nonce from Square — send to your server
        const response = await fetch("/api/square/charge", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            sourceId: token.token,
            amount: amount,    // In smallest currency unit (cents)
            verificationToken: verifiedBuyer?.token,
          }),
        });

        const result = await response.json();
        if (result.payment?.status === "COMPLETED") {
          alert("Payment successful!");
        }
      }}
      createVerificationDetails={() => ({
        amount: String(amount / 100),  // "29.99" format
        currencyCode: "USD",
        intent: "CHARGE",
        billingContact: {
          countryCode: "US",
          city: "San Francisco",
        },
      })}
    >
      <CreditCard
        style={{
          ".input-container": {
            borderRadius: "8px",
            border: "1px solid #e5e7eb",
          },
          ".input-container.is-focus": {
            borderColor: "#3b82f6",
          },
        }}
        buttonProps={{
          style: {
            backgroundColor: "#3b82f6",
            color: "#ffffff",
            width: "100%",
            marginTop: "16px",
          },
        }}
      />
    </PaymentForm>
  );
}

Server: Charge via Square Payments API

// app/api/square/charge/route.ts
import { Client, Environment, ApiError } from "square";

const squareClient = new Client({
  accessToken: process.env.SQUARE_ACCESS_TOKEN!,
  environment: process.env.NODE_ENV === "production"
    ? Environment.Production
    : Environment.Sandbox,
});

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

  try {
    const { result } = await squareClient.paymentsApi.createPayment({
      sourceId,                     // Nonce from Square Web Payments SDK
      idempotencyKey: crypto.randomUUID(),
      amountMoney: {
        amount: BigInt(amount),    // In cents
        currency: "USD",
      },
      verificationToken,            // Required for 3DS/SCA
      note: "Online order",
    });

    return Response.json({ payment: result.payment });
  } catch (error) {
    if (error instanceof ApiError) {
      return Response.json({ error: error.errors }, { status: 400 });
    }
    throw error;
  }
}

Feature Comparison

FeatureStripe ReactPayPal JS SDKSquare Web Payments
React library@stripe/react-stripe-js@paypal/react-paypal-jsreact-square-web-payments-sdk
Payment methods40+ (cards, wallets, BNPL)PayPal, Venmo, Pay Later, cardsCards, Apple Pay, Google Pay, ACH
Apple Pay / Google Pay✅ via Payment Element✅ via Smart Buttons
Buy Now Pay Later✅ Afterpay, Klarna, Affirm✅ Pay Later
Crypto
Server-side required✅ (Payment Intent)Optional (createOrder)✅ (Payments API)
Customization✅ Full CSS + Appearance API⚠️ Limited (button style only)✅ CSS variables
Fraud protection✅ Stripe Radar✅ PayPal risk analysis✅ Square risk
In-person POS✅ Stripe Terminal✅ PayPal Zettle✅ Square POS native
Subscription✅ First-class
TypeScript✅ Excellent✅ Good✅ Good
Processing fee2.9% + $0.303.49% + $0.492.9% + $0.30

When to Use Each

Choose @stripe/react-stripe-js if:

  • Maximum payment method coverage in one component (cards, Apple Pay, Google Pay, Afterpay, Klarna, Affirm, Link)
  • Full UI customization via the Appearance API — match your design system exactly
  • Subscription billing, trials, or usage-based pricing
  • Stripe Radar fraud scoring and SCA compliance are important
  • You want a single React component that adapts to the customer's location and browser

Choose PayPal JavaScript SDK if:

  • Target market has high PayPal trust/adoption (e-commerce, digital goods)
  • Venmo integration for US customers matters
  • Pay Later / Buy Now Pay Later via PayPal's brand (better conversion for PayPal users)
  • Client-side-only simple integration without a server is acceptable
  • Crypto payment acceptance via PayPal's crypto offering

Choose Square Web Payments SDK if:

  • Your business already uses Square for in-person POS transactions
  • Unified reporting across online and offline sales in Square Dashboard
  • Card on File for returning customers across Square's ecosystem
  • Lower-volume businesses where Square's flat-rate pricing is competitive

Methodology

Data sourced from Stripe official documentation (stripe.com/docs), PayPal Developer documentation (developer.paypal.com), Square Developer documentation (developer.squareup.com), npm download statistics as of February 2026, pricing pages as of February 2026, and community discussions from the Stripe Discord and r/webdev.


Related: Stripe Billing vs Chargebee vs Recurly for subscription management platforms built on top of these payment processors, or Polar vs Paddle vs Gumroad for developer-focused monetization platforms that abstract payment processing entirely.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.