@stripe/react-stripe-js vs PayPal JS SDK vs Square Web Payments 2026
@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 option —
createOrder/onApprovecan 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
| Feature | Stripe React | PayPal JS SDK | Square Web Payments |
|---|---|---|---|
| React library | ✅ @stripe/react-stripe-js | ✅ @paypal/react-paypal-js | ✅ react-square-web-payments-sdk |
| Payment methods | 40+ (cards, wallets, BNPL) | PayPal, Venmo, Pay Later, cards | Cards, 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 fee | 2.9% + $0.30 | 3.49% + $0.49 | 2.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.